载入中...
搜索中...
未找到
MeshParticleEmitter.cpp
浏览该文件的文档.
2
3#include <glm/geometric.hpp>
4#include <glm/gtc/constants.hpp>
5#include <glm/gtc/matrix_transform.hpp>
6
7#include <algorithm>
8#include <cmath>
9#include <limits>
10
11namespace eve::stylize {
12namespace {
13
14float finiteOr(float value, float fallback) { return std::isfinite(value) ? value : fallback; }
15
16graphics::Color mixColor(const graphics::Color& a, const graphics::Color& b, float t) {
17 return {a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t,
18 a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t};
19}
20
21template <typename Key, typename Value, typename Interpolate>
22Value sampleKeys(const std::vector<Key>& keys, float time, Value fallback, Interpolate interpolate) {
23 if (keys.empty()) return fallback;
24 if (time <= keys.front().time) return keys.front().value;
25 if (time >= keys.back().time) return keys.back().value;
26 const auto upper = std::upper_bound(keys.begin(), keys.end(), time,
27 [](float value, const Key& key) { return value < key.time; });
28 const auto& right = *upper;
29 const auto& left = *(upper - 1);
30 const float width = right.time - left.time;
31 const float t = width > 1e-6f ? (time - left.time) / width : 1.f;
32 return interpolate(left.value, right.value, t);
33}
34
35template <typename Key>
36void sanitizeKeys(std::vector<Key>& keys) {
37 for (auto& key : keys) key.time = std::clamp(finiteOr(key.time, 0.f), 0.f, 1.f);
38 std::stable_sort(keys.begin(), keys.end(),
39 [](const Key& a, const Key& b) { return a.time < b.time; });
40 keys.erase(std::unique(keys.begin(), keys.end(),
41 [](const Key& a, const Key& b) {
42 return std::abs(a.time - b.time) <= 1e-6f;
43 }),
44 keys.end());
45}
46
47float sampleScalar(const std::vector<MeshParticleScalarKey>& keys, float time, float fallback) {
48 return sampleKeys(keys, time, fallback,
49 [](float a, float b, float t) { return a + (b - a) * t; });
50}
51
52graphics::Color sampleColor(const std::vector<MeshParticleColorKey>& keys, float time,
53 const graphics::Color& fallback) {
54 return sampleKeys(keys, time, fallback,
55 [](const auto& a, const auto& b, float t) { return mixColor(a, b, t); });
56}
57
58glm::vec3 safeDirection(glm::vec3 direction) {
59 const float length = glm::length(direction);
60 return length > 1e-6f && std::isfinite(length) ? direction / length : glm::vec3(0.f, 1.f, 0.f);
61}
62
63} // namespace
64
66 std::uint64_t stableId = 0;
67 glm::vec3 position{0.f};
68 glm::vec3 velocity{0.f};
69 float age = 0.f;
70 float lifetime = 1.f;
71 float rotationRadians = 0.f;
72 float angularVelocity = 0.f;
73};
74
76
77std::size_t MeshParticleEmitter::size() const noexcept { return particles_.size(); }
78
80 : config_(std::move(config)) {
81 config_.capacity = std::max(1u, config_.capacity);
82 config_.emissionRate = std::max(0.f, finiteOr(config_.emissionRate, 0.f));
83 config_.lifetimeMin = std::max(1e-4f, finiteOr(config_.lifetimeMin, 1.f));
84 config_.lifetimeMax = std::max(config_.lifetimeMin, finiteOr(config_.lifetimeMax, config_.lifetimeMin));
85 config_.speedMin = finiteOr(config_.speedMin, 0.f);
86 config_.speedMax = std::max(config_.speedMin, finiteOr(config_.speedMax, config_.speedMin));
87 config_.direction = safeDirection(config_.direction);
88 config_.boxExtents = glm::max(glm::abs(config_.boxExtents), glm::vec3(0.f));
89 config_.sphereRadius = std::max(0.f, finiteOr(config_.sphereRadius, 0.f));
90 config_.coneAngleRadians = std::clamp(finiteOr(config_.coneAngleRadians, 0.f), 0.f, glm::pi<float>());
91 config_.damping = std::max(0.f, finiteOr(config_.damping, 0.f));
92 config_.rotationMinRadians = finiteOr(config_.rotationMinRadians, 0.f);
93 config_.rotationMaxRadians =
94 std::max(config_.rotationMinRadians,
95 finiteOr(config_.rotationMaxRadians, config_.rotationMinRadians));
96 config_.angularVelocityMin = finiteOr(config_.angularVelocityMin, 0.f);
97 config_.angularVelocityMax =
98 std::max(config_.angularVelocityMin,
99 finiteOr(config_.angularVelocityMax, config_.angularVelocityMin));
100 config_.fixedStepSeconds = std::max(1e-4f, finiteOr(config_.fixedStepSeconds, 1.f / 60.f));
101 config_.maximumSubsteps = std::max(1u, config_.maximumSubsteps);
102 config_.loopDurationSeconds = std::max(0.f, finiteOr(config_.loopDurationSeconds, 0.f));
103 std::stable_sort(config_.bursts.begin(), config_.bursts.end(),
104 [](const auto& a, const auto& b) { return a.timeSeconds < b.timeSeconds; });
105 sanitizeKeys(config_.scaleCurve);
106 sanitizeKeys(config_.velocityCurve);
107 sanitizeKeys(config_.colorGradient);
108 particles_.reserve(config_.capacity);
109 reset();
110}
111
113 timeline_ = 0.f;
114 accumulator_ = 0.f;
115 emissionAccumulator_ = 0.f;
116 nextBurst_ = 0;
117 randomState_ = config_.randomSeed ? config_.randomSeed : 1u;
118 emitting_ = true;
119}
120
122 particles_.clear();
123 nextStableId_ = 1;
124 start();
125 emitting_ = false;
126}
127
128std::uint32_t MeshParticleEmitter::emit(std::uint32_t count) { return spawn(count, nullptr); }
129
130float MeshParticleEmitter::randomUnit() {
131 randomState_ ^= randomState_ << 13u;
132 randomState_ ^= randomState_ >> 17u;
133 randomState_ ^= randomState_ << 5u;
134 return static_cast<float>(randomState_ >> 8u) * (1.f / 16777216.f);
135}
136
137glm::vec3 MeshParticleEmitter::randomDirection() {
138 const float z = randomUnit() * 2.f - 1.f;
139 const float angle = randomUnit() * glm::two_pi<float>();
140 const float radius = std::sqrt(std::max(0.f, 1.f - z * z));
141 const glm::vec3 sphere(radius * std::cos(angle), z, radius * std::sin(angle));
142 if (config_.shape != MeshParticleShape::Cone) return sphere;
143
144 const float cosLimit = std::cos(config_.coneAngleRadians);
145 const float coneY = cosLimit + (1.f - cosLimit) * randomUnit();
146 const float coneRadius = std::sqrt(std::max(0.f, 1.f - coneY * coneY));
147 const float coneAngle = randomUnit() * glm::two_pi<float>();
148 glm::vec3 local(coneRadius * std::cos(coneAngle), coneY, coneRadius * std::sin(coneAngle));
149 const glm::vec3 up(0.f, 1.f, 0.f);
150 const float alignment = glm::dot(up, config_.direction);
151 if (alignment > 0.9999f) return local;
152 if (alignment < -0.9999f) return glm::vec3(local.x, -local.y, -local.z);
153 const glm::vec3 axis = glm::normalize(glm::cross(up, config_.direction));
154 return glm::vec3(glm::rotate(glm::mat4(1.f), std::acos(alignment), axis) * glm::vec4(local, 0.f));
155}
156
157glm::vec3 MeshParticleEmitter::spawnOffset() {
158 switch (config_.shape) {
160 return randomDirection() * (std::cbrt(randomUnit()) * config_.sphereRadius);
162 return {(randomUnit() * 2.f - 1.f) * config_.boxExtents.x,
163 (randomUnit() * 2.f - 1.f) * config_.boxExtents.y,
164 (randomUnit() * 2.f - 1.f) * config_.boxExtents.z};
167 return glm::vec3(0.f);
168 }
169 return glm::vec3(0.f);
170}
171
172std::uint32_t MeshParticleEmitter::spawn(std::uint32_t count, MeshParticleAdvanceReport* report) {
173 const std::uint32_t available = config_.capacity - static_cast<std::uint32_t>(particles_.size());
174 const std::uint32_t accepted = std::min(count, available);
175 if (report) {
176 report->spawned += accepted;
177 report->dropped += count - accepted;
178 }
179 for (std::uint32_t i = 0; i < accepted; ++i) {
180 Particle particle;
181 particle.stableId = nextStableId_++;
182 particle.position = origin_ + spawnOffset();
183 const float speed = config_.speedMin + (config_.speedMax - config_.speedMin) * randomUnit();
184 particle.velocity = (config_.shape == MeshParticleShape::Cone ? randomDirection()
185 : config_.direction) * speed;
186 particle.lifetime = config_.lifetimeMin +
187 (config_.lifetimeMax - config_.lifetimeMin) * randomUnit();
188 particle.rotationRadians = config_.rotationMinRadians +
189 (config_.rotationMaxRadians - config_.rotationMinRadians) *
190 randomUnit();
191 particle.angularVelocity = config_.angularVelocityMin +
192 (config_.angularVelocityMax - config_.angularVelocityMin) *
193 randomUnit();
194 particles_.push_back(particle);
195 }
196 return accepted;
197}
198
199void MeshParticleEmitter::simulateStep(float dt, MeshParticleAdvanceReport& report) {
200 for (auto& particle : particles_) {
201 const float previousAge = particle.age;
202 particle.age += dt;
203 particle.velocity += config_.gravity * dt;
204 particle.velocity *= std::exp(-config_.damping * dt);
205 const float normalizedAge = std::clamp(previousAge / particle.lifetime, 0.f, 1.f);
206 particle.position +=
207 particle.velocity * (sampleScalar(config_.velocityCurve, normalizedAge, 1.f) * dt);
208 particle.rotationRadians += particle.angularVelocity * dt;
209 }
210 const auto firstExpired = std::remove_if(particles_.begin(), particles_.end(),
211 [](const Particle& p) { return p.age >= p.lifetime; });
212 report.expired += static_cast<std::uint32_t>(std::distance(firstExpired, particles_.end()));
213 particles_.erase(firstExpired, particles_.end());
214
215 if (!emitting_) return;
216 const float previousTimeline = timeline_;
217 timeline_ += dt;
218 while (nextBurst_ < config_.bursts.size() &&
219 config_.bursts[nextBurst_].timeSeconds <= timeline_ + 1e-6f) {
220 if (config_.bursts[nextBurst_].timeSeconds >= previousTimeline - 1e-6f)
221 (void)spawn(config_.bursts[nextBurst_].count, &report);
222 ++nextBurst_;
223 }
224 emissionAccumulator_ += config_.emissionRate * dt;
225 const auto continuous = static_cast<std::uint32_t>(std::floor(emissionAccumulator_));
226 emissionAccumulator_ -= static_cast<float>(continuous);
227 (void)spawn(continuous, &report);
228
229 if (config_.looping && config_.loopDurationSeconds > 0.f &&
230 timeline_ >= config_.loopDurationSeconds) {
231 timeline_ = std::fmod(timeline_, config_.loopDurationSeconds);
232 nextBurst_ = 0;
233 }
234}
235
238 if (!std::isfinite(deltaSeconds) || deltaSeconds <= 0.f) {
239 report.alive = static_cast<std::uint32_t>(particles_.size());
240 return report;
241 }
242 accumulator_ += deltaSeconds;
243 while (accumulator_ + 1e-7f >= config_.fixedStepSeconds &&
244 report.simulatedSteps < config_.maximumSubsteps) {
245 simulateStep(config_.fixedStepSeconds, report);
246 accumulator_ -= config_.fixedStepSeconds;
247 ++report.simulatedSteps;
248 }
249 if (accumulator_ >= config_.fixedStepSeconds) {
250 const float retained = std::fmod(accumulator_, config_.fixedStepSeconds);
251 report.discardedTimeSeconds = accumulator_ - retained;
252 accumulator_ = retained;
253 }
254 report.alive = static_cast<std::uint32_t>(particles_.size());
255 return report;
256}
257
258std::vector<MeshParticleInstance> MeshParticleEmitter::snapshot() const {
259 std::vector<MeshParticleInstance> result;
260 result.reserve(particles_.size());
261 for (const auto& particle : particles_) {
262 const float t = std::clamp(particle.age / particle.lifetime, 0.f, 1.f);
263 const float linearScale = config_.scaleStart + (config_.scaleEnd - config_.scaleStart) * t;
264 const float scale = config_.scaleCurve.empty()
265 ? linearScale
266 : sampleScalar(config_.scaleCurve, t, linearScale);
267 glm::mat4 model = glm::translate(glm::mat4(1.f), particle.position);
268 model = glm::rotate(model, particle.rotationRadians, safeDirection(particle.velocity));
269 model = glm::scale(model, glm::vec3(scale));
270 const graphics::Color linearColor = mixColor(config_.colorStart, config_.colorEnd, t);
271 result.push_back(
272 {particle.stableId, model, sampleColor(config_.colorGradient, t, linearColor), t});
273 }
274 return result;
275}
276
277} // namespace eve::stylize
double value
float z
Definition AnimClip.cpp:738
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
std::uint32_t key
HexVec3 up
HexVec3 left
HexVec3 right
std::uint32_t width
std::string local
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float radius
float t
glm::mat4 model
RoadLaneDirection direction
std::uint32_t count
float angle
MeshParticleEmitter(MeshParticleEmitterConfig config={})
Construct an emitter and sanitize invalid scalar ranges.
std::uint32_t emit(std::uint32_t count)
Emit an immediate burst at the current origin.
std::size_t size() const noexcept
Return the number of live particles.
std::vector< MeshParticleInstance > snapshot() const
Build render transforms and colors in stable particle-ID order.
void reset()
Remove every particle and reset timeline/random state.
MeshParticleAdvanceReport advance(float deltaSeconds)
Advance deterministic fixed-step simulation.
void start()
Start from timeline zero and arm every configured burst.
~MeshParticleEmitter()
Destroy owned particle state after its private type is complete.
Vec3 interpolate(Vec3 start, Vec3 end, float amount) noexcept
Interpolate.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
Observable result of advancing one mesh-particle emitter.
Deterministic simulation and emission settings for a 3D mesh emitter.
std::vector< MeshParticleBurst > bursts
std::vector< MeshParticleScalarKey > velocityCurve
Optional normalized-age velocity multiplier; empty evaluates to one.
std::vector< MeshParticleScalarKey > scaleCurve
Optional normalized-age scale multiplier; empty uses scaleStart/scaleEnd.
std::vector< MeshParticleColorKey > colorGradient
Optional normalized-age color gradient; empty uses colorStart/colorEnd.