载入中...
搜索中...
未找到
Procedural.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <cmath>
7#include <vector>
8
9#include <glm/glm.hpp>
10
11namespace eve::graphics::hair {
12namespace {
13
14struct Triangle {
15 uint32_t i0 = 0, i1 = 0, i2 = 0;
16 float area = 0.f;
17 glm::vec3 n{0.f, 1.f, 0.f};
18};
19
20glm::vec3 readPos(const float *pos, int i) {
21 return glm::vec3(pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]);
22}
23
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]);
27}
28
29uint32_t mixSeed(uint32_t seed, uint32_t i) {
30 seed ^= 0x9e3779b9u + (i << 6) + (i >> 2);
31 seed *= 0x85ebca6bu;
32 return seed ^ (seed >> 13);
33}
34
35float hash01(uint32_t s) {
36 s ^= s >> 16;
37 s *= 0x7feb352du;
38 s ^= s >> 15;
39 s *= 0x846ca68bu;
40 s ^= s >> 16;
41 return float(s >> 8) * (1.f / 16777215.f);
42}
43
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) {
47 tris.clear();
48 cdf.clear();
49 if (!posXYZ || !indices || vertexCount < 3 || indexCount < 3 || (indexCount % 3) != 0) return false;
50
51 float total = 0.f;
52 for (int t = 0; t + 2 < indexCount; t += 3) {
53 const uint32_t i0 = indices[t];
54 const uint32_t i1 = indices[t + 1];
55 const uint32_t i2 = indices[t + 2];
56 if (int(i0) >= vertexCount || int(i1) >= vertexCount || int(i2) >= vertexCount) continue;
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;
63 n /= twice;
64 if (nrmXYZ) {
65 glm::vec3 ns =
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);
68 }
69 if (n.y < minSlopeDot) continue;
70 Triangle tri;
71 tri.i0 = i0;
72 tri.i1 = i1;
73 tri.i2 = i2;
74 tri.area = 0.5f * twice;
75 tri.n = n;
76 total += tri.area;
77 tris.push_back(tri);
78 cdf.push_back(total);
79 }
80 return total > 1e-12f && !tris.empty();
81}
82
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;
88 return idx;
89}
90
91glm::vec3 sampleOnTriangle(const float *posXYZ, const Triangle &tri, float u, float v) {
92 if (u + v > 1.f) {
93 u = 1.f - u;
94 v = 1.f - v;
95 }
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);
100}
101
102} // namespace
103
104Result<StrandsDatas> generateOnMesh(const float *posXYZ, const float *nrmXYZ, int vertexCount,
105 const uint32_t *indices, int indexCount,
106 const ProceduralParams &params) {
107 if (!posXYZ || !indices) {
109 DiagnosticCode::InvalidArgument, "generateOnMesh: null mesh buffers", "hair.procedural"));
110 }
111 if (params.strandCount <= 0 || params.pointsPerStrand < 2) {
113 DiagnosticCode::InvalidArgument, "generateOnMesh: strandCount/pointsPerStrand invalid",
114 "hair.procedural"));
115 }
116 if (!(params.length > 0.f) || !(params.rootRadius >= 0.f) || !(params.tipRadius >= 0.f)) {
118 DiagnosticCode::InvalidArgument, "generateOnMesh: non-positive length/radius",
119 "hair.procedural"));
120 }
121
122 std::vector<Triangle> tris;
123 std::vector<float> cdf;
124 if (!buildTriangles(posXYZ, nrmXYZ, vertexCount, indices, indexCount, params.minSlopeDot, tris,
125 cdf)) {
127 DiagnosticCode::InvalidArgument, "generateOnMesh: no usable triangles",
128 "hair.procedural.mesh"));
129 }
130
131 StrandsDatas out;
132 std::vector<StrandPoint> points;
133 std::vector<StrandCurve> curves;
134 points.reserve(size_t(params.strandCount) * size_t(params.pointsPerStrand));
135 curves.reserve(size_t(params.strandCount));
136
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))];
143
144 float bu = uBary;
145 float bv = vBary;
146 if (bu + bv > 1.f) {
147 bu = 1.f - bu;
148 bv = 1.f - bv;
149 }
150 const float bw = 1.f - bu - bv;
151 const glm::vec3 root = sampleOnTriangle(posXYZ, tri, bu, bv);
152
153 glm::vec3 n = tri.n;
154 if (nrmXYZ) {
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);
159 else
160 n = tri.n;
161 }
162
163 glm::vec3 tangent = glm::cross(n, glm::vec3(0.f, 0.f, 1.f));
164 if (glm::dot(tangent, tangent) < 1e-8f) tangent = glm::cross(n, glm::vec3(1.f, 0.f, 0.f));
165 tangent = glm::normalize(tangent);
166 const glm::vec3 bitangent = glm::normalize(glm::cross(n, tangent));
167
168 const float len =
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)));
173
175 curve.pointOffset = uint32_t(points.size());
176 curve.pointCount = uint32_t(params.pointsPerStrand);
177 float accum = 0.f;
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);
186 prev = pos;
187 StrandPoint sp;
188 sp.position = pos;
189 sp.u = t;
190 sp.radius = params.rootRadius * (1.f - t) + params.tipRadius * t;
191 points.push_back(sp);
192 }
193 curve.length = accum;
194 curves.push_back(curve);
195 }
196
197 out.setPoints(std::move(points));
198 out.setCurves(std::move(curves));
199 auto check = out.validate();
200 if (!check.ok()) return Result<StrandsDatas>::failure(check.status());
201 return Result<StrandsDatas>::success(std::move(out));
202}
203
205 if (!(sizeX > 0.f) || !(sizeZ > 0.f)) {
207 DiagnosticCode::InvalidArgument, "generateOnPlane: non-positive size",
208 "hair.procedural.plane"));
209 }
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};
215 return generateOnMesh(pos, nrm, 4, idx, 6, params);
216}
217
218} // namespace eve::graphics::hair
LogicalId target
int root
Definition AnimSmr.cpp:119
const std::string & s
Vec3 tangent
Definition CaveMesh.cpp:80
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
std::uint32_t vertexCount
std::uint32_t indexCount
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
float area
Definition Grass.cpp:62
glm::vec3 n
Definition Grass.cpp:63
std::vector< std::uint32_t > indices
float v
std::int32_t c
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int idx
std::uint32_t seed
Definition PointSet.cpp:807
std::shared_ptr< const std::vector< glm::vec2 > > points
float t
Heightmap curve
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
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
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).
Definition Graphics.h:58
Result< StrandsDatas > generateOnPlane(float sizeX, float sizeZ, const ProceduralParams &params)
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 &params)
Grow strands from area-weighted samples on an indexed triangle mesh.
Parameters for growing strands on a triangle mesh (scalp / body).
Definition Procedural.h:16
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].