9#include <glm/geometric.hpp>
10#include <glm/mat3x3.hpp>
15bool meshLooksValid(
const SkinTriMesh &
mesh) {
16 return mesh.posXYZ &&
mesh.indices &&
mesh.vertexCount > 0 &&
mesh.indexCount >= 3 &&
17 (
mesh.indexCount % 3) == 0;
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]};
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)) {
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));
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;
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};
60 return glm::dot(ap, ap);
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};
69 return glm::dot(bp, bp);
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);
77 const glm::vec3 diff =
p - closest;
78 return glm::dot(diff, diff);
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};
87 return glm::dot(cp, cp);
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);
95 const glm::vec3 diff =
p - closest;
96 return glm::dot(diff, diff);
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));
103 closest =
b +
w * (
c -
b);
104 const glm::vec3 diff =
p - closest;
105 return glm::dot(diff, diff);
108 const float denom = 1.f / (va + vb + vc);
109 const float v = vb * denom;
110 const float w = vc * denom;
113 const glm::vec3 diff =
p - closest;
114 return glm::dot(diff, diff);
117glm::vec3 evaluateRoot(
const SkinTriMesh &
mesh,
const RootAttach &attach) {
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;
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) {
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);
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;
150 r[0][0] = cosA +
x *
x *
cx;
154 r[1][1] = cosA +
y *
y *
cx;
158 r[2][2] = cosA +
z *
z *
cx;
167 if (!meshLooksValid(restMesh)) {
170 "hair.binding.mesh"));
173 const uint32_t triCount = uint32_t(restMesh.
indexCount / 3);
175 roots_.reserve(
strands.curveCount());
177 for (
size_t ci = 0; ci <
strands.curveCount(); ++ci) {
178 const auto pts =
strands.curvePoints(ci);
182 "hair.binding.curve"));
184 const glm::vec3
root = pts[0].position;
188 float bestDist2 = std::numeric_limits<float>::max();
190 for (uint32_t ti = 0; ti < triCount; ++ti) {
192 if (!triangleVerts(restMesh, ti,
a,
b,
c)) {
195 "hair.binding.triangle"));
197 glm::vec3
bary, closest;
198 const float d2 = closestOnTriangle(
root,
a,
b,
c,
bary, closest);
199 if (d2 < bestDist2) {
212 roots_.push_back(attach);
220 if (
index >= roots_.size())
return nullptr;
221 return &roots_[
index];
229 if (!meshLooksValid(deformedMesh)) {
232 "hair.binding.mesh"));
234 if (roots_.size() != restStrands.
curveCount()) {
237 "GroomBinding::deform: root count mismatch (rebuild binding)",
"hair.binding.roots"));
240 std::vector<StrandPoint>
points(restStrands.
points().begin(), restStrands.
points().end());
241 std::vector<StrandCurve> curves(restStrands.
curves().begin(), restStrands.
curves().end());
243 for (
size_t ci = 0; ci < roots_.size(); ++ci) {
246 if (
curve.pointCount == 0)
continue;
252 "hair.binding.triangle"));
254 const glm::vec3 skinRoot =
256 const glm::vec3 delta = skinRoot - attach.
restRoot;
259 for (uint32_t pi = 0; pi <
curve.pointCount; ++pi) {
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) {
Stable, structured diagnostics shared by engine modules.
HexCoordinates to
Cell the unit walks towards on this segment.
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.
const RootAttach * rootAt(size_t index) const
Root at.
Result< void > build(const StrandsDatas &strands, const SkinTriMesh &restMesh)
Project every strand root onto the closest rest-mesh triangle. @ownership Overwrites this binding; st...
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.
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.
size_t curveCount() const
std::span< const StrandPoint > points() const
Borrowed points; invalidated by mutation.
Procedural hair/fur card meshes + material/LOD helpers (no strand simulation).
BindingDeformMode
How bound strands follow a deformed skin triangle.
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...
glm::vec3 restNormal
Rest triangle geometric normal (unit).
glm::vec3 barycentric
Barycentric weights (u,v,w) with u+v+w ≈ 1.
glm::vec3 restRoot
Strand root position at bind time (rest pose).
Borrowed triangle mesh used as a scalp / skin bind target.
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 ...