32constexpr float kPi = 3.14159265358979323846f;
34float clampf(
float x,
float lo,
float hi) {
return std::min(hi, std::max(lo,
x)); }
42Projection projectionForAxis(
const std::string &axis) {
43 if (axis ==
"x")
return {1, 2, 0};
44 if (axis ==
"y")
return {0, 2, 1};
65void rasterizeCrossSection(
const std::vector<Tri2D> &tris,
float dc,
int w,
int h,
67 const size_t pixelCount = size_t(
w) * size_t(
h);
68 std::vector<std::vector<float>> hits(pixelCount);
69 std::vector<float> bestY(pixelCount, 1e30f);
70 std::vector<uint32_t> bestRGB(pixelCount, 0xffffffu);
72 for (
const auto &tri : tris) {
73 const glm::vec2 &
a = tri.px[0];
74 const glm::vec2 &
b = tri.px[1];
75 const glm::vec2 &
c = tri.px[2];
76 int x0 = int(std::floor(std::min(
a.x, std::min(
b.x,
c.x))));
77 int x1 = int(std::ceil(std::max(
a.x, std::max(
b.x,
c.x))));
78 int y0 = int(std::floor(std::min(
a.y, std::min(
b.y,
c.y))));
79 int y1 = int(std::ceil(std::max(
a.y, std::max(
b.y,
c.y))));
82 x1 = std::min(
w - 1, x1);
83 y1 = std::min(
h - 1, y1);
85 const glm::vec2 e0 =
b -
a;
86 const glm::vec2 e1 =
c -
a;
87 const float denom = e0.x * e1.y - e0.y * e1.x;
88 if (std::fabs(denom) < 1e-12f)
continue;
90 for (
int y = y0;
y <= y1; ++
y) {
91 for (
int x = x0;
x <= x1; ++
x) {
92 const glm::vec2
p(
float(
x) + 0.5f,
float(
y) + 0.5f);
93 const glm::vec2
d =
p -
a;
94 const float u = (
d.x * e1.y -
d.y * e1.x) / denom;
95 const float v = (e0.x *
d.y - e0.y *
d.x) / denom;
96 if (
u < -1e-4f || v < -1e-4f || u + v > 1.f + 1e-4f)
continue;
97 const float w0 = 1.f -
u -
v;
98 const float depthAt = w0 * tri.depth[0] +
u * tri.depth[1] +
v * tri.depth[2];
99 if (depthAt <= dc)
continue;
101 const size_t i = size_t(
y) * size_t(
w) + size_t(
x);
102 hits[i].push_back(depthAt);
103 if (depthAt < bestY[i]) {
105 bestRGB[i] = (uint32_t(
clampf(tri.color.r, 0.f, 1.f) * 255.f) << 16) |
106 (uint32_t(
clampf(tri.color.g, 0.f, 1.f) * 255.f) << 8) |
107 uint32_t(
clampf(tri.color.b, 0.f, 1.f) * 255.f);
113 std::vector<float> uniqueHits;
114 for (
size_t i = 0; i < pixelCount; ++i) {
115 if (hits[i].empty())
continue;
116 std::sort(hits[i].begin(), hits[i].end());
118 for (
float d : hits[i]) {
121 if (uniqueHits.empty() ||
122 d - uniqueHits.back() > 1e-5f * std::max(1.f, std::fabs(
d)))
123 uniqueHits.push_back(
d);
125 if (uniqueHits.size() % 2 == 0)
continue;
126 uint8_t *
px = rgba + i * 4;
127 px[0] = uint8_t(bestRGB[i] >> 16);
128 px[1] = uint8_t(bestRGB[i] >> 8);
129 px[2] = uint8_t(bestRGB[i]);
134std::vector<image::ImageData *> sliceArrays(
const float *
pos,
const float *nrm,
const float *rgb,
135 int vertexCount,
const uint32_t *indices,
int indexCount,
136 const SliceOptions &opt) {
137 if (!
pos || vertexCount < 3 || !indices || indexCount < 3)
138 throw eve::Exception(
"SpriteStack.sliceMesh: invalid mesh arrays");
139 if (opt.layerCount <= 0)
throw eve::Exception(
"SpriteStack.sliceMesh: layerCount must be > 0");
140 if (opt.imageW <= 0 || opt.imageH <= 0)
141 throw eve::Exception(
"SpriteStack.sliceMesh: image size must be > 0");
143 const Projection
proj = projectionForAxis(opt.axis);
145 glm::vec3 mn(1e30f), mx(-1e30f);
146 for (
int i = 0; i < vertexCount; ++i) {
147 const glm::vec3
p(
pos[i * 3],
pos[i * 3 + 1],
pos[i * 3 + 2]);
148 mn = glm::min(mn,
p);
149 mx = glm::max(mx,
p);
151 if (mn.x > mx.x)
throw eve::Exception(
"SpriteStack.sliceMesh: empty mesh AABB");
153 const float d0 = mn[
proj.d];
154 const float d1 = opt.thickness > 0.f ? d0 + opt.thickness * float(opt.layerCount - 1)
156 const float slab = opt.thickness > 0.f ? opt.thickness : (d1 - d0) /
float(opt.layerCount);
157 if (slab <= 0.f)
throw eve::Exception(
"SpriteStack.sliceMesh: zero slice thickness");
159 const float uMin = mn[
proj.u] - opt.padding * std::max(1.f, mx[
proj.u] - mn[
proj.u]);
160 const float uMax = mx[
proj.u] + opt.padding * std::max(1.f, mx[
proj.u] - mn[
proj.u]);
161 const float vMin = mn[
proj.v] - opt.padding * std::max(1.f, mx[
proj.v] - mn[
proj.v]);
162 const float vMax = mx[
proj.v] + opt.padding * std::max(1.f, mx[
proj.v] - mn[
proj.v]);
163 const float uSpan = uMax - uMin;
164 const float vSpan = vMax - vMin;
165 if (uSpan <= 0.f || vSpan <= 0.f)
166 throw eve::Exception(
"SpriteStack.sliceMesh: degenerate projected AABB");
168 const glm::vec3 viewDir =
169 proj.d == 0 ? glm::vec3(1.f, 0.f, 0.f)
170 : (
proj.
d == 1 ? glm::vec3(0.f, 1.f, 0.f) : glm::vec3(0.f, 0.f, 1.f));
172 std::vector<image::ImageData *> layers;
173 layers.reserve(
size_t(opt.layerCount));
174 const int triangleCount = indexCount / 3;
175 std::vector<Tri2D> tris;
176 tris.reserve(
size_t(triangleCount));
178 for (
int t = 0; t < triangleCount; ++t) {
179 const uint32_t
i0 = indices[t * 3];
180 const uint32_t
i1 = indices[t * 3 + 1];
181 const uint32_t
i2 = indices[t * 3 + 2];
182 if (
int(
i0) >= vertexCount ||
int(
i1) >= vertexCount ||
int(
i2) >= vertexCount)
continue;
190 const float rr = (rgb[
i0 * 3] + rgb[
i1 * 3] + rgb[
i2 * 3]) / 3.f;
191 const float gg = (rgb[
i0 * 3 + 1] + rgb[
i1 * 3 + 1] + rgb[
i2 * 3 + 1]) / 3.f;
192 const float bb = (rgb[
i0 * 3 + 2] + rgb[
i1 * 3 + 2] + rgb[
i2 * 3 + 2]) / 3.f;
198 glm::vec3
n = glm::cross(
b -
a,
c -
a);
199 if (glm::dot(
n,
n) > 1e-12f) {
200 n = glm::normalize(
n);
201 const float s = 0.72f + 0.28f * std::abs(glm::dot(
n, viewDir));
209 tri.px[0] = glm::vec2((
a[
proj.u] - uMin) / uSpan *
float(opt.imageW - 1),
210 (1.f - (
a[
proj.v] - vMin) / vSpan) *
float(opt.imageH - 1));
211 tri.px[1] = glm::vec2((
b[
proj.u] - uMin) / uSpan *
float(opt.imageW - 1),
212 (1.f - (
b[
proj.v] - vMin) / vSpan) *
float(opt.imageH - 1));
213 tri.px[2] = glm::vec2((
c[
proj.u] - uMin) / uSpan *
float(opt.imageW - 1),
214 (1.f - (
c[
proj.v] - vMin) / vSpan) *
float(opt.imageH - 1));
215 tri.depth[0] =
a[
proj.d];
216 tri.depth[1] =
b[
proj.d];
217 tri.depth[2] =
c[
proj.d];
222 for (
int li = 0; li < opt.layerCount; ++li) {
223 auto *img =
new image::ImageData(opt.imageW, opt.imageH,
"RGBA8");
224 auto *
data =
static_cast<uint8_t *
>(img->getData());
225 std::memset(data, 0,
size_t(opt.imageW) *
size_t(opt.imageH) * 4);
226 const float dc = d0 + (float(li) + 0.5f) * slab;
227 rasterizeCrossSection(tris, dc, opt.imageW, opt.imageH, data);
228 layers.push_back(img);
233void makeBox(std::vector<float> &
pos, std::vector<float> &nrm, std::vector<uint32_t> &
idx) {
234 const float h = 0.5f;
235 const glm::vec3 corners[8] = {
236 {-
h, -
h, -
h}, {
h, -
h, -
h}, {
h,
h, -
h}, {-
h,
h, -
h},
237 {-
h, -
h,
h}, {
h, -
h,
h}, {
h,
h,
h}, {-
h,
h,
h},
239 const glm::vec3 faces[6] = {
240 {0.f, 0.f, -1.f}, {0.f, 0.f, 1.f}, {-1.f, 0.f, 0.f},
241 {1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 1.f, 0.f},
243 const int quads[6][4] = {
244 {0, 1, 2, 3}, {5, 4, 7, 6}, {4, 0, 3, 7},
245 {1, 5, 6, 2}, {4, 5, 1, 0}, {3, 2, 6, 7},
247 for (
int f = 0;
f < 6; ++
f) {
248 for (
int k = 0; k < 4; ++k) {
249 const glm::vec3 &
p = corners[quads[
f][k]];
250 pos.insert(
pos.end(), {p.x, p.y, p.z});
251 nrm.insert(nrm.end(), {faces[f].x, faces[f].y, faces[f].z});
253 const uint32_t base = uint32_t(
f * 4);
254 idx.insert(
idx.end(), {base, base + 1, base + 2, base, base + 2, base + 3});
258void makeLathe(
const std::string &
kind,
int slices, std::vector<float> &
pos,
259 std::vector<float> &nrm, std::vector<uint32_t> &
idx) {
260 const bool sphere =
kind ==
"sphere";
261 const bool cone =
kind ==
"cone";
262 const int stacks = sphere ? 12 : 1;
263 auto ring = [&](
float y,
float radius,
float nY) {
265 const uint32_t base = uint32_t(
pos.size() / 3);
266 for (
int s = 0;
s < slices; ++
s) {
267 const float a = float(
s) / float(slices) * 2.f * kPi;
268 const glm::vec3
n(std::cos(
a), nY, std::sin(
a));
269 pos.insert(
pos.end(), {std::cos(a) * radius, y, std::sin(a) * radius});
270 nrm.insert(nrm.end(), {n.x, n.y, n.z});
274 auto connectRings = [&](uint32_t r0, uint32_t r1) {
275 for (
int s = 0;
s < slices; ++
s) {
276 const uint32_t s0 = r0 + uint32_t(
s);
277 const uint32_t s1 = r0 + uint32_t((
s + 1) % slices);
278 const uint32_t t0 = r1 + uint32_t(
s);
279 const uint32_t t1 = r1 + uint32_t((
s + 1) % slices);
280 idx.insert(
idx.end(), {s0, t0, t1, s0, t1, s1});
283 auto cap = [&](
float y,
float radius,
float nY) {
284 const uint32_t center = uint32_t(
pos.size() / 3);
285 pos.insert(
pos.end(), {0.f, y, 0.f});
286 nrm.insert(nrm.end(), {0.f, nY, 0.f});
287 for (
int s = 0;
s < slices; ++
s) {
288 const float a = float(
s) / float(slices) * 2.f * kPi;
289 pos.insert(
pos.end(), {std::cos(a) * radius, y, std::sin(a) * radius});
290 nrm.insert(nrm.end(), {0.f, nY, 0.f});
291 const uint32_t ring0 = center + 1;
292 const uint32_t a0 = ring0 + uint32_t(
s);
293 const uint32_t b0 = ring0 + uint32_t((
s + 1) % slices);
295 idx.insert(
idx.end(), {center, b0, a0});
297 idx.insert(
idx.end(), {center, a0, b0});
302 std::vector<uint32_t> rings;
303 for (
int st = 0; st < stacks; ++st) {
304 const float t0 = float(st) / float(stacks);
305 const float y0 = std::cos(t0 * kPi) * 0.5f;
306 const float r0 = std::sin(t0 * kPi) * 0.5f;
307 rings.push_back(ring(y0, r0, -std::sin(t0 * kPi)));
309 for (
int st = 0; st + 1 < stacks; ++st)
310 connectRings(rings[
size_t(st)], rings[
size_t(st + 1)]);
315 const uint32_t apex = uint32_t(
pos.size() / 3);
316 pos.insert(
pos.end(), {0.f, 0.5f, 0.f});
317 nrm.insert(nrm.end(), {0.f, 1.f, 0.f});
318 for (
int s = 0;
s < slices; ++
s) {
319 const float a0 = float(
s) / float(slices) * 2.f * kPi;
320 const float a1 = float(
s + 1) / float(slices) * 2.f * kPi;
321 const uint32_t b0 = uint32_t(
pos.size() / 3);
322 pos.insert(
pos.end(), {std::cos(a0) * 0.5f, -0.5f, std::sin(a0) * 0.5f});
323 nrm.insert(nrm.end(), {std::cos(a0) * 0.7071f, 0.7071f, std::sin(a0) * 0.7071f});
324 pos.insert(
pos.end(), {std::cos(a1) * 0.5f, -0.5f, std::sin(a1) * 0.5f});
325 nrm.insert(nrm.end(), {std::cos(a1) * 0.7071f, 0.7071f, std::sin(a1) * 0.7071f});
326 idx.insert(
idx.end(), {apex, b0, b0 + 1});
328 cap(-0.5f, 0.5f, -1.f);
333 const uint32_t bottom = ring(-0.5f, 0.5f, 0.f);
334 const uint32_t top = ring(0.5f, 0.5f, 0.f);
335 connectRings(bottom, top);
336 cap(0.5f, 0.5f, 1.f);
337 cap(-0.5f, 0.5f, -1.f);
350 std::vector<float>
pos, nrm, rgb;
351 std::vector<uint32_t>
idx;
352 const int meshCount =
model->getMeshCount();
353 for (
int m = 0;
m < meshCount; ++
m) {
354 const aiMesh *am =
model->getMesh(
m);
356 const uint32_t base = uint32_t(
pos.size() / 3);
357 for (
unsigned v = 0;
v < am->mNumVertices; ++
v) {
358 const aiVector3D &
p = am->mVertices[
v];
359 pos.insert(
pos.end(), {p.x, p.y, p.z});
361 const aiVector3D &
n = am->mNormals[
v];
362 nrm.insert(nrm.end(), {n.x, n.y, n.z});
364 if (am->mColors && am->mColors[0]) {
365 const aiColor4D &
c = am->mColors[0][
v];
366 rgb.insert(rgb.end(), {c.r, c.g, c.b});
369 for (
unsigned f = 0;
f < am->mNumFaces; ++
f) {
370 const auto &face = am->mFaces[
f];
371 if (face.mNumIndices != 3)
continue;
372 idx.insert(
idx.end(), {base + face.mIndices[0], base + face.mIndices[1],
373 base + face.mIndices[2]});
378 in.nrmXYZ = nrm.empty() ? nullptr : nrm.data();
379 in.rgb = rgb.empty() ? nullptr : rgb.data();
380 in.vertexCount = int(
pos.size() / 3);
381 in.indices =
idx.data();
382 in.indexCount = int(
idx.size());
383 return sliceArrays(in.posXYZ, in.nrmXYZ, in.rgb, in.vertexCount, in.indices, in.indexCount, opt);
388 std::vector<float>
pos, nrm;
389 std::vector<uint32_t>
idx;
392 }
else if (
kind ==
"cylinder" ||
kind ==
"sphere" ||
kind ==
"cone") {
395 throw eve::Exception(
"SpriteStack.slicePrimitive: unknown kind '%s' (box|cylinder|sphere|cone)",
400 in.nrmXYZ = nrm.data();
401 in.vertexCount = int(
pos.size() / 3);
402 in.indices =
idx.data();
403 in.indexCount = int(
idx.size());
404 return sliceArrays(in.posXYZ, in.nrmXYZ,
nullptr, in.vertexCount, in.indices, in.indexCount, opt);
413std::vector<uint32_t> copySpv(
const uint32_t *data,
size_t count) {
414 return std::vector<uint32_t>(data, data + count);
417glm::mat4 billboardModel(
const glm::vec3 ¢er,
float width,
float height,
418 const glm::vec3 &
eye) {
419 glm::vec3
d =
eye - center;
421 if (glm::length(
d) < 1e-4f)
d = glm::vec3(0.f, 0.f, 1.f);
422 d = glm::normalize(
d);
423 const glm::vec3 up(0.f, 1.f, 0.f);
424 const glm::vec3 right = glm::normalize(glm::cross(up,
d));
425 const glm::vec3 forward = glm::normalize(glm::cross(right, up));
427 m[0] = glm::vec4(right *
width, 0.f);
428 m[1] = glm::vec4(up *
height, 0.f);
429 m[2] = glm::vec4(forward, 0.f);
430 m[3] = glm::vec4(center, 1.f);
434graphics::Camera3D *findActiveCamera3D() {
435 if (ecs::current()->getManager<graphics::Camera3D>() ==
nullptr)
return nullptr;
436 auto camView = ecs::View<graphics::Camera3D, graphics::Camera3D::Data>();
437 for (
auto it = camView.begin(); it != camView.end(); ++it) {
439 if (!
data->active || !
data->entity)
continue;
448 if (count < 0)
throw eve::Exception(
"SpriteStack3D.setLayerCount: count must be >= 0");
450 layers_.assign(
size_t(count), Layer{});
457 if (index < 0 || index >= layerCount_)
458 throw eve::Exception(
"SpriteStack3D.setLayerTexture: index %d out of range [0,%d)", index,
460 layers_[size_t(index)] = Layer{texture, glm::vec4(0.f, 0.f, 1.f, 1.f)};
465 if (index < 0 || index >= layerCount_)
return nullptr;
466 return layers_[size_t(index)].texture;
470 if (!gfx)
throw eve::Exception(
"SpriteStack3D.setLayerImage: null graphics");
471 if (!img)
throw eve::Exception(
"SpriteStack3D.setLayerImage: null ImageData");
476 if (!gfx)
throw eve::Exception(
"SpriteStack3D.setLayerFile: null graphics");
482 if (!gfx)
throw eve::Exception(
"SpriteStack3D.setLayersFromAtlas: null graphics");
483 if (!atlas)
throw eve::Exception(
"SpriteStack3D.setLayersFromAtlas: null atlas");
484 if (layerCount <= 0)
throw eve::Exception(
"SpriteStack3D.setLayersFromAtlas: count must be > 0");
486 for (
int i = 0; i < layerCount; ++i) {
487 const float u0 = float(i) / float(layerCount);
488 const float u1 = float(i + 1) / float(layerCount);
489 layers_[size_t(i)] = Layer{atlas, glm::vec4(u0, 0.f, u1, 1.f)};
543 if (mode !=
"vertical" && mode !=
"horizontal")
544 throw eve::Exception(
"SpriteStack3D.setMode: unknown mode '%s' (vertical|horizontal)",
560 shadowOpacity_ = clampf(opacity, 0.f, 1.f);
577 outlineWidth_ = std::max(0.f,
width);
594 if (quad_ && shader_)
return;
596 const float posXYZ[] = {-0.5f, 0.5f, 0.f, 0.5f, 0.5f, 0.f,
597 0.5f, -0.5f, 0.f, -0.5f, -0.5f, 0.f};
598 const float nrmXYZ[] = {0.f, 0.f, 1.f, 0.f, 0.f, 1.f,
599 0.f, 0.f, 1.f, 0.f, 0.f, 1.f};
600 const float uvST[] = {0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f};
601 const uint32_t indices[] = {0, 1, 2, 0, 2, 3};
605 auto vert = copySpv(sprite_stack_vert_spv, sprite_stack_vert_spv_count);
606 auto frag = copySpv(sprite_stack_frag_spv, sprite_stack_frag_spv_count);
609 throw eve::Exception(
"SpriteStack3D.render: failed to create slice shader");
612 shader_->
sendVec4(
"uvRect", 0.f, 0.f, 1.f, 1.f);
613 shader_->
sendFloat(
"alphaCutoff", alphaCutoff_);
617void SpriteStack3D::collectSlices(
const SpriteStack3D &stack,
const glm::vec3 &
eye,
618 std::vector<SliceDraw> &out) {
619 if (!stack.visible_ || stack.layerCount_ <= 0)
return;
620 const glm::mat4 yawM = glm::rotate(glm::mat4(1.f), stack.yaw_, glm::vec3(0.f, 1.f, 0.f));
621 const float half = 0.5f * float(stack.layerCount_ - 1);
622 for (
int i = 0; i < stack.layerCount_; ++i) {
623 graphics::Texture *tex = stack.layers_[size_t(i)].texture;
625 const float offset = stack.thickness_ * (float(i) - half);
626 glm::vec3 center(stack.x_, stack.y_, stack.z_);
627 if (stack.mode_ ==
"horizontal") {
630 const glm::vec4 off = yawM * glm::vec4(0.f, 0.f, offset, 0.f);
631 center += glm::vec3(off);
634 const glm::vec3
d = center -
eye;
635 s.distSq = glm::dot(
d,
d);
636 if (stack.mode_ ==
"horizontal") {
639 s.model = glm::translate(glm::mat4(1.f), center);
640 s.model = glm::rotate(
s.model, stack.yaw_, glm::vec3(0.f, 1.f, 0.f));
641 s.model = glm::rotate(
s.model, -kPi * 0.5f, glm::vec3(1.f, 0.f, 0.f));
642 s.model = glm::scale(
s.model, glm::vec3(stack.width_, stack.height_, 1.f));
644 s.model = billboardModel(center, stack.width_, stack.height_,
eye);
647 s.uv = stack.layers_[size_t(i)].uv;
652SpriteStack3D::SliceDraw SpriteStack3D::makeShadowDraw(
const SpriteStack3D &stack,
654 const glm::vec3 &
eye) {
656 const glm::vec3 light =
657 glm::normalize(glm::vec3(stack.shadowLightX_, stack.shadowLightY_, stack.shadowLightZ_));
658 const glm::vec3 center(
s.model[3]);
661 const float planeY = stack.shadowPlaneY_ + 0.02f;
662 const float t = (planeY - center.y) / light.y;
663 const glm::vec3
c = center + light * t;
664 out.model = glm::translate(glm::mat4(1.f),
c);
665 out.model = glm::rotate(out.model, -kPi * 0.5f, glm::vec3(1.f, 0.f, 0.f));
666 out.model = glm::scale(out.model, glm::vec3(stack.width_, stack.height_, 1.f));
667 const glm::vec3
d =
c -
eye;
668 out.distSq = glm::dot(
d,
d);
672SpriteStack3D::SliceDraw SpriteStack3D::makeOutlineDraw(
const SpriteStack3D &stack,
674 const glm::vec3 &
eye) {
676 const glm::vec3 center(
s.model[3]);
677 glm::vec3 away = center -
eye;
678 if (glm::length(away) < 1e-6f) away = glm::vec3(0.f, 0.f, 1.f);
679 away = glm::normalize(away);
683 out.model[3] = glm::vec4(center + away * 0.02f, 1.f);
684 const float k = stack.outlineWidth_;
685 out.model = out.model * glm::scale(glm::mat4(1.f), glm::vec3(1.f + 2.f * k, 1.f + 2.f * k, 1.f));
689std::vector<SpriteStack3D *> &SpriteStack3D::gbufferStacks() {
690 static std::vector<SpriteStack3D *> stacks;
694void SpriteStack3D::registerGbufferDrawer() {
695 static bool registered =
false;
696 if (registered)
return;
700 const glm::mat4 &
viewProj,
float ) {
706 float eyeX,
float eyeY,
float eyeZ,
float nearZ,
708 const glm::vec3
eye(eyeX, eyeY, eyeZ);
709 std::vector<SliceDraw> slices;
711 if (!st || !st->visible_ || st->layerCount_ <= 0)
continue;
712 st->ensureResources(&gfx);
713 if (!st->quad_)
continue;
715 collectSlices(*st,
eye, slices);
716 for (
const auto &
s : slices) {
717 const glm::mat4 mvp =
viewProj *
s.model;
719 st->tintR_, st->tintG_, st->tintB_);
726 auto &stacks = gbufferStacks();
727 auto it = std::find(stacks.begin(), stacks.end(),
this);
728 if (
enabled && it == stacks.end()) stacks.push_back(
this);
729 if (!
enabled && it != stacks.end()) stacks.erase(it);
730 registerGbufferDrawer();
735std::vector<SpriteStack3D *> &SpriteStack3D::shadowCasterStacks() {
736 static std::vector<SpriteStack3D *> stacks;
740void SpriteStack3D::registerShadowDrawer() {
741 static bool registered =
false;
742 if (registered)
return;
747 drawShadowCasterStacks(gfx, lightVP, cam.
eyeX, cam.
eyeY, cam.
eyeZ);
752 const glm::mat4 &lightVP,
float eyeX,
float eyeY,
754 const glm::vec3
eye(eyeX, eyeY, eyeZ);
755 std::vector<SliceDraw> slices;
757 if (!st || !st->visible_ || st->layerCount_ <= 0)
continue;
758 st->ensureResources(&gfx);
759 if (!st->quad_)
continue;
761 collectSlices(*st,
eye, slices);
762 for (
const auto &
s : slices) {
773 auto &stacks = shadowCasterStacks();
774 auto it = std::find(stacks.begin(), stacks.end(),
this);
775 if (cast && it == stacks.end()) stacks.push_back(
this);
776 if (!cast && it != stacks.end()) stacks.erase(it);
777 registerShadowDrawer();
783 auto &gb = gbufferStacks();
784 auto it = std::find(gb.begin(), gb.end(),
this);
785 if (it != gb.end()) gb.erase(it);
786 auto &sc = shadowCasterStacks();
787 it = std::find(sc.begin(), sc.end(),
this);
788 if (it != sc.end()) sc.erase(it);
792 if (!gfx)
throw eve::Exception(
"SpriteStack3D.render: null graphics");
793 if (!visible_ || layerCount_ <= 0)
return;
794 if (!camera) camera = findActiveCamera3D();
797 ensureResources(gfx);
798 if (!quad_ || !shader_)
return;
800 auto cd = camera->data();
801 const glm::vec3
eye(cd->eyeX, cd->eyeY, cd->eyeZ);
802 const glm::vec3 target(cd->targetX, cd->targetY, cd->targetZ);
803 const glm::vec3 up(cd->upX, cd->upY, cd->upZ);
805 const glm::mat4 viewM = glm::lookAtRH(
eye, target, up);
806 const float fovRad = cd->fovYDeg * 0.017453292519943295f;
807 const glm::mat4 projM =
814 shader_->
sendFloat(
"alphaCutoff", alphaCutoff_);
816 std::vector<SliceDraw> slices;
817 slices.reserve(
size_t(layerCount_));
818 collectSlices(*
this,
eye, slices);
819 if (slices.empty())
return;
822 std::sort(slices.begin(), slices.end(),
827 if (shadowEnabled_ && shadowLightY_ < -1e-4f) {
829 for (
const auto &
s : slices) {
831 shader_->
sendVec4(
"uvRect",
s.uv.x,
s.uv.y,
s.uv.z,
s.uv.w);
837 if (outlineWidth_ > 0.f) {
839 for (
const auto &
s : slices) {
841 shader_->
sendVec4(
"uvRect",
s.uv.x,
s.uv.y,
s.uv.z,
s.uv.w);
846 const glm::vec4 tint(tintR_, tintG_, tintB_, tintA_);
847 for (
const auto &
s : slices) {
848 shader_->
sendVec4(
"uvRect",
s.uv.x,
s.uv.y,
s.uv.z,
s.uv.w);
861uint32_t packTint(
float r,
float g,
float b,
float a) {
862 return (uint32_t(clampf(r, 0.f, 1.f) * 255.f) << 24) |
863 (uint32_t(clampf(g, 0.f, 1.f) * 255.f) << 16) |
864 (uint32_t(clampf(
b, 0.f, 1.f) * 255.f) << 8) |
865 uint32_t(clampf(
a, 0.f, 1.f) * 255.f);
870 float((t >> 16) & 0xff) / 255.f,
871 float((t >> 8) & 0xff) / 255.f,
float(t & 0xff) / 255.f);
877 if (!stack)
throw eve::Exception(
"SpriteStackBatch.add: null stack");
878 if (std::find(stacks_.begin(), stacks_.end(), stack) == stacks_.end()) {
879 stacks_.push_back(stack);
880 forceRebuild_ =
true;
885 auto it = std::find(stacks_.begin(), stacks_.end(), stack);
886 if (it != stacks_.end()) {
888 forceRebuild_ =
true;
895 forceRebuild_ =
true;
902 auto vert = copySpv(sprite_stack_vert_spv, sprite_stack_vert_spv_count);
903 auto frag = copySpv(sprite_stack_frag_spv, sprite_stack_frag_spv_count);
906 throw eve::Exception(
"SpriteStackBatch.render: failed to create slice shader");
909 shader_->
sendVec4(
"uvRect", 0.f, 0.f, 1.f, 1.f);
910 shader_->
sendFloat(
"alphaCutoff", 0.05f);
914 if (!gfx)
throw eve::Exception(
"SpriteStackBatch.render: null graphics");
915 if (stacks_.empty())
return;
916 if (!camera) camera = findActiveCamera3D();
920 if (!shader_)
return;
922 auto cd = camera->data();
923 const glm::vec3
eye(cd->eyeX, cd->eyeY, cd->eyeZ);
924 const glm::vec3 target(cd->targetX, cd->targetY, cd->targetZ);
925 const glm::vec3 up(cd->upX, cd->upY, cd->upZ);
927 const glm::mat4 viewM = glm::lookAtRH(
eye, target, up);
928 const float fovRad = cd->fovYDeg * 0.017453292519943295f;
929 const glm::mat4 projM =
936 shader_->
sendFloat(
"alphaCutoff", 0.05f);
937 shader_->
sendVec4(
"uvRect", 0.f, 0.f, 1.f, 1.f);
941 struct ColoredSlice {
945 std::vector<ColoredSlice> slices;
947 float nearest = 1e30f;
950 std::unordered_map<GroupKey, GroupBuild, GroupKeyHash> builds;
952 if (!st || !st->visible_)
continue;
953 std::vector<SpriteStack3D::SliceDraw> tmp;
954 SpriteStack3D::collectSlices(*st,
eye, tmp);
955 const uint64_t stamp = st->getVersion();
956 std::vector<GroupBuild::ColoredSlice> colored;
958 for (
const auto &
s : tmp) colored.push_back({
s, baseColor});
959 if (st->shadowEnabled_ && st->shadowLightY_ < -1e-4f) {
961 for (
const auto &
s : tmp)
963 {SpriteStack3D::makeShadowDraw(*st,
s,
eye), shadowColor});
965 if (st->outlineWidth_ > 0.f) {
968 for (
const auto &
s : tmp)
970 {SpriteStack3D::makeOutlineDraw(*st,
s,
eye), outlineColor});
972 for (
const auto &cs : colored) {
973 GroupKey key{cs.draw.texture,
974 packTint(cs.color.r, cs.color.g, cs.color.b, cs.color.a)};
975 GroupBuild &
b = builds[key];
977 b.color = unpackTint(key.tint);
978 b.slices.push_back(cs);
979 b.stamp = std::max(
b.stamp, stamp);
980 b.nearest = std::min(
b.nearest, cs.draw.distSq);
983 if (builds.empty())
return;
986 std::vector<GroupBuild *> order;
987 order.reserve(builds.size());
988 for (
auto &kv : builds) order.push_back(&kv.second);
989 std::sort(order.begin(), order.end(),
990 [](
const GroupBuild *
a,
const GroupBuild *
b) { return a->nearest > b->nearest; });
992 const glm::vec3 corners[4] = {{-0.5f, 0.5f, 0.f}, {0.5f, 0.5f, 0.f},
993 {0.5f, -0.5f, 0.f}, {-0.5f, -0.5f, 0.f}};
994 const glm::vec2 quadUVs[4] = {{0.f, 0.f}, {1.f, 0.f}, {1.f, 1.f}, {0.f, 1.f}};
995 std::vector<float>
pos, nrm, uv;
996 std::vector<uint32_t>
idx;
998 for (GroupBuild *gb : order) {
999 Group &g = groups_[gb->key];
1000 std::sort(gb->slices.begin(), gb->slices.end(),
1001 [](
const GroupBuild::ColoredSlice &
a,
const GroupBuild::ColoredSlice &
b) {
1002 return a.draw.distSq > b.draw.distSq;
1004 const int sliceCount = int(gb->slices.size());
1005 const int vc = sliceCount * 4;
1006 const int ic = sliceCount * 6;
1008 pos.resize(
size_t(vc) * 3);
1009 nrm.resize(
size_t(vc) * 3);
1010 uv.resize(
size_t(vc) * 2);
1011 idx.resize(
size_t(ic));
1012 for (
int si = 0; si < sliceCount; ++si) {
1013 const SpriteStack3D::SliceDraw &
s = gb->slices[size_t(si)].draw;
1014 for (
int c = 0;
c < 4; ++
c) {
1015 const glm::vec4
p =
s.model * glm::vec4(corners[
c], 1.f);
1016 const size_t vi = size_t(si * 4 +
c);
1017 pos[vi * 3 + 0] =
p.x;
1018 pos[vi * 3 + 1] =
p.y;
1019 pos[vi * 3 + 2] =
p.z;
1020 nrm[vi * 3 + 0] = 0.f;
1021 nrm[vi * 3 + 1] = 1.f;
1022 nrm[vi * 3 + 2] = 0.f;
1023 uv[vi * 2 + 0] =
s.uv.x + quadUVs[
c].x * (
s.uv.z -
s.uv.x);
1024 uv[vi * 2 + 1] =
s.uv.y + quadUVs[
c].y * (
s.uv.w -
s.uv.y);
1026 const uint32_t base = uint32_t(si * 4);
1027 const size_t di = size_t(si) * 6;
1029 idx[di + 1] = base + 1;
1030 idx[di + 2] = base + 2;
1032 idx[di + 4] = base + 2;
1033 idx[di + 5] = base + 3;
1036 if (forceRebuild_ || !g.mesh) {
1044 g.vertexCapacity = vc;
1045 g.indexCapacity = ic;
1046 }
else if (g.stamp != gb->stamp) {
1049 g.stamp = gb->stamp;
1050 gfx->
drawMeshShader(g.mesh, glm::mat4(1.f), gb->key.texture, gb->color, shader_);
1052 forceRebuild_ =
false;
1062 if (!gfx)
throw eve::Exception(
"SpriteStack.newStack: null graphics");
1067 if (!gfx)
throw eve::Exception(
"SpriteStack.newBatch: null graphics");
1071std::vector<image::ImageData *> SpriteStack::slicePrimitive(
const std::string &
kind,
int layerCount,
1072 int imageW,
int imageH,
1073 const std::string &axis,
1085 int imageW,
int imageH,
1086 const std::string &axis,
float thickness) {
1096void SpriteStack::expose(ssq::Table &table) {
1097 auto cls = table.addClass(
name, SpriteStack::create,
false);
1102 std::function<SpriteStack3D *()>([]() ->
SpriteStack3D * {
return nullptr; }),
true);
1103 stack.addFunc(
"setLayerCount", &SpriteStack3D::setLayerCount);
1104 stack.addFunc(
"getLayerCount", &SpriteStack3D::getLayerCount);
1105 stack.addFunc(
"setLayerTexture", &SpriteStack3D::setLayerTexture);
1106 stack.addFunc(
"getLayerTexture", &SpriteStack3D::getLayerTexture);
1107 stack.addFunc(
"setLayerImage", &SpriteStack3D::setLayerImage);
1108 stack.addFunc(
"setLayerFile", &SpriteStack3D::setLayerFile);
1109 stack.addFunc(
"setLayersFromAtlas", &SpriteStack3D::setLayersFromAtlas);
1110 stack.addFunc(
"setThickness", &SpriteStack3D::setThickness);
1111 stack.addFunc(
"getThickness", &SpriteStack3D::getThickness);
1112 stack.addFunc(
"setSize", &SpriteStack3D::setSize);
1113 stack.addFunc(
"getWidth", &SpriteStack3D::getWidth);
1114 stack.addFunc(
"getHeight", &SpriteStack3D::getHeight);
1115 stack.addFunc(
"setPosition", &SpriteStack3D::setPosition);
1116 stack.addFunc(
"setYaw", &SpriteStack3D::setYaw);
1117 stack.addFunc(
"setTint", &SpriteStack3D::setTint);
1118 stack.addFunc(
"setAlphaCutoff", &SpriteStack3D::setAlphaCutoff);
1119 stack.addFunc(
"setVisible", &SpriteStack3D::setVisible);
1120 stack.addFunc(
"getVisible", &SpriteStack3D::getVisible);
1121 stack.addFunc(
"setMode", &SpriteStack3D::setMode);
1122 stack.addFunc(
"getMode", &SpriteStack3D::getMode);
1123 stack.addFunc(
"setShadowEnabled", &SpriteStack3D::setShadowEnabled);
1124 stack.addFunc(
"getShadowEnabled", &SpriteStack3D::getShadowEnabled);
1125 stack.addFunc(
"setShadowOpacity", &SpriteStack3D::setShadowOpacity);
1126 stack.addFunc(
"setShadowLight", &SpriteStack3D::setShadowLight);
1127 stack.addFunc(
"setShadowPlaneY", &SpriteStack3D::setShadowPlaneY);
1128 stack.addFunc(
"setOutline", &SpriteStack3D::setOutline);
1129 stack.addFunc(
"getOutlineWidth", &SpriteStack3D::getOutlineWidth);
1130 stack.addFunc(
"setOutlineColor", &SpriteStack3D::setOutlineColor);
1131 stack.addFunc(
"setGbufferEnabled", &SpriteStack3D::setGbufferEnabled);
1132 stack.addFunc(
"getGbufferEnabled", &SpriteStack3D::getGbufferEnabled);
1133 stack.addFunc(
"setCastShadow", &SpriteStack3D::setCastShadow);
1134 stack.addFunc(
"getCastShadow", &SpriteStack3D::getCastShadow);
1139 if (self) self->render(gfx);
1141 stack.addFunc(
"renderWithCamera", &SpriteStack3D::render);
1143 auto batch = table.addClass<SpriteStackBatch>(
1145 std::function<SpriteStackBatch *()>([]() -> SpriteStackBatch * {
return nullptr; }),
true);
1146 batch.addFunc(
"add", &SpriteStackBatch::add);
1147 batch.addFunc(
"remove", &SpriteStackBatch::remove);
1148 batch.addFunc(
"clear", &SpriteStackBatch::clear);
1149 batch.addFunc(
"getStackCount", &SpriteStackBatch::getStackCount);
1152 std::function<
void(SpriteStackBatch *, graphics::Graphics *)>([](SpriteStackBatch *self,
1153 graphics::Graphics *gfx) {
1154 if (self) self->render(gfx);
1156 batch.addFunc(
"renderWithCamera", &SpriteStackBatch::render);
1159void SpriteStack::expose(ssq::Class &
cls) {
1160 cls.addFunc(
"getName", &SpriteStack::getName);
1161 cls.addFunc(
"newStack", &SpriteStack::newStack);
1162 cls.addFunc(
"newBatch", &SpriteStack::newBatch);
1163 cls.addFunc(
"slicePrimitive", &SpriteStack::slicePrimitive);
1164 cls.addFunc(
"sliceModel", &SpriteStack::sliceModel);