Skip to content

Commit b3c3627

Browse files
authored
Allow using seeded noise for particle velocity and spawn position offset (#6675)
* Add noise backend for particles # Conflicts: # code/particle/ParticleEffect.cpp * noise frontend * Proper scaling for noise * make normalize safe
1 parent 5078826 commit b3c3627

7 files changed

Lines changed: 148 additions & 33 deletions

File tree

code/math/vecmat.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -500,18 +500,26 @@ float vm_vec_normalize(vec3d *v)
500500
// If vector is 0,0,0, return 1.0f, and change v to 1,0,0.
501501
// Otherwise return the magnitude.
502502
// No warning() generated for null vector, as it is expected that some vectors may be null.
503-
float vm_vec_copy_normalize_safe(vec3d *dest, const vec3d *src)
503+
float vm_vec_copy_normalize_safe(vec3d *dest, const vec3d *src, bool fallbackToZeroVec)
504504
{
505505
float m;
506506

507507
m = vm_vec_mag(src);
508508

509509
// Mainly here to trap attempts to normalize a null vector.
510510
if (fl_near_zero(m)) {
511-
dest->xyz.x = 1.0f;
512-
dest->xyz.y = 0.0f;
513-
dest->xyz.z = 0.0f;
514-
return 1.0f;
511+
if (fallbackToZeroVec) {
512+
dest->xyz.x = 0.0f;
513+
dest->xyz.y = 0.0f;
514+
dest->xyz.z = 0.0f;
515+
return 0.0f;
516+
}
517+
else {
518+
dest->xyz.x = 1.0f;
519+
dest->xyz.y = 0.0f;
520+
dest->xyz.z = 0.0f;
521+
return 1.0f;
522+
}
515523
}
516524

517525
float im = 1.0f / m;
@@ -527,9 +535,9 @@ float vm_vec_copy_normalize_safe(vec3d *dest, const vec3d *src)
527535
// If vector is 0,0,0, return 1.0f, and change v to 1,0,0.
528536
// Otherwise return the magnitude.
529537
// No warning() generated for null vector.
530-
float vm_vec_normalize_safe(vec3d *v)
538+
float vm_vec_normalize_safe(vec3d *v, bool fallbackToZeroVec)
531539
{
532-
return vm_vec_copy_normalize_safe(v,v);
540+
return vm_vec_copy_normalize_safe(v,v, fallbackToZeroVec);
533541
}
534542

535543
//return the normalized direction vector between two points

code/math/vecmat.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,9 @@ float vm_vec_copy_normalize(vec3d *dest, const vec3d *src);
229229
float vm_vec_normalize(vec3d *v);
230230

231231
// This version of vector normalize checks for the null vector before normalization.
232-
// If it is detected, it returns the vector 1, 0, 0.
233-
float vm_vec_copy_normalize_safe(vec3d *dest, const vec3d *src);
234-
float vm_vec_normalize_safe(vec3d *v);
232+
// If it is detected, it returns the vector 1, 0, 0 (or 0, 0, 0 if fallbackToZeroVec is true)..
233+
float vm_vec_copy_normalize_safe(vec3d *dest, const vec3d *src, bool fallbackToZeroVec = false);
234+
float vm_vec_normalize_safe(vec3d *v, bool fallbackToZeroVec = false);
235235

236236
//return the normalized direction vector between two points
237237
//dest = normalized(end - start). Returns mag of direction vector

code/particle/ParticleEffect.cpp

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "render/3d.h"
77

8+
#include <anl.h>
9+
810
namespace particle {
911

1012
ParticleEffect::ParticleEffect(SCP_string name)
@@ -31,11 +33,15 @@ ParticleEffect::ParticleEffect(SCP_string name)
3133
m_lifetime(::util::UniformFloatRange(0.0f)),
3234
m_length(::util::UniformFloatRange(0.0f)),
3335
m_vel_inherit(::util::UniformFloatRange(0.0f)),
34-
m_velocity_scaling(::util::UniformFloatRange(0.0f)),
36+
m_velocity_scaling(::util::UniformFloatRange(1.0f)),
37+
m_velocity_noise_scaling(::util::UniformFloatRange(1.0f)),
38+
m_position_noise_scaling(::util::UniformFloatRange(1.0f)),
3539
m_vel_inherit_from_orientation(std::nullopt),
3640
m_vel_inherit_from_position(std::nullopt),
3741
m_velocityVolume(nullptr),
3842
m_spawnVolume(nullptr),
43+
m_velocityNoise(nullptr),
44+
m_spawnNoise(nullptr),
3945
m_manual_offset (std::nullopt),
4046
m_particleTrail(ParticleEffectHandle::invalid()),
4147
m_size_lifetime_curve(-1),
@@ -87,10 +93,14 @@ ParticleEffect::ParticleEffect(SCP_string name,
8793
m_length(::util::UniformFloatRange(0.0f)),
8894
m_vel_inherit(vel_inherit),
8995
m_velocity_scaling(velocity_scaling),
96+
m_velocity_noise_scaling(::util::UniformFloatRange(1.0f)),
97+
m_position_noise_scaling(::util::UniformFloatRange(1.0f)),
9098
m_vel_inherit_from_orientation(vel_inherit_from_orientation),
9199
m_vel_inherit_from_position(vel_inherit_from_position),
92100
m_velocityVolume(std::move(velocityVolume)),
93101
m_spawnVolume(std::move(spawnVolume)),
102+
m_velocityNoise(nullptr),
103+
m_spawnNoise(nullptr),
94104
m_manual_offset (std::nullopt),
95105
m_particleTrail(particleTrail),
96106
m_size_lifetime_curve(-1),
@@ -134,26 +144,41 @@ matrix ParticleEffect::getNewDirection(const matrix& hostOrientation, const std:
134144
}
135145
}
136146

137-
void ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& vel, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const {
138-
const auto& [pos, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset);
147+
void ParticleEffect::sampleNoise(vec3d& noiseTarget, const matrix* orientation, std::pair<anl::CKernel, anl::CInstructionIndex>& noise, const std::tuple<const ParticleSource&, const size_t&>& source, ParticleCurvesOutput noiseMult, ParticleCurvesOutput noiseTimeMult, ParticleCurvesOutput noiseSeed) const {
148+
auto& [kernel, instruction] = noise;
149+
anl::CNoiseExecutor executor(kernel);
150+
const auto& color = executor.evaluateColor(
151+
ParticleSource::getEffectRunningTime(source)
152+
* m_modular_curves.get_output(noiseTimeMult, source)
153+
, m_modular_curves.get_output(noiseSeed, source), instruction);
154+
155+
vec3d noiseSampleLocal{{{ color.r, color.g, color.b }}};
156+
noiseSampleLocal *= m_modular_curves.get_output(noiseMult, source);
157+
158+
vm_vec_unrotate(&noiseTarget, &noiseSampleLocal, orientation);
159+
}
139160

161+
void ParticleEffect::processSource(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const {
140162
if (m_affectedByDetail){
141163
if (Detail.num_particles > 0)
142164
particle_percent *= (0.5f + (0.25f * static_cast<float>(Detail.num_particles - 1)));
143165
else
144166
return; //Will not emit on current detail settings, but may in the future.
145167
}
146168

169+
auto modularCurvesInput = std::forward_as_tuple(source, effectNumber);
170+
171+
const auto& [pos, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset);
172+
173+
const auto& orientation = getNewDirection(hostOrientation, source.m_normal);
174+
147175
if (m_distanceCulled > 0.f) {
148176
float min_dist = 125.0f;
149177
float dist = vm_vec_dist_quick(&pos, &Eye_position) / m_distanceCulled;
150178
if (dist > min_dist)
151179
particle_percent *= min_dist / dist;
152180
}
153181

154-
const auto& orientation = getNewDirection(hostOrientation, source.m_normal);
155-
156-
auto modularCurvesInput = std::forward_as_tuple(source, effectNumber);
157182
particle_percent *= m_particleChance * m_modular_curves.get_output(ParticleCurvesOutput::PARTICLE_NUM_MULT, modularCurvesInput);
158183
float radiusMultiplier = m_modular_curves.get_output(ParticleCurvesOutput::RADIUS_MULT, modularCurvesInput);
159184
float lengthMultiplier = m_modular_curves.get_output(ParticleCurvesOutput::LENGTH_MULT, modularCurvesInput);
@@ -163,6 +188,18 @@ void ParticleEffect::processSource(float interp, const ParticleSource& source, s
163188
float positionInheritVelocityMultiplier = m_modular_curves.get_output(ParticleCurvesOutput::POSITION_INHERIT_VELOCITY_MULT, modularCurvesInput);
164189
float orientationInheritVelocityMultiplier = m_modular_curves.get_output(ParticleCurvesOutput::ORIENTATION_INHERIT_VELOCITY_MULT, modularCurvesInput);
165190

191+
vec3d velNoise = ZERO_VECTOR;
192+
if (m_velocityNoise != nullptr) {
193+
sampleNoise(velNoise, &orientation, *m_velocityNoise, modularCurvesInput, ParticleCurvesOutput::VELOCITY_NOISE_MULT, ParticleCurvesOutput::VELOCITY_NOISE_TIME_MULT, ParticleCurvesOutput::VELOCITY_NOISE_SEED);
194+
velNoise *= m_velocity_noise_scaling.next();
195+
}
196+
197+
vec3d posNoise = ZERO_VECTOR;
198+
if (m_spawnNoise != nullptr) {
199+
sampleNoise(posNoise, &orientation, *m_spawnNoise, modularCurvesInput, ParticleCurvesOutput::SPAWN_POSITION_NOISE_MULT, ParticleCurvesOutput::SPAWN_POSITION_NOISE_TIME_MULT, ParticleCurvesOutput::SPAWN_POSITION_NOISE_SEED);
200+
posNoise *= m_position_noise_scaling.next();
201+
}
202+
166203
float num = m_particleNum.next() * particle_percent;
167204
unsigned int num_spawn;
168205

@@ -177,7 +214,7 @@ void ParticleEffect::processSource(float interp, const ParticleSource& source, s
177214
particle_info info;
178215

179216
info.pos = pos;
180-
info.vel = vel;
217+
info.vel = velParent;
181218

182219
if (m_parent_local) {
183220
info.attached_objnum = parent;
@@ -189,45 +226,45 @@ void ParticleEffect::processSource(float interp, const ParticleSource& source, s
189226
}
190227

191228
if (m_vel_inherit_absolute)
192-
vm_vec_normalize_quick(&info.vel);
229+
vm_vec_normalize_safe(&info.vel, true);
193230

194231
info.vel *= m_vel_inherit.next() * inheritVelocityMultiplier;
195232

196-
vec3d velocity = ZERO_VECTOR;
197-
vec3d localPos = ZERO_VECTOR;
233+
vec3d localVelocity = velNoise;
234+
vec3d localPos = posNoise;
198235

199236
if (m_spawnVolume != nullptr) {
200237
localPos += m_spawnVolume->sampleRandomPoint(orientation, modularCurvesInput);
201238
}
202239

203240
if (m_velocityVolume != nullptr) {
204-
velocity += m_velocityVolume->sampleRandomPoint(orientation, modularCurvesInput) * (m_velocity_scaling.next() * velocityVolumeMultiplier);
241+
localVelocity += m_velocityVolume->sampleRandomPoint(orientation, modularCurvesInput) * (m_velocity_scaling.next() * velocityVolumeMultiplier);
205242
}
206243

207244
if (m_vel_inherit_from_orientation.has_value()) {
208-
velocity += orientation.vec.fvec * (m_vel_inherit_from_orientation->next() * orientationInheritVelocityMultiplier);
245+
localVelocity += orientation.vec.fvec * (m_vel_inherit_from_orientation->next() * orientationInheritVelocityMultiplier);
209246
}
210247

211248
if (m_vel_inherit_from_position.has_value()) {
212249
vec3d velFromPos = localPos;
213250
if (m_vel_inherit_from_position_absolute)
214251
vm_vec_normalize_safe(&velFromPos);
215-
velocity += velFromPos * (m_vel_inherit_from_position->next() * positionInheritVelocityMultiplier);
252+
localVelocity += velFromPos * (m_vel_inherit_from_position->next() * positionInheritVelocityMultiplier);
216253
}
217254

218255
if (m_velocity_directional_scaling != VelocityScaling::NONE) {
219-
// Scale the vector with a random velocity sample and also multiply that with cos(angle between
220-
// info.vel and sourceDir) That should produce good looking directions where the maximum velocity is
256+
// Scale the vector with a random localVelocity sample and also multiply that with cos(angle between
257+
// info.vel and sourceDir) That should produce good looking directions where the maximum localVelocity is
221258
// only achieved when the particle travels directly on the normal/reflect vector
222259
vec3d normalizedVelocity;
223-
vm_vec_copy_normalize_safe(&normalizedVelocity, &velocity);
260+
vm_vec_copy_normalize_safe(&normalizedVelocity, &localVelocity);
224261
float dot = vm_vec_dot(&normalizedVelocity, &orientation.vec.fvec);
225-
vm_vec_scale(&velocity,
262+
vm_vec_scale(&localVelocity,
226263
m_velocity_directional_scaling == VelocityScaling::DOT ? dot : 1.f / std::max(0.001f, dot));
227264
}
228265

229266
info.pos += localPos;
230-
info.vel += velocity;
267+
info.vel += localVelocity;
231268

232269
info.bitmap = m_bitmap_list[m_bitmap_range.next()];
233270

code/particle/ParticleEffect.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class EffectHost;
1616
//Due to parsing shenanigans in weapons, this needs a forward-declare here
1717
int parse_weapon(int, bool, const char*);
1818

19+
namespace anl {
20+
class CKernel;
21+
class CInstructionIndex;
22+
}
23+
1924
namespace particle {
2025

2126
/**
@@ -64,6 +69,12 @@ class ParticleEffect {
6469
INHERIT_VELOCITY_MULT,
6570
POSITION_INHERIT_VELOCITY_MULT,
6671
ORIENTATION_INHERIT_VELOCITY_MULT,
72+
VELOCITY_NOISE_MULT,
73+
VELOCITY_NOISE_TIME_MULT,
74+
VELOCITY_NOISE_SEED,
75+
SPAWN_POSITION_NOISE_MULT,
76+
SPAWN_POSITION_NOISE_TIME_MULT,
77+
SPAWN_POSITION_NOISE_SEED,
6778

6879
NUM_VALUES
6980
};
@@ -101,13 +112,18 @@ class ParticleEffect {
101112
::util::ParsedRandomFloatRange m_length;
102113
::util::ParsedRandomFloatRange m_vel_inherit;
103114
::util::ParsedRandomFloatRange m_velocity_scaling;
115+
::util::ParsedRandomFloatRange m_velocity_noise_scaling;
116+
::util::ParsedRandomFloatRange m_position_noise_scaling;
104117

105118
std::optional<::util::ParsedRandomFloatRange> m_vel_inherit_from_orientation;
106119
std::optional<::util::ParsedRandomFloatRange> m_vel_inherit_from_position;
107120

108121
std::shared_ptr<::particle::ParticleVolume> m_velocityVolume;
109122
std::shared_ptr<::particle::ParticleVolume> m_spawnVolume;
110123

124+
std::shared_ptr<std::pair<anl::CKernel, anl::CInstructionIndex>> m_velocityNoise;
125+
std::shared_ptr<std::pair<anl::CKernel, anl::CInstructionIndex>> m_spawnNoise;
126+
111127
std::optional<vec3d> m_manual_offset;
112128

113129
ParticleEffectHandle m_particleTrail;
@@ -119,6 +135,7 @@ class ParticleEffect {
119135
float m_distanceCulled; //Kinda deprecated. Only used by the oldest of legacy effects.
120136

121137
matrix getNewDirection(const matrix& hostOrientation, const std::optional<vec3d>& normal) const;
138+
void sampleNoise(vec3d& noiseTarget, const matrix* orientation, std::pair<anl::CKernel, anl::CInstructionIndex>& noise, const std::tuple<const ParticleSource&, const size_t&>& source, ParticleCurvesOutput noiseMult, ParticleCurvesOutput noiseTimeMult, ParticleCurvesOutput noiseSeed) const;
122139
public:
123140
/**
124141
* @brief Initializes the base ParticleEffect
@@ -173,7 +190,13 @@ class ParticleEffect {
173190
std::pair {"Velocity Volume Mult", ParticleCurvesOutput::VOLUME_VELOCITY_MULT},
174191
std::pair {"Velocity Inherit Mult", ParticleCurvesOutput::INHERIT_VELOCITY_MULT},
175192
std::pair {"Velocity Position Inherit Mult", ParticleCurvesOutput::POSITION_INHERIT_VELOCITY_MULT},
176-
std::pair {"Velocity Orientation Inherit Mult", ParticleCurvesOutput::ORIENTATION_INHERIT_VELOCITY_MULT}
193+
std::pair {"Velocity Orientation Inherit Mult", ParticleCurvesOutput::ORIENTATION_INHERIT_VELOCITY_MULT},
194+
std::pair {"Velocity Noise Mult", ParticleCurvesOutput::VELOCITY_NOISE_MULT},
195+
std::pair {"Velocity Noise Time Mult", ParticleCurvesOutput::VELOCITY_NOISE_TIME_MULT},
196+
std::pair {"Velocity Noise Seed", ParticleCurvesOutput::VELOCITY_NOISE_SEED},
197+
std::pair {"Spawn Position Noise Mult", ParticleCurvesOutput::SPAWN_POSITION_NOISE_MULT},
198+
std::pair {"Spawn Position Noise Time Mult", ParticleCurvesOutput::SPAWN_POSITION_NOISE_TIME_MULT},
199+
std::pair {"Spawn Position Noise Seed", ParticleCurvesOutput::SPAWN_POSITION_NOISE_SEED}
177200
},
178201
std::pair {"Trigger Radius", modular_curves_submember_input<&ParticleSource::m_triggerRadius>{}},
179202
std::pair {"Trigger Velocity", modular_curves_submember_input<&ParticleSource::m_triggerVelocity>{}},
@@ -185,7 +208,8 @@ class ParticleEffect {
185208
modular_curves_submember_input<&ParticleSource::getEffect, &SCP_vector<ParticleEffect>::size>,
186209
ModularCurvesMathOperators::division>{}})
187210
.derive_modular_curves_input_only_subset<size_t>(
188-
std::pair {"Spawntime Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingTime>{}}
211+
std::pair {"Spawntime Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingTime>{}},
212+
std::pair {"Time Running", modular_curves_functional_full_input<&ParticleSource::getEffectRunningTime>{}}
189213
);
190214

191215
MODULAR_CURVE_SET(m_modular_curves, modular_curves_definition);

code/particle/ParticleParse.cpp

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "particle/volumes/ConeVolume.h"
44
#include "particle/volumes/SpheroidVolume.h"
55

6+
#include <anl.h>
7+
68
namespace particle {
79

810
//
@@ -141,9 +143,30 @@ namespace particle {
141143
}
142144
}
143145

146+
static void parseVelocityNoise(ParticleEffect &effect) {
147+
if (optional_string("+Velocity Noise:")) {
148+
SCP_string func;
149+
stuff_string(func, F_RAW);
150+
anl::CKernel kernel;
151+
anl::CExpressionBuilder builder(kernel);
152+
anl::CInstructionIndex instruction = builder.eval(func);
153+
effect.m_velocityNoise = std::make_shared<std::pair<anl::CKernel, anl::CInstructionIndex>>(std::move(kernel), std::move(instruction));
154+
}
155+
if (optional_string("+Velocity Noise Scale:")) {
156+
effect.m_velocity_noise_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
157+
}
158+
}
159+
144160
template<bool modern = true> static void parseVelocityVolumeScale(ParticleEffect &effect) {
145-
if (internal::required_string_if_new(modern ? "+Velocity Volume Scale:" : "+Velocity:", false)) {
146-
effect.m_velocity_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
161+
if constexpr (modern) {
162+
if (optional_string("+Velocity Volume Scale:")) {
163+
effect.m_velocity_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
164+
}
165+
}
166+
else {
167+
if (internal::required_string_if_new("+Velocity:", false)) {
168+
effect.m_velocity_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
169+
}
147170
}
148171
}
149172

@@ -170,6 +193,20 @@ namespace particle {
170193
}
171194
}
172195

196+
static void parsePositionNoise(ParticleEffect &effect) {
197+
if (optional_string("+Spawn Position Noise:")) {
198+
SCP_string func;
199+
stuff_string(func, F_RAW);
200+
anl::CKernel kernel;
201+
anl::CExpressionBuilder builder(kernel);
202+
anl::CInstructionIndex instruction = builder.eval(func);
203+
effect.m_spawnNoise = std::make_shared<std::pair<anl::CKernel, anl::CInstructionIndex>>(std::move(kernel), std::move(instruction));
204+
}
205+
if (optional_string("+Spawn Position Noise Scale:")) {
206+
effect.m_position_noise_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
207+
}
208+
}
209+
173210
static void parseVelocityInheritFromPosition(ParticleEffect &effect) {
174211
if (optional_string("+Velocity From Position:")) {
175212
effect.m_vel_inherit_from_position.emplace(::util::ParsedRandomFloatRange::parseRandomRange());
@@ -283,8 +320,10 @@ namespace particle {
283320
parseDirection(effect);
284321
parseOffset(effect);
285322
parsePositionVolume(effect);
323+
parsePositionNoise(effect);
286324
parseVelocityVolume(effect);
287325
parseVelocityVolumeScale(effect);
326+
parseVelocityNoise(effect);
288327
parseVelocityDirectionScale(effect);
289328
parseVelocityInheritFromPosition(effect);
290329
parseVelocityInheritFromOrientation(effect);

code/particle/ParticleSource.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void ParticleSource::finishCreation() {
3333

3434
for (const auto& effect : ParticleManager::get()->getEffect(m_effect)) {
3535
const auto& [begin, end] = effect.getEffectDuration();
36-
m_timing.emplace_back(SourceTiming{begin, end});
36+
m_timing.emplace_back(SourceTiming{timestamp_delta(begin, 0), begin, end});
3737
}
3838
}
3939

@@ -101,4 +101,8 @@ void ParticleSource::setHost(std::unique_ptr<EffectHost> host) {
101101
float ParticleSource::getEffectRemainingTime(const std::tuple<const ParticleSource&, const size_t&>& source) {
102102
return i2fl(timestamp_until(std::get<0>(source).m_timing[std::get<1>(source)].m_endTimestamp)) / i2fl(MILLISECONDS_PER_SECOND);
103103
}
104+
105+
float ParticleSource::getEffectRunningTime(const std::tuple<const ParticleSource&, const size_t&>& source) {
106+
return i2fl(timestamp_since(std::get<0>(source).m_timing[std::get<1>(source)].m_startTimestamp)) / i2fl(MILLISECONDS_PER_SECOND);
107+
}
104108
}

0 commit comments

Comments
 (0)