11#include <glm/geometric.hpp>
16glm::vec3 rootOf(
const StrandsDatas &datas,
size_t curveIndex) {
17 const auto pts = datas.curvePoints(curveIndex);
18 return pts.empty() ? glm::vec3(0.f) : pts[0].
position;
21glm::vec3 sampleCurveAtU(
const StrandsDatas &datas,
size_t curveIndex,
float u) {
22 const auto pts = datas.curvePoints(curveIndex);
23 if (pts.empty())
return {};
24 if (pts.size() == 1)
return pts[0].position;
25 const float t = std::clamp(
u, 0.f, 1.f);
28 while (
i1 + 1 < pts.size() && pts[
i1].u <
t) ++
i1;
29 const size_t i0 =
i1 - 1;
30 const float u0 = pts[
i0].u;
31 const float u1 = pts[
i1].u;
33 if (std::fabs(u1 - u0) > 1e-8f) {
34 alpha = (
t - u0) / (u1 - u0);
36 alpha = float(
i1 -
i0) > 0.f ? 1.f : 0.f;
38 alpha = std::clamp(alpha, 0.f, 1.f);
39 return pts[
i0].position * (1.f - alpha) + pts[
i1].
position * alpha;
59 if (
strands.curveCount() == 0) {
64 const float frac = std::clamp(guideFraction, 0.01f, 1.f);
66 std::max<size_t>(1,
size_t(std::ceil(
double(
strands.curveCount()) *
double(frac))));
67 const size_t step = std::max<size_t>(1,
strands.curveCount() / keep);
69 std::vector<StrandPoint>
points;
70 std::vector<StrandCurve> curves;
74 for (
size_t ci = 0; ci <
strands.curveCount() && curves.size() < keep; ci +=
step) {
75 const auto pts =
strands.curvePoints(ci);
76 if (pts.size() < 2)
continue;
79 curve.pointCount = uint32_t(pts.size());
81 for (
size_t pi = 0; pi < pts.size(); ++pi) {
86 curves.push_back(
curve);
115 const float exp = weightExponent(mode);
116 std::vector<StrandGuideWeights> out(
strands.curveCount());
118 for (
size_t si = 0; si <
strands.curveCount(); ++si) {
124 std::vector<Cand> cands;
126 for (
size_t gi = 0; gi < guides.
curveCount(); ++gi) {
127 const glm::vec3
d = rootOf(guides, gi) -
root;
128 cands.push_back({uint32_t(gi), glm::dot(
d,
d)});
130 const size_t take = std::min(
size_t(k), cands.size());
131 std::partial_sort(cands.begin(), cands.begin() + std::ptrdiff_t(take), cands.end(),
132 [](
const Cand &
a,
const Cand &
b) { return a.dist2 < b.dist2; });
136 for (
size_t i = 0; i < take; ++i) {
137 const float dist = std::sqrt(std::max(cands[i].dist2, 0.f));
138 const float w = 1.f / std::pow(std::max(dist, 1e-4f), exp);
139 row.influencers[row.count] = {cands[i].index,
w};
144 for (
int i = 0; i < row.count; ++i) row.influencers[i].weight /= wSum;
145 }
else if (row.count > 0) {
146 row.influencers[0].weight = 1.f;
147 for (
int i = 1; i < row.count; ++i) row.influencers[i].weight = 0.f;
157 const std::vector<StrandGuideWeights> &
weights,
163 auto g1 = guidesDeformed.
validate();
168 "interpolateStrands: guide rest/deformed curve count mismatch",
"hair.guides"));
173 "interpolateStrands: weights size mismatch",
"hair.guides.weights"));
176 std::vector<StrandPoint>
points(strandsRest.
points().begin(), strandsRest.
points().end());
177 std::vector<StrandCurve> curves(strandsRest.
curves().begin(), strandsRest.
curves().end());
179 for (
size_t si = 0; si < strandsRest.
curveCount(); ++si) {
182 if (
curve.pointCount == 0 || row.count <= 0)
continue;
185 glm::vec3 delta(0.f);
186 for (
int i = 0; i < row.count; ++i) {
187 const auto &inf = row.influencers[i];
188 const glm::vec3 rest = rootOf(guidesRest, inf.guideIndex);
189 const glm::vec3 def = rootOf(guidesDeformed, inf.guideIndex);
190 delta += (def - rest) * inf.weight;
192 for (uint32_t pi = 0; pi <
curve.pointCount; ++pi) {
199 for (uint32_t pi = 0; pi <
curve.pointCount; ++pi) {
201 glm::vec3 delta(0.f);
202 for (
int i = 0; i < row.count; ++i) {
203 const auto &inf = row.influencers[i];
204 const glm::vec3 restP = sampleCurveAtU(guidesRest, inf.guideIndex, pt.
u);
205 const glm::vec3 defP = sampleCurveAtU(guidesDeformed, inf.guideIndex, pt.
u);
206 delta += (defP - restP) * inf.weight;
Stable, structured diagnostics shared by engine modules.
std::array< float, 3 > position
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).
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).
InterpolationMode
How render strands follow deformed guides (UE interpolation analogue).
Result< StrandsDatas > extractGuides(const StrandsDatas &strands, float guideFraction)
Pick a deterministic subset of curves as guides (density thinning).
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...
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).
One strand as a contiguous point range. @ownership Value type owned by StrandsDatas.
Up to kMaxInfluences guides influencing one render strand. @ownership Owned by the weights table retu...
static constexpr int kMaxInfluences
One control point on a hair curve (UE FHairStrandsDatas point analogue). @ownership Value type owned ...
float u
Root-to-tip parameter in [0, 1].