载入中...
搜索中...
未找到
GroomInstance.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4#include "common/Exception.h"
5#include "graphics/Color.h"
6#include "graphics/Graphics.h"
9#include "graphics/Mesh.h"
10#include "graphics/Shader.h"
11#include "graphics/Texture.h"
13
14#include <algorithm>
15#include <cmath>
16#include <vector>
17
18namespace eve::graphics::hair {
19namespace {
20
22CardMeshData buildCardsFromStrands(const StrandsDatas &strands, float widthScale) {
23 CardMeshData combined;
24 const float width = std::max(1e-4f, 0.004f * widthScale);
25 std::vector<float> pointsXYZ;
26 for (size_t ci = 0; ci < strands.curveCount(); ++ci) {
27 const auto pts = strands.curvePoints(ci);
28 if (pts.size() < 2) continue;
29 pointsXYZ.clear();
30 pointsXYZ.reserve(pts.size() * 3);
31 float avgRadius = 0.f;
32 for (const StrandPoint &p : pts) {
33 pointsXYZ.push_back(p.position.x);
34 pointsXYZ.push_back(p.position.y);
35 pointsXYZ.push_back(p.position.z);
36 avgRadius += p.radius;
37 }
38 avgRadius /= float(pts.size());
39 const float cardWidth = std::max(width, avgRadius * 4.f * widthScale);
40 appendCardMesh(combined, buildCardsAlongPolyline(pointsXYZ.data(), int(pts.size()), cardWidth));
41 }
42 return combined;
43}
44
45} // namespace
46
48 if (!gfx_) throw eve::Exception("GroomInstance: null graphics");
49}
50
52
53const GroomGroup *GroomInstance::primaryGroup() const { return asset_.groupAt(0); }
54
55size_t GroomInstance::resolveLodIndex(const GroomGroup &group) const {
56 if (group.lods.empty()) return 0;
57 if (forcedLod_ >= 0) {
58 size_t lodIndex = size_t(forcedLod_);
59 if (lodIndex >= group.lods.size()) lodIndex = group.lods.size() - 1;
60 return lodIndex;
61 }
62 return selectLodIndex(group.lods, screenSize_);
63}
64
65StrandsDatas GroomInstance::decimatedStrands(const StrandsDatas &src, float curveFraction) const {
66 StrandsDatas out;
67 if (src.curveCount() == 0) return out;
68 const float frac = std::clamp(curveFraction, 0.05f, 1.f);
69 const size_t keep =
70 std::max<size_t>(1, size_t(std::ceil(double(src.curveCount()) * double(frac))));
71 const size_t step = std::max<size_t>(1, src.curveCount() / keep);
72
73 std::vector<StrandPoint> points;
74 std::vector<StrandCurve> curves;
75 points.reserve(src.pointCount());
76 curves.reserve(keep);
77
78 for (size_t ci = 0; ci < src.curveCount(); ci += step) {
79 if (curves.size() >= keep) break;
80 const auto pts = src.curvePoints(ci);
81 if (pts.size() < 2) continue;
82 StrandCurve curve;
83 curve.pointOffset = uint32_t(points.size());
84 curve.pointCount = uint32_t(pts.size());
85 float len = 0.f;
86 for (size_t pi = 0; pi < pts.size(); ++pi) {
87 points.push_back(pts[pi]);
88 if (pi > 0) len += glm::length(pts[pi].position - pts[pi - 1].position);
89 }
90 curve.length = len;
91 curves.push_back(curve);
92 }
93 out.setPoints(std::move(points));
94 out.setCurves(std::move(curves));
95 return out;
96}
97
98Result<void> GroomInstance::rebuildClusterGrids() {
99 groupCull_.assign(asset_.groupCount(), GroupCullState{});
100 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
101 const GroomGroup *group = asset_.groupAt(gi);
102 if (!group) continue;
103 auto built = groupCull_[gi].clusters.build(group->strands, clusterCellSize_);
104 if (!built.ok()) return Result<void>::failure(built.status());
105 }
106 return Result<void>::success();
107}
108
109Result<void> GroomInstance::bakeFromStrands(StrandsDatas strands, const char *debugName) {
110 auto ok = strands.validate();
111 if (!ok.ok()) return Result<void>::failure(ok.status());
112
113 GroomGroup group;
114 group.name = debugName ? debugName : "default";
115 group.groupId = 0;
116 group.strands = std::move(strands);
117
118 // Mid LOD uses HairCards geometry (PR #400); far culls to None.
119 GroomLod nearLod;
120 nearLod.screenSize = 1.f;
121 nearLod.representation = Representation::Strands;
122 nearLod.curveFraction = 1.f;
123 GroomLod midLod;
124 midLod.screenSize = 0.35f;
125 midLod.representation = Representation::Cards;
126 midLod.curveFraction = 0.3f;
127 midLod.thicknessScale = 2.f;
128 GroomLod farLod;
129 farLod.screenSize = 0.12f;
130 farLod.representation = Representation::None;
131 farLod.curveFraction = 0.05f;
132 group.lods = {nearLod, midLod, farLod};
133
134 GroomAsset asset;
135 auto add = asset.addGroup(std::move(group));
136 if (!add.ok()) return Result<void>::failure(add.status());
137 return setAsset(asset);
138}
139
141 auto ok = asset.validate();
142 if (!ok.ok()) return Result<void>::failure(ok.status());
143 asset_ = asset;
144 const bool keepSim = guideSimEnabled_;
145 clearGuideSimulation();
146 auto grid = rebuildClusterGrids();
147 if (!grid.ok()) return Result<void>::failure(grid.status());
148 auto rebuilt = rebuild();
149 if (!rebuilt.ok()) return Result<void>::failure(rebuilt.status());
150 if (keepSim) {
151 return enableGuideSimulation(guideSimParams_, guideFraction_, guideInterpMode_);
152 }
153 return Result<void>::success();
154}
155
157 const ProceduralParams &params) {
158 auto strands = generateOnPlane(sizeX, sizeZ, params);
159 if (!strands.ok()) return Result<void>::failure(strands.status());
160 return bakeFromStrands(std::move(strands).value(), "procedural_plane");
161}
162
163Result<void> GroomInstance::bakeProceduralMesh(const float *posXYZ, const float *nrmXYZ,
164 int vertexCount, const uint32_t *indices,
165 int indexCount, const ProceduralParams &params) {
166 auto strands = generateOnMesh(posXYZ, nrmXYZ, vertexCount, indices, indexCount, params);
167 if (!strands.ok()) return Result<void>::failure(strands.status());
168 return bakeFromStrands(std::move(strands).value(), "procedural_mesh");
169}
170
171void GroomInstance::setForcedLod(int lod) { forcedLod_ = lod; }
172
173int GroomInstance::getForcedLod() const { return forcedLod_; }
174
175void GroomInstance::setScreenSize(float screenSize) {
176 screenSize_ = std::clamp(screenSize, 0.f, 1.f);
177}
178
179float GroomInstance::getScreenSize() const { return screenSize_; }
180
181void GroomInstance::setWidthScale(float scale) { widthScale_ = scale > 1e-6f ? scale : 1e-6f; }
182
183float GroomInstance::getWidthScale() const { return widthScale_; }
184
185void GroomInstance::setSideHint(float x, float y, float z) { sideHint_ = glm::vec3(x, y, z); }
186
188
189bool GroomInstance::isClusterCullingEnabled() const { return clusterCulling_; }
190
191void GroomInstance::setMarschnerLobes(float r, float tt, float trt) {
192 marschnerR_ = r >= 0.f ? r : 0.f;
193 marschnerTT_ = tt >= 0.f ? tt : 0.f;
194 marschnerTRT_ = trt >= 0.f ? trt : 0.f;
195 applyShadingParams();
196}
197
198float GroomInstance::getMarschnerR() const { return marschnerR_; }
199
200float GroomInstance::getMarschnerTT() const { return marschnerTT_; }
201
202float GroomInstance::getMarschnerTRT() const { return marschnerTRT_; }
203
204void GroomInstance::setSelfShadow(float strength, float bias, float rootAo) {
205 selfShadowStrength_ = std::clamp(strength, 0.f, 1.f);
206 selfShadowBias_ = std::clamp(bias, 0.f, 1.f);
207 rootAoStrength_ = std::clamp(rootAo, 0.f, 1.f);
208 applyShadingParams();
209}
210
211float GroomInstance::getSelfShadowStrength() const { return selfShadowStrength_; }
212
213float GroomInstance::getSelfShadowBias() const { return selfShadowBias_; }
214
215float GroomInstance::getRootAoStrength() const { return rootAoStrength_; }
216
217void GroomInstance::clearGuideSimulation() {
218 groupSim_.clear();
219 guideSimEnabled_ = false;
220}
221
222Result<void> GroomInstance::setupGuideSimulation(float guideFraction, InterpolationMode mode) {
223 groupSim_.assign(asset_.groupCount(), GroupSimState{});
224 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
225 const GroomGroup *group = asset_.groupAt(gi);
226 if (!group) continue;
227 GroupSimState &state = groupSim_[gi];
228 state.restStrands = group->strands;
229
230 if (group->guides.curveCount() > 0) {
231 state.restGuides = group->guides;
232 } else {
233 auto guides = extractGuides(group->strands, guideFraction);
234 if (!guides.ok()) return Result<void>::failure(guides.status());
235 state.restGuides = std::move(guides).value();
236 }
237
238 auto weights = buildGuideWeights(state.restStrands, state.restGuides, 3, mode);
239 if (!weights.ok()) return Result<void>::failure(weights.status());
240 state.weights = std::move(weights).value();
241
242 auto reset = state.simulator.reset(state.restGuides, guideSimParams_);
243 if (!reset.ok()) return Result<void>::failure(reset.status());
244
245 state.deformedStrands = state.restStrands;
246 state.active = true;
247 }
248 guideFraction_ = std::clamp(guideFraction, 0.01f, 1.f);
249 guideInterpMode_ = mode;
250 guideSimEnabled_ = true;
251 return Result<void>::success();
252}
253
255 InterpolationMode mode) {
256 if (asset_.groupCount() == 0) {
258 DiagnosticCode::InvalidArgument, "GroomInstance::enableGuideSimulation: no groups",
259 "hair.groom.sim"));
260 }
261 guideSimParams_ = params;
262 auto setup = setupGuideSimulation(guideFraction, mode);
263 if (!setup.ok()) {
264 clearGuideSimulation();
265 return Result<void>::failure(setup.status());
266 }
267 return rebuild();
268}
269
271 clearGuideSimulation();
272 if (asset_.groupCount() == 0) return Result<void>::success();
273 return rebuild();
274}
275
276bool GroomInstance::isGuideSimulationEnabled() const { return guideSimEnabled_; }
277
279 guideSimParams_ = params;
280 for (GroupSimState &state : groupSim_) {
281 if (state.active) state.simulator.setParams(guideSimParams_);
282 }
283}
284
285const GuideSimParams &GroomInstance::getGuideSimParams() const { return guideSimParams_; }
286
288 if (!guideSimEnabled_) return Result<void>::success();
289 if (groupSim_.size() != asset_.groupCount()) {
290 auto setup = setupGuideSimulation(guideFraction_, guideInterpMode_);
291 if (!setup.ok()) return Result<void>::failure(setup.status());
292 }
293
294 for (size_t gi = 0; gi < groupSim_.size(); ++gi) {
295 GroupSimState &state = groupSim_[gi];
296 if (!state.active) continue;
297 auto stepped = state.simulator.step(dt);
298 if (!stepped.ok()) return Result<void>::failure(stepped.status());
299 auto deformedGuides = state.simulator.snapshot();
300 if (!deformedGuides.ok()) return Result<void>::failure(deformedGuides.status());
301 auto strands = interpolateStrands(state.restStrands, state.restGuides,
302 deformedGuides.value(), state.weights, guideInterpMode_);
303 if (!strands.ok()) return Result<void>::failure(strands.status());
304 state.deformedStrands = std::move(strands).value();
305 }
306 return rebuild();
307}
308
309Mesh *GroomInstance::getMesh() const { return mesh_; }
310
311Shader *GroomInstance::getShader() const { return shader_; }
312
313Texture *GroomInstance::getTexture() const { return texture_; }
314
315size_t GroomInstance::getGroupCount() const { return asset_.groupCount(); }
316
317int GroomInstance::getActiveLodIndex() const { return activeLodIndex_; }
318
319int GroomInstance::getActiveRepresentation() const { return int(activeRepresentation_); }
320
322 size_t total = 0;
323 for (const GroupCullState &state : groupCull_) total += state.clusters.clusterCount();
324 return total;
325}
326
328 if (clusterCulling_) {
329 int total = 0;
330 bool anyMask = false;
331 for (const GroupCullState &state : groupCull_) {
332 if (!state.hasVisibility) continue;
333 anyMask = true;
334 total += int(state.visibleCurves.size());
335 }
336 if (anyMask) return total;
337 }
338 int total = 0;
339 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
340 const GroomGroup *g = asset_.groupAt(gi);
341 if (g) total += int(g->strands.curveCount());
342 }
343 return total;
344}
345
346void GroomInstance::applyShadingParams() {
347 if (!shader_) return;
348 shader_->sendFloat("marschnerR", marschnerR_);
349 shader_->sendFloat("marschnerTT", marschnerTT_);
350 shader_->sendFloat("marschnerTRT", marschnerTRT_);
351 shader_->sendFloat("selfShadowStrength", selfShadowStrength_);
352 shader_->sendFloat("selfShadowBias", selfShadowBias_);
353 shader_->sendFloat("rootAoStrength", rootAoStrength_);
354}
355
356Result<void> GroomInstance::ensureDrawResources() {
357 if (!shader_) {
358 shader_ = createShader(gfx_);
359 if (!shader_) {
361 DiagnosticCode::Failed, "GroomInstance: hair shader failed", "hair.groom.shader"));
362 }
363 }
364 applyShadingParams();
365 if (!texture_) {
366 const uint8_t white[4] = {210, 170, 120, 255};
367 texture_ = gfx_->newTexture(1, 1, white);
368 if (!texture_) {
370 DiagnosticCode::Failed, "GroomInstance: texture failed", "hair.groom.texture"));
371 }
372 }
373 return Result<void>::success();
374}
375
377 if (!viewProj16) {
379 DiagnosticCode::InvalidArgument, "GroomInstance::updateVisibility: null viewProj",
380 "hair.groom.viewProj"));
381 }
382 if (groupCull_.size() != asset_.groupCount()) {
383 auto grid = rebuildClusterGrids();
384 if (!grid.ok()) return Result<void>::failure(grid.status());
385 }
386 for (GroupCullState &state : groupCull_) {
387 auto visibleClusters = state.clusters.cullClusters(viewProj16);
388 if (!visibleClusters.ok()) return Result<void>::failure(visibleClusters.status());
389 auto curves = state.clusters.collectCurveIndices(visibleClusters.value());
390 if (!curves.ok()) return Result<void>::failure(curves.status());
391 state.visibleCurves = std::move(curves).value();
392 state.hasVisibility = true;
393 }
394 return rebuild();
395}
396
398 if (asset_.groupCount() == 0) {
400 DiagnosticCode::InvalidArgument, "GroomInstance::rebuild: no groups", "hair.groom"));
401 }
402 if (groupCull_.size() != asset_.groupCount()) {
403 auto grid = rebuildClusterGrids();
404 if (!grid.ok()) return Result<void>::failure(grid.status());
405 }
406
407 // Report LOD from the primary group (representative for script getters).
408 const GroomGroup *primary = primaryGroup();
409 if (primary && !primary->lods.empty()) {
410 activeLodIndex_ = int(resolveLodIndex(*primary));
411 activeRepresentation_ = primary->lods[size_t(activeLodIndex_)].representation;
412 } else {
413 activeLodIndex_ = 0;
414 activeRepresentation_ = Representation::None;
415 }
416
417 RibbonMesh ribbonCombined;
418 CardMeshData cardCombined;
419 bool anyRibbon = false;
420 bool anyCards = false;
421
422 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
423 const GroomGroup *group = asset_.groupAt(gi);
424 if (!group || group->lods.empty()) continue;
425
426 const size_t lodIndex = resolveLodIndex(*group);
427 const GroomLod &lod = group->lods[lodIndex];
428 if (lod.representation == Representation::None) continue;
429 if (lod.representation == Representation::Meshes) {
430 mesh_ = nullptr;
433 "GroomInstance::rebuild: Meshes LOD not implemented yet",
434 "hair.groom.representation"));
435 }
436
437 StrandsDatas source = group->strands;
438 if (guideSimEnabled_ && gi < groupSim_.size() && groupSim_[gi].active) {
439 source = groupSim_[gi].deformedStrands;
440 }
441 if (clusterCulling_ && gi < groupCull_.size() && groupCull_[gi].hasVisibility) {
442 if (groupCull_[gi].visibleCurves.empty()) continue;
443 auto filtered = filterStrandsByCurves(source, groupCull_[gi].visibleCurves);
444 if (!filtered.ok()) return Result<void>::failure(filtered.status());
445 source = std::move(filtered).value();
446 }
447
448 StrandsDatas strands = decimatedStrands(source, lod.curveFraction);
449 auto ok = strands.validate();
450 if (!ok.ok()) return Result<void>::failure(ok.status());
451
452 if (lod.representation == Representation::Cards) {
453 appendCardMesh(cardCombined,
454 buildCardsFromStrands(strands, widthScale_ * lod.thicknessScale));
455 anyCards = true;
456 continue;
457 }
458
459 RibbonParams ribbon;
460 ribbon.sideHint = sideHint_;
461 ribbon.widthScale = widthScale_ * lod.thicknessScale;
462 auto meshData = buildRibbons(strands, ribbon);
463 if (!meshData.ok()) return Result<void>::failure(meshData.status());
464 appendRibbonMesh(ribbonCombined, meshData.value());
465 anyRibbon = true;
466 }
467
468 // Prefer the primary group's representation when both ribbon and cards exist.
469 const bool useCards =
470 anyCards && (!anyRibbon || activeRepresentation_ == Representation::Cards);
471
472 if (useCards) {
473 if (cardCombined.indices.empty()) {
474 mesh_ = nullptr;
475 return Result<void>::success();
476 }
477 mesh_ = uploadCardMesh(gfx_, cardCombined);
478 if (!mesh_) {
480 DiagnosticCode::Failed, "GroomInstance::rebuild: card mesh upload failed",
481 "hair.groom.cards"));
482 }
483 return ensureDrawResources();
484 }
485
486 if (!anyRibbon || ribbonCombined.indices.empty()) {
487 mesh_ = nullptr;
488 return Result<void>::success();
489 }
490
491 mesh_ = gfx_->newMeshFromArrays(ribbonCombined.posXYZ.data(), ribbonCombined.nrmXYZ.data(),
492 ribbonCombined.uvST.data(), ribbonCombined.vertexCount(),
493 ribbonCombined.indices.data(), ribbonCombined.indexCount());
494 if (!mesh_) {
496 DiagnosticCode::Failed, "GroomInstance::rebuild: mesh upload failed", "hair.groom.mesh"));
497 }
498
499 return ensureDrawResources();
500}
501
502void GroomInstance::draw() { draw(lastModel_); }
503
504void GroomInstance::draw(const glm::mat4 &model) {
505 lastModel_ = model;
506 if (!gfx_ || !mesh_ || !shader_ || !texture_) return;
507 applyShadingParams();
508 const Color tint(1.f, 1.f, 1.f, 1.f);
509 gfx_->drawMeshShader(mesh_, model, texture_, tint, shader_);
510}
511
513 int total = 0;
514 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
515 const GroomGroup *g = asset_.groupAt(gi);
516 if (g) total += int(g->strands.curveCount());
517 }
518 return total;
519}
520
522 int total = 0;
523 for (size_t gi = 0; gi < asset_.groupCount(); ++gi) {
524 const GroomGroup *g = asset_.groupAt(gi);
525 if (g) total += int(g->strands.pointCount());
526 }
527 return total;
528}
529
530} // namespace eve::graphics::hair
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
building::EdgeCurveGroup group
int strands
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
std::uint32_t vertexCount
std::uint32_t indexCount
tensor::Graph g
Definition GpuGraph.cpp:7
glm::vec4 tint
double r
std::vector< std::uint32_t > indices
std::uint32_t width
std::array< float, 3 > position
std::array< float, 3 > scale
std::shared_ptr< const std::vector< glm::vec2 > > points
glm::mat4 model
int lod
float bias
Heightmap curve
float weights[3]
float step
Definition TreeMesh.cpp:314
const UnitySourceAsset & source
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.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
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)....
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
Custom GPU program.
Definition Shader.h:39
void sendFloat(const std::string &name, float x)
Definition Shader.cpp:95
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
Shared CPU groom container (UE UGroomAsset analogue).
Definition GroomAsset.h:52
const GroomGroup * groupAt(size_t index) const
Result< void > validate() const
Validate every group strands buffer and LOD table.
Shader * getShader() const
Returns the shader.
void setClusterCullingEnabled(bool enabled)
Enable/disable CPU cluster frustum filtering before mesh bake. When enabled, call updateVisibility wi...
GroomInstance(Graphics *gfx)
Groom instance.
float getSelfShadowBias() const
Returns the self shadow bias.
int getPointCount() const
Returns the point count.
Result< void > update(float dt)
Advance guide simulation by dt and rebuild the GPU mesh. No-op success when simulation is disabled.
Result< void > bakeProceduralPlane(float sizeX, float sizeZ, const ProceduralParams &params={})
Bake a procedural plane groom into a single default group.
void setSideHint(float x, float y, float z)
Sets the side hint.
Result< void > bakeProceduralMesh(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, const ProceduralParams &params={})
Bake procedural strands on an indexed triangle mesh.
float getMarschnerR() const
Returns the marschner r.
void setSelfShadow(float strength, float bias, float rootAo)
Cheap analytical self-shadow (fiber wrap + along-strand root AO).
void setWidthScale(float scale)
Sets the width scale.
Result< void > updateVisibility(const float *viewProj16)
Frustum-cull every group's cluster grid and rebuild GPU mesh.
Result< void > disableGuideSimulation()
Disable guide simulation and restore rest strands on next rebuild.
int getCurveCount() const
Returns the curve count.
float getWidthScale() const
Returns the width scale.
Result< void > setAsset(const GroomAsset &asset)
Replace runtime strands from a shared asset (deep copy of groups). @ownership Asset remains owned by ...
float getMarschnerTRT() const
Returns the marschner trt.
bool isClusterCullingEnabled() const
True when cluster culling enabled.
int getForcedLod() const
Returns the forced lod.
size_t getClusterCount() const
Returns the cluster count.
Result< void > rebuild()
Rebuild GPU mesh from all groups (LOD + optional visibility).
float getMarschnerTT() const
Returns the marschner tt.
const GuideSimParams & getGuideSimParams() const
Returns the guide sim params.
int getVisibleCurveCount() const
Returns the visible curve count.
float getScreenSize() const
Returns the screen size.
size_t getGroupCount() const
Returns the group count.
Mesh * getMesh() const
Returns the mesh.
void setGuideSimParams(const GuideSimParams &params)
void setMarschnerLobes(float r, float tt, float trt)
Marschner-approximate lobe weights (R / TT / TRT) applied to the hair shader. Defaults: 1 / 0....
int getActiveRepresentation() const
Returns the active representation.
float getRootAoStrength() const
Returns the root ao strength.
float getSelfShadowStrength() const
Returns the self shadow strength.
int getActiveLodIndex() const
Returns the active lod index.
void setScreenSize(float screenSize)
Screen-size hint in [0,1] used when forced LOD is disabled.
void setForcedLod(int lod)
Force LOD index; -1 restores automatic selection.
Result< void > enableGuideSimulation(const GuideSimParams &params={}, float guideFraction=0.15f, InterpolationMode mode=InterpolationMode::Offset)
Enable lightweight guide XPBD/Verlet for every group.
Texture * getTexture() const
Returns the texture.
CPU-authoritative strand buffer (UE FHairStrandsDatas analogue).
std::vector< ParamSpec > params
Procedural hair/fur card meshes + material/LOD helpers (no strand simulation).
Definition Graphics.h:58
CardMeshData buildCardsAlongPolyline(const float *pointsXYZ, int pointCount, float width)
Build one card per polyline segment, facing a camera-facing side approximated by world up.
Definition HairCards.cpp:75
InterpolationMode
How render strands follow deformed guides (UE interpolation analogue).
Definition Guides.h:19
Result< RibbonMesh > buildRibbons(const StrandsDatas &strands, const RibbonParams &params)
Expand validated strands into stable (non-view-facing) ribbons.
Result< StrandsDatas > generateOnPlane(float sizeX, float sizeZ, const ProceduralParams &params)
Grow strands on a Y-up XZ plane centered at the origin.
Result< StrandsDatas > extractGuides(const StrandsDatas &strands, float guideFraction)
Pick a deterministic subset of curves as guides (density thinning).
Definition Guides.cpp:56
Result< StrandsDatas > filterStrandsByCurves(const StrandsDatas &src, const std::vector< uint32_t > &curveIndices)
Copy a subset of curves into a new StrandsDatas (preserves point order).
Result< StrandsDatas > interpolateStrands(const StrandsDatas &strandsRest, const StrandsDatas &guidesRest, const StrandsDatas &guidesDeformed, const std::vector< StrandGuideWeights > &weights, InterpolationMode mode)
Drive rest render strands with deformed guides using precomputed weights. @ownership Returned datas u...
Definition Guides.cpp:154
Result< StrandsDatas > generateOnMesh(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, const ProceduralParams &params)
Grow strands from area-weighted samples on an indexed triangle mesh.
Mesh * uploadCardMesh(Graphics *gfx, const CardMeshData &data)
Upload card arrays as a Graphics-owned Mesh.
Result< std::vector< StrandGuideWeights > > buildGuideWeights(const StrandsDatas &strands, const StrandsDatas &guides, int maxInfluences, InterpolationMode mode)
Build k-nearest guide weights for every render strand (by root distance).
Definition Guides.cpp:103
void appendRibbonMesh(RibbonMesh &dst, const RibbonMesh &src)
Append src into dst, rebasing triangle indices by dst vertex count. @ownership Both meshes remain cal...
void appendCardMesh(CardMeshData &dst, const CardMeshData &src)
Append src into dst (vertex/index rebase).
size_t selectLodIndex(const std::vector< GroomLod > &lods, float screenSize)
Pick the LOD index for a given screen size (largest screenSize ≤ query).
Shader * createShader(Graphics *gfx)
Create the default hair shader (alpha-blended, anisotropic specular). Owned by Graphics.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
bool enabled
CPU-side interleaved mesh arrays for upload via Graphics::newMeshFromArrays.
Definition HairCards.h:27
std::vector< uint32_t > indices
Definition HairCards.h:31
One groom group (UE hair group analogue: bangs / scalp / beard …). @ownership Owned by GroomAsset.
Definition GroomAsset.h:31
std::vector< GroomLod > lods
Definition GroomAsset.h:40
One LOD entry for a groom group.
Definition GroomAsset.h:17
Parameters for CPU guide-strand dynamics (UE groom sim lite analogue).
Definition Simulation.h:21
Parameters for growing strands on a triangle mesh (scalp / body).
Definition Procedural.h:16
Triangle-list ribbon mesh expanded from strand curves.
int indexCount() const
Index count.
std::vector< float > posXYZ
std::vector< float > nrmXYZ
int vertexCount() const
Vertex count.
std::vector< uint32_t > indices
RibbonParams public API.
glm::vec3 sideHint
World-space side hint used to extrude the ribbon (normalized internally).
float widthScale
Multiplier applied to each point radius.