17constexpr float kPi = 3.14159265358979323846f;
19float clampf(
float x,
float lo,
float hi) {
return std::min(hi, std::max(lo,
x)); }
27Projection projectionForAxis(
const std::string &axis) {
28 if (
axis ==
"x")
return {1, 2, 0};
29 if (axis ==
"y")
return {0, 2, 1};
50void rasterizeCrossSection(
const std::vector<Tri2D> &tris,
float dc,
int w,
int h,
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);
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))));
67 x1 = std::min(
w - 1, x1);
68 y1 = std::min(
h - 1, y1);
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;
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;
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]) {
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);
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());
103 for (
float d : hits[i]) {
106 if (uniqueHits.empty() ||
107 d - uniqueHits.back() > 1e-5f * std::max(1.f, std::fabs(
d)))
108 uniqueHits.push_back(
d);
110 if (uniqueHits.size() % 2 == 0)
continue;
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]);
119std::vector<image::ImageData *> sliceArrays(
const float *
pos,
const float *nrm,
const float *rgb,
121 const SliceOptions &opt) {
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");
128 const Projection
proj = projectionForAxis(opt.axis);
130 glm::vec3 mn(1e30f), mx(-1e30f);
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);
136 if (mn.x > mx.x)
throw eve::Exception(
"SpriteStack.sliceMesh: empty mesh AABB");
138 const float d0 = mn[
proj.d];
139 const float d1 = opt.thickness > 0.f ? d0 + opt.thickness * float(opt.layerCount - 1)
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");
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");
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));
157 std::vector<image::ImageData *> layers;
158 layers.reserve(
size_t(opt.layerCount));
160 std::vector<Tri2D> tris;
161 tris.reserve(
size_t(triangleCount));
163 for (
int t = 0;
t < triangleCount; ++
t) {
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;
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));
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];
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);
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},
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},
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},
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});
238 const uint32_t base = uint32_t(
f * 4);
239 idx.insert(
idx.end(), {base, base + 1, base + 2, base, base + 2, base + 3});
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) {
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});
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});
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);
280 idx.insert(
idx.end(), {center, b0, a0});
282 idx.insert(
idx.end(), {center, a0, b0});
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)));
294 for (
int st = 0; st + 1 < stacks; ++st)
295 connectRings(rings[
size_t(st)], rings[
size_t(st + 1)]);
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});
313 cap(-0.5f, 0.5f, -1.f);
318 const uint32_t
bottom = ring(-0.5f, 0.5f, 0.f);
319 const uint32_t
top = ring(0.5f, 0.5f, 0.f);
321 cap(0.5f, 0.5f, 1.f);
322 cap(-0.5f, 0.5f, -1.f);
329 input.indexCount, opt);
334 std::vector<float>
pos, nrm;
335 std::vector<uint32_t>
idx;
338 }
else if (
kind ==
"cylinder" ||
kind ==
"sphere" ||
kind ==
"cone") {
341 throw eve::Exception(
"SpriteStack.slicePrimitive: unknown kind '%s' (box|cylinder|sphere|cone)",
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);
std::uint32_t vertexCount
std::vector< std::uint32_t > indices
EVENGINE_API_FOUNDATION public API.
medialoader::Colorf Colorf
float clampf(float v, float lo, float hi)
Clampf.
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.
Options for baking a mesh into equal-sized RGBA sprite layers.