载入中...
搜索中...
未找到
PrimitiveDrawList.cpp
浏览该文件的文档.
2
3#include "common/Assert.h"
4
5#include <glm/geometric.hpp>
6#include <glm/gtc/constants.hpp>
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <limits>
12#include <memory>
13#include <mutex>
14#include <numeric>
15#include <string>
16#include <unordered_map>
17#include <utility>
18
19namespace eve::graphics {
20namespace {
21
22eve::Result<PrimitiveRecordStatus> primitiveBudgetFailure(std::size_t hardLimit, std::size_t current) {
24 eve::DiagnosticCode::Failed, "primitive command hard limit exceeded", "commandBudget",
25 {{"hardLimit", std::to_string(hardLimit)}, {"current", std::to_string(current)}}, "graphics.primitive"));
26}
27
28template <typename Point>
29std::vector<float> cumulativeLengths(std::span<const Point> points, bool closed) {
30 std::vector<float> lengths(points.size() + (closed ? 1u : 0u), 0.f);
31 for (std::size_t i = 1; i < points.size(); ++i) {
32 lengths[i] = lengths[i - 1] + glm::length(points[i] - points[i - 1]);
33 }
34 if (closed) lengths.back() = lengths[points.size() - 1] + glm::length(points.front() - points.back());
35 return lengths;
36}
37
38template <typename Point>
39void validatePoints(std::span<const Point> points, bool closed) {
40 EV_PARAM_CHECK(points.size() >= 2, "a polyline requires at least two points");
41 const bool validClosedCount = !closed || points.size() >= 3;
42 EV_PARAM_CHECK(validClosedCount, "a closed polyline requires at least three points");
43 for (const auto& point : points) {
44 for (glm::length_t i = 0; i < point.length(); ++i) {
45 EV_PARAM_CHECK(std::isfinite(point[i]), "polyline points must be finite");
46 }
47 }
48}
49
50void validateRadius(float radius) {
51 const bool valid = std::isfinite(radius) && radius >= 0.f;
52 EV_PARAM_CHECK(valid, "primitive radius must be finite and non-negative");
53}
54
55glm::vec2 transformPoint2D(const glm::mat3& transform, glm::vec2 point) {
56 const glm::vec3 transformed = transform * glm::vec3(point, 1.f);
57 return {transformed.x, transformed.y};
58}
59
60void validateSegments(std::uint32_t segments) {
61 const bool valid = segments >= 3 && segments <= 4096;
62 EV_PARAM_CHECK(valid, "primitive circle segments must be in [3, 4096]");
63}
64
65std::pair<glm::vec3, glm::vec3> perpendicularBasis(glm::vec3 axis) {
66 const float axisLength = glm::length(axis);
67 const bool validAxis = std::isfinite(axisLength) && axisLength > 1e-6f;
68 EV_PARAM_CHECK(validAxis, "primitive axis must have non-zero finite length");
69 const glm::vec3 direction = axis / axisLength;
70 const glm::vec3 helper = std::fabs(direction.y) < 0.9f ? glm::vec3(0.f, 1.f, 0.f) : glm::vec3(1.f, 0.f, 0.f);
71 const glm::vec3 u = glm::normalize(glm::cross(direction, helper));
72 return {u, glm::normalize(glm::cross(direction, u))};
73}
74
75struct CachedUnitCircle {
76 std::shared_ptr<const std::vector<glm::vec2>> points;
77 bool hit = false;
78};
79
80CachedUnitCircle unitCircle(std::uint32_t segments) {
81 static std::mutex cacheMutex;
82 static std::unordered_map<std::uint32_t, std::shared_ptr<const std::vector<glm::vec2>>> cache;
83 {
84 const std::scoped_lock lock(cacheMutex);
85 if (const auto found = cache.find(segments); found != cache.end()) return {found->second, true};
86 }
87 auto generated = std::make_shared<std::vector<glm::vec2>>();
88 generated->reserve(segments);
89 for (std::uint32_t i = 0; i < segments; ++i) {
90 const float angle = glm::two_pi<float>() * static_cast<float>(i) / static_cast<float>(segments);
91 generated->push_back({std::cos(angle), std::sin(angle)});
92 }
93 const std::scoped_lock lock(cacheMutex);
94 const auto [iterator, inserted] = cache.emplace(segments, generated);
95 return {iterator->second, !inserted};
96}
97
98std::vector<glm::vec3> circlePoints(glm::vec3 center, glm::vec3 u, glm::vec3 v, float radius, std::uint32_t segments,
99 PrimitiveDrawStatistics& statistics) {
100 const CachedUnitCircle circle = unitCircle(segments);
101 if (circle.hit) ++statistics.cacheHits;
102 std::vector<glm::vec3> points;
103 points.reserve(segments);
104 for (const glm::vec2 unit : *circle.points) points.push_back(center + radius * (unit.x * u + unit.y * v));
105 return points;
106}
107
108float signedArea(std::span<const glm::vec2> polygon) {
109 float area = 0.f;
110 for (std::size_t i = 0; i < polygon.size(); ++i) {
111 const glm::vec2 a = polygon[i];
112 const glm::vec2 b = polygon[(i + 1u) % polygon.size()];
113 area += a.x * b.y - b.x * a.y;
114 }
115 return area * 0.5f;
116}
117
118bool pointInTriangle(glm::vec2 point, glm::vec2 a, glm::vec2 b, glm::vec2 c) {
119 const auto cross = [](glm::vec2 u, glm::vec2 v) { return u.x * v.y - u.y * v.x; };
120 const float ab = cross(b - a, point - a);
121 const float bc = cross(c - b, point - b);
122 const float ca = cross(a - c, point - c);
123 return (ab >= -1e-6f && bc >= -1e-6f && ca >= -1e-6f) || (ab <= 1e-6f && bc <= 1e-6f && ca <= 1e-6f);
124}
125
126bool pointInPolygon(glm::vec2 point, std::span<const glm::vec2> polygon) {
127 bool inside = false;
128 for (std::size_t i = 0, previous = polygon.size() - 1u; i < polygon.size(); previous = i++) {
129 const glm::vec2 a = polygon[i];
130 const glm::vec2 b = polygon[previous];
131 if ((a.y > point.y) != (b.y > point.y) && point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x)
132 inside = !inside;
133 }
134 return inside;
135}
136
137float cross2D(glm::vec2 a, glm::vec2 b, glm::vec2 c) {
138 const glm::vec2 ab = b - a;
139 const glm::vec2 ac = c - a;
140 return ab.x * ac.y - ab.y * ac.x;
141}
142
143bool properSegmentsIntersect(glm::vec2 a, glm::vec2 b, glm::vec2 c, glm::vec2 d) {
144 const float abC = cross2D(a, b, c);
145 const float abD = cross2D(a, b, d);
146 const float cdA = cross2D(c, d, a);
147 const float cdB = cross2D(c, d, b);
148 return ((abC > 1e-6f && abD < -1e-6f) || (abC < -1e-6f && abD > 1e-6f)) &&
149 ((cdA > 1e-6f && cdB < -1e-6f) || (cdA < -1e-6f && cdB > 1e-6f));
150}
151
152bool bridgeIsVisible(glm::vec2 holePoint, glm::vec2 outerPoint, std::span<const glm::vec2> outer,
153 std::span<const glm::vec2> hole) {
154 for (std::size_t i = 0; i < outer.size(); ++i)
155 if (properSegmentsIntersect(holePoint, outerPoint, outer[i], outer[(i + 1u) % outer.size()])) return false;
156 for (std::size_t i = 0; i < hole.size(); ++i)
157 if (properSegmentsIntersect(holePoint, outerPoint, hole[i], hole[(i + 1u) % hole.size()])) return false;
158 const glm::vec2 midpoint = (holePoint + outerPoint) * 0.5f;
159 return pointInPolygon(midpoint, outer) && !pointInPolygon(midpoint, hole);
160}
161
162std::vector<glm::vec2> bridgeHole(std::vector<glm::vec2> outer, std::vector<glm::vec2> hole) {
163 if (signedArea(outer) < 0.f) std::reverse(outer.begin(), outer.end());
164 if (signedArea(hole) > 0.f) std::reverse(hole.begin(), hole.end());
165 std::size_t holeIndex = 0;
166 for (std::size_t i = 1; i < hole.size(); ++i)
167 if (hole[i].x > hole[holeIndex].x || (hole[i].x == hole[holeIndex].x && hole[i].y < hole[holeIndex].y))
168 holeIndex = i;
169 std::size_t outerIndex = outer.size();
170 float bestDistanceSquared = std::numeric_limits<float>::max();
171 for (std::size_t i = 0; i < outer.size(); ++i) {
172 if (!bridgeIsVisible(hole[holeIndex], outer[i], outer, hole)) continue;
173 const glm::vec2 delta = outer[i] - hole[holeIndex];
174 const float distanceSquared = glm::dot(delta, delta);
175 if (distanceSquared < bestDistanceSquared) {
176 bestDistanceSquared = distanceSquared;
177 outerIndex = i;
178 }
179 }
180 EV_PARAM_CHECK(outerIndex != outer.size(), "path fill could not connect a hole to its outer contour");
181 std::vector<glm::vec2> merged;
182 merged.reserve(outer.size() + hole.size() + 2u);
183 merged.insert(merged.end(), outer.begin(), outer.begin() + outerIndex + 1u);
184 for (std::size_t offset = 0; offset <= hole.size(); ++offset)
185 merged.push_back(hole[(holeIndex + offset) % hole.size()]);
186 merged.push_back(outer[outerIndex]);
187 merged.insert(merged.end(), outer.begin() + outerIndex + 1u, outer.end());
188 return merged;
189}
190
191std::vector<std::array<std::size_t, 3>> triangulateSimplePolygon(std::span<const glm::vec2> polygon) {
192 std::vector<std::array<std::size_t, 3>> triangles;
193 if (polygon.size() < 3) return triangles;
194 std::vector<std::size_t> remaining(polygon.size());
195 std::iota(remaining.begin(), remaining.end(), 0u);
196 if (signedArea(polygon) < 0.f) std::reverse(remaining.begin(), remaining.end());
197 while (remaining.size() > 3) {
198 bool clipped = false;
199 for (std::size_t cursor = 0; cursor < remaining.size(); ++cursor) {
200 const std::size_t previous = remaining[(cursor + remaining.size() - 1u) % remaining.size()];
201 const std::size_t current = remaining[cursor];
202 const std::size_t next = remaining[(cursor + 1u) % remaining.size()];
203 const glm::vec2 a = polygon[previous];
204 const glm::vec2 b = polygon[current];
205 const glm::vec2 c = polygon[next];
206 const float turn = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
207 if (turn <= 1e-6f) continue;
208 bool containsVertex = false;
209 for (const std::size_t candidate : remaining) {
210 if (candidate == previous || candidate == current || candidate == next) continue;
211 if (glm::length(polygon[candidate] - a) <= 1e-6f || glm::length(polygon[candidate] - b) <= 1e-6f ||
212 glm::length(polygon[candidate] - c) <= 1e-6f)
213 continue;
214 if (pointInTriangle(polygon[candidate], a, b, c)) {
215 containsVertex = true;
216 break;
217 }
218 }
219 if (containsVertex) continue;
220 triangles.push_back({previous, current, next});
221 remaining.erase(remaining.begin() + static_cast<std::ptrdiff_t>(cursor));
222 clipped = true;
223 break;
224 }
225 EV_PARAM_CHECK(clipped, "path fill contour must be a simple non-self-intersecting polygon");
226 }
227 triangles.push_back({remaining[0], remaining[1], remaining[2]});
228 return triangles;
229}
230
231} // namespace
232
233PrimitiveCanvas2D::PrimitiveCanvas2D(std::size_t hardCommandLimit) : hardCommandLimit_(hardCommandLimit) {
234 EV_PARAM_CHECK(hardCommandLimit_ > 0, "primitive command limit must be positive");
235}
236
237void PrimitiveCanvas2D::save() { stack_.push_back(transform_); }
238
240 EV_PARAM_CHECK(!stack_.empty(), "primitive canvas restore requires a matching save");
241 transform_ = stack_.back();
242 stack_.pop_back();
243}
244
245void PrimitiveCanvas2D::concat(const glm::mat3& transform) { transform_ *= transform; }
246
247void PrimitiveCanvas2D::drawLine(glm::vec2 a, glm::vec2 b, const PrimitivePaint& paint) {
248 const std::array points{a, b};
249 drawPolyline(points, false, paint);
250}
251
252void PrimitiveCanvas2D::drawPoint(glm::vec2 point, const PrimitivePaint& paint) {
253 PrimitivePaint pointPaint = paint;
254 pointPaint.mode = PaintMode::Fill;
255 if (paint.stroke.cap == LineCap::Square)
256 drawRect(point - glm::vec2(paint.stroke.width * 0.5f), point + glm::vec2(paint.stroke.width * 0.5f),
257 pointPaint);
258 else
259 drawCircle(point, paint.stroke.width * 0.5f, pointPaint, 16);
260}
261
262void PrimitiveCanvas2D::drawPolyline(std::span<const glm::vec2> points, bool closed, const PrimitivePaint& paint) {
263 auto result = tryDrawPolyline(points, closed, paint);
264 std::move(result).expect("drawPolyline compatibility API exceeded its command budget");
265}
266
268 const PrimitivePaint& paint) {
269 validatePoints(points, closed);
270 paint.validate();
271 if (commands_.size() + triangles_.size() >= hardCommandLimit_) {
272 ++statistics_.droppedCommands;
273 return primitiveBudgetFailure(hardCommandLimit_, commands_.size() + triangles_.size());
274 }
275 commands_.push_back({std::vector<glm::vec2>(points.begin(), points.end()), cumulativeLengths(points, closed),
276 transform_, paint, closed});
277 ++statistics_.commandCount;
278 statistics_.segmentCount += points.size() - 1 + (closed ? 1u : 0u);
280}
281
282void PrimitiveCanvas2D::drawPath(const Path2D& path, const PrimitivePaint& paint, float tolerance) {
283 const auto contours = path.flatten(tolerance);
284 if (paint.mode != PaintMode::Stroke) {
285 struct FillBoundary {
286 std::vector<glm::vec2> points;
287 bool outer = false;
288 std::vector<std::vector<glm::vec2>> holes;
289 };
290 std::vector<FillBoundary> boundaries;
291 for (std::size_t contourIndex = 0; contourIndex < contours.size(); ++contourIndex) {
292 const auto& contour = contours[contourIndex];
293 if (!contour.closed || contour.points.size() < 3) continue;
294 int windingBefore = 0;
295 int containingCount = 0;
296 for (std::size_t other = 0; other < contours.size(); ++other) {
297 if (other == contourIndex || !contours[other].closed || contours[other].points.size() < 3) continue;
298 if (pointInPolygon(contour.points.front(), contours[other].points)) {
299 ++containingCount;
300 windingBefore += signedArea(contours[other].points) >= 0.f ? 1 : -1;
301 }
302 }
303 const int ownWinding = signedArea(contour.points) >= 0.f ? 1 : -1;
304 const bool beforeFilled =
305 path.fillRule() == PathFillRule::EvenOdd ? containingCount % 2 != 0 : windingBefore != 0;
306 const bool afterFilled =
307 path.fillRule() == PathFillRule::EvenOdd ? !beforeFilled : windingBefore + ownWinding != 0;
308 if (beforeFilled == afterFilled) continue;
309 boundaries.push_back({contour.points, !beforeFilled && afterFilled, {}});
310 }
311 for (FillBoundary& boundary : boundaries) {
312 if (boundary.outer) continue;
313 FillBoundary* owner = nullptr;
314 float ownerArea = std::numeric_limits<float>::max();
315 for (FillBoundary& candidate : boundaries) {
316 const float area = std::fabs(signedArea(candidate.points));
317 if (candidate.outer && area < ownerArea && pointInPolygon(boundary.points.front(), candidate.points)) {
318 owner = &candidate;
319 ownerArea = area;
320 }
321 }
322 EV_PARAM_CHECK(owner != nullptr, "path fill hole requires a containing outer contour");
323 owner->holes.push_back(boundary.points);
324 }
325 for (FillBoundary& boundary : boundaries) {
326 if (!boundary.outer) continue;
327 std::vector<glm::vec2> polygon = boundary.points;
328 for (const auto& hole : boundary.holes) polygon = bridgeHole(std::move(polygon), hole);
329 for (const auto triangle : triangulateSimplePolygon(polygon)) {
330 drawTriangle(polygon[triangle[0]], polygon[triangle[1]], polygon[triangle[2]], paint);
331 }
332 }
333 for (const FillBoundary& boundary : boundaries) drawCoverageFringe(boundary.points, paint);
334 }
335 for (const auto& contour : contours) {
336 if (contour.points.size() >= 2 && paint.mode != PaintMode::Fill)
337 drawPolyline(contour.points, contour.closed, paint);
338 }
339}
340
341void PrimitiveCanvas2D::drawTriangle(glm::vec2 a, glm::vec2 b, glm::vec2 c, const PrimitivePaint& paint) {
342 const std::array points{a, b, c};
343 validatePoints(std::span<const glm::vec2>(points), true);
344 paint.validate();
345 EV_PARAM_CHECK(commands_.size() + triangles_.size() < hardCommandLimit_,
346 "primitive 2D command hard limit exceeded");
347 triangles_.push_back({points, transform_, paint});
348 ++statistics_.commandCount;
349}
350
351void PrimitiveCanvas2D::drawColoredTriangle(glm::vec2 a, glm::vec2 b, glm::vec2 c, Color colorA, Color colorB,
352 Color colorC, const PrimitivePaint& paint) {
353 const std::array points{a, b, c};
354 validatePoints(std::span<const glm::vec2>(points), true);
355 paint.validate();
356 EV_PARAM_CHECK(commands_.size() + triangles_.size() < hardCommandLimit_,
357 "primitive 2D command hard limit exceeded");
358 triangles_.push_back({points, transform_, paint, std::array<Color, 3>{colorA, colorB, colorC}});
359 ++statistics_.commandCount;
360}
361
362void PrimitiveCanvas2D::drawCoverageFringe(std::span<const glm::vec2> outline, const PrimitivePaint& paint) {
363 if (!paint.antialias || outline.size() < 3) return;
364 std::vector<glm::vec2> points;
365 points.reserve(outline.size());
366 for (const glm::vec2 point : outline) points.push_back(transformPoint2D(transform_, point));
367 const float orientation = signedArea(points) >= 0.f ? 1.f : -1.f;
368 std::vector<glm::vec2> outer(points.size());
369 for (std::size_t i = 0; i < points.size(); ++i) {
370 const glm::vec2 previous = points[(i + points.size() - 1u) % points.size()];
371 const glm::vec2 current = points[i];
372 const glm::vec2 next = points[(i + 1u) % points.size()];
373 const glm::vec2 previousDirection = glm::normalize(current - previous);
374 const glm::vec2 nextDirection = glm::normalize(next - current);
375 const glm::vec2 previousOutward = glm::vec2(previousDirection.y, -previousDirection.x) * orientation;
376 const glm::vec2 nextOutward = glm::vec2(nextDirection.y, -nextDirection.x) * orientation;
377 glm::vec2 bisector = previousOutward + nextOutward;
378 if (glm::length(bisector) <= 1e-6f) bisector = nextOutward;
379 bisector = glm::normalize(bisector);
380 const float denominator = std::max(0.25f, glm::dot(bisector, nextOutward));
381 outer[i] = current + bisector * std::min(2.f, 0.5f / denominator);
382 }
383 Color transparent = paint.color;
384 transparent.a = 0.f;
385 const std::size_t required = points.size() * 2u;
386 const bool hasCapacity = commands_.size() + triangles_.size() + required <= hardCommandLimit_;
387 if (!hasCapacity) statistics_.droppedCommands += required;
388 EV_PARAM_CHECK(hasCapacity, "primitive 2D coverage fringe exceeds command hard limit");
389 for (std::size_t i = 0; i < points.size(); ++i) {
390 const std::size_t next = (i + 1u) % points.size();
391 triangles_.push_back({{points[i], points[next], outer[i]},
392 glm::mat3(1.f),
393 paint,
394 std::array<Color, 3>{paint.color, paint.color, transparent}});
395 triangles_.push_back({{points[next], outer[next], outer[i]},
396 glm::mat3(1.f),
397 paint,
398 std::array<Color, 3>{paint.color, transparent, transparent}});
399 statistics_.commandCount += 2u;
400 }
401}
402
403void PrimitiveCanvas2D::drawRect(glm::vec2 minimum, glm::vec2 maximum, const PrimitivePaint& paint) {
404 const bool ordered = minimum.x <= maximum.x && minimum.y <= maximum.y;
405 EV_PARAM_CHECK(ordered, "primitive rectangle minimum must not exceed maximum");
406 const std::array outline{minimum, glm::vec2(maximum.x, minimum.y), maximum, glm::vec2(minimum.x, maximum.y)};
407 if (paint.mode != PaintMode::Stroke) {
408 drawTriangle(outline[0], outline[1], outline[2], paint);
409 drawTriangle(outline[0], outline[2], outline[3], paint);
410 drawCoverageFringe(outline, paint);
411 }
412 if (paint.mode != PaintMode::Fill) drawPolyline(outline, true, paint);
413}
414
415void PrimitiveCanvas2D::drawRoundedRect(glm::vec2 minimum, glm::vec2 maximum, glm::vec2 radii,
416 const PrimitivePaint& paint, std::uint32_t cornerSegments) {
417 const glm::vec2 size = maximum - minimum;
418 const bool valid = minimum.x <= maximum.x && minimum.y <= maximum.y && std::isfinite(radii.x) &&
419 std::isfinite(radii.y) && radii.x >= 0.f && radii.y >= 0.f;
420 EV_PARAM_CHECK(valid, "rounded rectangle bounds and radii must be valid");
421 validateSegments(std::max(3u, cornerSegments));
422 radii = glm::min(radii, size * 0.5f);
423 std::vector<glm::vec2> outline;
424 outline.reserve((cornerSegments + 1u) * 4u);
425 const std::array centers{glm::vec2(maximum.x - radii.x, minimum.y + radii.y), maximum - radii,
426 glm::vec2(minimum.x + radii.x, maximum.y - radii.y), minimum + radii};
427 for (std::uint32_t corner = 0; corner < 4; ++corner) {
428 const float start = -glm::half_pi<float>() + glm::half_pi<float>() * corner;
429 for (std::uint32_t i = 0; i <= cornerSegments; ++i) {
430 const float angle =
431 start + glm::half_pi<float>() * static_cast<float>(i) / static_cast<float>(cornerSegments);
432 outline.push_back(centers[corner] + glm::vec2(std::cos(angle) * radii.x, std::sin(angle) * radii.y));
433 }
434 }
435 const glm::vec2 center = (minimum + maximum) * 0.5f;
436 if (paint.mode != PaintMode::Stroke)
437 for (std::size_t i = 0; i < outline.size(); ++i)
438 drawTriangle(center, outline[i], outline[(i + 1u) % outline.size()], paint);
439 if (paint.mode != PaintMode::Stroke) drawCoverageFringe(outline, paint);
440 if (paint.mode != PaintMode::Fill) drawPolyline(outline, true, paint);
441}
442
443void PrimitiveCanvas2D::drawCircle(glm::vec2 center, float radius, const PrimitivePaint& paint,
444 std::uint32_t segments) {
445 drawEllipse(center, glm::vec2(radius), paint, segments);
446}
447
448void PrimitiveCanvas2D::drawCircle(glm::vec2 center, float radius, const PrimitivePaint& paint,
449 const RadialTessellation& tessellation) {
450 validateRadius(radius);
451 const glm::vec2 transformedCenter = transformPoint2D(transform_, center);
452 const float projectedRadius =
453 std::max(glm::length(transformPoint2D(transform_, center + glm::vec2(radius, 0.f)) - transformedCenter),
454 glm::length(transformPoint2D(transform_, center + glm::vec2(0.f, radius)) - transformedCenter));
455 drawCircle(center, radius, paint, resolveRadialSegments(tessellation, projectedRadius));
456}
457
458void PrimitiveCanvas2D::drawEllipse(glm::vec2 center, glm::vec2 radii, const PrimitivePaint& paint,
459 std::uint32_t segments) {
460 const bool validRadii = std::isfinite(radii.x) && std::isfinite(radii.y) && radii.x >= 0.f && radii.y >= 0.f;
461 EV_PARAM_CHECK(validRadii, "ellipse radii must be finite and non-negative");
462 validateSegments(segments);
463 const CachedUnitCircle circle = unitCircle(segments);
464 if (circle.hit) ++statistics_.cacheHits;
465 std::vector<glm::vec2> outline;
466 outline.reserve(segments);
467 for (const glm::vec2 unit : *circle.points) outline.push_back(center + unit * radii);
468 if (paint.mode != PaintMode::Stroke) {
469 for (std::uint32_t i = 0; i < segments; ++i) {
470 drawTriangle(center, outline[i], outline[(i + 1u) % segments], paint);
471 }
472 drawCoverageFringe(outline, paint);
473 }
474 if (paint.mode != PaintMode::Fill) drawPolyline(outline, true, paint);
475}
476
477void PrimitiveCanvas2D::drawArc(glm::vec2 center, glm::vec2 radii, float startRadians, float sweepRadians,
478 const PrimitivePaint& paint, std::uint32_t segments) {
479 const bool validArc = std::isfinite(startRadians) && std::isfinite(sweepRadians) && std::isfinite(radii.x) &&
480 std::isfinite(radii.y) && radii.x >= 0.f && radii.y >= 0.f;
481 EV_PARAM_CHECK(validArc, "arc values must be finite and radii non-negative");
482 validateSegments(segments);
483 std::vector<glm::vec2> arc;
484 arc.reserve(segments + 1u);
485 for (std::uint32_t i = 0; i <= segments; ++i) {
486 const float t = static_cast<float>(i) / static_cast<float>(segments);
487 const float angle = startRadians + sweepRadians * t;
488 arc.push_back(center + glm::vec2(std::cos(angle) * radii.x, std::sin(angle) * radii.y));
489 }
490 if (paint.mode != PaintMode::Stroke) {
491 for (std::uint32_t i = 0; i < segments; ++i) {
492 drawTriangle(center, arc[i], arc[i + 1], paint);
493 }
494 std::vector<glm::vec2> sector;
495 sector.reserve(arc.size() + 1u);
496 sector.push_back(center);
497 sector.insert(sector.end(), arc.begin(), arc.end());
498 drawCoverageFringe(sector, paint);
499 }
500 if (paint.mode != PaintMode::Fill) drawPolyline(arc, false, paint);
501}
502
504 commands_.clear();
505 triangles_.clear();
506 stack_.clear();
507 transform_ = glm::mat3(1.f);
508 statistics_ = {};
509}
510
512 : context_(std::move(context)), hardCommandLimit_(hardCommandLimit) {
513 context_.validate();
514 EV_PARAM_CHECK(hardCommandLimit_ > 0, "primitive command limit must be positive");
515}
516
517void PrimitiveSceneCanvas3D::save() { stack_.push_back(transform_); }
518
520 EV_PARAM_CHECK(!stack_.empty(), "primitive scene canvas restore requires a matching save");
521 transform_ = stack_.back();
522 stack_.pop_back();
523}
524
525void PrimitiveSceneCanvas3D::concat(const glm::mat4& transform) { transform_ *= transform; }
526
527void PrimitiveSceneCanvas3D::drawLine(glm::vec3 a, glm::vec3 b, const ScenePrimitivePaint& paint) {
528 const std::array points{a, b};
529 drawPolyline(points, false, paint);
530}
531
533 const glm::vec3 cameraRight{context_.view[0][0], context_.view[1][0], context_.view[2][0]};
534 const float worldRadius =
536 ? paint.stroke.width * 0.5f
537 : context_.nearPlane * paint.stroke.width / static_cast<float>(context_.viewportSize.y);
538 drawLine(point - cameraRight * worldRadius, point + cameraRight * worldRadius, paint);
539}
540
541void PrimitiveSceneCanvas3D::drawPolyline(std::span<const glm::vec3> points, bool closed,
542 const ScenePrimitivePaint& paint) {
543 auto result = tryDrawPolyline(points, closed, paint);
544 std::move(result).expect("drawPolyline compatibility API exceeded its command budget");
545}
546
548 bool closed,
549 const ScenePrimitivePaint& paint) {
550 validatePoints(points, closed);
551 paint.validate();
552 if (commands_.size() + triangles_.size() >= hardCommandLimit_) {
553 ++statistics_.droppedCommands;
554 return primitiveBudgetFailure(hardCommandLimit_, commands_.size() + triangles_.size());
555 }
556 commands_.push_back({std::vector<glm::vec3>(points.begin(), points.end()), cumulativeLengths(points, closed),
557 transform_, paint, closed});
558 ++statistics_.commandCount;
559 statistics_.segmentCount += points.size() - 1 + (closed ? 1u : 0u);
561}
562
564 const ScenePrimitivePaint& paint) {
565 const float directionLength = glm::length(direction);
566 const bool validDirection = std::isfinite(directionLength) && directionLength > 1e-6f;
567 EV_PARAM_CHECK(validDirection, "primitive ray direction must be finite and non-zero");
568 const bool validLength = std::isfinite(length) && length >= 0.f;
569 EV_PARAM_CHECK(validLength, "primitive ray length must be finite and non-negative");
570 drawLine(origin, origin + direction / directionLength * length, paint);
571}
572
573void PrimitiveSceneCanvas3D::drawTriangle(glm::vec3 a, glm::vec3 b, glm::vec3 c, const ScenePrimitivePaint& paint) {
574 const std::array points{a, b, c};
575 validatePoints(std::span<const glm::vec3>(points), true);
576 paint.validate();
577 if (paint.mode != PaintMode::Stroke) recordTriangle(a, b, c, paint);
578 if (paint.mode != PaintMode::Fill) drawPolyline(points, true, paint);
579}
580
581void PrimitiveSceneCanvas3D::recordTriangle(glm::vec3 a, glm::vec3 b, glm::vec3 c, const ScenePrimitivePaint& paint) {
582 EV_PARAM_CHECK(commands_.size() + triangles_.size() < hardCommandLimit_,
583 "primitive 3D command hard limit exceeded");
584 triangles_.push_back({{a, b, c}, transform_, paint});
585 ++statistics_.commandCount;
586}
587
588void PrimitiveSceneCanvas3D::drawQuad(glm::vec3 a, glm::vec3 b, glm::vec3 c, glm::vec3 d,
589 const ScenePrimitivePaint& paint) {
590 if (paint.mode != PaintMode::Stroke) {
591 recordTriangle(a, b, c, paint);
592 recordTriangle(a, c, d, paint);
593 }
594 if (paint.mode != PaintMode::Fill) {
595 const std::array outline{a, b, c, d};
596 drawPolyline(outline, true, paint);
597 }
598}
599
600void PrimitiveSceneCanvas3D::drawDisk(glm::vec3 center, glm::vec3 normal, float radius,
601 const ScenePrimitivePaint& paint, std::uint32_t segments) {
602 validateRadius(radius);
603 validateSegments(segments);
604 const auto [u, v] = perpendicularBasis(normal);
605 const auto outline = circlePoints(center, u, v, radius, segments, statistics_);
606 if (paint.mode != PaintMode::Stroke)
607 for (std::uint32_t i = 0; i < segments; ++i)
608 recordTriangle(center, outline[i], outline[(i + 1u) % segments], paint);
609 if (paint.mode != PaintMode::Fill) drawPolyline(outline, true, paint);
610}
611
612void PrimitiveSceneCanvas3D::drawArc(glm::vec3 center, glm::vec3 normal, glm::vec3 zeroDirection, float radius,
613 float startRadians, float sweepRadians, const ScenePrimitivePaint& paint,
614 std::uint32_t segments) {
615 validateRadius(radius);
616 validateSegments(segments);
617 const bool validAngles = std::isfinite(startRadians) && std::isfinite(sweepRadians);
618 EV_PARAM_CHECK(validAngles, "primitive arc angles must be finite");
619 const glm::vec3 n = glm::normalize(normal);
620 glm::vec3 u = zeroDirection - n * glm::dot(zeroDirection, n);
621 EV_PARAM_CHECK(glm::length(u) > 1e-6f, "primitive arc zero direction must not be parallel to normal");
622 u = glm::normalize(u);
623 const glm::vec3 v = glm::normalize(glm::cross(n, u));
624 std::vector<glm::vec3> points;
625 points.reserve(segments + 1u);
626 for (std::uint32_t i = 0; i <= segments; ++i) {
627 const float angle = startRadians + sweepRadians * static_cast<float>(i) / static_cast<float>(segments);
628 points.push_back(center + radius * (std::cos(angle) * u + std::sin(angle) * v));
629 }
630 if (paint.mode != PaintMode::Stroke)
631 for (std::uint32_t i = 0; i < segments; ++i) recordTriangle(center, points[i], points[i + 1u], paint);
632 if (paint.mode != PaintMode::Fill) drawPolyline(points, false, paint);
633}
634
635void PrimitiveSceneCanvas3D::drawAabb(glm::vec3 minimum, glm::vec3 maximum, const ScenePrimitivePaint& paint) {
636 const bool ordered = minimum.x <= maximum.x && minimum.y <= maximum.y && minimum.z <= maximum.z;
637 EV_PARAM_CHECK(ordered, "primitive AABB minimum must not exceed maximum");
638 const std::array corners{glm::vec3{minimum.x, minimum.y, minimum.z}, glm::vec3{maximum.x, minimum.y, minimum.z},
639 glm::vec3{maximum.x, maximum.y, minimum.z}, glm::vec3{minimum.x, maximum.y, minimum.z},
640 glm::vec3{minimum.x, minimum.y, maximum.z}, glm::vec3{maximum.x, minimum.y, maximum.z},
641 glm::vec3{maximum.x, maximum.y, maximum.z}, glm::vec3{minimum.x, maximum.y, maximum.z}};
642 if (paint.mode != PaintMode::Stroke) {
643 recordTriangle(corners[0], corners[3], corners[2], paint);
644 recordTriangle(corners[0], corners[2], corners[1], paint);
645 recordTriangle(corners[4], corners[5], corners[6], paint);
646 recordTriangle(corners[4], corners[6], corners[7], paint);
647 recordTriangle(corners[0], corners[1], corners[5], paint);
648 recordTriangle(corners[0], corners[5], corners[4], paint);
649 recordTriangle(corners[1], corners[2], corners[6], paint);
650 recordTriangle(corners[1], corners[6], corners[5], paint);
651 recordTriangle(corners[2], corners[3], corners[7], paint);
652 recordTriangle(corners[2], corners[7], corners[6], paint);
653 recordTriangle(corners[3], corners[0], corners[4], paint);
654 recordTriangle(corners[3], corners[4], corners[7], paint);
655 }
656 if (paint.mode != PaintMode::Fill) {
657 const std::array<std::uint8_t, 24> edges{0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6,
658 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7};
659 for (std::size_t i = 0; i < edges.size(); i += 2) drawLine(corners[edges[i]], corners[edges[i + 1]], paint);
660 }
661}
662
663void PrimitiveSceneCanvas3D::drawObb(glm::vec3 center, const std::array<glm::vec3, 3>& halfAxes,
664 const ScenePrimitivePaint& paint) {
665 for (const glm::vec3 axis : halfAxes) EV_PARAM_CHECK(glm::length(axis) > 1e-6f, "OBB half axes must be non-zero");
666 std::array<glm::vec3, 8> corners{};
667 for (std::uint32_t index = 0; index < 8; ++index)
668 corners[index] = center + (index & 1u ? halfAxes[0] : -halfAxes[0]) +
669 (index & 2u ? halfAxes[1] : -halfAxes[1]) + (index & 4u ? halfAxes[2] : -halfAxes[2]);
670 drawFrustum(corners, paint);
671}
672
673void PrimitiveSceneCanvas3D::drawGrid(glm::vec3 origin, glm::vec3 axisU, glm::vec3 axisV, std::uint32_t cellsU,
674 std::uint32_t cellsV, const ScenePrimitivePaint& paint) {
675 const bool validCellsU = cellsU > 0 && cellsU <= 4096;
676 const bool validCellsV = cellsV > 0 && cellsV <= 4096;
677 const bool validAxes = glm::length(axisU) > 1e-6f && glm::length(axisV) > 1e-6f;
678 EV_PARAM_CHECK(validCellsU, "grid cellsU must be in [1, 4096]");
679 EV_PARAM_CHECK(validCellsV, "grid cellsV must be in [1, 4096]");
680 EV_PARAM_CHECK(validAxes, "grid axes must have non-zero length");
681 for (std::uint32_t i = 0; i <= cellsU; ++i) {
682 const float t = static_cast<float>(i) / static_cast<float>(cellsU);
683 drawLine(origin + axisU * t, origin + axisU * t + axisV, paint);
684 }
685 for (std::uint32_t i = 0; i <= cellsV; ++i) {
686 const float t = static_cast<float>(i) / static_cast<float>(cellsV);
687 drawLine(origin + axisV * t, origin + axisV * t + axisU, paint);
688 }
689}
690
692 std::uint32_t segments) {
693 validateRadius(radius);
694 validateSegments(segments);
695 if (paint.mode != PaintMode::Stroke) {
696 const std::uint32_t rings = std::max(3u, segments / 2u);
697 for (std::uint32_t ring = 0; ring < rings; ++ring) {
698 const float latitudeA = -glm::half_pi<float>() + glm::pi<float>() * ring / rings;
699 const float latitudeB = -glm::half_pi<float>() + glm::pi<float>() * (ring + 1u) / rings;
700 for (std::uint32_t slice = 0; slice < segments; ++slice) {
701 const float longitudeA = glm::two_pi<float>() * slice / segments;
702 const float longitudeB = glm::two_pi<float>() * (slice + 1u) / segments;
703 const auto point = [&](float latitude, float longitude) {
704 return center + radius * glm::vec3(std::cos(latitude) * std::cos(longitude), std::sin(latitude),
705 std::cos(latitude) * std::sin(longitude));
706 };
707 const glm::vec3 a = point(latitudeA, longitudeA);
708 const glm::vec3 b = point(latitudeA, longitudeB);
709 const glm::vec3 c = point(latitudeB, longitudeB);
710 const glm::vec3 d = point(latitudeB, longitudeA);
711 recordTriangle(a, b, c, paint);
712 recordTriangle(a, c, d, paint);
713 }
714 }
715 }
716 if (paint.mode != PaintMode::Fill) {
717 drawPolyline(circlePoints(center, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, radius, segments, statistics_), true,
718 paint);
719 drawPolyline(circlePoints(center, {1.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, radius, segments, statistics_), true,
720 paint);
721 drawPolyline(circlePoints(center, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}, radius, segments, statistics_), true,
722 paint);
723 }
724}
725
727 const RadialTessellation& tessellation) {
728 validateRadius(radius);
729 const glm::mat4 modelView = context_.view * transform_;
730 const glm::vec3 viewCenter = glm::vec3(modelView * glm::vec4(center, 1.f));
731 const glm::vec3 viewEdge = glm::vec3(modelView * glm::vec4(center + glm::vec3(radius, 0.f, 0.f), 1.f));
732 const glm::vec4 centerClip = context_.projection * glm::vec4(viewCenter, 1.f);
733 const glm::vec4 edgeClip = context_.projection * glm::vec4(viewEdge, 1.f);
734 float projectedRadius = 0.f;
735 if (centerClip.w > 1e-8f && edgeClip.w > 1e-8f) {
736 const glm::vec2 centerNdc = glm::vec2(centerClip) / centerClip.w;
737 const glm::vec2 edgeNdc = glm::vec2(edgeClip) / edgeClip.w;
738 projectedRadius = glm::length((edgeNdc - centerNdc) * glm::vec2(context_.viewportSize) * 0.5f);
739 }
740 drawSphere(center, radius, paint, resolveRadialSegments(tessellation, projectedRadius));
741}
742
743void PrimitiveSceneCanvas3D::drawCapsule(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint& paint,
744 std::uint32_t segments) {
745 validateRadius(radius);
746 validateSegments(segments);
747 drawCylinder(a, b, radius, paint, segments);
748 drawSphere(a, radius, paint, segments);
749 drawSphere(b, radius, paint, segments);
750}
751
752void PrimitiveSceneCanvas3D::drawCylinder(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint& paint,
753 std::uint32_t segments) {
754 validateRadius(radius);
755 validateSegments(segments);
756 const auto [u, v] = perpendicularBasis(b - a);
757 const auto circleA = circlePoints(a, u, v, radius, segments, statistics_);
758 const auto circleB = circlePoints(b, u, v, radius, segments, statistics_);
759 if (paint.mode != PaintMode::Stroke) {
760 for (std::uint32_t i = 0; i < segments; ++i) {
761 const std::uint32_t next = (i + 1u) % segments;
762 recordTriangle(a, circleA[next], circleA[i], paint);
763 recordTriangle(b, circleB[i], circleB[next], paint);
764 recordTriangle(circleA[i], circleA[next], circleB[next], paint);
765 recordTriangle(circleA[i], circleB[next], circleB[i], paint);
766 }
767 }
768 if (paint.mode != PaintMode::Fill) {
769 drawPolyline(circleA, true, paint);
770 drawPolyline(circleB, true, paint);
771 for (std::uint32_t i : {0u, segments / 4u, segments / 2u, (segments * 3u) / 4u})
772 drawLine(circleA[i % segments], circleB[i % segments], paint);
773 }
774}
775
776void PrimitiveSceneCanvas3D::drawCone(glm::vec3 apex, glm::vec3 axis, float height, float radius,
777 const ScenePrimitivePaint& paint, std::uint32_t segments) {
778 validateRadius(radius);
779 validateSegments(segments);
780 const bool validHeight = std::isfinite(height) && height >= 0.f;
781 EV_PARAM_CHECK(validHeight, "cone height must be finite and non-negative");
782 const auto [u, v] = perpendicularBasis(axis);
783 const glm::vec3 direction = glm::normalize(axis);
784 const auto circle = circlePoints(apex + direction * height, u, v, radius, segments, statistics_);
785 if (paint.mode != PaintMode::Stroke) {
786 const glm::vec3 base = apex + direction * height;
787 for (std::uint32_t i = 0; i < segments; ++i) {
788 const std::uint32_t next = (i + 1u) % segments;
789 recordTriangle(base, circle[next], circle[i], paint);
790 recordTriangle(apex, circle[i], circle[next], paint);
791 }
792 }
793 if (paint.mode != PaintMode::Fill) {
794 drawPolyline(circle, true, paint);
795 for (std::uint32_t i : {0u, segments / 4u, segments / 2u, (segments * 3u) / 4u})
796 drawLine(apex, circle[i % segments], paint);
797 }
798}
799
800void PrimitiveSceneCanvas3D::drawArrow(glm::vec3 from, glm::vec3 to, float headLength, float headRadius,
801 const ScenePrimitivePaint& paint) {
802 validateRadius(headRadius);
803 const glm::vec3 axis = to - from;
804 const float length = glm::length(axis);
805 const bool validAxis = std::isfinite(length) && length > 1e-6f;
806 EV_PARAM_CHECK(validAxis, "arrow endpoints must define a non-zero finite axis");
807 const bool validHead = std::isfinite(headLength) && headLength >= 0.f && headLength <= length;
808 EV_PARAM_CHECK(validHead, "arrow head length must be finite and within arrow length");
809 const glm::vec3 direction = axis / length;
810 const glm::vec3 base = to - direction * headLength;
811 drawLine(from, base, paint);
812 drawCone(to, -direction, headLength, headRadius, paint, 16);
813}
814
815void PrimitiveSceneCanvas3D::drawFrustum(const std::array<glm::vec3, 8>& corners, const ScenePrimitivePaint& paint) {
816 static constexpr std::array<std::array<std::uint8_t, 4>, 6> faces{
817 {{{0, 1, 3, 2}}, {{4, 6, 7, 5}}, {{0, 4, 5, 1}}, {{2, 3, 7, 6}}, {{0, 2, 6, 4}}, {{1, 5, 7, 3}}}};
818 if (paint.mode != PaintMode::Stroke)
819 for (const auto& face : faces) {
820 recordTriangle(corners[face[0]], corners[face[1]], corners[face[2]], paint);
821 recordTriangle(corners[face[0]], corners[face[2]], corners[face[3]], paint);
822 }
823 if (paint.mode != PaintMode::Fill) {
824 static constexpr std::array<std::uint8_t, 24> edges{0, 1, 1, 3, 3, 2, 2, 0, 4, 5, 5, 7,
825 7, 6, 6, 4, 0, 4, 1, 5, 2, 6, 3, 7};
826 for (std::size_t i = 0; i < edges.size(); i += 2) drawLine(corners[edges[i]], corners[edges[i + 1]], paint);
827 }
828}
829
831 commands_.clear();
832 triangles_.clear();
833 stack_.clear();
834 transform_ = glm::mat4(1.f);
835 statistics_ = {};
836}
837
838} // namespace eve::graphics
Duration start
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
Definition Assert.h:30
std::string from
float length
Definition CaveMesh.cpp:94
Vec3 radii
Definition CaveMesh.cpp:56
int triangle
float maximum[3]
float minimum[3]
float u
Definition Grass.cpp:233
float area
Definition Grass.cpp:62
glm::vec3 n
Definition Grass.cpp:63
std::uint32_t ab
std::uint32_t ac
float v
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::uint32_t height
size_t offset
bool required
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< TriangleRef > triangles
Texture * normal
graphics::Canvas * previous
float radius
std::string path
Definition PlayHost.cpp:110
std::shared_ptr< const std::vector< glm::vec2 > > points
bool hit
float d
float t
V3 origin
Definition RoadBake.cpp:138
RoadLaneDirection direction
bool found
double current
TacticalUnit * unit
CommandLogBoundary boundary
Anchor rule, see above.
TacticalUnit::TurnResources turn
std::size_t cursor
float size
Definition TreeMesh.cpp:156
uint32_t index
std::vector< char > inside
std::vector< int > edges
const VegetationPresetContext & context
glm::vec3 point
float angle
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Owning backend-neutral 2D vector path.
void drawCircle(glm::vec2 center, float radius, const PrimitivePaint &paint, std::uint32_t segments=48)
Draws a circle using the requested segment quality.
void drawLine(glm::vec2 a, glm::vec2 b, const PrimitivePaint &paint)
Records a two-point line. Input data is copied.
void drawEllipse(glm::vec2 center, glm::vec2 radii, const PrimitivePaint &paint, std::uint32_t segments=48)
Draws an axis-aligned ellipse using the requested segment quality.
void restore()
Restores the most recently saved state.
void drawPolyline(std::span< const glm::vec2 > points, bool closed, const PrimitivePaint &paint)
Records an owning polyline snapshot.
void drawArc(glm::vec2 center, glm::vec2 radii, float startRadians, float sweepRadians, const PrimitivePaint &paint, std::uint32_t segments=32)
Draws an arc or filled sector. Angles are radians.
void drawRect(glm::vec2 minimum, glm::vec2 maximum, const PrimitivePaint &paint)
Draws an axis-aligned rectangle with fill/stroke paint semantics.
void drawRoundedRect(glm::vec2 minimum, glm::vec2 maximum, glm::vec2 radii, const PrimitivePaint &paint, std::uint32_t cornerSegments=8)
Draws a rounded axis-aligned rectangle with per-axis corner radii.
void concat(const glm::mat3 &transform)
Post-concatenates a local transform.
void reset()
Clears commands and state while retaining allocated capacity.
void drawPath(const Path2D &path, const PrimitivePaint &paint, float tolerance=0.25f)
Flattens and records every non-empty contour in path.
PrimitiveCanvas2D(std::size_t hardCommandLimit=65536)
Primitive canvas 2 d.
void drawPoint(glm::vec2 point, const PrimitivePaint &paint)
Draws a point using stroke width and cap semantics.
void save()
Pushes the complete current transform state.
eve::Result< PrimitiveRecordStatus > tryDrawPolyline(std::span< const glm::vec2 > points, bool closed, const PrimitivePaint &paint)
Atomically records an owning polyline or returns a structured budget failure.
void drawArrow(glm::vec3 from, glm::vec3 to, float headLength, float headRadius, const ScenePrimitivePaint &paint)
Records a shaft and four-sided arrow head.
void drawRay(glm::vec3 origin, glm::vec3 direction, float length, const ScenePrimitivePaint &paint)
Draws ray.
void drawSphere(glm::vec3 center, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records three orthogonal great circles.
void concat(const glm::mat4 &transform)
Concat.
void drawCapsule(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws capsule.
void drawLine(glm::vec3 a, glm::vec3 b, const ScenePrimitivePaint &paint)
Draws line.
void drawFrustum(const std::array< glm::vec3, 8 > &corners, const ScenePrimitivePaint &paint)
Draws frustum.
PrimitiveSceneCanvas3D(SceneDrawContext context, std::size_t hardCommandLimit=65536)
Primitive scene canvas 3 d.
void drawObb(glm::vec3 center, const std::array< glm::vec3, 3 > &halfAxes, const ScenePrimitivePaint &paint)
Draws obb.
void drawGrid(glm::vec3 origin, glm::vec3 axisU, glm::vec3 axisV, std::uint32_t cellsU, std::uint32_t cellsV, const ScenePrimitivePaint &paint)
Records a planar grid spanned by two caller-provided axes.
void drawAabb(glm::vec3 minimum, glm::vec3 maximum, const ScenePrimitivePaint &paint)
Records the twelve edges of an axis-aligned box.
void drawPolyline(std::span< const glm::vec3 > points, bool closed, const ScenePrimitivePaint &paint)
Draws polyline.
void drawArc(glm::vec3 center, glm::vec3 normal, glm::vec3 zeroDirection, float radius, float startRadians, float sweepRadians, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws arc.
void drawCylinder(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records endpoint circles and four side lines of a cylinder.
void drawPoint(glm::vec3 point, const ScenePrimitivePaint &paint)
Draws point.
void drawDisk(glm::vec3 center, glm::vec3 normal, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws disk.
void drawTriangle(glm::vec3 a, glm::vec3 b, glm::vec3 c, const ScenePrimitivePaint &paint)
Draws triangle.
void drawQuad(glm::vec3 a, glm::vec3 b, glm::vec3 c, glm::vec3 d, const ScenePrimitivePaint &paint)
Draws quad.
void drawCone(glm::vec3 apex, glm::vec3 axis, float height, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records the base circle and four side lines of a cone.
eve::Result< PrimitiveRecordStatus > tryDrawPolyline(std::span< const glm::vec3 > points, bool closed, const ScenePrimitivePaint &paint)
Atomically records an owning 3D polyline or returns a structured budget failure.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
std::uint32_t resolveRadialSegments(const RadialTessellation &tessellation, float projectedRadiusPixels)
Resolves a radial tessellation policy to a validated segment count.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
bool pointInPolygon(const Vec2 &p, const Polygon &poly)
Point-in-polygon test (ray casting; boundary counts as inside).
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
double signedArea(const Polygon &poly)
Return the raw (possibly negative) signed area of a polygon ring.
float distanceSquared(float ax, float ay, float bx, float by)
int axis(int64_t a, size_t rank)
Axis.
Backend-neutral fill and stroke paint.
void validate() const
Validates paint state used by a draw command.
Quality and screen-error policy used by circles and curved spatial primitives.
Immutable camera and viewport facts used to resolve one 3D draw list. Projection uses the engine's RH...
void validate() const
Validates viewport and clip-plane values.
Additional scene-pass state carried by every 3D command.