载入中...
搜索中...
未找到
Simulation.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <cmath>
7#include <utility>
8
9#include <glm/geometric.hpp>
10
11namespace eve::graphics::hair {
12namespace {
13
14constexpr float kEpsilon = 1e-8f;
15
16} // namespace
17
19 auto ok = guides.validate();
20 if (!ok.ok()) return Result<void>::failure(ok.status());
21 if (guides.curveCount() == 0) {
23 DiagnosticCode::InvalidArgument, "GuideSimulator::reset: empty guides",
24 "hair.sim.guides"));
25 }
26
27 params_ = params;
28 params_.damping = std::clamp(params_.damping, 0.f, 1.f);
29 params_.iterations = std::clamp(params_.iterations, 1, 16);
30 params_.compliance = std::max(0.f, params_.compliance);
31 params_.maxDt = std::max(1e-4f, params_.maxDt);
32
33 restPoints_.assign(guides.points().begin(), guides.points().end());
34 curves_.clear();
35 curves_.reserve(guides.curveCount());
36 segments_.clear();
37 pos_.clear();
38 prev_.clear();
39 pinned_.clear();
40
41 pos_.reserve(guides.pointCount());
42 prev_.reserve(guides.pointCount());
43 pinned_.reserve(guides.pointCount());
44
45 for (size_t ci = 0; ci < guides.curveCount(); ++ci) {
46 const auto pts = guides.curvePoints(ci);
47 if (pts.size() < 2) {
50 "GuideSimulator::reset: guide curve needs >= 2 points", "hair.sim.curve"));
51 }
52 CurveSpan span;
53 span.pointOffset = uint32_t(pos_.size());
54 span.pointCount = uint32_t(pts.size());
55 float len = 0.f;
56 for (size_t pi = 0; pi < pts.size(); ++pi) {
57 const glm::vec3 p = pts[pi].position;
58 pos_.push_back(p);
59 prev_.push_back(p);
60 pinned_.push_back(pi == 0 ? uint8_t(1) : uint8_t(0));
61 if (pi > 0) {
62 const float seg = glm::length(pts[pi].position - pts[pi - 1].position);
63 len += seg;
64 Segment s;
65 s.i0 = span.pointOffset + uint32_t(pi - 1);
66 s.i1 = span.pointOffset + uint32_t(pi);
67 s.restLength = std::max(seg, kEpsilon);
68 segments_.push_back(s);
69 }
70 }
71 span.length = len;
72 curves_.push_back(span);
73 }
74
75 return Result<void>::success();
76}
77
79 restPoints_.clear();
80 curves_.clear();
81 segments_.clear();
82 pos_.clear();
83 prev_.clear();
84 pinned_.clear();
85}
86
88 params_ = params;
89 params_.damping = std::clamp(params_.damping, 0.f, 1.f);
90 params_.iterations = std::clamp(params_.iterations, 1, 16);
91 params_.compliance = std::max(0.f, params_.compliance);
92 params_.maxDt = std::max(1e-4f, params_.maxDt);
93}
94
95Result<void> GuideSimulator::setPinnedRoots(const float *rootPositionsXYZ, int rootCount) {
96 if (!isReady()) {
98 DiagnosticCode::InvalidArgument, "GuideSimulator::setPinnedRoots: not ready",
99 "hair.sim"));
100 }
101 if (!rootPositionsXYZ || rootCount != int(curves_.size())) {
104 "GuideSimulator::setPinnedRoots: rootCount must equal curveCount", "hair.sim.roots"));
105 }
106 for (size_t ci = 0; ci < curves_.size(); ++ci) {
107 const uint32_t idx = curves_[ci].pointOffset;
108 const glm::vec3 p{rootPositionsXYZ[ci * 3 + 0], rootPositionsXYZ[ci * 3 + 1],
109 rootPositionsXYZ[ci * 3 + 2]};
110 if (!std::isfinite(p.x) || !std::isfinite(p.y) || !std::isfinite(p.z)) {
112 DiagnosticCode::InvalidArgument, "GuideSimulator::setPinnedRoots: non-finite",
113 "hair.sim.roots"));
114 }
115 const glm::vec3 delta = p - pos_[idx];
116 pos_[idx] = p;
117 prev_[idx] = p;
118 for (uint32_t pi = 1; pi < curves_[ci].pointCount; ++pi) {
119 pos_[idx + pi] += delta;
120 prev_[idx + pi] += delta;
121 }
122 }
123 return Result<void>::success();
124}
125
126void GuideSimulator::solveDistances(float dt) {
127 const float dt2 = dt * dt;
128 const float alpha = params_.compliance / std::max(dt2, kEpsilon);
129
130 for (int it = 0; it < params_.iterations; ++it) {
131 for (const Segment &seg : segments_) {
132 glm::vec3 &p0 = pos_[seg.i0];
133 glm::vec3 &p1 = pos_[seg.i1];
134 const float w0 = pinned_[seg.i0] ? 0.f : 1.f;
135 const float w1 = pinned_[seg.i1] ? 0.f : 1.f;
136 const float wSum = w0 + w1;
137 if (wSum < kEpsilon) continue;
138
139 glm::vec3 delta = p1 - p0;
140 float len = glm::length(delta);
141 if (len < kEpsilon) {
142 delta = glm::vec3(0.f, kEpsilon, 0.f);
143 len = kEpsilon;
144 }
145 const glm::vec3 n = delta / len;
146 const float C = len - seg.restLength;
147 const float corr = C / (wSum + alpha);
148 p0 += n * (corr * w0);
149 p1 -= n * (corr * w1);
150 }
151 }
152}
153
154void GuideSimulator::resolveCollisions() {
155 const float yPlane = params_.collisionY;
156 if (yPlane < -1.0e5f) return;
157 for (size_t i = 0; i < pos_.size(); ++i) {
158 if (pinned_[i]) continue;
159 if (pos_[i].y < yPlane) {
160 pos_[i].y = yPlane;
161 if (prev_[i].y < yPlane) prev_[i].y = yPlane;
162 }
163 }
164}
165
167 if (!isReady()) {
169 DiagnosticCode::InvalidArgument, "GuideSimulator::step: not ready", "hair.sim"));
170 }
171 if (!std::isfinite(dt) || dt <= 0.f) {
173 DiagnosticCode::InvalidArgument, "GuideSimulator::step: dt must be positive finite",
174 "hair.sim.dt"));
175 }
176
177 const float stepDt = std::min(dt, params_.maxDt);
178 const float damp = std::pow(std::clamp(params_.damping, 0.f, 1.f), stepDt);
179 const glm::vec3 accel = params_.gravity + params_.wind;
180
181 for (size_t i = 0; i < pos_.size(); ++i) {
182 if (pinned_[i]) {
183 prev_[i] = pos_[i];
184 continue;
185 }
186 const glm::vec3 cur = pos_[i];
187 const glm::vec3 vel = (cur - prev_[i]) * damp;
188 pos_[i] = cur + vel + accel * (stepDt * stepDt);
189 prev_[i] = cur;
190 }
191
192 solveDistances(stepDt);
193 resolveCollisions();
194
195 for (size_t i = 0; i < pos_.size(); ++i) {
196 if (pinned_[i]) pos_[i] = prev_[i];
197 }
198 return Result<void>::success();
199}
200
202 if (!isReady()) {
204 DiagnosticCode::InvalidArgument, "GuideSimulator::snapshot: not ready", "hair.sim"));
205 }
206 if (restPoints_.size() != pos_.size()) {
208 DiagnosticCode::InvariantViolation, "GuideSimulator::snapshot: size mismatch",
209 "hair.sim"));
210 }
211
212 std::vector<StrandPoint> points = restPoints_;
213 for (size_t i = 0; i < pos_.size(); ++i) {
214 points[i].position = pos_[i];
215 }
216 std::vector<StrandCurve> curves;
217 curves.reserve(curves_.size());
218 for (const CurveSpan &span : curves_) {
220 curve.pointOffset = span.pointOffset;
221 curve.pointCount = span.pointCount;
222 float len = 0.f;
223 for (uint32_t pi = 1; pi < span.pointCount; ++pi) {
224 len += glm::length(pos_[span.pointOffset + pi] - pos_[span.pointOffset + pi - 1]);
225 }
226 curve.length = len;
227 curves.push_back(curve);
228 }
229
230 StrandsDatas out;
231 out.setPoints(std::move(points));
232 out.setCurves(std::move(curves));
233 auto valid = out.validate();
234 if (!valid.ok()) return Result<StrandsDatas>::failure(valid.status());
235 return Result<StrandsDatas>::success(std::move(out));
236}
237
238} // namespace eve::graphics::hair
float y
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
glm::vec3 n
Definition Grass.cpp:63
std::array< float, 3 > position
bool valid
int idx
std::shared_ptr< const std::vector< glm::vec2 > > points
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
void setParams(const GuideSimParams &params)
Sets the params.
Result< void > step(float dt)
Advance simulation by dt seconds (clamped by params.maxDt).
Result< StrandsDatas > snapshot() const
Export current particle positions as StrandsDatas (same topology). @ownership Returned buffer uniquel...
const GuideSimParams & params() const
Params.
Definition Simulation.h:70
Result< void > reset(const StrandsDatas &guides, const GuideSimParams &params={})
Capture rest topology and pin every curve root. @ownership Copies guides; caller retains the source.
Result< void > setPinnedRoots(const float *rootPositionsXYZ, int rootCount)
Teleport pinned roots (e.g. after skin binding deform).
bool isReady() const
True when ready.
Definition Simulation.h:73
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::span< const StrandPoint > points() const
Borrowed points; invalidated by mutation.
std::span< const StrandPoint > curvePoints(size_t curveIndex) const
Borrowed points of one curve.
std::vector< ParamSpec > params
Procedural hair/fur card meshes + material/LOD helpers (no strand simulation).
Definition Graphics.h:58
Parameters for CPU guide-strand dynamics (UE groom sim lite analogue).
Definition Simulation.h:21
int iterations
XPBD solver iterations per substep (clamped to [1, 16]).
Definition Simulation.h:26
float collisionY
Infinite ground plane y = collisionY; particles below are pushed up. Set very low (default) to disabl...
Definition Simulation.h:39
float compliance
XPBD distance compliance (m/N); 0 ≈ hard constraint. Larger values allow more stretch.
Definition Simulation.h:31
float damping
Velocity retention per second (0 = freeze, 1 = no damping).
Definition Simulation.h:24
float maxDt
Clamp for a single step (seconds).
Definition Simulation.h:33
One strand as a contiguous point range. @ownership Value type owned by StrandsDatas.