载入中...
搜索中...
未找到
Binding.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9#include <glm/geometric.hpp>
10#include <glm/mat3x3.hpp>
11
12namespace eve::graphics::hair {
13namespace {
14
15bool meshLooksValid(const SkinTriMesh &mesh) {
16 return mesh.posXYZ && mesh.indices && mesh.vertexCount > 0 && mesh.indexCount >= 3 &&
17 (mesh.indexCount % 3) == 0;
18}
19
20glm::vec3 vertexAt(const SkinTriMesh &mesh, uint32_t index) {
21 const float *p = mesh.posXYZ + size_t(index) * 3u;
22 return {p[0], p[1], p[2]};
23}
24
25bool triangleVerts(const SkinTriMesh &mesh, uint32_t tri, glm::vec3 &a, glm::vec3 &b, glm::vec3 &c) {
26 const uint32_t base = tri * 3u;
27 if (base + 2u >= uint32_t(mesh.indexCount)) return false;
28 const uint32_t i0 = mesh.indices[base];
29 const uint32_t i1 = mesh.indices[base + 1u];
30 const uint32_t i2 = mesh.indices[base + 2u];
31 if (i0 >= uint32_t(mesh.vertexCount) || i1 >= uint32_t(mesh.vertexCount) ||
32 i2 >= uint32_t(mesh.vertexCount)) {
33 return false;
34 }
35 a = vertexAt(mesh, i0);
36 b = vertexAt(mesh, i1);
37 c = vertexAt(mesh, i2);
38 return true;
39}
40
41glm::vec3 triangleNormal(const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &c) {
42 const glm::vec3 n = glm::cross(b - a, c - a);
43 const float len2 = glm::dot(n, n);
44 if (len2 < 1e-20f) return {0.f, 1.f, 0.f};
45 return n * (1.f / std::sqrt(len2));
46}
47
49float closestOnTriangle(const glm::vec3 &p, const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &c,
50 glm::vec3 &bary, glm::vec3 &closest) {
51 const glm::vec3 ab = b - a;
52 const glm::vec3 ac = c - a;
53 const glm::vec3 ap = p - a;
54
55 const float d1 = glm::dot(ab, ap);
56 const float d2 = glm::dot(ac, ap);
57 if (d1 <= 0.f && d2 <= 0.f) {
58 bary = {1.f, 0.f, 0.f};
59 closest = a;
60 return glm::dot(ap, ap);
61 }
62
63 const glm::vec3 bp = p - b;
64 const float d3 = glm::dot(ab, bp);
65 const float d4 = glm::dot(ac, bp);
66 if (d3 >= 0.f && d4 <= d3) {
67 bary = {0.f, 1.f, 0.f};
68 closest = b;
69 return glm::dot(bp, bp);
70 }
71
72 const float vc = d1 * d4 - d3 * d2;
73 if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) {
74 const float v = d1 / (d1 - d3);
75 bary = {1.f - v, v, 0.f};
76 closest = a + v * ab;
77 const glm::vec3 diff = p - closest;
78 return glm::dot(diff, diff);
79 }
80
81 const glm::vec3 cp = p - c;
82 const float d5 = glm::dot(ab, cp);
83 const float d6 = glm::dot(ac, cp);
84 if (d6 >= 0.f && d5 <= d6) {
85 bary = {0.f, 0.f, 1.f};
86 closest = c;
87 return glm::dot(cp, cp);
88 }
89
90 const float vb = d5 * d2 - d1 * d6;
91 if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) {
92 const float w = d2 / (d2 - d6);
93 bary = {1.f - w, 0.f, w};
94 closest = a + w * ac;
95 const glm::vec3 diff = p - closest;
96 return glm::dot(diff, diff);
97 }
98
99 const float va = d3 * d6 - d5 * d4;
100 if (va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) {
101 const float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
102 bary = {0.f, 1.f - w, w};
103 closest = b + w * (c - b);
104 const glm::vec3 diff = p - closest;
105 return glm::dot(diff, diff);
106 }
107
108 const float denom = 1.f / (va + vb + vc);
109 const float v = vb * denom;
110 const float w = vc * denom;
111 bary = {1.f - v - w, v, w};
112 closest = a + ab * v + ac * w;
113 const glm::vec3 diff = p - closest;
114 return glm::dot(diff, diff);
115}
116
117glm::vec3 evaluateRoot(const SkinTriMesh &mesh, const RootAttach &attach) {
118 glm::vec3 a, b, c;
119 if (!triangleVerts(mesh, attach.triangleIndex, a, b, c)) return attach.restRoot;
120 return attach.barycentric.x * a + attach.barycentric.y * b + attach.barycentric.z * c;
121}
122
123glm::mat3 rotationFromTo(const glm::vec3 &from, const glm::vec3 &to) {
124 const glm::vec3 f = glm::normalize(from);
125 const glm::vec3 t = glm::normalize(to);
126 const float cosA = std::clamp(glm::dot(f, t), -1.f, 1.f);
127 if (cosA > 0.9999f) return glm::mat3(1.f);
128 if (cosA < -0.9999f) {
129 // 180°: pick a stable perpendicular and reflect through that plane twice.
130 glm::vec3 axis = glm::cross(f, glm::vec3(1.f, 0.f, 0.f));
131 if (glm::dot(axis, axis) < 1e-8f) axis = glm::cross(f, glm::vec3(0.f, 0.f, 1.f));
132 axis = glm::normalize(axis);
133 glm::mat3 r(0.f);
134 r[0][0] = 2.f * axis.x * axis.x - 1.f;
135 r[1][0] = 2.f * axis.x * axis.y;
136 r[2][0] = 2.f * axis.x * axis.z;
137 r[0][1] = 2.f * axis.y * axis.x;
138 r[1][1] = 2.f * axis.y * axis.y - 1.f;
139 r[2][1] = 2.f * axis.y * axis.z;
140 r[0][2] = 2.f * axis.z * axis.x;
141 r[1][2] = 2.f * axis.z * axis.y;
142 r[2][2] = 2.f * axis.z * axis.z - 1.f;
143 return r;
144 }
145 const glm::vec3 axis = glm::normalize(glm::cross(f, t));
146 const float s = std::sqrt(std::max(0.f, 1.f - cosA * cosA));
147 const float cx = 1.f - cosA;
148 const float x = axis.x, y = axis.y, z = axis.z;
149 glm::mat3 r(1.f);
150 r[0][0] = cosA + x * x * cx;
151 r[0][1] = y * x * cx + z * s;
152 r[0][2] = z * x * cx - y * s;
153 r[1][0] = x * y * cx - z * s;
154 r[1][1] = cosA + y * y * cx;
155 r[1][2] = z * y * cx + x * s;
156 r[2][0] = x * z * cx + y * s;
157 r[2][1] = y * z * cx - x * s;
158 r[2][2] = cosA + z * z * cx;
159 return r;
160}
161
162} // namespace
163
165 auto ok = strands.validate();
166 if (!ok.ok()) return Result<void>::failure(ok.status());
167 if (!meshLooksValid(restMesh)) {
169 DiagnosticCode::InvalidArgument, "GroomBinding::build: invalid skin mesh",
170 "hair.binding.mesh"));
171 }
172
173 const uint32_t triCount = uint32_t(restMesh.indexCount / 3);
174 roots_.clear();
175 roots_.reserve(strands.curveCount());
176
177 for (size_t ci = 0; ci < strands.curveCount(); ++ci) {
178 const auto pts = strands.curvePoints(ci);
179 if (pts.empty()) {
181 DiagnosticCode::InvariantViolation, "GroomBinding::build: empty curve",
182 "hair.binding.curve"));
183 }
184 const glm::vec3 root = pts[0].position;
185
186 RootAttach attach;
187 attach.restRoot = root;
188 float bestDist2 = std::numeric_limits<float>::max();
189 bool found = false;
190 for (uint32_t ti = 0; ti < triCount; ++ti) {
191 glm::vec3 a, b, c;
192 if (!triangleVerts(restMesh, ti, a, b, c)) {
194 DiagnosticCode::InvalidArgument, "GroomBinding::build: bad triangle index",
195 "hair.binding.triangle"));
196 }
197 glm::vec3 bary, closest;
198 const float d2 = closestOnTriangle(root, a, b, c, bary, closest);
199 if (d2 < bestDist2) {
200 bestDist2 = d2;
201 attach.triangleIndex = ti;
202 attach.barycentric = bary;
203 attach.restNormal = triangleNormal(a, b, c);
204 found = true;
205 }
206 }
207 if (!found) {
209 DiagnosticCode::Failed, "GroomBinding::build: no triangle found",
210 "hair.binding"));
211 }
212 roots_.push_back(attach);
213 }
214 return Result<void>::success();
215}
216
217void GroomBinding::clear() { roots_.clear(); }
218
220 if (index >= roots_.size()) return nullptr;
221 return &roots_[index];
222}
223
225 const SkinTriMesh &deformedMesh,
226 BindingDeformMode mode) const {
227 auto ok = restStrands.validate();
228 if (!ok.ok()) return Result<StrandsDatas>::failure(ok.status());
229 if (!meshLooksValid(deformedMesh)) {
231 DiagnosticCode::InvalidArgument, "GroomBinding::deform: invalid skin mesh",
232 "hair.binding.mesh"));
233 }
234 if (roots_.size() != restStrands.curveCount()) {
237 "GroomBinding::deform: root count mismatch (rebuild binding)", "hair.binding.roots"));
238 }
239
240 std::vector<StrandPoint> points(restStrands.points().begin(), restStrands.points().end());
241 std::vector<StrandCurve> curves(restStrands.curves().begin(), restStrands.curves().end());
242
243 for (size_t ci = 0; ci < roots_.size(); ++ci) {
244 const RootAttach &attach = roots_[ci];
245 const StrandCurve &curve = curves[ci];
246 if (curve.pointCount == 0) continue;
247
248 glm::vec3 a, b, c;
249 if (!triangleVerts(deformedMesh, attach.triangleIndex, a, b, c)) {
251 DiagnosticCode::InvalidArgument, "GroomBinding::deform: bad deformed triangle",
252 "hair.binding.triangle"));
253 }
254 const glm::vec3 skinRoot =
255 attach.barycentric.x * a + attach.barycentric.y * b + attach.barycentric.z * c;
256 const glm::vec3 delta = skinRoot - attach.restRoot;
257
258 if (mode == BindingDeformMode::Rigid) {
259 for (uint32_t pi = 0; pi < curve.pointCount; ++pi) {
260 points[curve.pointOffset + pi].position += delta;
261 }
262 } else {
263 const glm::vec3 defN = triangleNormal(a, b, c);
264 const glm::mat3 rot = rotationFromTo(attach.restNormal, defN);
265 for (uint32_t pi = 0; pi < curve.pointCount; ++pi) {
266 StrandPoint &pt = points[curve.pointOffset + pi];
267 const glm::vec3 local = pt.position - attach.restRoot;
268 pt.position = skinRoot + rot * local;
269 }
270 }
271 }
272
273 StrandsDatas out;
274 out.setPoints(std::move(points));
275 out.setCurves(std::move(curves));
276 auto valid = out.validate();
277 if (!valid.ok()) return Result<StrandsDatas>::failure(valid.status());
278 return Result<StrandsDatas>::success(std::move(out));
279}
280
281} // namespace eve::graphics::hair
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
std::string from
const std::string & s
int strands
float cx
Definition CardTypes.cpp:33
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
glm::vec3 n
Definition Grass.cpp:63
std::uint32_t ab
double r
std::uint32_t ac
float v
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string local
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float f
std::shared_ptr< const std::vector< glm::vec2 > > points
float t
Mesh * mesh
bool found
Heightmap curve
uint32_t index
glm::vec4 bary
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
const RootAttach * rootAt(size_t index) const
Root at.
Definition Binding.cpp:219
Result< void > build(const StrandsDatas &strands, const SkinTriMesh &restMesh)
Project every strand root onto the closest rest-mesh triangle. @ownership Overwrites this binding; st...
Definition Binding.cpp:164
Result< StrandsDatas > deform(const StrandsDatas &restStrands, const SkinTriMesh &deformedMesh, BindingDeformMode mode=BindingDeformMode::Rigid) const
Deform rest strands using this binding and a deformed skin mesh.
Definition Binding.cpp:224
CPU-authoritative strand buffer (UE FHairStrandsDatas analogue).
std::span< const StrandCurve > curves() const
Borrowed curves; invalidated by mutation.
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::span< const StrandPoint > points() const
Borrowed points; invalidated by mutation.
Procedural hair/fur card meshes + material/LOD helpers (no strand simulation).
Definition Graphics.h:58
BindingDeformMode
How bound strands follow a deformed skin triangle.
Definition Binding.h:37
int axis(int64_t a, size_t rank)
Axis.
One strand root projected onto a skin triangle (UE binding entry analogue). @ownership Owned by Groom...
Definition Binding.h:43
glm::vec3 restNormal
Rest triangle geometric normal (unit).
Definition Binding.h:50
glm::vec3 barycentric
Barycentric weights (u,v,w) with u+v+w ≈ 1.
Definition Binding.h:46
glm::vec3 restRoot
Strand root position at bind time (rest pose).
Definition Binding.h:48
Borrowed triangle mesh used as a scalp / skin bind target.
Definition Binding.h:22
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 ...