Skip to content

Commit c8ea9c6

Browse files
authored
Model surface particles (scp-fs2open#7447)
* Pick model surface particle by sampling random vertex * Parse and keep buffers * Add local position scaling * Fix warning * Fix warning 2 * Fix warning 3 * Fix warning 4 * Add scale parameter * Incorporate feedback
1 parent 15e7528 commit c8ea9c6

25 files changed

Lines changed: 173 additions & 20 deletions

code/model/model_flags.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Model {
1010
Is_live_debris, // whether current submodel is a live debris model
1111
Is_thruster, // is an engine thruster submodel
1212
Is_damaged, // is a submodel that represents a damaged submodel (e.g. a -destroyed version of some other submodel)
13+
Is_lod, // is a submodel that is a lower LOD of a different submodel
1314
Do_not_scale_detail_distances, // if set should not scale boxes or spheres based on 'model detail' settings
1415
Gun_rotation, // for animated weapon models
1516
Instant_rotate_accel, // rotating submodels instantly reach their desired velocity

code/model/modelread.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ SCP_vector<bsp_collision_tree> Bsp_collision_tree_list;
7272

7373
const ubyte* Macro_ubyte_bounds = nullptr;
7474

75+
//If true, CPU-side vertex buffers are deleted once the model is on-GPU.
76+
//This is typically desired for memory reasons, but will prevent certain type of particles.
77+
bool Model_load_clear_CPU_buffers = true;
78+
7579
static int model_initted = 0;
7680

7781
#ifndef NDEBUG
@@ -1138,7 +1142,8 @@ void create_vertex_buffer(polymodel *pm, const model_read_deferred_tasks& deferr
11381142
interp_pack_vertex_buffers(pm, i);
11391143

11401144
// release temporary memory
1141-
pm->submodel[i].buffer.release();
1145+
if (Model_load_clear_CPU_buffers)
1146+
pm->submodel[i].buffer.release();
11421147
pm->submodel[i].trans_buffer.release();
11431148
}
11441149

@@ -3447,6 +3452,7 @@ int model_load(const char* filename, ship_info* sip, ErrorType error_type, bool
34473452
if (dl2 >= sm1->num_details ) sm1->num_details = dl2+1;
34483453
sm1->details[dl2] = j;
34493454
mprintf(( "Submodel '%s' is detail level %d of '%s'\n", sm2->name, dl2 + 1, sm1->name ));
3455+
sm2->flags.set(Model::Submodel_flags::Is_lod);
34503456
lower_to_higher_detail_submodels.emplace(sm2->name, sm1->name);
34513457
}
34523458
}

code/model/modelrender.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ extern color Wireframe_color;
2929

3030
extern int Lab_object_detail_level;
3131

32+
extern bool Model_load_clear_CPU_buffers;
33+
3234
typedef enum {
3335
TECH_SHIP,
3436
TECH_WEAPON,

code/particle/EffectHost.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class EffectHost {
2727
}
2828

2929
virtual std::pair<int, int> getParentObjAndSig() const { return {-1, -1}; }
30+
virtual int getParentSubmodel() const { return -1; }
3031

3132
virtual float getLifetime() const { return -1.f; }
3233

code/particle/ParticleEffect.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,28 @@ void ParticleEffect::sampleNoise(vec3d& noiseTarget, const matrix* orientation,
185185
vm_vec_unrotate(&noiseTarget, &noiseSampleLocal, orientation);
186186
}
187187

188+
vec3d ParticleEffect::adaptPosition(const vec3d& pos, int parent) const {
189+
if (parent < 0 || !m_local_position_scaling.has_value()) {
190+
return pos;
191+
}
192+
193+
vec3d pos_local = pos;
194+
195+
if (!m_parent_local) {
196+
pos_local -= Objects[parent].pos;
197+
vm_vec_rotate(&pos_local, &pos_local, &Objects[parent].orient);
198+
}
199+
200+
pos_local *= m_local_position_scaling->next();
201+
202+
if (!m_parent_local) {
203+
vm_vec_unrotate(&pos_local, &pos_local, &Objects[parent].orient);
204+
vm_vec_add2(&pos_local, &Objects[parent].pos);
205+
}
206+
207+
return pos_local;
208+
}
209+
188210
/*
189211
* In persistent mode (should only ever be used by scripting, really), this function returns pointers to the persistent particles
190212
* In non-persistent mode, this function returns the multiplier for the next spawn time. This is because the source cannot know about the curve evaluation that is required to get this factor
@@ -213,7 +235,8 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
213235
}
214236
}
215237

216-
const auto& [pos, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset);
238+
const auto& [pos_hit, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset);
239+
const vec3d& pos = adaptPosition(pos_hit, parent);
217240

218241
vec3d posGlobal = pos;
219242
if (m_parent_local && parent >= 0) {
@@ -290,11 +313,11 @@ auto ParticleEffect::processSourceInternal(float interp, const ParticleSource& s
290313
vec3d localPos = posNoise;
291314

292315
if (m_spawnVolume != nullptr) {
293-
localPos += m_spawnVolume->sampleRandomPoint(orientation, modularCurvesInput, particleFraction);
316+
localPos += m_spawnVolume->sampleRandomPoint(orientation, modularCurvesInput, particleFraction, *source.m_host);
294317
}
295318

296319
if (m_velocityVolume != nullptr) {
297-
localVelocity += m_velocityVolume->sampleRandomPoint(orientation, modularCurvesInput, particleFraction) * (m_velocity_scaling.next() * velocityVolumeMultiplier);
320+
localVelocity += m_velocityVolume->sampleRandomPoint(orientation, modularCurvesInput, particleFraction, *source.m_host) * (m_velocity_scaling.next() * velocityVolumeMultiplier);
298321
}
299322

300323
if (m_manual_velocity_offset.has_value()) {

code/particle/ParticleEffect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class ParticleEffect {
168168

169169
std::optional<::util::ParsedRandomFloatRange> m_vel_inherit_from_orientation;
170170
std::optional<::util::ParsedRandomFloatRange> m_vel_inherit_from_position;
171+
std::optional<::util::ParsedRandomFloatRange> m_local_position_scaling;
171172

172173
std::shared_ptr<::particle::ParticleVolume> m_velocityVolume;
173174
std::shared_ptr<::particle::ParticleVolume> m_spawnVolume;
@@ -186,6 +187,7 @@ class ParticleEffect {
186187
float m_distanceCulled; //Kinda deprecated. Only used by the oldest of legacy effects.
187188

188189
matrix getNewDirection(const matrix& hostOrientation, const std::optional<vec3d>& normal) const;
190+
vec3d adaptPosition(const vec3d& pos, int parent) const;
189191

190192
template<bool isPersistent>
191193
auto processSourceInternal(float interp, const ParticleSource& source, size_t effectNumber, const vec3d& velParent, int parent, int parent_sig, float parentLifetime, float parentRadius, float particle_percent) const;

code/particle/ParticleParse.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <anl.h>
99

10+
#include "volumes/ModelSurfaceVolume.h"
11+
1012
namespace particle {
1113

1214
//
@@ -92,6 +94,12 @@ namespace particle {
9294
}
9395
}
9496

97+
static void parseLocalPositionScaling(ParticleEffect& effect) {
98+
if (optional_string("+Local position scaling:")) {
99+
effect.m_local_position_scaling = ::util::ParsedRandomFloatRange::parseRandomRange();
100+
}
101+
}
102+
95103
static void parseParentLocal(ParticleEffect& effect) {
96104
if (optional_string("+Remain local to parent:")) {
97105
stuff_boolean(&effect.m_parent_local);
@@ -125,7 +133,7 @@ namespace particle {
125133

126134
static std::shared_ptr<ParticleVolume> parseVolume() {
127135

128-
int type = required_string_one_of(4, "Spheroid", "Cone", "Ring", "Point"); //... and future volumes
136+
int type = required_string_one_of(5, "Spheroid", "Cone", "Ring", "Point", "ModelSurface"); //... and future volumes
129137
std::shared_ptr<ParticleVolume> volume;
130138

131139
switch (type) {
@@ -145,6 +153,10 @@ namespace particle {
145153
required_string("Point");
146154
volume = std::make_shared<PointVolume>();
147155
break;
156+
case 4:
157+
required_string("ModelSurface");
158+
volume = std::make_shared<ModelSurfaceVolume>();
159+
break;
148160
default:
149161
UNREACHABLE("Invalid volume type specified!");
150162
}
@@ -372,6 +384,7 @@ namespace particle {
372384
parseRadius(effect);
373385
parseLength(effect);
374386
parseLifetime(effect);
387+
parseLocalPositionScaling(effect);
375388
parseParentLocal(effect);
376389
parseLightEmissionSettings(effect);
377390

@@ -723,4 +736,4 @@ namespace particle {
723736
"Sphere",
724737
"Volume"
725738
};
726-
}
739+
}

code/particle/ParticleVolume.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
#include "globalincs/pstypes.h"
44
#include "parse/parselo.h"
5+
#include "particle/EffectHost.h"
56

67
#include <optional>
78

89
namespace particle {
910
class ParticleSource;
1011
class ParticleVolume {
1112
public:
12-
virtual vec3d sampleRandomPoint(const matrix &orientation, const std::tuple<const ParticleSource&, const size_t&, const vec3d&>& source, float particlesFraction) = 0;
13+
virtual vec3d sampleRandomPoint(const matrix &orientation, const std::tuple<const ParticleSource&, const size_t&, const vec3d&>& source, float particlesFraction, const EffectHost& host) = 0;
1314

1415
virtual void parse() = 0;
1516

code/particle/hosts/EffectHostSubmodel.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include "ship/ship.h"
66

77
EffectHostSubmodel::EffectHostSubmodel(const object* objp, int submodel, vec3d offset, matrix orientationOverride, bool orientationOverrideRelative) :
8-
EffectHost(orientationOverride, orientationOverrideRelative), m_offset(offset), m_objnum(OBJ_INDEX(objp)), m_objsig(objp->signature), m_submodel(submodel) {}
8+
EffectHost(orientationOverride, orientationOverrideRelative), m_offset(offset), m_objnum(OBJ_INDEX(objp)), m_objsig(objp->signature), m_submodel(submodel), m_modelnum(object_get_model(&Objects[m_objnum])->id) {}
99

1010
std::pair<vec3d, matrix> EffectHostSubmodel::getPositionAndOrientation(bool relativeToParent, float interp, const std::optional<vec3d>& tabled_offset) const {
1111
vec3d pos = m_offset;
@@ -60,10 +60,14 @@ std::pair<int, int> EffectHostSubmodel::getParentObjAndSig() const {
6060
return { m_objnum, m_objsig };
6161
}
6262

63+
int EffectHostSubmodel::getParentSubmodel() const {
64+
return m_submodel;
65+
}
66+
6367
float EffectHostSubmodel::getHostRadius() const {
6468
return Objects[m_objnum].radius;
6569
}
6670

6771
bool EffectHostSubmodel::isValid() const {
68-
return m_objnum >= 0 && m_submodel >= 0 && Objects[m_objnum].signature == m_objsig && object_get_model_num(&Objects[m_objnum]) >= 0 && object_get_model_instance_num(&Objects[m_objnum]) >= 0;
72+
return m_objnum >= 0 && m_submodel >= 0 && Objects[m_objnum].signature == m_objsig && object_get_model_num(&Objects[m_objnum]) >= 0 && object_get_model_instance_num(&Objects[m_objnum]) >= 0 && object_get_model(&Objects[m_objnum])->id == m_modelnum;
6973
}

code/particle/hosts/EffectHostSubmodel.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class EffectHostSubmodel : public EffectHost {
77

88
vec3d m_offset;
99

10-
int m_objnum, m_objsig, m_submodel;
10+
int m_objnum, m_objsig, m_submodel, m_modelnum;
1111
public:
1212
EffectHostSubmodel(const object* objp, int submodel, vec3d offset, matrix orientationOverride = vmd_identity_matrix, bool orientationOverrideRelative = true);
1313

@@ -16,6 +16,7 @@ class EffectHostSubmodel : public EffectHost {
1616
vec3d getVelocity() const override;
1717

1818
std::pair<int, int> getParentObjAndSig() const override;
19+
int getParentSubmodel() const override;
1920

2021
float getHostRadius() const override;
2122

0 commit comments

Comments
 (0)