载入中...
搜索中...
未找到
PrimitiveTessellator.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
12namespace eve::graphics {
13namespace {
14
15void appendQuad(ResolvedPrimitiveTriangles& output, const std::array<glm::vec4, 4>& positions, Color color,
16 float distanceA, float distanceB, std::uint32_t commandIndex) {
17 const std::array<std::uint8_t, 6> order{0, 1, 2, 1, 3, 2};
18 for (std::uint8_t index : order) {
19 const bool end = index == 1 || index == 3;
20 const bool positiveEdge = index == 2 || index == 3;
21 output.vertices.push_back(
22 {positions[index], color, end ? distanceB : distanceA, positiveEdge ? 1.f : -1.f, commandIndex});
23 }
24 ++output.statistics.triangleCount;
25 ++output.statistics.triangleCount;
26}
27
28void appendTriangle(ResolvedPrimitiveTriangles& output, glm::vec4 a, glm::vec4 b, glm::vec4 c, Color color,
29 float distance, std::uint32_t commandIndex) {
30 output.vertices.push_back({a, color, distance, 0.f, commandIndex});
31 output.vertices.push_back({b, color, distance, 0.f, commandIndex});
32 output.vertices.push_back({c, color, distance, 0.f, commandIndex});
33 ++output.statistics.triangleCount;
34}
35
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;
42}
43
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) {
47 appendColoredTriangle(output, positions[0], positions[1], positions[2], colors[0], colors[1], colors[2], distanceA,
48 distanceB, commandIndex);
49 appendColoredTriangle(output, positions[1], positions[3], positions[2], colors[1], colors[3], colors[2], distanceB,
50 distanceB, commandIndex);
51}
52
53glm::vec4 pixelToClip(glm::vec2 point, glm::ivec2 viewport) {
54 return {point.x * 2.f / static_cast<float>(viewport.x) - 1.f, point.y * 2.f / static_cast<float>(viewport.y) - 1.f,
55 0.f, 1.f};
56}
57
58void appendCap2D(ResolvedPrimitiveTriangles& output, glm::vec2 point, glm::vec2 outward, float halfWidth, LineCap cap,
59 Color color, float distance, std::uint32_t commandIndex, glm::ivec2 viewport) {
60 if (cap == LineCap::Butt) return;
61 const glm::vec2 normal{-outward.y, outward.x};
62 if (cap == LineCap::Square) {
63 const std::array positions{pixelToClip(point - normal * halfWidth, viewport),
64 pixelToClip(point + outward * halfWidth - normal * halfWidth, viewport),
65 pixelToClip(point + normal * halfWidth, viewport),
66 pixelToClip(point + outward * halfWidth + normal * halfWidth, viewport)};
67 appendQuad(output, positions, color, distance, distance, commandIndex);
68 return;
69 }
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),
77 pixelToClip(point + glm::vec2(std::cos(b), std::sin(b)) * halfWidth, viewport), color, distance,
78 commandIndex);
79 }
80}
81
82void appendJoin2D(ResolvedPrimitiveTriangles& output, glm::vec2 point, glm::vec2 previousDirection,
83 glm::vec2 nextDirection, float halfWidth, const StrokeStyle& stroke, Color color, float distance,
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;
92 const glm::vec2 previousPoint = point + previousOuter * halfWidth;
93 const glm::vec2 nextPoint = point + nextOuter * halfWidth;
94 if (stroke.join == LineJoin::Round) {
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),
105 pixelToClip(point + glm::vec2(std::cos(b), std::sin(b)) * halfWidth, viewport), color,
106 distance, commandIndex);
107 }
108 return;
109 }
110 if (stroke.join == LineJoin::Miter) {
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),
118 color, distance, commandIndex);
119 return;
120 }
121 }
122 }
123 appendTriangle(output, pixelToClip(point, viewport), pixelToClip(previousPoint, viewport),
124 pixelToClip(nextPoint, viewport), color, distance, commandIndex);
125}
126
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;
130}
131
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;
134 return {ndc.x * reference.w, ndc.y * reference.w, reference.z, reference.w};
135}
136
137void appendAntialiasedStroke2D(ResolvedPrimitiveTriangles& output, glm::vec2 a, glm::vec2 b, glm::vec2 unitNormal,
138 float halfWidth, Color color, float distanceA, float distanceB,
139 std::uint32_t commandIndex, glm::ivec2 viewport, bool antialias) {
140 if (!antialias) {
141 const std::array positions{
142 pixelToClip(a - unitNormal * halfWidth, viewport), pixelToClip(b - unitNormal * halfWidth, viewport),
143 pixelToClip(a + unitNormal * halfWidth, viewport), pixelToClip(b + unitNormal * halfWidth, viewport)};
144 appendQuad(output, positions, color, distanceA, distanceB, commandIndex);
145 return;
146 }
147 constexpr float feather = 0.5f;
148 const float inner = std::max(0.f, halfWidth - feather);
149 const float outer = halfWidth + feather;
150 Color transparent = color;
151 transparent.a = 0.f;
152 if (inner > 1e-6f) {
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);
157 }
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);
166}
167
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,
171 bool antialias) {
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) {
176 return std::array{
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)};
179 };
180 if (!antialias) {
181 appendQuad(output, makePositions(halfWidthA, halfWidthB), color, distanceA, distanceB, commandIndex);
182 return;
183 }
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;
189 Color transparent = color;
190 transparent.a = 0.f;
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);
201}
202
203void appendCapClip(ResolvedPrimitiveTriangles& output, glm::vec4 pointClip, glm::vec2 outward, float halfWidth,
204 LineCap cap, Color color, float distance, std::uint32_t commandIndex, glm::ivec2 viewport) {
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};
208 if (cap == LineCap::Square) {
209 const std::array positions{
210 pixelToClipAt(point - normal * halfWidth, pointClip, viewport),
211 pixelToClipAt(point + outward * halfWidth - normal * halfWidth, pointClip, viewport),
212 pixelToClipAt(point + normal * halfWidth, pointClip, viewport),
213 pixelToClipAt(point + outward * halfWidth + normal * halfWidth, pointClip, viewport)};
214 appendQuad(output, positions, color, distance, distance, commandIndex);
215 return;
216 }
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),
225 color, distance, commandIndex);
226 }
227}
228
229void appendJoinClip(ResolvedPrimitiveTriangles& output, glm::vec4 pointClip, glm::vec2 previousDirection,
230 glm::vec2 nextDirection, float halfWidth, const StrokeStyle& stroke, Color color, float distance,
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;
240 const glm::vec2 nextPoint = point + nextOuter * halfWidth;
241 if (stroke.join == LineJoin::Round) {
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),
253 color, distance, commandIndex);
254 }
255 return;
256 }
257 if (stroke.join == LineJoin::Miter) {
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);
266 return;
267 }
268 }
269 }
270 appendTriangle(output, pointClip, pixelToClipAt(previousPoint, pointClip, viewport),
271 pixelToClipAt(nextPoint, pointClip, viewport), color, distance, commandIndex);
272}
273
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);
279 normal = glm::normalize(normal) * (worldWidth * 0.5f);
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));
284}
285
286struct VisibleDashSpan {
287 float begin = 0.f;
288 float end = 1.f;
289};
290
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}};
294 dash->validate();
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;
299
300 std::size_t intervalIndex = 0;
301 while (patternPosition >= dash->intervals[intervalIndex] && intervalIndex + 1 < dash->intervals.size()) {
302 patternPosition -= dash->intervals[intervalIndex++];
303 }
304
305 std::vector<VisibleDashSpan> spans;
306 float cursor = 0.f;
307 while (cursor < length - 1e-6f) {
308 const float available = dash->intervals[intervalIndex] - patternPosition;
309 const float next = std::min(length, cursor + available);
310 if (intervalIndex % 2 == 0 && next > cursor) {
311 spans.push_back({cursor / length, next / length});
312 }
313 cursor = next;
314 patternPosition = 0.f;
315 intervalIndex = (intervalIndex + 1) % dash->intervals.size();
316 }
317 return spans;
318}
319
320void appendDashedQuad(ResolvedPrimitiveTriangles& output, const std::array<glm::vec4, 4>& positions, Color color,
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{
325 glm::mix(positions[0], positions[1], span.begin), glm::mix(positions[0], positions[1], span.end),
326 glm::mix(positions[2], positions[3], span.begin), glm::mix(positions[2], positions[3], span.end)};
327 appendQuad(output, clipped, color, glm::mix(pathDistanceA, pathDistanceB, span.begin),
328 glm::mix(pathDistanceA, pathDistanceB, span.end), commandIndex);
329 }
330}
331
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};
335}
336
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;
350 if (!aVisible) {
351 a = intersection;
352 distanceA = intersectionDistance;
353 } else {
354 b = intersection;
355 distanceB = intersectionDistance;
356 }
357 return true;
358}
359
360std::vector<glm::vec3> clipViewPolygonToNear(std::span<const glm::vec3> input, float nearPlane) {
361 std::vector<glm::vec3> output;
362 if (input.empty()) return output;
363 const float planeZ = -nearPlane;
364 glm::vec3 previous = input.back();
365 bool previousInside = previous.z <= planeZ;
366 for (const glm::vec3 current : input) {
367 const bool currentInside = current.z <= planeZ;
368 if (currentInside != previousInside) {
369 const float t = (planeZ - previous.z) / (current.z - previous.z);
370 output.push_back(previous + (current - previous) * t);
371 }
372 if (currentInside) output.push_back(current);
374 previousInside = currentInside;
375 }
376 return output;
377}
378
379} // namespace
380
382 const bool validViewport = viewport.x > 0 && viewport.y > 0;
383 EV_PARAM_CHECK(validViewport, "primitive 2D resolve viewport must be positive");
385 output.statistics = canvas.statistics();
386
387 for (std::size_t triangleIndex = 0; triangleIndex < canvas.triangles().size(); ++triangleIndex) {
388 const auto& triangle = canvas.triangles()[triangleIndex];
389 const std::size_t firstVertex = output.vertices.size();
390 for (std::size_t pointIndex = 0; pointIndex < triangle.points.size(); ++pointIndex) {
391 const glm::vec2 point = triangle.points[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);
395 const Color color = triangle.vertexColors ? (*triangle.vertexColors)[pointIndex] : triangle.paint.color;
396 output.vertices.push_back({clip, color, 0.f, 0.f, static_cast<std::uint32_t>(triangleIndex)});
397 }
398 ++output.statistics.triangleCount;
399 output.batches2D.push_back(
400 {firstVertex, output.vertices.size() - firstVertex, triangle.paint.blend, output.batches2D.size()});
401 }
402
403 for (std::size_t commandIndex = 0; commandIndex < canvas.commands().size(); ++commandIndex) {
404 const auto& command = canvas.commands()[commandIndex];
405 const std::size_t firstVertex = output.vertices.size();
406 const std::size_t segmentCount = command.points.size() - 1 + (command.closed ? 1u : 0u);
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];
414 const glm::vec2 direction = b - a;
415 const float length = glm::length(direction);
416 if (!std::isfinite(length) || length <= 1e-6f) continue;
417 const float width = command.paint.stroke.width;
418 const glm::vec2 unitDirection = direction / length;
419 const glm::vec2 unitNormal{-unitDirection.y, unitDirection.x};
420 const float halfWidth = width * 0.5f;
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);
437 }
438 }
439 }
440 if (!command.paint.stroke.dash) {
441 for (std::size_t point = command.closed ? 0u : 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);
450 }
451 if (!command.closed) {
452 const glm::vec2 startDirection = glm::normalize(transformed[1] - transformed[0]);
453 const glm::vec2 endDirection =
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);
461 }
462 }
463 if (output.vertices.size() > firstVertex)
464 output.batches2D.push_back(
465 {firstVertex, output.vertices.size() - firstVertex, command.paint.blend, output.batches2D.size()});
466 }
467 output.statistics.uploadBytes = output.vertices.size() * sizeof(PrimitiveTriangleVertex);
468 output.statistics.batchCount = output.batches2D.size();
469 return output;
470}
471
473 const SceneDrawContext& context = canvas.context();
476 output.statistics = canvas.statistics();
477
478 std::size_t sequence = 0;
479 for (const TriangleCommand3D& triangle : canvas.triangles()) {
480 const glm::mat4 modelView = context.view * triangle.transform;
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) {
486 ++sequence;
487 continue;
488 }
489 const std::size_t firstVertex = output.vertices.size();
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);
497 output.vertices.push_back({clip, triangle.paint.color, 0.f, 0.f, batchIndex});
498 if (std::fabs(clip.w) > 1e-8f) {
499 depthSum += clip.z / clip.w;
500 ++depthCount;
501 }
502 }
503 ++output.statistics.triangleCount;
504 }
505 output.batches3D.push_back({firstVertex, output.vertices.size() - firstVertex, triangle.paint,
506 depthCount ? depthSum / static_cast<float>(depthCount) : 1.f, sequence++});
507 }
508
509 for (std::size_t commandIndex = 0; commandIndex < canvas.commands().size(); ++commandIndex) {
510 const auto& command = canvas.commands()[commandIndex];
511 const std::size_t firstVertex = output.vertices.size();
512 const std::size_t segmentCount = command.points.size() - 1 + (command.closed ? 1u : 0u);
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;
525
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;
533
534 std::array<glm::vec4, 4> positions;
535 if (command.paint.stroke.widthSpace == WidthSpace::ScreenPixels) {
536 glm::vec2 direction = ndcB - ndcA;
537 direction = glm::normalize(direction * glm::vec2(context.viewportSize));
538 const glm::vec2 pixelNormal{-direction.y, direction.x};
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)};
544 } else {
545 const glm::vec3 direction = glm::normalize(viewB - viewA);
546 glm::vec3 normal = glm::cross(direction, glm::vec3(0.f, 0.f, -1.f));
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);
549 positions = {context.projection * glm::vec4(viewA - normal, 1.f),
550 context.projection * glm::vec4(viewB - normal, 1.f),
551 context.projection * glm::vec4(viewA + normal, 1.f),
552 context.projection * glm::vec4(viewB + normal, 1.f)};
553 }
554 const bool screenDash =
555 command.paint.stroke.dash && command.paint.stroke.dash->space == DashSpace::ScreenPixels;
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);
580 }
581 }
582 cumulativeScreenDistance += screenLength;
583 }
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 ? 0u : 1u;
587 point < (command.closed ? viewPoints.size() : viewPoints.size() - 1u); ++point) {
588 if (viewPoints[point].z > -context.nearPlane) continue;
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;
600 if (command.paint.stroke.widthSpace == WidthSpace::WorldUnits) {
601 halfWidth = projectedWorldHalfWidth(viewPoints[point], viewPoints[next] - viewPoints[previous],
602 command.paint.stroke.width, context);
603 }
604 appendJoinClip(output, pointClip, previousDirection, nextDirection, halfWidth, command.paint.stroke,
605 command.paint.color, command.cumulativeWorldLengths[point], batchIndex,
606 context.viewportSize);
607 }
608 if (!command.closed) {
609 const auto appendEndpoint = [&](std::size_t point, std::size_t adjacent) {
610 if (viewPoints[point].z > -context.nearPlane) return;
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;
617 if (command.paint.stroke.widthSpace == WidthSpace::WorldUnits)
618 halfWidth = projectedWorldHalfWidth(viewPoints[point], viewPoints[adjacent] - viewPoints[point],
619 command.paint.stroke.width, context);
620 appendCapClip(output, pointClip, -direction, halfWidth, command.paint.stroke.cap,
621 command.paint.color, command.cumulativeWorldLengths[point], batchIndex,
622 context.viewportSize);
623 };
624 appendEndpoint(0u, 1u);
625 appendEndpoint(viewPoints.size() - 1u, viewPoints.size() - 2u);
626 }
627 }
628 if (output.vertices.size() > firstVertex) {
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;
633 }
634 output.batches3D.push_back({firstVertex, output.vertices.size() - firstVertex, command.paint,
635 depthSum / static_cast<float>(output.vertices.size() - firstVertex),
636 sequence++});
637 } else {
638 ++sequence;
639 }
640 }
641 output.statistics.uploadBytes = output.vertices.size() * sizeof(PrimitiveTriangleVertex);
642 output.statistics.batchCount = output.vertices.empty() ? 0u : 1u;
643 return output;
644}
645
646} // namespace eve::graphics
Duration start
float z
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 output
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
EvpackChunkInput input
Definition Evpack.cpp:170
int triangle
std::uint32_t firstVertex
glm::vec4 clip
float u
Definition Grass.cpp:233
std::vector< float > positions
std::int32_t c
std::uint32_t width
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
std::vector< std::int32_t > order
Texture * normal
graphics::Canvas * previous
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
float halfWidth
float begin
float t
std::vector< float > colors
RoadLaneDirection direction
double current
TacticalUnit::TurnResources turn
std::size_t cursor
V3 endDirection
Definition TreeMesh.cpp:245
float size
Definition TreeMesh.cpp:156
uint32_t index
const VegetationPresetContext & context
glm::vec3 point
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:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
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...
Definition Color.h:13
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).
Definition HexMetrics.h:76
WidgetDesc viewport(std::string id, float width, float height)
Viewport.
Definition Widget.cpp:443
Resolved triangle vertex consumed by primitive GPU backends.
Owning backend-neutral triangle stream for one primitive pass.
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.
glm::vec4 color