载入中...
搜索中...
未找到
SpriteStack.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "image/ImageData.h"
5
6#include <algorithm>
7#include <cmath>
8#include <cstring>
9#include <glm/glm.hpp>
10#include <string>
11#include <utility>
12#include <vector>
13
14namespace eve::spritestack {
15namespace {
16
17constexpr float kPi = 3.14159265358979323846f;
18
19float clampf(float x, float lo, float hi) { return std::min(hi, std::max(lo, x)); }
20
21struct Projection {
22 int u = 0; // model axis -> image column
23 int v = 0; // model axis -> image row
24 int d = 0; // model axis -> slice depth
25};
26
27Projection projectionForAxis(const std::string &axis) {
28 if (axis == "x") return {1, 2, 0};
29 if (axis == "y") return {0, 2, 1}; // top-down layers
30 return {0, 1, 2}; // vertical bread slices (default)
31}
32
33struct Tri2D {
34 glm::vec2 px[3]{};
35 float depth[3]{};
36 image::ImageData::Colorf color{1.f, 1.f, 1.f, 1.f};
37};
38
50void rasterizeCrossSection(const std::vector<Tri2D> &tris, float dc, int w, int h,
51 uint8_t *rgba) {
52 const size_t pixelCount = size_t(w) * size_t(h);
53 std::vector<std::vector<float>> hits(pixelCount);
54 std::vector<float> bestY(pixelCount, 1e30f);
55 std::vector<uint32_t> bestRGB(pixelCount, 0xffffffu);
56
57 for (const auto &tri : tris) {
58 const glm::vec2 &a = tri.px[0];
59 const glm::vec2 &b = tri.px[1];
60 const glm::vec2 &c = tri.px[2];
61 int x0 = int(std::floor(std::min(a.x, std::min(b.x, c.x))));
62 int x1 = int(std::ceil(std::max(a.x, std::max(b.x, c.x))));
63 int y0 = int(std::floor(std::min(a.y, std::min(b.y, c.y))));
64 int y1 = int(std::ceil(std::max(a.y, std::max(b.y, c.y))));
65 x0 = std::max(0, x0);
66 y0 = std::max(0, y0);
67 x1 = std::min(w - 1, x1);
68 y1 = std::min(h - 1, y1);
69
70 const glm::vec2 e0 = b - a;
71 const glm::vec2 e1 = c - a;
72 const float denom = e0.x * e1.y - e0.y * e1.x;
73 if (std::fabs(denom) < 1e-12f) continue;
74
75 for (int y = y0; y <= y1; ++y) {
76 for (int x = x0; x <= x1; ++x) {
77 const glm::vec2 p(float(x) + 0.5f, float(y) + 0.5f);
78 const glm::vec2 d = p - a;
79 const float u = (d.x * e1.y - d.y * e1.x) / denom;
80 const float v = (e0.x * d.y - e0.y * d.x) / denom;
81 if (u < -1e-4f || v < -1e-4f || u + v > 1.f + 1e-4f) continue;
82 const float w0 = 1.f - u - v;
83 const float depthAt = w0 * tri.depth[0] + u * tri.depth[1] + v * tri.depth[2];
84 if (depthAt <= dc) continue;
85
86 const size_t i = size_t(y) * size_t(w) + size_t(x);
87 hits[i].push_back(depthAt);
88 if (depthAt < bestY[i]) {
89 bestY[i] = depthAt;
90 bestRGB[i] = (uint32_t(clampf(tri.color.r, 0.f, 1.f) * 255.f) << 16) |
91 (uint32_t(clampf(tri.color.g, 0.f, 1.f) * 255.f) << 8) |
92 uint32_t(clampf(tri.color.b, 0.f, 1.f) * 255.f);
93 }
94 }
95 }
96 }
97
98 std::vector<float> uniqueHits;
99 for (size_t i = 0; i < pixelCount; ++i) {
100 if (hits[i].empty()) continue;
101 std::sort(hits[i].begin(), hits[i].end());
102 uniqueHits.clear();
103 for (float d : hits[i]) {
104 // Shared edges/vertices report the same surface with slightly
105 // different interpolated depths (float weight sums); group them.
106 if (uniqueHits.empty() ||
107 d - uniqueHits.back() > 1e-5f * std::max(1.f, std::fabs(d)))
108 uniqueHits.push_back(d);
109 }
110 if (uniqueHits.size() % 2 == 0) continue; // even = outside the solid
111 uint8_t *px = rgba + i * 4;
112 px[0] = uint8_t(bestRGB[i] >> 16);
113 px[1] = uint8_t(bestRGB[i] >> 8);
114 px[2] = uint8_t(bestRGB[i]);
115 px[3] = 255;
116 }
117}
118
119std::vector<image::ImageData *> sliceArrays(const float *pos, const float *nrm, const float *rgb,
120 int vertexCount, const uint32_t *indices, int indexCount,
121 const SliceOptions &opt) {
122 if (!pos || vertexCount < 3 || !indices || indexCount < 3)
123 throw eve::Exception("SpriteStack.sliceMesh: invalid mesh arrays");
124 if (opt.layerCount <= 0) throw eve::Exception("SpriteStack.sliceMesh: layerCount must be > 0");
125 if (opt.imageW <= 0 || opt.imageH <= 0)
126 throw eve::Exception("SpriteStack.sliceMesh: image size must be > 0");
127
128 const Projection proj = projectionForAxis(opt.axis);
129
130 glm::vec3 mn(1e30f), mx(-1e30f);
131 for (int i = 0; i < vertexCount; ++i) {
132 const glm::vec3 p(pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]);
133 mn = glm::min(mn, p);
134 mx = glm::max(mx, p);
135 }
136 if (mn.x > mx.x) throw eve::Exception("SpriteStack.sliceMesh: empty mesh AABB");
137
138 const float d0 = mn[proj.d];
139 const float d1 = opt.thickness > 0.f ? d0 + opt.thickness * float(opt.layerCount - 1)
140 : mx[proj.d];
141 const float slab = opt.thickness > 0.f ? opt.thickness : (d1 - d0) / float(opt.layerCount);
142 if (slab <= 0.f) throw eve::Exception("SpriteStack.sliceMesh: zero slice thickness");
143
144 const float uMin = mn[proj.u] - opt.padding * std::max(1.f, mx[proj.u] - mn[proj.u]);
145 const float uMax = mx[proj.u] + opt.padding * std::max(1.f, mx[proj.u] - mn[proj.u]);
146 const float vMin = mn[proj.v] - opt.padding * std::max(1.f, mx[proj.v] - mn[proj.v]);
147 const float vMax = mx[proj.v] + opt.padding * std::max(1.f, mx[proj.v] - mn[proj.v]);
148 const float uSpan = uMax - uMin;
149 const float vSpan = vMax - vMin;
150 if (uSpan <= 0.f || vSpan <= 0.f)
151 throw eve::Exception("SpriteStack.sliceMesh: degenerate projected AABB");
152
153 const glm::vec3 viewDir =
154 proj.d == 0 ? glm::vec3(1.f, 0.f, 0.f)
155 : (proj.d == 1 ? glm::vec3(0.f, 1.f, 0.f) : glm::vec3(0.f, 0.f, 1.f));
156
157 std::vector<image::ImageData *> layers;
158 layers.reserve(size_t(opt.layerCount));
159 const int triangleCount = indexCount / 3;
160 std::vector<Tri2D> tris;
161 tris.reserve(size_t(triangleCount));
162
163 for (int t = 0; t < triangleCount; ++t) {
164 const uint32_t i0 = indices[t * 3];
165 const uint32_t i1 = indices[t * 3 + 1];
166 const uint32_t i2 = indices[t * 3 + 2];
167 if (int(i0) >= vertexCount || int(i1) >= vertexCount || int(i2) >= vertexCount) continue;
168
169 const glm::vec3 a(pos[i0 * 3], pos[i0 * 3 + 1], pos[i0 * 3 + 2]);
170 const glm::vec3 b(pos[i1 * 3], pos[i1 * 3 + 1], pos[i1 * 3 + 2]);
171 const glm::vec3 c(pos[i2 * 3], pos[i2 * 3 + 1], pos[i2 * 3 + 2]);
172
173 image::ImageData::Colorf color{opt.tintR, opt.tintG, opt.tintB, 1.f};
174 if (rgb) {
175 const float rr = (rgb[i0 * 3] + rgb[i1 * 3] + rgb[i2 * 3]) / 3.f;
176 const float gg = (rgb[i0 * 3 + 1] + rgb[i1 * 3 + 1] + rgb[i2 * 3 + 1]) / 3.f;
177 const float bb = (rgb[i0 * 3 + 2] + rgb[i1 * 3 + 2] + rgb[i2 * 3 + 2]) / 3.f;
178 color.r *= rr;
179 color.g *= gg;
180 color.b *= bb;
181 }
182 if (opt.shade) {
183 glm::vec3 n = glm::cross(b - a, c - a);
184 if (glm::dot(n, n) > 1e-12f) {
185 n = glm::normalize(n);
186 const float s = 0.72f + 0.28f * std::abs(glm::dot(n, viewDir));
187 color.r *= s;
188 color.g *= s;
189 color.b *= s;
190 }
191 }
192
193 Tri2D tri;
194 tri.px[0] = glm::vec2((a[proj.u] - uMin) / uSpan * float(opt.imageW - 1),
195 (1.f - (a[proj.v] - vMin) / vSpan) * float(opt.imageH - 1));
196 tri.px[1] = glm::vec2((b[proj.u] - uMin) / uSpan * float(opt.imageW - 1),
197 (1.f - (b[proj.v] - vMin) / vSpan) * float(opt.imageH - 1));
198 tri.px[2] = glm::vec2((c[proj.u] - uMin) / uSpan * float(opt.imageW - 1),
199 (1.f - (c[proj.v] - vMin) / vSpan) * float(opt.imageH - 1));
200 tri.depth[0] = a[proj.d];
201 tri.depth[1] = b[proj.d];
202 tri.depth[2] = c[proj.d];
203 tri.color = color;
204 tris.push_back(tri);
205 }
206
207 for (int li = 0; li < opt.layerCount; ++li) {
208 auto *img = new image::ImageData(opt.imageW, opt.imageH, "RGBA8");
209 auto *data = static_cast<uint8_t *>(img->getData());
210 std::memset(data, 0, size_t(opt.imageW) * size_t(opt.imageH) * 4);
211 const float dc = d0 + (float(li) + 0.5f) * slab;
212 rasterizeCrossSection(tris, dc, opt.imageW, opt.imageH, data);
213 layers.push_back(img);
214 }
215 return layers;
216}
217
218void makeBox(std::vector<float> &pos, std::vector<float> &nrm, std::vector<uint32_t> &idx) {
219 const float h = 0.5f;
220 const glm::vec3 corners[8] = {
221 {-h, -h, -h}, {h, -h, -h}, {h, h, -h}, {-h, h, -h},
222 {-h, -h, h}, {h, -h, h}, {h, h, h}, {-h, h, h},
223 };
224 const glm::vec3 faces[6] = {
225 {0.f, 0.f, -1.f}, {0.f, 0.f, 1.f}, {-1.f, 0.f, 0.f},
226 {1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 1.f, 0.f},
227 };
228 const int quads[6][4] = {
229 {0, 1, 2, 3}, {5, 4, 7, 6}, {4, 0, 3, 7},
230 {1, 5, 6, 2}, {4, 5, 1, 0}, {3, 2, 6, 7},
231 };
232 for (int f = 0; f < 6; ++f) {
233 for (int k = 0; k < 4; ++k) {
234 const glm::vec3 &p = corners[quads[f][k]];
235 pos.insert(pos.end(), {p.x, p.y, p.z});
236 nrm.insert(nrm.end(), {faces[f].x, faces[f].y, faces[f].z});
237 }
238 const uint32_t base = uint32_t(f * 4);
239 idx.insert(idx.end(), {base, base + 1, base + 2, base, base + 2, base + 3});
240 }
241}
242
243void makeLathe(const std::string &kind, int slices, std::vector<float> &pos,
244 std::vector<float> &nrm, std::vector<uint32_t> &idx) {
245 const bool sphere = kind == "sphere";
246 const bool cone = kind == "cone";
247 const int stacks = sphere ? 12 : 1;
248 auto ring = [&](float y, float radius, float nY) {
249 // Push `slices` vertices forming one horizontal ring.
250 const uint32_t base = uint32_t(pos.size() / 3);
251 for (int s = 0; s < slices; ++s) {
252 const float a = float(s) / float(slices) * 2.f * kPi;
253 const glm::vec3 n(std::cos(a), nY, std::sin(a));
254 pos.insert(pos.end(), {std::cos(a) * radius, y, std::sin(a) * radius});
255 nrm.insert(nrm.end(), {n.x, n.y, n.z});
256 }
257 return base;
258 };
259 auto connectRings = [&](uint32_t r0, uint32_t r1) {
260 for (int s = 0; s < slices; ++s) {
261 const uint32_t s0 = r0 + uint32_t(s);
262 const uint32_t s1 = r0 + uint32_t((s + 1) % slices);
263 const uint32_t t0 = r1 + uint32_t(s);
264 const uint32_t t1 = r1 + uint32_t((s + 1) % slices);
265 idx.insert(idx.end(), {s0, t0, t1, s0, t1, s1});
266 }
267 };
268 auto cap = [&](float y, float radius, float nY) {
269 const uint32_t center = uint32_t(pos.size() / 3);
270 pos.insert(pos.end(), {0.f, y, 0.f});
271 nrm.insert(nrm.end(), {0.f, nY, 0.f});
272 for (int s = 0; s < slices; ++s) {
273 const float a = float(s) / float(slices) * 2.f * kPi;
274 pos.insert(pos.end(), {std::cos(a) * radius, y, std::sin(a) * radius});
275 nrm.insert(nrm.end(), {0.f, nY, 0.f});
276 const uint32_t ring0 = center + 1;
277 const uint32_t a0 = ring0 + uint32_t(s);
278 const uint32_t b0 = ring0 + uint32_t((s + 1) % slices);
279 if (nY > 0.f)
280 idx.insert(idx.end(), {center, b0, a0});
281 else
282 idx.insert(idx.end(), {center, a0, b0});
283 }
284 };
285
286 if (sphere) {
287 std::vector<uint32_t> rings;
288 for (int st = 0; st < stacks; ++st) {
289 const float t0 = float(st) / float(stacks);
290 const float y0 = std::cos(t0 * kPi) * 0.5f;
291 const float r0 = std::sin(t0 * kPi) * 0.5f;
292 rings.push_back(ring(y0, r0, -std::sin(t0 * kPi)));
293 }
294 for (int st = 0; st + 1 < stacks; ++st)
295 connectRings(rings[size_t(st)], rings[size_t(st + 1)]);
296 return;
297 }
298
299 if (cone) {
300 const uint32_t apex = uint32_t(pos.size() / 3);
301 pos.insert(pos.end(), {0.f, 0.5f, 0.f});
302 nrm.insert(nrm.end(), {0.f, 1.f, 0.f});
303 for (int s = 0; s < slices; ++s) {
304 const float a0 = float(s) / float(slices) * 2.f * kPi;
305 const float a1 = float(s + 1) / float(slices) * 2.f * kPi;
306 const uint32_t b0 = uint32_t(pos.size() / 3);
307 pos.insert(pos.end(), {std::cos(a0) * 0.5f, -0.5f, std::sin(a0) * 0.5f});
308 nrm.insert(nrm.end(), {std::cos(a0) * 0.7071f, 0.7071f, std::sin(a0) * 0.7071f});
309 pos.insert(pos.end(), {std::cos(a1) * 0.5f, -0.5f, std::sin(a1) * 0.5f});
310 nrm.insert(nrm.end(), {std::cos(a1) * 0.7071f, 0.7071f, std::sin(a1) * 0.7071f});
311 idx.insert(idx.end(), {apex, b0, b0 + 1});
312 }
313 cap(-0.5f, 0.5f, -1.f);
314 return;
315 }
316
317 // cylinder
318 const uint32_t bottom = ring(-0.5f, 0.5f, 0.f);
319 const uint32_t top = ring(0.5f, 0.5f, 0.f);
320 connectRings(bottom, top);
321 cap(0.5f, 0.5f, 1.f);
322 cap(-0.5f, 0.5f, -1.f);
323}
324
325} // namespace
326
327std::vector<image::ImageData *> sliceMeshToLayers(const SliceInput &input, const SliceOptions &opt) {
328 return sliceArrays(input.posXYZ, input.nrmXYZ, input.rgb, input.vertexCount, input.indices,
329 input.indexCount, opt);
330}
331
332std::vector<image::ImageData *> slicePrimitiveToLayers(const std::string &kind,
333 const SliceOptions &opt) {
334 std::vector<float> pos, nrm;
335 std::vector<uint32_t> idx;
336 if (kind == "box") {
337 makeBox(pos, nrm, idx);
338 } else if (kind == "cylinder" || kind == "sphere" || kind == "cone") {
339 makeLathe(kind, 32, pos, nrm, idx);
340 } else {
341 throw eve::Exception("SpriteStack.slicePrimitive: unknown kind '%s' (box|cylinder|sphere|cone)",
342 kind.c_str());
343 }
344 SliceInput in{};
345 in.posXYZ = pos.data();
346 in.nrmXYZ = nrm.data();
347 in.vertexCount = int(pos.size() / 3);
348 in.indices = idx.data();
349 in.indexCount = int(idx.size());
350 return sliceArrays(in.posXYZ, in.nrmXYZ, nullptr, in.vertexCount, in.indices, in.indexCount, opt);
351}
352
353// ---------------------------------------------------------------------------
354// Legacy 3D renderer removed: SpriteStack is now a pure 2D technique. Keep the
355// original implementation excluded temporarily so the slicer above remains a
356// reviewable, behavior-preserving move while the 2D renderer lives in its own TU.
357} // namespace eve::spritestack
SQInteger top
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t vertexCount
std::uint32_t indexCount
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::vector< std::uint32_t > indices
float v
std::int32_t c
int h
std::vector< Colorf > px
TokenKind kind
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int idx
float f
float radius
float begin
float d
float t
glm::mat4 proj
std::uint32_t depth
float bottom
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
medialoader::Colorf Colorf
Definition ImageData.h:41
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
std::vector< image::ImageData * > slicePrimitiveToLayers(const std::string &kind, const SliceOptions &opt)
Slice primitive to layers.
std::vector< image::ImageData * > sliceMeshToLayers(const SliceInput &input, const SliceOptions &opt)
Slice mesh to layers.
int axis(int64_t a, size_t rank)
Axis.
CPU triangle-mesh input used to bake horizontal RGBA sprite slices.
Definition SpriteStack.h:26
Options for baking a mesh into equal-sized RGBA sprite layers.
Definition SpriteStack.h:36
glm::vec4 color