12#include "graphics/shaders/mesh3d_grass_frag_spv.inc"
13#include "graphics/shaders/mesh3d_grass_vert_spv.inc"
25#include <unordered_map>
33std::vector<uint32_t> copySpv(
const uint32_t *data,
size_t count) {
34 return std::vector<uint32_t>(data, data +
count);
37float radicalInverse(uint32_t
n, uint32_t base) {
38 const float inv = 1.f / float(base);
42 val += float(
n % base) *
f;
49float wrap01(
float x) {
50 x =
x - std::floor(
x);
51 return x < 0.f ?
x + 1.f :
x;
54uint32_t mixSeed(uint32_t
seed, uint32_t i) {
55 seed ^= 0x9e3779b9u + (i << 6) + (i >> 2);
63 glm::vec3
n{0.f, 1.f, 0.f};
66glm::vec3 readPos(
const float *
pos,
int i) {
67 return glm::vec3(
pos[i * 3],
pos[i * 3 + 1],
pos[i * 3 + 2]);
70glm::vec3 readNrm(
const float *nrm,
int i) {
71 if (!nrm)
return glm::vec3(0.f, 1.f, 0.f);
72 return glm::vec3(nrm[i * 3], nrm[i * 3 + 1], nrm[i * 3 + 2]);
75bool buildTriangles(
const float *posXYZ,
const float *nrmXYZ,
int vertexCount,
77 std::vector<Triangle> &tris, std::vector<float> &cdf) {
89 const glm::vec3
a = readPos(posXYZ,
int(
i0));
90 const glm::vec3
b = readPos(posXYZ,
int(
i1));
91 const glm::vec3
c = readPos(posXYZ,
int(
i2));
92 glm::vec3
n = glm::cross(
b -
a,
c -
a);
93 const float twiceArea = glm::length(
n);
94 if (twiceArea < 1e-10f)
continue;
97 glm::vec3 ns = readNrm(nrmXYZ,
int(
i0)) + readNrm(nrmXYZ,
int(
i1)) + readNrm(nrmXYZ,
int(
i2));
98 if (glm::dot(ns, ns) > 1e-8f)
n = glm::normalize(ns);
100 if (
n.y < minSlopeDot)
continue;
105 tri.area = 0.5f * twiceArea;
109 cdf.push_back(total);
111 return total > 1e-12f && !tris.empty();
114int pickTriangle(
const std::vector<float> &cdf,
float u) {
115 const float target =
u * cdf.back();
116 auto it = std::lower_bound(cdf.begin(), cdf.end(),
target);
117 int idx = int(it - cdf.begin());
118 if (
idx >=
int(cdf.size()))
idx = int(cdf.size()) - 1;
122void sampleOnTriangle(
const float *posXYZ,
const Triangle &tri,
float u,
float v, glm::vec3 &
p) {
127 const glm::vec3
a = readPos(posXYZ,
int(tri.i0));
128 const glm::vec3
b = readPos(posXYZ,
int(tri.i1));
129 const glm::vec3
c = readPos(posXYZ,
int(tri.i2));
135 bool operator==(
const GridKey &o)
const {
return x == o.x &&
y == o.y &&
z == o.z; }
139 size_t operator()(
const GridKey &k)
const {
140 return size_t(k.x) * 73856093u ^ size_t(k.y) * 19349663u ^ size_t(k.z) * 83492791u;
144GridKey toKey(
const glm::vec3 &
p,
float cell) {
145 return {int(std::floor(
p.x /
cell)), int(std::floor(
p.y /
cell)), int(std::floor(
p.z /
cell))};
148bool tooClose(
const glm::vec3 &
p,
float radius,
149 const std::vector<Point> &accepted,
150 const std::unordered_map<GridKey, std::vector<int>, GridKeyHash> &grid,
float cell) {
151 const GridKey
c = toKey(
p,
cell);
153 for (
int dz = -1;
dz <= 1; ++
dz) {
154 for (
int dy = -1;
dy <= 1; ++
dy) {
155 for (
int dx = -1;
dx <= 1; ++
dx) {
157 auto it =
grid.find(k);
158 if (it ==
grid.end())
continue;
160 const glm::vec3
d = accepted[size_t(
idx)].position -
p;
161 if (glm::dot(
d,
d) < r2)
return true;
169float hash01(uint32_t
id) {
170 float h = std::fmod(
float(
id) * 0.61803398875f, 1.f);
171 if (
h < 0.f)
h += 1.f;
175float clampf(
float x,
float lo,
float hi) {
return std::min(hi, std::max(lo,
x)); }
177float coverEdge(
float distPx,
float radiusPx) {
179 return clampf(radiusPx + 0.65f - distPx, 0.f, 1.f);
182void stampOver(std::vector<uint8_t> &rgba,
int w,
int h,
int x,
int y,
float luma,
float a) {
183 if (
x < 0 || y < 0 || x >=
w ||
y >=
h)
return;
185 luma =
clampf(luma, 0.f, 1.f);
186 if (
a < 0.02f)
return;
187 const size_t i = (size_t(
y) * size_t(
w) + size_t(
x)) * 4u;
188 const float oa = float(rgba[i + 3]) / 255.f;
189 const float or_ = float(rgba[i + 0]) / 255.f;
190 const float og = float(rgba[i + 1]) / 255.f;
191 const float ob = float(rgba[i + 2]) / 255.f;
192 const float outA =
a + oa * (1.f -
a);
193 if (outA < 1e-4f)
return;
194 const float nr = (luma *
a + or_ * oa * (1.f -
a)) / outA;
195 const float ng = (luma *
a + og * oa * (1.f -
a)) / outA;
196 const float nb = (luma *
a + ob * oa * (1.f -
a)) / outA;
197 rgba[i + 0] = uint8_t(std::round(
clampf(nr, 0.f, 1.f) * 255.f));
198 rgba[i + 1] = uint8_t(std::round(
clampf(ng, 0.f, 1.f) * 255.f));
199 rgba[i + 2] = uint8_t(std::round(
clampf(nb, 0.f, 1.f) * 255.f));
200 rgba[i + 3] = uint8_t(std::round(
clampf(outA, 0.f, 1.f) * 255.f));
203void stampBlade(std::vector<uint8_t> &rgba,
int atlasW,
int atlasH,
int ox,
int frameW,
int frameH,
206 const float fw = float(frameW);
207 const float fh = float(frameH);
209 for (
int y = 0;
y < frameH; ++
y) {
210 for (
int x = 0;
x < frameW; ++
x) {
211 const float u = (float(
x) + 0.5f) / fw;
212 const float v = 1.f - (float(
y) + 0.5f) / fh;
213 if (v < -0.02f || v >
height + 0.04f)
continue;
215 const float cx = baseU +
lean *
t *
t;
217 const float dist = std::abs(
u -
cx) * fw;
218 float cov = coverEdge(dist, half * fw);
220 cov *=
clampf((
v + 0.02f) / 0.05f, 0.f, 1.f);
221 if (cov > 0.5f) cov = 1.f;
222 else if (cov < 0.25f) cov = 0.f;
223 if (cov <= 0.f)
continue;
224 const float side = (
u -
cx) * fw;
226 if (
side > 0.15f) luma *= 0.78f;
227 stampOver(rgba, atlasW, atlasH,
ox +
x,
y, luma, cov);
242void stampTuft(std::vector<uint8_t> &rgba,
int atlasW,
int atlasH,
int ox,
int frameW,
int frameH,
245 const float fw = float(frameW);
246 const float fh = float(frameH);
247 for (
int y = 0;
y < frameH; ++
y) {
248 for (
int x = 0;
x < frameW; ++
x) {
249 const float u = (float(
x) + 0.5f) / fw;
250 const float v = 1.f - (float(
y) + 0.5f) / fh;
251 if (
v > 0.16f)
continue;
252 const float cx = 0.50f + wind * 0.02f;
253 const float hw = 0.16f * (1.f -
v / 0.16f);
254 float cov = coverEdge(std::abs(
u -
cx) * fw, hw * fw);
255 if (cov > 0.5f) cov = 1.f;
256 else if (cov < 0.25f) cov = 0.f;
257 if (cov > 0.f) stampOver(rgba, atlasW, atlasH,
ox +
x,
y, 0.72f, cov);
262 const BladeDesc blades[] = {
263 {0.50f, 0.98f, 0.02f, 0.070f, 0.018f, 0.86f, 1.00f}, {0.38f, 0.90f, -0.14f, 0.062f, 0.016f, 0.82f, 0.97f},
264 {0.62f, 0.92f, 0.16f, 0.062f, 0.016f, 0.82f, 0.97f}, {0.28f, 0.74f, -0.26f, 0.056f, 0.016f, 0.78f, 0.93f},
265 {0.72f, 0.76f, 0.28f, 0.056f, 0.016f, 0.78f, 0.93f}, {0.44f, 0.84f, -0.06f, 0.050f, 0.014f, 0.84f, 0.98f},
266 {0.56f, 0.86f, 0.08f, 0.050f, 0.014f, 0.84f, 0.98f},
268 const float outline = 1.15f / fw;
269 for (
const BladeDesc &
b : blades) {
270 const float lean =
b.lean + wind * 0.32f;
271 const float u =
b.u + wind * 0.03f;
272 stampBlade(rgba, atlasW, atlasH,
ox, frameW, frameH,
u,
b.height,
lean,
b.baseHalf + outline,
273 b.tipHalf + outline * 0.5f, 0.42f, 0.55f);
274 stampBlade(rgba, atlasW, atlasH,
ox, frameW, frameH,
u,
b.height,
lean,
b.baseHalf,
b.tipHalf,
283 shader->sendFloat(
"frameCount",
float(std::max(
info.frames, 1)));
284 shader->sendFloat(
"atlasCols",
float(std::max(
info.atlasCols, 1)));
285 shader->sendFloat(
"atlasRows",
float(std::max(
info.atlasRows, 1)));
286 shader->sendFloat(
"grassVariantCount",
float(std::max(
info.grassVariants, 1)));
287 shader->sendFloat(
"leafVariantCount",
float(std::max(
info.leafVariants, 1)));
288 shader->sendFloat(
"leafRowOffset",
float(std::max(
info.leafRowOffset, 0)));
293 shader->sendFloat(
"alwaysDark", alwaysDark ? 1.f : 0.f);
298 shader->sendFloat(
"time", seconds);
303 shader->sendFloat(
"frameDuration", seconds > 1e-4f ? seconds : 1e-4f);
307 if (!gfx)
throw eve::Exception(
"grass::createShader: null graphics");
312 auto vert = copySpv(mesh3d_grass_vert_spv, mesh3d_grass_vert_spv_count);
313 auto frag = copySpv(mesh3d_grass_frag_spv, mesh3d_grass_frag_spv_count);
317 throw eve::Exception(
"grass::createShader: failed to create grass shader");
322int swayFrame(
float time,
float frameDuration, uint32_t instanceId,
int frameCount) {
323 if (frameCount < 1) frameCount = 1;
324 const float dur = frameDuration > 1e-4f ? frameDuration : 1e-4f;
325 const float t =
time / dur + hash01(instanceId) * float(frameCount);
326 int f = int(std::floor(
t)) % frameCount;
327 if (
f < 0)
f += frameCount;
331int swayAtlasWidth(
int frameW,
int frames) {
return std::max(frameW, 1) * std::max(frames, 1); }
335 frameW = std::max(frameW, 8);
336 frameH = std::max(frameH, 8);
337 frames = std::max(frames, 1);
340 rgbaOut.assign(
size_t(
w *
h * 4), 0);
342 for (
int f = 0;
f < frames; ++
f) {
343 const float wind = (float(
f) - 1.5f) / 1.5f;
344 stampTuft(rgbaOut,
w,
h,
f * frameW, frameW, frameH, wind);
349 if (!gfx)
throw eve::Exception(
"grass::createSwayAtlas: null graphics");
350 std::vector<uint8_t> rgba;
360bool readWholeFile(
const std::string &
path, std::vector<char> &out) {
361 std::ifstream in(
path, std::ios::binary);
362 if (!in)
return false;
363 out.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
364 return in.good() || !out.empty();
367void maskToTintable(std::vector<uint8_t> &rgba) {
368 for (
size_t i = 0; i + 3 < rgba.size(); i += 4) {
369 const uint8_t luma = std::max(rgba[i], std::max(rgba[i + 1], rgba[i + 2]));
370 const uint8_t
a = std::max(luma, rgba[i + 3]);
378bool loadSwayMaskPng(
const std::string &
path, std::vector<uint8_t> &rgba,
int &
w,
int &
h) {
379 std::vector<char> raw;
380 if (!readWholeFile(
path, raw))
return false;
382 if (!image)
return false;
384 std::unique_ptr<eve::image::ImageData> img(
image->newImageData(&
bytes));
385 if (!img)
return false;
387 h = img->getHeight();
388 if (
w < 2 ||
h < 2)
return false;
389 const size_t n = size_t(
w) * size_t(
h) * 4u;
390 if (img->getSize() <
n)
return false;
392 std::memcpy(rgba.data(), img->getData(),
n);
393 maskToTintable(rgba);
397void blitRgba(std::vector<uint8_t> &dst,
int dw,
int dh,
int dx,
int dy,
398 const std::vector<uint8_t> &src,
int sw,
int sh) {
399 for (
int y = 0;
y < sh; ++
y) {
400 const int ty =
dy +
y;
401 if (ty < 0 || ty >= dh)
continue;
402 for (
int x = 0;
x < sw; ++
x) {
403 const int tx =
dx +
x;
404 if (tx < 0 || tx >= dw)
continue;
405 const size_t si = (size_t(
y) * size_t(sw) + size_t(
x)) * 4u;
406 const size_t di = (size_t(ty) * size_t(dw) + size_t(tx)) * 4u;
407 dst[di + 0] = src[si + 0];
408 dst[di + 1] = src[si + 1];
409 dst[di + 2] = src[si + 2];
410 dst[di + 3] = src[si + 3];
418 const std::vector<std::string> &leafFiles, std::vector<uint8_t> &rgbaOut,
420 if (grassFiles.empty())
throw eve::Exception(
"grass::packSwayAtlasRGBA: no grass atlas files");
423 std::vector<uint8_t> rgba;
427 auto loadOne = [](
const std::string &
path) {
429 if (!loadSwayMaskPng(
path, img.rgba, img.w, img.h))
434 std::vector<Loaded> grass;
435 grass.reserve(grassFiles.size());
436 for (
const auto &
p : grassFiles) grass.push_back(loadOne(
p));
437 std::vector<Loaded> leaf;
438 leaf.reserve(leafFiles.size());
439 for (
const auto &
p : leafFiles) leaf.push_back(loadOne(
p));
441 const int tw = grass.front().w;
442 const int th = grass.front().h;
443 auto checkSize = [tw, th](
const Loaded &img,
const char *
kind) {
444 if (img.w != tw || img.h != th)
445 throw eve::Exception(
"grass::packSwayAtlasRGBA: %s atlas size mismatch (%dx%d vs %dx%d)",
446 kind, img.w, img.h, tw, th);
448 for (
const auto &img : grass) checkSize(img,
"grass");
449 for (
const auto &img : leaf) checkSize(img,
"leaf");
451 const int nGrass = int(grass.size());
452 const int nLeaf = int(leaf.size());
453 const int nX = std::max(nGrass, std::max(nLeaf, 1));
454 const int nY = nLeaf > 0 ? 2 : 1;
456 info.grassVariants = nGrass;
457 info.leafVariants = nLeaf > 0 ? nLeaf : 1;
458 info.leafRowOffset = nLeaf > 0 ? 2 : 0;
459 info.atlasCols = nX * 2;
460 info.atlasRows = nY * 2;
461 info.width = nX * tw;
462 info.height = nY * th;
463 rgbaOut.assign(
size_t(
info.width) *
size_t(
info.height) * 4u, 0);
465 for (
int i = 0; i < nGrass; ++i)
466 blitRgba(rgbaOut,
info.width,
info.height, i * tw, 0, grass[
size_t(i)].rgba, tw, th);
467 for (
int i = 0; i < nLeaf; ++i)
468 blitRgba(rgbaOut,
info.width,
info.height, i * tw, th, leaf[
size_t(i)].rgba, tw, th);
472 const std::vector<std::string> &leafFiles,
474 if (!gfx)
throw eve::Exception(
"grass::createSwayAtlasFromFiles: null graphics");
476 std::vector<uint8_t> rgba;
478 if (infoOut) *infoOut =
info;
487 std::vector<Point> out;
488 if (
count <= 0)
return out;
489 std::vector<Triangle> tris;
490 std::vector<float> cdf;
494 out.reserve(
size_t(
count));
495 for (
int i = 0; i <
count; ++i) {
496 const uint32_t
n = mixSeed(
seed, uint32_t(i + 1));
497 const float uTri = wrap01(radicalInverse(
n, 2));
498 const float u = wrap01(radicalInverse(
n, 3));
499 const float v = wrap01(radicalInverse(
n, 5));
500 const Triangle &tri = tris[size_t(pickTriangle(cdf, uTri))];
502 sampleOnTriangle(posXYZ, tri,
u,
v,
p.position);
505 p.scale = 0.85f + 0.3f * hash01(
p.id +
seed);
514 std::vector<Point> accepted;
515 if (
params.maxPoints <= 0 ||
params.radius <= 1e-6f)
return accepted;
517 std::vector<Triangle> tris;
518 std::vector<float> cdf;
524 std::unordered_map<GridKey, std::vector<int>, GridKeyHash> grid;
525 const int attempts = std::max(
params.maxPoints * 24,
params.maxPoints + 16);
527 for (
int i = 0; i < attempts && int(accepted.size()) <
params.maxPoints; ++i) {
528 const uint32_t
n = mixSeed(
params.seed, uint32_t(i + 1));
529 const float uTri = wrap01(radicalInverse(
n, 2));
530 const float u = wrap01(radicalInverse(
n, 3));
531 const float v = wrap01(radicalInverse(
n, 5));
532 const Triangle &tri = tris[size_t(pickTriangle(cdf, uTri))];
534 sampleOnTriangle(posXYZ, tri,
u,
v,
pos);
535 if (tooClose(
pos,
params.radius, accepted, grid,
cell))
continue;
540 p.id = uint32_t(accepted.size());
541 p.scale = 0.85f + 0.3f * hash01(
p.id +
params.seed);
542 const int idx = int(accepted.size());
543 accepted.push_back(
p);
550void makePlane(
float sizeX,
float sizeZ,
int segX,
int segZ, std::vector<float> &posXYZ,
551 std::vector<float> &nrmXYZ, std::vector<uint32_t> &
indices) {
552 segX = std::max(segX, 1);
553 segZ = std::max(segZ, 1);
554 sizeX = std::max(sizeX, 1e-3f);
555 sizeZ = std::max(sizeZ, 1e-3f);
556 const int nx = segX + 1;
557 const int nz = segZ + 1;
561 posXYZ.reserve(
size_t(
nx *
nz * 3));
562 nrmXYZ.reserve(
size_t(
nx *
nz * 3));
563 indices.reserve(
size_t(segX * segZ * 6));
565 for (
int z = 0;
z <
nz; ++
z) {
566 for (
int x = 0;
x <
nx; ++
x) {
567 const float px = (float(
x) / float(segX) - 0.5f) * sizeX;
568 const float pz = (float(
z) / float(segZ) - 0.5f) * sizeZ;
569 posXYZ.push_back(
px);
570 posXYZ.push_back(0.f);
571 posXYZ.push_back(
pz);
572 nrmXYZ.push_back(0.f);
573 nrmXYZ.push_back(1.f);
574 nrmXYZ.push_back(0.f);
577 for (
int z = 0;
z < segZ; ++
z) {
578 for (
int x = 0;
x < segX; ++
x) {
579 const uint32_t
i0 = uint32_t(
z *
nx +
x);
580 const uint32_t
i1 =
i0 + 1;
581 const uint32_t
i2 =
i0 + uint32_t(
nx);
582 const uint32_t i3 =
i2 + 1;
602 if (!reflectionCaptureEnabled_ || (reflectionCaptureMask_ &
mask) == 0
u)
return;
613 auto f=std::get_if<float>(&
a.value);
auto i=std::get_if<int64_t>(&
a.value);
614 if(
a.field==
"m_globalGrassDensity"){
if(!
f||!std::isfinite(*
f)||*
f<=0)
return fail(
"grass density multiplier must be positive and finite");photoModeDensity_=*
f;}
615 else if(
a.field==
"m_globalGrassDistance"){
if(!
f||!std::isfinite(*
f)||*
f<=0)
return fail(
"grass distance multiplier must be positive and finite");photoModeDistance_=*
f;}
616 else if(
a.field==
"m_cameraCellDistance"){
if(!
f||!std::isfinite(*
f)||*
f<=0)
return fail(
"camera-cell distance multiplier must be positive and finite");photoModeCellDistance_=*
f;}
617 else if(
a.field==
"m_cameraCellSubdivision"){
if(!i||*i<-8||*i>8)
return fail(
"camera-cell subdivision must be in [-8,8]");photoModeCellSubdivision_=int(*i);}
618 else return fail(
"unsupported grass photo-mode field");
621void GrassField::applyPhotoModeShaderState(){
622 const float cellScale=photoModeCellDistance_*std::exp2(
float(-photoModeCellSubdivision_));
623 detailHardDistance_=baseDetailHardDistance_*photoModeDistance_*photoModeCellDistance_;
624 detailDensity_=std::clamp(baseDetailDensity_*photoModeDensity_,0.f,1.f);
625 if(!shader_||!foliageProfile_)
return;
626 shader_->
sendFloat(
"atlasCols",baseDetailRenderDistance_*photoModeDistance_*cellScale);
627 shader_->
sendFloat(
"atlasRows",baseDetailFadeRange_*photoModeDistance_*cellScale);
628 shader_->
sendFloat(
"leafRowOffset",std::floor(detailHardDistance_)+detailDensity_*0.5f);
633 if (!gfx_)
throw eve::Exception(
"GrassField::bake: null graphics");
646 const auto densePts =
648 const auto sparsePts =
650 denseCount_ = int(densePts.size());
651 sparseCount_ = int(sparsePts.size());
658 if (!
params.grassAtlasFiles.empty()) {
677 shader_->
sendFloat(
"frameDuration", frameDuration_);
680 denseMesh_ =
nullptr;
681 sparseMesh_ =
nullptr;
682 if (!denseMesh.indices.empty())
683 denseMesh_ = gfx_->
newMeshFromArrays(denseMesh.posXYZ.data(), denseMesh.nrmXYZ.data(),
684 denseMesh.uvST.data(),
int(denseMesh.posXYZ.size() / 3),
685 denseMesh.indices.data(),
int(denseMesh.indices.size()));
686 if (!sparseMesh.indices.empty())
689 sparseMesh.uvST.data(),
int(sparseMesh.posXYZ.size() / 3),
690 sparseMesh.indices.data(),
int(sparseMesh.indices.size()));
698 std::vector<float>
pos, nrm;
699 std::vector<uint32_t>
idx;
715 frameDuration_ = seconds > 1e-4f ? seconds : 1e-4f;
723 if (!gfx_ || !shader_ || !atlas_)
return;
725 if (!foliageProfile_) {
std::uint32_t vertexCount
std::vector< std::uint32_t > indices
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
In-memory byte buffer implementing eve::Data (ref-counted).
virtual std::string getBackendName() const =0
Renderer backend id used by sibling modules (e.g. Gpgpu).
virtual Shader * newMeshShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv)=0
Create a Mesh3D custom shader (MeshVertex + Frame UBO + albedo). Empty vert → default mesh3d....
virtual Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false)=0
Creates a texture. @ownership Caller deletes unless documented otherwise.
virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader)=0
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
virtual Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
virtual Shader * newMeshShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl)=0
Create a Mesh3D custom shader from WGSL source (WebGPU backend). The WGSL must declare the engine's F...
virtual void setMesh3DNormalTexture(Texture *normal)=0
Optional normal map for the next drawMesh / drawMeshShader (nullptr = flat).
virtual void setMesh3DHeightTexture(Texture *height)=0
Optional height map for parallax (R channel; nullptr = flat / off).
void setFrameDuration(float seconds)
PhotoModeFieldAcceptance acceptsPhotoModeField(const PhotoModeAssignment &assignment) const noexcept override
Return true only for fields owned by this provider.
void bake(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, const BakeParams ¶ms)
void bakePlane(float sizeX, float sizeZ, int segX, int segZ)
void setTime(float seconds)
GrassField(Graphics *gfx)
Result< void > applyPhotoModeField(const PhotoModeAssignment &assignment) override
Apply one accepted assignment atomically on the game thread.
void setPhotoModeAuthority(bool enabled)
Register or revoke this field as the unique Pcg photo-mode grass authority.
static void removeCaptureExtraDrawer(uint64_t token)
Unregister an offscreen capture contributor.
static uint64_t addCaptureExtraDrawer(uint32_t reflectionCaptureMask, CaptureExtraDrawer drawer)
Register custom forward geometry for offscreen captures.
void sendFloat(const std::string &name, float x)
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
This module is responsible for decoding files such as PNG, GIF, JPEG into raw pixel data,...
std::vector< ParamSpec > params
float clampf(float v, float lo, float hi)
Clampf.
t3ssel8r-style stylized grass.
void bindLayer(Shader *shader, bool alwaysDark)
Binds layer.
void setFrameDuration(Shader *shader, float seconds)
Sets the frame duration.
int swayFrame(float time, float frameDuration, uint32_t instanceId, int frameCount)
Discrete 4-frame index with a per-instance phase offset.
Texture * createSwayAtlasFromFiles(Graphics *gfx, const std::vector< std::string > &grassFiles, const std::vector< std::string > &leafFiles, PackedAtlasInfo *infoOut)
Creates sway atlas from files.
Shader * createShader(Graphics *gfx)
Creates shader.
int swayAtlasWidth(int frameW, int frames)
Sway atlas width.
std::vector< Point > sampleHalton(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, int count, uint32_t seed, float minSlopeDot)
Area-weighted Halton samples (deterministic, evenly spread).
void makeSwayAtlasRGBA(int frameW, int frameH, int frames, std::vector< uint8_t > &rgbaOut)
Procedural 4-frame fallback atlas (horizontal strip). GPU paths should load authored 2x2 PNG masks vi...
Texture * createSwayAtlas(Graphics *gfx, int frameW, int frameH, int frames)
Creates sway atlas.
EVENGINE_API_BACKENDS void bindDefaults(Shader *shader)
Binds defaults.
int swayAtlasHeight(int frameH)
Sway atlas height.
std::vector< Point > samplePoisson(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, const SampleParams ¶ms)
Fast Poisson-disk / dart-throwing blue noise on a triangle mesh.
void packSwayAtlasRGBA(const std::vector< std::string > &grassFiles, const std::vector< std::string > &leafFiles, std::vector< uint8_t > &rgbaOut, PackedAtlasInfo &info)
Load white-on-black (or RGBA) 2x2 sway PNGs and pack them into one atlas.
void makePlane(float sizeX, float sizeZ, int segX, int segZ, std::vector< float > &posXYZ, std::vector< float > &nrmXYZ, std::vector< uint32_t > &indices)
Unit XZ plane (Y-up) for tests / demos.
void setTime(Shader *shader, float seconds)
Sets the time.
EVENGINE_API_BACKENDS BillboardMesh buildBillboards(const std::vector< Point > &points, float width=0.62f, float height=0.95f, bool alwaysDark=false)
Expand a unit rectangle per point. Vertex layout: pos = grass root uv = quad corner in [0,...
void bindAtlasLayout(Shader *shader, const PackedAtlasInfo &info)
Binds atlas layout.
constexpr const char * kGrassVertWgsl
constexpr const char * kGrassFragWgsl
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
PhotoModeFieldAcceptance
Capability implemented by the composition root that routes assignments to domain owners.
One reversible field assignment in a photo-mode transaction.
Options for Graphics::newTexture / newCubemap. When generateMipmaps is true and sampler....
static TextureSampler nearest()
Nearest.
static TextureSampler linear()
Linear.
Layout of a packed 2x2-per-variant sway atlas (4 grass + 2 leaf typical).
float minSlopeDot
Skip faces whose Y-up slope is below this (0 = keep walls).