Skip to content

Commit dc50aaa

Browse files
authored
Particle Scripting API (#7061)
* Expose particle effects to scripting * Allow creation of particle sources * Add API for particle sources * bugfixes
1 parent 2c04313 commit dc50aaa

5 files changed

Lines changed: 289 additions & 0 deletions

File tree

code/particle/ParticleManager.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ void ParticleManager::shutdown() {
3838
ParticleSource* ParticleManager::createSource() {
3939
ParticleSource* source;
4040

41+
m_sourceValidityCounter++;
42+
4143
// If we are currently in the onFrame function, adding stuff to the vector would invalidate the iterator currently in use
4244
if (m_processingSources) {
4345
m_deferredSourceAdding.emplace_back();
@@ -80,9 +82,12 @@ void ParticleManager::doFrame(float) {
8082
TRACE_SCOPE(tracing::ProcessParticleEffects);
8183

8284
m_processingSources = true;
85+
bool changehappened = false;
8386

8487
for (auto source = std::begin(m_sources); source != std::end(m_sources);) {
8588
if (!source->isValid() || !source->process()) {
89+
changehappened = true;
90+
8691
// if we're sitting on the very last source, popping-back will invalidate the iterator!
8792
if (std::next(source) == m_sources.end()) {
8893
m_sources.pop_back();
@@ -102,9 +107,13 @@ void ParticleManager::doFrame(float) {
102107
m_processingSources = false;
103108

104109
for (auto& source : m_deferredSourceAdding) {
110+
changehappened = true;
105111
m_sources.push_back(std::move(source));
106112
}
107113
m_deferredSourceAdding.clear();
114+
115+
if (changehappened)
116+
m_sourceValidityCounter++;
108117
}
109118

110119
ParticleEffectHandle ParticleManager::addEffect(ParticleEffect&& effect)
@@ -179,6 +188,10 @@ void ParticleManager::clearSources() {
179188
m_deferredSourceAdding.clear();
180189
}
181190

191+
uint32_t ParticleManager::getSourceValidityCounter() const {
192+
return m_sourceValidityCounter;
193+
}
194+
182195
namespace util {
183196
ParticleEffectHandle parseEffect(const SCP_string& objectName)
184197
{

code/particle/ParticleManager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class ParticleManager {
3737
SCP_vector<ParticleSource> m_sources; //!< The currently active sources
3838

3939
bool m_processingSources = false; //!< @c true if sources are currently being processed
40+
41+
uint32_t m_sourceValidityCounter = 0;
4042
/**
4143
* If the sources are currently being processed, no additional sources can be added. Instead, they are added to this
4244
* vector and then added to the main vector when processing is done.
@@ -148,6 +150,8 @@ class ParticleManager {
148150
* @return A wrapper class which allows access to the created sources
149151
*/
150152
ParticleSource* createSource(ParticleEffectHandle index);
153+
154+
uint32_t getSourceValidityCounter() const;
151155
};
152156

153157
namespace internal {

code/scripting/api/libs/tables.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
#include "scripting/api/objs/team_colors.h"
1212
#include "scripting/api/objs/weaponclass.h"
1313
#include "scripting/api/objs/wingformation.h"
14+
#include "scripting/api/objs/particle.h"
1415

1516
#include "decals/decals.h"
1617
#include "fireball/fireballs.h"
1718
#include "menuui/techmenu.h"
1819
#include "mission/missionmessage.h"
1920
#include "ship/ship.h"
2021
#include "weapon/weapon.h"
22+
#include "particle/ParticleManager.h"
2123

2224

2325
extern bool Ships_inited;
@@ -373,5 +375,16 @@ ADE_FUNC(__len, l_Tables_TeamColors, nullptr, "Number of team colors", "number"
373375
return ade_set_args(L, "i", static_cast<int>(Team_Names.size()));
374376
}
375377

378+
//*****SUBLIBRARY: Tables/ShipTypes
379+
ADE_LIB_DERIV(l_Tables_ParticleEffects, "ParticleEffects", nullptr, nullptr, l_Tables);
380+
ADE_INDEXER(l_Tables_ParticleEffects, "string Name", "Array of particle effects", "particle_effect", "Particle Effect handle, or invalid handle if name is invalid")
381+
{
382+
const char* name;
383+
if (!ade_get_args(L, "*s", &name))
384+
return ade_set_error(L, "o", l_ParticleEffect.Set(particle::ParticleEffectHandle::invalid()));
385+
386+
return ade_set_args(L, "o", l_ParticleEffect.Set(particle::ParticleManager::get()->getEffectByName(name)));
387+
}
388+
376389
}
377390
}

code/scripting/api/objs/particle.cpp

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include "particle.h"
55
#include "vecmath.h"
66
#include "object.h"
7+
#include "model.h"
8+
#include "particle/ParticleManager.h"
9+
#include "particle/ParticleEffect.h"
710

811
namespace scripting {
912
namespace api {
@@ -20,6 +23,18 @@ bool particle_h::isValid() const {
2023
return !part.expired();
2124
}
2225

26+
particle_source_h::particle_source_h(particle::ParticleSource* source_p, uint32_t sourceValidityCounter_p) {
27+
this->source = source_p;
28+
this->sourceValidityCounter = sourceValidityCounter_p;
29+
}
30+
31+
particle::ParticleSource* particle_source_h::Get() const {
32+
return isValid() ? this->source : nullptr;
33+
}
34+
35+
bool particle_source_h::isValid() const {
36+
return particle::ParticleManager::get()->getSourceValidityCounter() == sourceValidityCounter;
37+
}
2338

2439
//**********HANDLE: Particle
2540
ADE_OBJ(l_Particle, particle_h, "particle", "Handle to a particle");
@@ -214,6 +229,230 @@ ADE_FUNC_DEPRECATED(setColor, l_Particle, "number r, number g, number b", "Sets
214229
}
215230

216231

232+
ADE_OBJ(l_ParticleEffect, ::particle::ParticleEffectHandle, "particle_effect", "Handle to a tabled particle effect");
233+
234+
ADE_FUNC(getName, l_ParticleEffect, nullptr, "Returns the name under which this effect is stored", "string", "the name of the particle effect, or an empty string for an invalid handle")
235+
{
236+
::particle::ParticleEffectHandle ph;
237+
if (!ade_get_args(L, "o", l_ParticleEffect.Get(&ph)))
238+
return ade_set_error(L, "s", "");
239+
240+
if (!ph.isValid())
241+
return ade_set_error(L, "s", "");
242+
243+
const auto& particle_effect = particle::ParticleManager::get()->getEffect(ph);
244+
if (particle_effect.empty())
245+
return ade_set_error(L, "s", "");
246+
247+
return ade_set_args(L, "s", particle_effect.front().getName().c_str());
248+
}
249+
250+
ADE_FUNC(createSource, l_ParticleEffect, nullptr, "Creates a new particle source, spawning particles as per this particle effect", "particle_source", "the particle source, or nil for an invalid handle")
251+
{
252+
::particle::ParticleEffectHandle ph;
253+
if (!ade_get_args(L, "o", l_ParticleEffect.Get(&ph)))
254+
return ADE_RETURN_NIL;
255+
256+
if (!ph.isValid())
257+
return ADE_RETURN_NIL;
258+
259+
auto source = particle::ParticleManager::get()->createSource(ph);
260+
return ade_set_args(L, "o", l_ParticleSource.Set(particle_source_h(source, particle::ParticleManager::get()->getSourceValidityCounter())));
261+
}
262+
263+
ADE_OBJ(l_ParticleSource, particle_source_h, "particle_source", "Handle to a particle source. Only valid immediately after acquiring.");
264+
265+
ADE_FUNC(setNormal, l_ParticleSource, "vector normal", "Sets the normal vector of this particle source.", nullptr, nullptr)
266+
{
267+
particle_source_h ps;
268+
vec3d normal;
269+
if (!ade_get_args(L, "oo", l_ParticleSource.Get(&ps), l_Vector.Get(&normal)))
270+
return ADE_RETURN_NIL;
271+
272+
particle::ParticleSource* psp = ps.Get();
273+
if (psp == nullptr)
274+
return ADE_RETURN_NIL;
275+
276+
vm_vec_normalize_safe(&normal);
277+
psp->setNormal(normal);
278+
279+
return ADE_RETURN_NIL;
280+
}
281+
282+
ADE_FUNC(setTriggerRadius, l_ParticleSource, "number triggerRadius", "Sets the trigger radius of this particle source.", nullptr, nullptr)
283+
{
284+
particle_source_h ps;
285+
float trigger;
286+
if (!ade_get_args(L, "of", l_ParticleSource.Get(&ps), &trigger))
287+
return ADE_RETURN_NIL;
288+
289+
particle::ParticleSource* psp = ps.Get();
290+
if (psp == nullptr)
291+
return ADE_RETURN_NIL;
292+
293+
psp->setTriggerRadius(trigger);
294+
295+
return ADE_RETURN_NIL;
296+
}
297+
298+
ADE_FUNC(setTriggerVelocity, l_ParticleSource, "number triggerVelocity", "Sets the trigger velocity of this particle source.", nullptr, nullptr)
299+
{
300+
particle_source_h ps;
301+
float trigger;
302+
if (!ade_get_args(L, "of", l_ParticleSource.Get(&ps), &trigger))
303+
return ADE_RETURN_NIL;
304+
305+
particle::ParticleSource* psp = ps.Get();
306+
if (psp == nullptr)
307+
return ADE_RETURN_NIL;
308+
309+
psp->setTriggerVelocity(trigger);
310+
311+
return ADE_RETURN_NIL;
312+
}
313+
314+
ADE_FUNC(createOnCoordinates, l_ParticleSource, "vector position, orientation orientation, [vector velocity = zero_vector, orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source at the specified position.", "boolean", "returns true if the source was successfully created, false otherwise")
315+
{
316+
particle_source_h ps;
317+
vec3d position;
318+
matrix_h orientation;
319+
vec3d velocity = ZERO_VECTOR;
320+
matrix_h orientationOverride(&vmd_identity_matrix);
321+
bool orientationOverrideRelative = true;
322+
323+
if (!ade_get_args(L, "ooo|oob", l_ParticleSource.Get(&ps), l_Vector.Get(&position), l_Matrix.Get(&orientation), l_Vector.Get(&velocity), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
324+
return ade_set_args(L, "b", false);
325+
326+
particle::ParticleSource* psp = ps.Get();
327+
if (psp == nullptr)
328+
return ade_set_args(L, "b", false);
329+
330+
psp->setHost(std::make_unique<EffectHostVector>(position, *orientation.GetMatrix(), velocity, *orientationOverride.GetMatrix(), orientationOverrideRelative));
331+
psp->finishCreation();
332+
333+
return ade_set_args(L, "b", true);
334+
}
335+
336+
ADE_FUNC(createOnObject, l_ParticleSource, "object object, vector offset, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified object.", "boolean", "returns true if the source was successfully created, false otherwise")
337+
{
338+
particle_source_h ps;
339+
object_h objh;
340+
vec3d position;
341+
matrix_h orientationOverride(&vmd_identity_matrix);
342+
bool orientationOverrideRelative = true;
343+
344+
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Vector.Get(&position), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
345+
return ade_set_args(L, "b", false);
346+
347+
if (!objh.isValid())
348+
return ade_set_args(L, "b", false);
349+
350+
particle::ParticleSource* psp = ps.Get();
351+
if (psp == nullptr)
352+
return ade_set_args(L, "b", false);
353+
354+
psp->setHost(std::make_unique<EffectHostObject>(objh.objp(), position, *orientationOverride.GetMatrix(), orientationOverrideRelative));
355+
psp->finishCreation();
356+
357+
return ade_set_args(L, "b", true);
358+
}
359+
360+
ADE_FUNC(createOnSubmodel, l_ParticleSource, "object object, submodel submodel, vector offset, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified submodel.", "boolean", "returns true if the source was successfully created, false otherwise")
361+
{
362+
particle_source_h ps;
363+
object_h objh;
364+
submodel_h subobjh;
365+
vec3d position;
366+
matrix_h orientationOverride(&vmd_identity_matrix);
367+
bool orientationOverrideRelative = true;
368+
369+
if (!ade_get_args(L, "oooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Submodel.Get(&subobjh), l_Vector.Get(&position), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
370+
return ade_set_args(L, "b", false);
371+
372+
if (!(subobjh.isValid() && objh.isValid()))
373+
return ade_set_args(L, "b", false);
374+
375+
particle::ParticleSource* psp = ps.Get();
376+
if (psp == nullptr)
377+
return ade_set_args(L, "b", false);
378+
379+
psp->setHost(std::make_unique<EffectHostSubmodel>(objh.objp(), subobjh.GetSubmodelIndex(), position, *orientationOverride.GetMatrix(), orientationOverrideRelative));
380+
psp->finishCreation();
381+
382+
return ade_set_args(L, "b", true);
383+
}
384+
385+
ADE_FUNC(createOnTurret, l_ParticleSource, "object object, submodel submodel, number firepoint, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified turret firepoint.", "boolean", "returns true if the source was successfully created, false otherwise")
386+
{
387+
particle_source_h ps;
388+
object_h objh;
389+
submodel_h subobjh;
390+
int firepoint;
391+
matrix_h orientationOverride(&vmd_identity_matrix);
392+
bool orientationOverrideRelative = true;
393+
394+
if (!ade_get_args(L, "oooi|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Submodel.Get(&subobjh), &firepoint, l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
395+
return ade_set_args(L, "b", false);
396+
397+
if (!(subobjh.isValid() && objh.isValid()))
398+
return ade_set_args(L, "b", false);
399+
400+
particle::ParticleSource* psp = ps.Get();
401+
if (psp == nullptr)
402+
return ade_set_args(L, "b", false);
403+
404+
psp->setHost(std::make_unique<EffectHostTurret>(objh.objp(), subobjh.GetSubmodelIndex(), firepoint, *orientationOverride.GetMatrix(), orientationOverrideRelative));
405+
psp->finishCreation();
406+
407+
return ade_set_args(L, "b", true);
408+
}
409+
410+
ADE_FUNC(createOnBeam, l_ParticleSource, "object object, submodel submodel, number firepoint, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source along the length of the beam.", "boolean", "returns true if the source was successfully created, false otherwise")
411+
{
412+
particle_source_h ps;
413+
object_h objh;
414+
matrix_h orientationOverride(&vmd_identity_matrix);
415+
bool orientationOverrideRelative = true;
416+
417+
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
418+
return ade_set_args(L, "b", false);
419+
420+
if (!objh.isValid() || objh.objp()->type != OBJ_BEAM)
421+
return ade_set_args(L, "b", false);
422+
423+
particle::ParticleSource* psp = ps.Get();
424+
if (psp == nullptr)
425+
return ade_set_args(L, "b", false);
426+
427+
psp->setHost(std::make_unique<EffectHostBeam>(objh.objp(), *orientationOverride.GetMatrix(), orientationOverrideRelative));
428+
psp->finishCreation();
429+
430+
return ade_set_args(L, "b", true);
431+
}
432+
433+
ADE_FUNC(createOnParticle, l_ParticleSource, "particle particle, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified persistent particle.", "boolean", "returns true if the source was successfully created, false otherwise")
434+
{
435+
particle_source_h ps;
436+
particle_h particle;
437+
matrix_h orientationOverride(&vmd_identity_matrix);
438+
bool orientationOverrideRelative = true;
439+
440+
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Particle.Get(&particle), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
441+
return ade_set_args(L, "b", false);
442+
443+
if (!particle.isValid())
444+
return ade_set_args(L, "b", false);
445+
446+
particle::ParticleSource* psp = ps.Get();
447+
if (psp == nullptr)
448+
return ade_set_args(L, "b", false);
449+
450+
psp->setHost(std::make_unique<EffectHostParticle>(particle.Get(), *orientationOverride.GetMatrix(), orientationOverrideRelative));
451+
psp->finishCreation();
452+
453+
return ade_set_args(L, "b", true);
454+
}
455+
217456
}
218457
}
219458

code/scripting/api/objs/particle.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "scripting/ade_api.h"
44
#include "particle/particle.h"
5+
#include "particle/ParticleSource.h"
56

67
namespace scripting {
78
namespace api {
@@ -22,6 +23,25 @@ class particle_h
2223

2324
DECLARE_ADE_OBJ(l_Particle, particle_h);
2425

26+
DECLARE_ADE_OBJ(l_ParticleEffect, particle::ParticleEffectHandle);
27+
28+
class particle_source_h
29+
{
30+
protected:
31+
particle::ParticleSource* source = nullptr;
32+
uint32_t sourceValidityCounter = 0;
33+
public:
34+
particle_source_h() = default;
35+
36+
explicit particle_source_h(particle::ParticleSource* source_p, uint32_t sourceValidityCounter_p);
37+
38+
particle::ParticleSource* Get() const;
39+
40+
bool isValid() const;
41+
};
42+
43+
DECLARE_ADE_OBJ(l_ParticleSource, particle_source_h);
44+
2545
}
2646
}
2747

0 commit comments

Comments
 (0)