5#include <glm/geometric.hpp>
6#include <glm/gtc/constants.hpp>
16 float distanceA,
float distanceB, std::uint32_t commandIndex) {
17 const std::array<std::uint8_t, 6>
order{0, 1, 2, 1, 3, 2};
20 const bool positiveEdge =
index == 2 ||
index == 3;
24 ++
output.statistics.triangleCount;
25 ++
output.statistics.triangleCount;
28void appendTriangle(ResolvedPrimitiveTriangles&
output, glm::vec4
a, glm::vec4
b, glm::vec4
c,
Color color,
29 float distance, std::uint32_t commandIndex) {
33 ++
output.statistics.triangleCount;
36void appendColoredTriangle(ResolvedPrimitiveTriangles&
output, glm::vec4
a, glm::vec4
b, glm::vec4
c,
Color colorA,
37 Color colorB,
Color colorC,
float distanceA,
float distanceB, std::uint32_t commandIndex) {
38 output.vertices.push_back({
a, colorA, distanceA, 0.f, commandIndex});
39 output.vertices.push_back({
b, colorB, distanceB, 0.f, commandIndex});
40 output.vertices.push_back({
c, colorC, distanceA, 0.f, commandIndex});
41 ++
output.statistics.triangleCount;
44void appendColoredQuad(ResolvedPrimitiveTriangles&
output,
const std::array<glm::vec4, 4>&
positions,
45 const std::array<Color, 4>&
colors,
float distanceA,
float distanceB,
46 std::uint32_t commandIndex) {
48 distanceB, commandIndex);
50 distanceB, commandIndex);
53glm::vec4 pixelToClip(glm::vec2
point, glm::ivec2 viewport) {
61 const glm::vec2
normal{-outward.y, outward.x};
70 constexpr std::uint32_t capSegments = 8;
71 const float base = std::atan2(outward.y, outward.x) - glm::half_pi<float>();
72 for (std::uint32_t segment = 0; segment < capSegments; ++segment) {
73 const float a = base + glm::pi<float>() *
static_cast<float>(segment) / capSegments;
74 const float b = base + glm::pi<float>() *
static_cast<float>(segment + 1) / capSegments;
75 appendTriangle(
output, pixelToClip(
point, viewport),
76 pixelToClip(
point + glm::vec2(std::cos(
a), std::sin(
a)) *
halfWidth, viewport),
82void appendJoin2D(ResolvedPrimitiveTriangles&
output, glm::vec2
point, glm::vec2 previousDirection,
84 std::uint32_t commandIndex, glm::ivec2 viewport) {
85 const float turn = previousDirection.x * nextDirection.y - previousDirection.y * nextDirection.x;
86 if (std::fabs(
turn) <= 1e-5f)
return;
87 const float outerSign =
turn > 0.f ? -1.f : 1.f;
88 const glm::vec2 previousNormal{-previousDirection.y, previousDirection.x};
89 const glm::vec2 nextNormal{-nextDirection.y, nextDirection.x};
90 const glm::vec2 previousOuter = previousNormal * outerSign;
91 const glm::vec2 nextOuter = nextNormal * outerSign;
95 float start = std::atan2(previousOuter.y, previousOuter.x);
96 float sweep = std::atan2(nextOuter.y, nextOuter.x) -
start;
97 if (
turn > 0.f && sweep < 0.f) sweep += glm::two_pi<float>();
98 if (turn < 0.f && sweep > 0.f) sweep -= glm::two_pi<float>();
99 const std::uint32_t segments = std::max(1u,
static_cast<std::uint32_t
>(std::ceil(std::fabs(sweep) * 4.f)));
100 for (std::uint32_t segment = 0; segment < segments; ++segment) {
101 const float a =
start + sweep *
static_cast<float>(segment) / segments;
102 const float b =
start + sweep *
static_cast<float>(segment + 1) / segments;
103 appendTriangle(
output, pixelToClip(
point, viewport),
104 pixelToClip(
point + glm::vec2(std::cos(
a), std::sin(
a)) *
halfWidth, viewport),
111 const glm::vec2 bisector = glm::normalize(previousOuter + nextOuter);
112 const float denominator = glm::dot(bisector, previousOuter);
113 if (std::fabs(denominator) > 1e-5f) {
114 const float miterLength =
halfWidth / denominator;
115 if (std::fabs(miterLength) <= stroke.miterLimit *
halfWidth) {
116 appendTriangle(
output, pixelToClip(previousPoint, viewport),
117 pixelToClip(
point + bisector * miterLength, viewport), pixelToClip(nextPoint, viewport),
123 appendTriangle(
output, pixelToClip(
point, viewport), pixelToClip(previousPoint, viewport),
124 pixelToClip(nextPoint, viewport),
color,
distance, commandIndex);
127glm::vec2 clipToPixel(glm::vec4
clip, glm::ivec2 viewport) {
128 const glm::vec2 ndc = glm::vec2(
clip) /
clip.w;
129 return (ndc + 1.f) * glm::vec2(viewport) * 0.5f;
132glm::vec4 pixelToClipAt(glm::vec2 pixel, glm::vec4
reference, glm::ivec2 viewport) {
133 const glm::vec2 ndc = pixel * 2.f / glm::vec2(viewport) - 1.f;
137void appendAntialiasedStroke2D(ResolvedPrimitiveTriangles&
output, glm::vec2
a, glm::vec2
b, glm::vec2 unitNormal,
139 std::uint32_t commandIndex, glm::ivec2 viewport,
bool antialias) {
142 pixelToClip(
a - unitNormal *
halfWidth, viewport), pixelToClip(
b - unitNormal *
halfWidth, viewport),
143 pixelToClip(
a + unitNormal *
halfWidth, viewport), pixelToClip(
b + unitNormal *
halfWidth, viewport)};
147 constexpr float feather = 0.5f;
148 const float inner = std::max(0.f,
halfWidth - feather);
153 const std::array core{
154 pixelToClip(
a - unitNormal * inner, viewport), pixelToClip(
b - unitNormal * inner, viewport),
155 pixelToClip(
a + unitNormal * inner, viewport), pixelToClip(
b + unitNormal * inner, viewport)};
156 appendQuad(
output, core,
color, distanceA, distanceB, commandIndex);
158 const std::array negative{
159 pixelToClip(
a - unitNormal * outer, viewport), pixelToClip(
b - unitNormal * outer, viewport),
160 pixelToClip(
a - unitNormal * inner, viewport), pixelToClip(
b - unitNormal * inner, viewport)};
161 appendColoredQuad(
output, negative, {transparent, transparent,
color,
color}, distanceA, distanceB, commandIndex);
162 const std::array positive{
163 pixelToClip(
a + unitNormal * inner, viewport), pixelToClip(
b + unitNormal * inner, viewport),
164 pixelToClip(
a + unitNormal * outer, viewport), pixelToClip(
b + unitNormal * outer, viewport)};
165 appendColoredQuad(
output, positive, {
color,
color, transparent, transparent}, distanceA, distanceB, commandIndex);
168void appendAntialiasedStrokeClip(ResolvedPrimitiveTriangles&
output, glm::vec4 clipA, glm::vec4 clipB,
169 glm::vec2 pixelDirection,
float halfWidthA,
float halfWidthB,
Color color,
170 float distanceA,
float distanceB, std::uint32_t commandIndex, glm::ivec2 viewport,
172 const glm::vec2
normal{-pixelDirection.y, pixelDirection.x};
173 const glm::vec2
a = clipToPixel(clipA, viewport);
174 const glm::vec2
b = clipToPixel(clipB, viewport);
175 const auto makePositions = [&](
float widthA,
float widthB) {
177 pixelToClipAt(
a -
normal * widthA, clipA, viewport), pixelToClipAt(
b -
normal * widthB, clipB, viewport),
178 pixelToClipAt(
a +
normal * widthA, clipA, viewport), pixelToClipAt(
b +
normal * widthB, clipB, viewport)};
181 appendQuad(
output, makePositions(halfWidthA, halfWidthB),
color, distanceA, distanceB, commandIndex);
184 constexpr float feather = 0.5f;
185 const float innerA = std::max(0.f, halfWidthA - feather);
186 const float innerB = std::max(0.f, halfWidthB - feather);
187 const float outerA = halfWidthA + feather;
188 const float outerB = halfWidthB + feather;
191 if (innerA > 1e-6f || innerB > 1e-6f)
192 appendQuad(
output, makePositions(innerA, innerB),
color, distanceA, distanceB, commandIndex);
193 const std::array negative{
194 pixelToClipAt(
a -
normal * outerA, clipA, viewport), pixelToClipAt(
b -
normal * outerB, clipB, viewport),
195 pixelToClipAt(
a -
normal * innerA, clipA, viewport), pixelToClipAt(
b -
normal * innerB, clipB, viewport)};
196 appendColoredQuad(
output, negative, {transparent, transparent,
color,
color}, distanceA, distanceB, commandIndex);
197 const std::array positive{
198 pixelToClipAt(
a +
normal * innerA, clipA, viewport), pixelToClipAt(
b +
normal * innerB, clipB, viewport),
199 pixelToClipAt(
a +
normal * outerA, clipA, viewport), pixelToClipAt(
b +
normal * outerB, clipB, viewport)};
200 appendColoredQuad(
output, positive, {
color,
color, transparent, transparent}, distanceA, distanceB, commandIndex);
203void appendCapClip(ResolvedPrimitiveTriangles&
output, glm::vec4 pointClip, glm::vec2 outward,
float halfWidth,
205 if (cap ==
LineCap::Butt || std::fabs(pointClip.w) <= 1e-8f)
return;
206 const glm::vec2
point = clipToPixel(pointClip, viewport);
207 const glm::vec2
normal{-outward.y, outward.x};
217 constexpr std::uint32_t capSegments = 8;
218 const float base = std::atan2(outward.y, outward.x) - glm::half_pi<float>();
219 for (std::uint32_t segment = 0; segment < capSegments; ++segment) {
220 const float a = base + glm::pi<float>() *
static_cast<float>(segment) / capSegments;
221 const float b = base + glm::pi<float>() *
static_cast<float>(segment + 1) / capSegments;
222 appendTriangle(
output, pointClip,
223 pixelToClipAt(
point + glm::vec2(std::cos(
a), std::sin(
a)) *
halfWidth, pointClip, viewport),
224 pixelToClipAt(
point + glm::vec2(std::cos(
b), std::sin(
b)) *
halfWidth, pointClip, viewport),
229void appendJoinClip(ResolvedPrimitiveTriangles&
output, glm::vec4 pointClip, glm::vec2 previousDirection,
231 std::uint32_t commandIndex, glm::ivec2 viewport) {
232 if (std::fabs(pointClip.w) <= 1e-8f)
return;
233 const glm::vec2
point = clipToPixel(pointClip, viewport);
234 const float turn = previousDirection.x * nextDirection.y - previousDirection.y * nextDirection.x;
235 if (std::fabs(
turn) <= 1e-5f)
return;
236 const float outerSign =
turn > 0.f ? -1.f : 1.f;
237 const glm::vec2 previousOuter = glm::vec2(-previousDirection.y, previousDirection.x) * outerSign;
238 const glm::vec2 nextOuter = glm::vec2(-nextDirection.y, nextDirection.x) * outerSign;
239 const glm::vec2 previousPoint =
point + previousOuter *
halfWidth;
242 float start = std::atan2(previousOuter.y, previousOuter.x);
243 float sweep = std::atan2(nextOuter.y, nextOuter.x) -
start;
244 if (
turn > 0.f && sweep < 0.f) sweep += glm::two_pi<float>();
245 if (turn < 0.f && sweep > 0.f) sweep -= glm::two_pi<float>();
246 const std::uint32_t segments = std::max(1u,
static_cast<std::uint32_t
>(std::ceil(std::fabs(sweep) * 4.f)));
247 for (std::uint32_t segment = 0; segment < segments; ++segment) {
248 const float a =
start + sweep *
static_cast<float>(segment) / segments;
249 const float b =
start + sweep *
static_cast<float>(segment + 1) / segments;
250 appendTriangle(
output, pointClip,
251 pixelToClipAt(
point + glm::vec2(std::cos(
a), std::sin(
a)) *
halfWidth, pointClip, viewport),
252 pixelToClipAt(
point + glm::vec2(std::cos(
b), std::sin(
b)) *
halfWidth, pointClip, viewport),
258 const glm::vec2 bisector = glm::normalize(previousOuter + nextOuter);
259 const float denominator = glm::dot(bisector, previousOuter);
260 if (std::fabs(denominator) > 1e-5f) {
261 const float miterLength =
halfWidth / denominator;
262 if (std::fabs(miterLength) <= stroke.miterLimit *
halfWidth) {
263 appendTriangle(
output, pixelToClipAt(previousPoint, pointClip, viewport),
264 pixelToClipAt(
point + bisector * miterLength, pointClip, viewport),
265 pixelToClipAt(nextPoint, pointClip, viewport),
color,
distance, commandIndex);
270 appendTriangle(
output, pointClip, pixelToClipAt(previousPoint, pointClip, viewport),
271 pixelToClipAt(nextPoint, pointClip, viewport),
color,
distance, commandIndex);
274float projectedWorldHalfWidth(glm::vec3 viewPoint, glm::vec3
tangent,
float worldWidth,
275 const SceneDrawContext&
context) {
276 if (glm::length(
tangent) <= 1e-6f)
return 0.f;
277 glm::vec3
normal = glm::cross(glm::normalize(
tangent), glm::vec3(0.f, 0.f, -1.f));
278 if (glm::length(
normal) <= 1e-6f)
normal = glm::vec3(0.f, 1.f, 0.f);
280 const glm::vec4 centerClip =
context.projection * glm::vec4(viewPoint, 1.f);
281 const glm::vec4 edgeClip =
context.projection * glm::vec4(viewPoint +
normal, 1.f);
282 if (std::fabs(centerClip.w) <= 1e-8f || std::fabs(edgeClip.w) <= 1e-8f)
return 0.f;
283 return glm::length(clipToPixel(edgeClip,
context.viewportSize) - clipToPixel(centerClip,
context.viewportSize));
286struct VisibleDashSpan {
291std::vector<VisibleDashSpan> visibleDashSpans(
float distanceA,
float distanceB,
292 const std::optional<DashPattern>& dash) {
293 if (!dash || distanceB - distanceA <= 1e-8f)
return {{0.f, 1.f}};
295 const float period = dash->period();
296 const float length = distanceB - distanceA;
297 float patternPosition = std::fmod(distanceA + dash->phase, period);
298 if (patternPosition < 0.f) patternPosition += period;
300 std::size_t intervalIndex = 0;
301 while (patternPosition >= dash->intervals[intervalIndex] && intervalIndex + 1 < dash->intervals.size()) {
302 patternPosition -= dash->intervals[intervalIndex++];
305 std::vector<VisibleDashSpan> spans;
308 const float available = dash->intervals[intervalIndex] - patternPosition;
310 if (intervalIndex % 2 == 0 && next >
cursor) {
314 patternPosition = 0.f;
315 intervalIndex = (intervalIndex + 1) % dash->intervals.size();
321 float pathDistanceA,
float pathDistanceB,
float dashDistanceA,
float dashDistanceB,
322 const std::optional<DashPattern>& dash, std::uint32_t commandIndex) {
323 for (
const VisibleDashSpan span : visibleDashSpans(dashDistanceA, dashDistanceB, dash)) {
324 std::array<glm::vec4, 4> clipped{
327 appendQuad(
output, clipped,
color, glm::mix(pathDistanceA, pathDistanceB, span.begin),
328 glm::mix(pathDistanceA, pathDistanceB, span.end), commandIndex);
332glm::vec2 transformedPoint(
const glm::mat3& transform, glm::vec2
point) {
333 const glm::vec3 result = transform * glm::vec3(
point, 1.f);
334 return {result.x, result.y};
337bool clipViewSegmentToNear(glm::vec3&
a, glm::vec3&
b,
float& distanceA,
float& distanceB,
float nearPlane) {
338 const float planeZ = -nearPlane;
339 const bool aVisible =
a.z <= planeZ;
340 const bool bVisible =
b.z <= planeZ;
341 if (!aVisible && !bVisible)
return false;
342 if (aVisible && bVisible)
return true;
343 const glm::vec3 originalA =
a;
344 const glm::vec3 originalB =
b;
345 const float originalDistanceA = distanceA;
346 const float originalDistanceB = distanceB;
347 const float t = (planeZ - originalA.z) / (originalB.z - originalA.z);
348 const glm::vec3 intersection = originalA + (originalB - originalA) *
t;
349 const float intersectionDistance = originalDistanceA + (originalDistanceB - originalDistanceA) *
t;
352 distanceA = intersectionDistance;
355 distanceB = intersectionDistance;
360std::vector<glm::vec3> clipViewPolygonToNear(std::span<const glm::vec3>
input,
float nearPlane) {
361 std::vector<glm::vec3>
output;
363 const float planeZ = -nearPlane;
365 bool previousInside =
previous.z <= planeZ;
367 const bool currentInside =
current.z <= planeZ;
368 if (currentInside != previousInside) {
374 previousInside = currentInside;
382 const bool validViewport = viewport.x > 0 && viewport.y > 0;
383 EV_PARAM_CHECK(validViewport,
"primitive 2D resolve viewport must be positive");
387 for (std::size_t triangleIndex = 0; triangleIndex < canvas.
triangles().
size(); ++triangleIndex) {
390 for (std::size_t pointIndex = 0; pointIndex <
triangle.points.size(); ++pointIndex) {
392 const glm::vec2 transformed = transformedPoint(
triangle.transform,
point);
393 const glm::vec4
clip(transformed.x * 2.f /
static_cast<float>(viewport.x) - 1.f,
394 transformed.y * 2.f /
static_cast<float>(viewport.y) - 1.f, 0.f, 1.f);
396 output.vertices.push_back({
clip,
color, 0.f, 0.f,
static_cast<std::uint32_t
>(triangleIndex)});
398 ++
output.statistics.triangleCount;
399 output.batches2D.push_back(
403 for (std::size_t commandIndex = 0; commandIndex < canvas.
commands().size(); ++commandIndex) {
404 const auto& command = canvas.
commands()[commandIndex];
406 const std::size_t segmentCount = command.points.size() - 1 + (command.closed ? 1u : 0
u);
407 std::vector<glm::vec2> transformed;
408 transformed.reserve(command.points.size());
409 for (
const glm::vec2
point : command.points) transformed.push_back(transformedPoint(command.transform,
point));
410 for (std::size_t segment = 0; segment < segmentCount; ++segment) {
411 const std::size_t next = (segment + 1) % command.points.size();
412 const glm::vec2
a = transformed[segment];
413 const glm::vec2
b = transformed[next];
416 if (!std::isfinite(
length) ||
length <= 1e-6f)
continue;
417 const float width = command.paint.stroke.width;
419 const glm::vec2 unitNormal{-unitDirection.y, unitDirection.x};
421 const float distanceA = command.cumulativeLengths[segment];
422 const float distanceB = command.cumulativeLengths[segment + 1];
423 const auto spans = visibleDashSpans(distanceA, distanceB, command.paint.stroke.dash);
424 for (
const VisibleDashSpan span : spans) {
425 const glm::vec2 spanA = glm::mix(
a,
b, span.begin);
426 const glm::vec2 spanB = glm::mix(
a,
b, span.end);
427 const float spanDistanceA = glm::mix(distanceA, distanceB, span.begin);
428 const float spanDistanceB = glm::mix(distanceA, distanceB, span.end);
429 appendAntialiasedStroke2D(
output, spanA, spanB, unitNormal,
halfWidth, command.paint.color,
430 spanDistanceA, spanDistanceB,
static_cast<std::uint32_t
>(commandIndex),
431 viewport, command.paint.antialias);
432 if (command.paint.stroke.dash) {
433 appendCap2D(
output, spanA, -unitDirection,
halfWidth, command.paint.stroke.cap, command.paint.color,
434 spanDistanceA,
static_cast<std::uint32_t
>(commandIndex), viewport);
435 appendCap2D(
output, spanB, unitDirection,
halfWidth, command.paint.stroke.cap, command.paint.color,
436 spanDistanceB,
static_cast<std::uint32_t
>(commandIndex), viewport);
440 if (!command.paint.stroke.dash) {
441 for (std::size_t
point = command.closed ? 0
u : 1u;
442 point < (command.closed ? transformed.size() : transformed.size() - 1u); ++
point) {
443 const std::size_t
previous = (
point + transformed.size() - 1u) % transformed.size();
444 const std::size_t next = (
point + 1u) % transformed.size();
445 const glm::vec2 previousDirection = glm::normalize(transformed[
point] - transformed[
previous]);
446 const glm::vec2 nextDirection = glm::normalize(transformed[next] - transformed[
point]);
447 appendJoin2D(
output, transformed[
point], previousDirection, nextDirection,
448 command.paint.stroke.width * 0.5f, command.paint.stroke, command.paint.color,
449 command.cumulativeLengths[
point],
static_cast<std::uint32_t
>(commandIndex), viewport);
451 if (!command.closed) {
452 const glm::vec2 startDirection = glm::normalize(transformed[1] - transformed[0]);
454 glm::normalize(transformed.back() - transformed[transformed.size() - 2u]);
455 appendCap2D(
output, transformed.front(), -startDirection, command.paint.stroke.width * 0.5f,
456 command.paint.stroke.cap, command.paint.color, command.cumulativeLengths.front(),
457 static_cast<std::uint32_t
>(commandIndex), viewport);
458 appendCap2D(
output, transformed.back(),
endDirection, command.paint.stroke.width * 0.5f,
459 command.paint.stroke.cap, command.paint.color, command.cumulativeLengths.back(),
460 static_cast<std::uint32_t
>(commandIndex), viewport);
464 output.batches2D.push_back(
481 std::array<glm::vec3, 3> viewPoints{};
482 for (std::size_t i = 0; i < viewPoints.size(); ++i)
483 viewPoints[i] = glm::vec3(modelView * glm::vec4(
triangle.points[i], 1.f));
484 const std::vector<glm::vec3> clipped = clipViewPolygonToNear(viewPoints,
context.nearPlane);
485 if (clipped.size() < 3) {
490 float depthSum = 0.f;
491 std::size_t depthCount = 0;
492 const std::uint32_t batchIndex =
static_cast<std::uint32_t
>(
output.batches3D.size());
493 for (std::size_t i = 1; i + 1 < clipped.size(); ++i) {
494 const std::array fan{clipped[0], clipped[i], clipped[i + 1]};
495 for (
const glm::vec3 viewPoint : fan) {
496 const glm::vec4
clip =
context.projection * glm::vec4(viewPoint, 1.f);
498 if (std::fabs(
clip.w) > 1e-8f) {
503 ++
output.statistics.triangleCount;
506 depthCount ? depthSum /
static_cast<float>(depthCount) : 1.f,
sequence++});
509 for (std::size_t commandIndex = 0; commandIndex < canvas.
commands().size(); ++commandIndex) {
510 const auto& command = canvas.
commands()[commandIndex];
512 const std::size_t segmentCount = command.points.size() - 1 + (command.closed ? 1u : 0
u);
513 const glm::mat4 modelView =
context.view * command.transform;
514 std::vector<glm::vec3> viewPoints;
515 viewPoints.reserve(command.points.size());
516 for (
const glm::vec3
point : command.points) viewPoints.push_back(glm::vec3(modelView * glm::vec4(
point, 1.f)));
517 float cumulativeScreenDistance = 0.f;
518 for (std::size_t segment = 0; segment < segmentCount; ++segment) {
519 const std::size_t next = (segment + 1) % command.points.size();
520 glm::vec3 viewA = viewPoints[segment];
521 glm::vec3 viewB = viewPoints[next];
522 float distanceA = command.cumulativeWorldLengths[segment];
523 float distanceB = command.cumulativeWorldLengths[segment + 1];
524 if (!clipViewSegmentToNear(viewA, viewB, distanceA, distanceB,
context.nearPlane))
continue;
526 const glm::vec4 clipA =
context.projection * glm::vec4(viewA, 1.f);
527 const glm::vec4 clipB =
context.projection * glm::vec4(viewB, 1.f);
528 if (std::fabs(clipA.w) <= 1e-8f || std::fabs(clipB.w) <= 1e-8f)
continue;
529 const glm::vec2 ndcA = glm::vec2(clipA) / clipA.w;
530 const glm::vec2 ndcB = glm::vec2(clipB) / clipB.w;
531 const float screenLength = glm::length((ndcB - ndcA) * glm::vec2(
context.viewportSize) * 0.5f);
532 if (!std::isfinite(screenLength) || screenLength <= 1e-6f)
continue;
539 const glm::vec2 ndcOffset = pixelNormal * command.paint.stroke.width / glm::vec2(
context.viewportSize);
540 positions = {glm::vec4((ndcA - ndcOffset) * clipA.w, clipA.z, clipA.w),
541 glm::vec4((ndcB - ndcOffset) * clipB.w, clipB.z, clipB.w),
542 glm::vec4((ndcA + ndcOffset) * clipA.w, clipA.z, clipA.w),
543 glm::vec4((ndcB + ndcOffset) * clipB.w, clipB.z, clipB.w)};
545 const glm::vec3
direction = glm::normalize(viewB - viewA);
547 if (glm::length(
normal) <= 1e-6f)
normal = glm::vec3(0.f, 1.f, 0.f);
548 normal = glm::normalize(
normal) * (command.paint.stroke.width * 0.5f);
554 const bool screenDash =
556 const float dashDistanceA = screenDash ? cumulativeScreenDistance : distanceA;
557 const float dashDistanceB = screenDash ? cumulativeScreenDistance + screenLength : distanceB;
558 const std::uint32_t batchIndex =
static_cast<std::uint32_t
>(
output.batches3D.size());
559 const glm::vec2 pixelA = clipToPixel(clipA,
context.viewportSize);
560 const glm::vec2 pixelB = clipToPixel(clipB,
context.viewportSize);
561 const glm::vec2 pixelDirection = glm::normalize(pixelB - pixelA);
562 const float halfWidthA = glm::length(clipToPixel(
positions[0],
context.viewportSize) - pixelA);
563 const float halfWidthB = glm::length(clipToPixel(
positions[1],
context.viewportSize) - pixelB);
564 for (
const VisibleDashSpan span :
565 visibleDashSpans(dashDistanceA, dashDistanceB, command.paint.stroke.dash)) {
566 const float spanDistanceA = glm::mix(distanceA, distanceB, span.begin);
567 const float spanDistanceB = glm::mix(distanceA, distanceB, span.end);
568 appendAntialiasedStrokeClip(
569 output, glm::mix(clipA, clipB, span.begin), glm::mix(clipA, clipB, span.end), pixelDirection,
570 glm::mix(halfWidthA, halfWidthB, span.begin), glm::mix(halfWidthA, halfWidthB, span.end),
571 command.paint.color, spanDistanceA, spanDistanceB, batchIndex,
context.viewportSize,
572 command.paint.antialias);
573 if (command.paint.stroke.dash) {
574 appendCapClip(
output, glm::mix(clipA, clipB, span.begin), -pixelDirection,
575 glm::mix(halfWidthA, halfWidthB, span.begin), command.paint.stroke.cap,
576 command.paint.color, spanDistanceA, batchIndex,
context.viewportSize);
577 appendCapClip(
output, glm::mix(clipA, clipB, span.end), pixelDirection,
578 glm::mix(halfWidthA, halfWidthB, span.end), command.paint.stroke.cap,
579 command.paint.color, spanDistanceB, batchIndex,
context.viewportSize);
582 cumulativeScreenDistance += screenLength;
584 if (!command.paint.stroke.dash) {
585 const std::uint32_t batchIndex =
static_cast<std::uint32_t
>(
output.batches3D.size());
586 for (std::size_t
point = command.closed ? 0
u : 1u;
587 point < (command.closed ? viewPoints.size() : viewPoints.size() - 1u); ++
point) {
589 const std::size_t
previous = (
point + viewPoints.size() - 1u) % viewPoints.size();
590 const std::size_t next = (
point + 1u) % viewPoints.size();
591 const glm::vec4 previousClip =
context.projection * glm::vec4(viewPoints[
previous], 1.f);
592 const glm::vec4 pointClip =
context.projection * glm::vec4(viewPoints[
point], 1.f);
593 const glm::vec4 nextClip =
context.projection * glm::vec4(viewPoints[next], 1.f);
594 if (previousClip.w <= 1e-8f || pointClip.w <= 1e-8f || nextClip.w <= 1e-8f)
continue;
595 const glm::vec2 previousDirection = glm::normalize(clipToPixel(pointClip,
context.viewportSize) -
596 clipToPixel(previousClip,
context.viewportSize));
597 const glm::vec2 nextDirection = glm::normalize(clipToPixel(nextClip,
context.viewportSize) -
598 clipToPixel(pointClip,
context.viewportSize));
599 float halfWidth = command.paint.stroke.width * 0.5f;
602 command.paint.stroke.width,
context);
604 appendJoinClip(
output, pointClip, previousDirection, nextDirection,
halfWidth, command.paint.stroke,
605 command.paint.color, command.cumulativeWorldLengths[
point], batchIndex,
608 if (!command.closed) {
609 const auto appendEndpoint = [&](std::size_t
point, std::size_t adjacent) {
611 const glm::vec4 pointClip =
context.projection * glm::vec4(viewPoints[
point], 1.f);
612 const glm::vec4 adjacentClip =
context.projection * glm::vec4(viewPoints[adjacent], 1.f);
613 if (pointClip.w <= 1e-8f || adjacentClip.w <= 1e-8f)
return;
614 glm::vec2
direction = glm::normalize(clipToPixel(adjacentClip,
context.viewportSize) -
615 clipToPixel(pointClip,
context.viewportSize));
616 float halfWidth = command.paint.stroke.width * 0.5f;
618 halfWidth = projectedWorldHalfWidth(viewPoints[
point], viewPoints[adjacent] - viewPoints[
point],
619 command.paint.stroke.width,
context);
621 command.paint.color, command.cumulativeWorldLengths[
point], batchIndex,
624 appendEndpoint(0
u, 1u);
625 appendEndpoint(viewPoints.size() - 1u, viewPoints.size() - 2u);
629 float depthSum = 0.f;
630 for (std::size_t vertex =
firstVertex; vertex <
output.vertices.size(); ++vertex) {
631 const glm::vec4
clip =
output.vertices[vertex].clipPosition;
632 if (std::fabs(
clip.w) > 1e-8f) depthSum +=
clip.z /
clip.w;
642 output.statistics.batchCount =
output.vertices.empty() ? 0
u : 1u;
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
AuthorityStoreHandleRef reference
std::uint32_t firstVertex
std::vector< float > positions
graphics::Canvas * previous
std::unique_ptr< gpgpu::Sequence > sequence
std::vector< float > colors
RoadLaneDirection direction
TacticalUnit::TurnResources turn
const VegetationPresetContext & context
Frame-local Skia-style recorder for 2D line primitives.
const std::vector< TriangleCommand2D > & triangles() const noexcept
Triangles.
const std::vector< PolylineCommand2D > & commands() const noexcept
Commands.
const PrimitiveDrawStatistics & statistics() const noexcept
Statistics.
Frame-local scene recorder for 3D line primitives.
const std::vector< TriangleCommand3D > & triangles() const noexcept
Triangles.
const std::vector< PolylineCommand3D > & commands() const noexcept
Commands.
const SceneDrawContext & context() const noexcept
Context.
const PrimitiveDrawStatistics & statistics() const noexcept
Statistics.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
LineCap
Shape applied to each open line endpoint.
ResolvedPrimitiveTriangles resolvePrimitiveStrokes2D(const PrimitiveCanvas2D &canvas, glm::ivec2 viewport)
Resolves 2D stroke segment bodies into clip-space triangles.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
ResolvedPrimitiveTriangles resolvePrimitiveStrokes3D(const PrimitiveSceneCanvas3D &canvas)
Resolves 3D stroke segment bodies with near-plane clipping.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
WidgetDesc viewport(std::string id, float width, float height)
Viewport.
Resolved triangle vertex consumed by primitive GPU backends.
Owning backend-neutral triangle stream for one primitive pass.
PrimitiveDrawStatistics statistics
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.
One owning filled 3D triangle command.