17 glm::vec3
n{0.f, 1.f, 0.f};
20glm::vec3 readPos(
const float *
pos,
int i) {
21 return glm::vec3(
pos[i * 3],
pos[i * 3 + 1],
pos[i * 3 + 2]);
24glm::vec3 readNrm(
const float *nrm,
int i) {
25 if (!nrm)
return glm::vec3(0.f, 1.f, 0.f);
26 return glm::vec3(nrm[i * 3], nrm[i * 3 + 1], nrm[i * 3 + 2]);
29uint32_t mixSeed(uint32_t
seed, uint32_t i) {
30 seed ^= 0x9e3779b9u + (i << 6) + (i >> 2);
35float hash01(uint32_t
s) {
41 return float(
s >> 8) * (1.f / 16777215.f);
44bool buildTriangles(
const float *posXYZ,
const float *nrmXYZ,
int vertexCount,
const uint32_t *
indices,
45 int indexCount,
float minSlopeDot, std::vector<Triangle> &tris,
46 std::vector<float> &cdf) {
57 const glm::vec3
a = readPos(posXYZ,
int(
i0));
58 const glm::vec3
b = readPos(posXYZ,
int(
i1));
59 const glm::vec3
c = readPos(posXYZ,
int(
i2));
60 glm::vec3
n = glm::cross(
b -
a,
c -
a);
61 const float twice = glm::length(
n);
62 if (twice < 1e-10f)
continue;
66 readNrm(nrmXYZ,
int(
i0)) + readNrm(nrmXYZ,
int(
i1)) + readNrm(nrmXYZ,
int(
i2));
67 if (glm::dot(ns, ns) > 1e-8f)
n = glm::normalize(ns);
69 if (
n.y < minSlopeDot)
continue;
74 tri.area = 0.5f * twice;
80 return total > 1e-12f && !tris.empty();
83int pickTriangle(
const std::vector<float> &cdf,
float u) {
84 const float target =
u * cdf.back();
85 auto it = std::lower_bound(cdf.begin(), cdf.end(),
target);
86 int idx = int(it - cdf.begin());
87 if (
idx >=
int(cdf.size()))
idx = int(cdf.size()) - 1;
91glm::vec3 sampleOnTriangle(
const float *posXYZ,
const Triangle &tri,
float u,
float v) {
96 const glm::vec3
a = readPos(posXYZ,
int(tri.i0));
97 const glm::vec3
b = readPos(posXYZ,
int(tri.i1));
98 const glm::vec3
c = readPos(posXYZ,
int(tri.i2));
99 return a +
u * (
b -
a) +
v * (
c -
a);
111 if (
params.strandCount <= 0 ||
params.pointsPerStrand < 2) {
116 if (!(
params.length > 0.f) || !(
params.rootRadius >= 0.f) || !(
params.tipRadius >= 0.f)) {
122 std::vector<Triangle> tris;
123 std::vector<float> cdf;
128 "hair.procedural.mesh"));
132 std::vector<StrandPoint>
points;
133 std::vector<StrandCurve> curves;
135 curves.reserve(
size_t(
params.strandCount));
137 for (
int s = 0;
s <
params.strandCount; ++
s) {
138 const uint32_t
seed = mixSeed(
params.seed, uint32_t(
s));
139 const float uPick = hash01(
seed);
140 const float uBary = hash01(mixSeed(
seed, 1u));
141 const float vBary = hash01(mixSeed(
seed, 2u));
142 const Triangle &tri = tris[size_t(pickTriangle(cdf, uPick))];
150 const float bw = 1.f - bu - bv;
151 const glm::vec3
root = sampleOnTriangle(posXYZ, tri, bu, bv);
155 n = readNrm(nrmXYZ,
int(tri.i0)) * bw + readNrm(nrmXYZ,
int(tri.i1)) * bu +
156 readNrm(nrmXYZ,
int(tri.i2)) * bv;
157 if (glm::dot(
n,
n) > 1e-8f)
158 n = glm::normalize(
n);
163 glm::vec3
tangent = glm::cross(
n, glm::vec3(0.f, 0.f, 1.f));
166 const glm::vec3 bitangent = glm::normalize(glm::cross(
n,
tangent));
169 params.length + (hash01(mixSeed(
seed, 3u)) * 2.f - 1.f) *
params.lengthJitter;
170 const float safeLen = std::max(len,
params.length * 0.25f);
171 const float curlPhase = hash01(mixSeed(
seed, 4u)) * 6.2831853f;
172 const float curlAmp =
params.curlStrength * (0.5f + hash01(mixSeed(
seed, 5u)));
176 curve.pointCount = uint32_t(
params.pointsPerStrand);
178 glm::vec3 prev =
root;
179 for (
int p = 0;
p <
params.pointsPerStrand; ++
p) {
180 const float t = float(
p) / float(
params.pointsPerStrand - 1);
181 const float along =
t * safeLen;
182 const float curl = curlAmp * std::sin(curlPhase +
t * 6.2831853f * 1.5f);
183 const glm::vec3
pos =
184 root +
n * along +
tangent * (curl *
t) + bitangent * (curl * 0.35f *
t);
185 if (
p > 0) accum += glm::length(
pos - prev);
193 curve.length = accum;
194 curves.push_back(
curve);
205 if (!(sizeX > 0.f) || !(sizeZ > 0.f)) {
208 "hair.procedural.plane"));
210 const float hx = sizeX * 0.5f;
211 const float hz = sizeZ * 0.5f;
212 const float pos[12] = {-hx, 0.f, -hz, hx, 0.f, -hz, hx, 0.f, hz, -hx, 0.f, hz};
213 const float nrm[12] = {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0};
214 const uint32_t
idx[6] = {0, 1, 2, 0, 2, 3};
Stable, structured diagnostics shared by engine modules.
std::uint32_t vertexCount
std::vector< std::uint32_t > indices
std::shared_ptr< const std::vector< glm::vec2 > > points
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.
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.
CPU-authoritative strand buffer (UE FHairStrandsDatas analogue).
Result< void > validate() const
Verify offsets, counts, and finite values.
void setCurves(std::vector< StrandCurve > curves)
Sets the curves.
void setPoints(std::vector< StrandPoint > points)
Sets the points.
std::vector< ParamSpec > params
Procedural hair/fur card meshes + material/LOD helpers (no strand simulation).
Result< StrandsDatas > generateOnPlane(float sizeX, float sizeZ, const ProceduralParams ¶ms)
Grow strands on a Y-up XZ plane centered at the origin.
Result< StrandsDatas > generateOnMesh(const float *posXYZ, const float *nrmXYZ, int vertexCount, const uint32_t *indices, int indexCount, const ProceduralParams ¶ms)
Grow strands from area-weighted samples on an indexed triangle mesh.
Parameters for growing strands on a triangle mesh (scalp / body).
One strand as a contiguous point range. @ownership Value type owned by StrandsDatas.
One control point on a hair curve (UE FHairStrandsDatas point analogue). @ownership Value type owned ...
float u
Root-to-tip parameter in [0, 1].