882 lines
27 KiB
C++
882 lines
27 KiB
C++
/*
|
|
** Command & Conquer Renegade(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***************************************************************************
|
|
* *
|
|
* Project Name : G *
|
|
* *
|
|
* $Archive:: /VSS_Sync/ww3d2/part_emt.cpp $*
|
|
* *
|
|
* $Author:: Vss_sync $*
|
|
* *
|
|
* $Modtime:: 10/26/01 2:56p $*
|
|
* *
|
|
* $Revision:: 14 $*
|
|
* *
|
|
*-------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "part_emt.h"
|
|
#include "wwdebug.h"
|
|
#include "ww3d.h"
|
|
#include "assetmgr.h"
|
|
#include "part_ldr.h"
|
|
#include "w3derr.h"
|
|
#include "scene.h"
|
|
#include "texture.h"
|
|
#include "wwprofile.h"
|
|
#include <limits.h>
|
|
#include <gcd_lcm.h>
|
|
#include "texture.h"
|
|
#include "part_ldr.h"
|
|
|
|
|
|
// Global variable which is only used to communicate the worldspace emitter
|
|
// velocity from ParticleEmitterClass::Create_New_Particles() to
|
|
// ParticleEmitterClass::Initialize_Particle(), for velocity inheritance.
|
|
Vector3 InheritedWorldSpaceEmitterVel;
|
|
|
|
// This debug setting disables particles from being generated
|
|
bool ParticleEmitterClass::DebugDisable = false;
|
|
|
|
// This is used to set the global behavior of emitters...
|
|
// Should they be removed from the scene when they complete their
|
|
// emissions, or should they stay in the scene. (For editing purposes)
|
|
// (gth) 09/17/2000 - particle emitters now have a local RemoveOnComplete flag
|
|
// which is initialized to the state of DefaultRemoveOnComplete.
|
|
bool ParticleEmitterClass::DefaultRemoveOnComplete = true;
|
|
|
|
|
|
ParticleEmitterClass::ParticleEmitterClass(float emit_rate, unsigned int burst_size,
|
|
Vector3Randomizer *pos_rnd, Vector3 base_vel, Vector3Randomizer *vel_rnd, float out_vel,
|
|
float vel_inherit_factor,
|
|
ParticlePropertyStruct<Vector3> &color,
|
|
ParticlePropertyStruct<float> &opacity,
|
|
ParticlePropertyStruct<float> &size,
|
|
ParticlePropertyStruct<float> &rotation, float orient_rnd,
|
|
ParticlePropertyStruct<float> &frames,
|
|
ParticlePropertyStruct<float> &blur_times,
|
|
Vector3 accel, float max_age, TextureClass *tex, ShaderClass shader, int max_particles,
|
|
int max_buffer_size, bool pingpong,int render_mode,int frame_mode,
|
|
const W3dEmitterLinePropertiesStruct * line_props
|
|
) :
|
|
OneTimeBurstSize(1),
|
|
OneTimeBurst(false),
|
|
PosRand(pos_rnd),
|
|
BaseVel(base_vel * 0.001f),
|
|
VelRand(vel_rnd),
|
|
OutwardVel(out_vel * 0.001f),
|
|
VelInheritFactor(vel_inherit_factor),
|
|
EmitRemain(0U),
|
|
PrevQ(true),
|
|
PrevOrig(0.0, 0.0, 0.0),
|
|
Active(false),
|
|
FirstTime(true),
|
|
ParticlesLeft(max_particles),
|
|
MaxParticles(max_particles),
|
|
IsComplete(false),
|
|
RemoveOnComplete(DefaultRemoveOnComplete),
|
|
NameString(NULL),
|
|
UserString(NULL),
|
|
IsInScene(false)
|
|
{
|
|
EmitRate = emit_rate > 0.0f ? (unsigned int)(1000.0f / emit_rate) : 1000U;
|
|
BurstSize = burst_size != 0 ? burst_size : 1;
|
|
max_age = max_age > 0.0f ? max_age : 1.0f;
|
|
VelRand->Scale(0.001f);
|
|
|
|
// The maximum number of particles is determined by the emission rate, burst size and lifetime.
|
|
// However, it is capped both by the particle cap and by the maximum buffer size, if these are
|
|
// active.
|
|
int max_num = BurstSize * emit_rate * (max_age + 1);
|
|
if (max_particles > 0) max_num = MIN(max_num, max_particles);
|
|
if (max_buffer_size > 0) max_num = MIN(max_num, max_buffer_size);
|
|
max_num = MAX(max_num, 2); // max_num of 1 causes problems
|
|
|
|
Buffer = new ParticleBufferClass(this, max_num, color, opacity, size, rotation, orient_rnd,
|
|
frames, blur_times, accel/1000000.0f,max_age, tex, shader, pingpong, render_mode, frame_mode,
|
|
line_props);
|
|
SET_REF_OWNER( Buffer );
|
|
BufferSceneNeeded = true;
|
|
|
|
NameString = ::_strdup ("ParticleEmitter");
|
|
}
|
|
|
|
|
|
ParticleEmitterClass::ParticleEmitterClass(const ParticleEmitterClass & src) :
|
|
IsInScene(false),
|
|
RenderObjClass(src)
|
|
{
|
|
|
|
EmitRate = src.EmitRate;
|
|
BurstSize = src.BurstSize;
|
|
OneTimeBurstSize = src.OneTimeBurstSize;
|
|
OneTimeBurst = src.OneTimeBurst;
|
|
if (src.PosRand) {
|
|
PosRand = src.PosRand->Clone();
|
|
} else {
|
|
PosRand = NULL;
|
|
}
|
|
BaseVel = src.BaseVel;
|
|
if (src.VelRand) {
|
|
VelRand = src.VelRand->Clone();
|
|
} else {
|
|
VelRand = NULL;
|
|
}
|
|
OutwardVel = src.OutwardVel;
|
|
VelInheritFactor = src.VelInheritFactor;
|
|
EmitRemain = src.EmitRemain;
|
|
PrevQ = src.PrevQ;
|
|
PrevOrig = src.PrevOrig;
|
|
MaxParticles = src.MaxParticles;
|
|
ParticlesLeft = src.ParticlesLeft;
|
|
|
|
Buffer = (ParticleBufferClass *) src.Buffer->Clone();
|
|
Buffer->Set_Emitter(this);
|
|
SET_REF_OWNER( Buffer );
|
|
BufferSceneNeeded = true;
|
|
|
|
Active = true; // default to on
|
|
FirstTime = true;
|
|
IsComplete = false;
|
|
|
|
NameString = ::_strdup (src.NameString);
|
|
}
|
|
|
|
|
|
ParticleEmitterClass & ParticleEmitterClass::operator = (const ParticleEmitterClass & that)
|
|
{
|
|
RenderObjClass::operator = (that);
|
|
|
|
if (this != &that) {
|
|
assert(0); // TODO: if you hit this assert, please implement me !!!;-)
|
|
}
|
|
|
|
return * this;
|
|
}
|
|
|
|
|
|
ParticleEmitterClass::~ParticleEmitterClass(void)
|
|
{
|
|
Buffer->Emitter_Is_Dead();
|
|
Buffer->Release_Ref();
|
|
|
|
if (PosRand != NULL) {
|
|
delete PosRand;
|
|
PosRand = NULL;
|
|
}
|
|
|
|
if (VelRand != NULL) {
|
|
delete VelRand;
|
|
VelRand = NULL;
|
|
}
|
|
|
|
if (NameString != NULL) {
|
|
::free (NameString);
|
|
NameString = NULL;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
ParticleEmitterClass *
|
|
ParticleEmitterClass::Create_From_Definition (const ParticleEmitterDefClass &definition)
|
|
{
|
|
// Assume failure
|
|
ParticleEmitterClass *pemitter = NULL;
|
|
|
|
// Attempt to load the texture for this emitter
|
|
const char *ptexture_filename = definition.Get_Texture_Filename ();
|
|
TextureClass *ptexture = NULL;
|
|
if (ptexture_filename && ptexture_filename[0]) {
|
|
ptexture = WW3DAssetManager::Get_Instance()->Get_Texture(
|
|
ptexture_filename,
|
|
TextureClass::MIP_LEVELS_ALL,
|
|
WW3D_FORMAT_UNKNOWN);
|
|
// false); // no compression for particle textures!
|
|
}
|
|
|
|
ShaderClass shader;
|
|
definition.Get_Shader (shader);
|
|
if (WW3DAssetManager::Get_Instance()->Get_Activate_Fog_On_Load()) {
|
|
shader.Enable_Fog ("ParticleEmitterClass");
|
|
}
|
|
/*if (ptexture) {
|
|
// If texture has an alpha channel do alpha blending instead of additive
|
|
// (which is the default for point groups):
|
|
srTextureIFace::Dimensions dimensions;
|
|
ptexture->getDimensions(dimensions);
|
|
if (dimensions.pixelFormat.aBits > 0) {
|
|
shader = ShaderClass::_PresetAlphaSpriteShader;
|
|
}
|
|
}*/
|
|
|
|
//
|
|
// Peek at the definition's keyframes
|
|
//
|
|
ParticlePropertyStruct<Vector3> color_keys;
|
|
ParticlePropertyStruct<float> opacity_keys;
|
|
ParticlePropertyStruct<float> size_keys;
|
|
ParticlePropertyStruct<float> rotation_keys;
|
|
ParticlePropertyStruct<float> frame_keys;
|
|
ParticlePropertyStruct<float> blur_time_keys;
|
|
|
|
definition.Get_Color_Keyframes (color_keys);
|
|
definition.Get_Opacity_Keyframes (opacity_keys);
|
|
definition.Get_Size_Keyframes (size_keys);
|
|
definition.Get_Rotation_Keyframes (rotation_keys);
|
|
definition.Get_Frame_Keyframes (frame_keys);
|
|
definition.Get_Blur_Time_Keyframes (blur_time_keys);
|
|
|
|
//
|
|
// Create the emitter
|
|
//
|
|
pemitter = NEW_REF( ParticleEmitterClass, ( definition.Get_Emission_Rate (),
|
|
definition.Get_Burst_Size (),
|
|
definition.Get_Creation_Volume (),
|
|
definition.Get_Velocity (),
|
|
definition.Get_Velocity_Random (),
|
|
definition.Get_Outward_Vel (),
|
|
definition.Get_Vel_Inherit (),
|
|
color_keys,
|
|
opacity_keys,
|
|
size_keys,
|
|
rotation_keys,
|
|
definition.Get_Initial_Orientation_Random(),
|
|
frame_keys,
|
|
blur_time_keys,
|
|
definition.Get_Acceleration (),
|
|
definition.Get_Lifetime (),
|
|
ptexture,
|
|
shader,
|
|
definition.Get_Max_Emissions (),
|
|
0,
|
|
false,
|
|
definition.Get_Render_Mode (),
|
|
definition.Get_Frame_Mode (),
|
|
definition.Get_Line_Properties ()) );
|
|
|
|
if (color_keys.KeyTimes != NULL) delete [] color_keys.KeyTimes;
|
|
if (color_keys.Values != NULL) delete [] color_keys.Values;
|
|
if (opacity_keys.KeyTimes != NULL) delete [] opacity_keys.KeyTimes;
|
|
if (opacity_keys.Values != NULL) delete [] opacity_keys.Values;
|
|
if (size_keys.KeyTimes != NULL) delete [] size_keys.KeyTimes;
|
|
if (size_keys.Values != NULL) delete [] size_keys.Values;
|
|
if (rotation_keys.KeyTimes != NULL) delete [] rotation_keys.KeyTimes;
|
|
if (rotation_keys.Values != NULL) delete [] rotation_keys.Values;
|
|
if (frame_keys.KeyTimes != NULL) delete [] frame_keys.KeyTimes;
|
|
if (frame_keys.Values != NULL) delete [] frame_keys.Values;
|
|
if (blur_time_keys.KeyTimes != NULL) delete [] blur_time_keys.KeyTimes;
|
|
if (blur_time_keys.Values != NULL) delete [] blur_time_keys.Values;
|
|
|
|
// Pass the name along to the emitter
|
|
pemitter->Set_Name (definition.Get_Name ());
|
|
|
|
// release our reference to particle texture.
|
|
if (ptexture) {
|
|
REF_PTR_RELEASE(ptexture);
|
|
ptexture = 0;
|
|
}
|
|
|
|
// Return a pointer to the new emitter
|
|
return pemitter;
|
|
}
|
|
|
|
RenderObjClass * ParticleEmitterClass::Clone(void) const
|
|
{
|
|
return new ParticleEmitterClass(*this);
|
|
}
|
|
|
|
void ParticleEmitterClass::Restart(void)
|
|
{
|
|
// calling Start will cause all internal counters to reset
|
|
Start();
|
|
}
|
|
|
|
void ParticleEmitterClass::Notify_Added(SceneClass * scene)
|
|
{
|
|
RenderObjClass::Notify_Added(scene);
|
|
scene->Register(this,SceneClass::ON_FRAME_UPDATE);
|
|
if (FirstTime == false) {
|
|
Active = true;
|
|
}
|
|
IsInScene = true;
|
|
}
|
|
|
|
void ParticleEmitterClass::Notify_Removed(SceneClass * scene)
|
|
{
|
|
scene->Unregister(this,SceneClass::ON_FRAME_UPDATE);
|
|
RenderObjClass::Notify_Removed(scene);
|
|
Active = false;
|
|
IsInScene = false;
|
|
|
|
//Buffer->Emitter_Is_Dead();
|
|
}
|
|
|
|
// Scales the size of all particles and effects positions/velocities of
|
|
// particles emitted after the Scale() call (but not before)
|
|
void ParticleEmitterClass::Scale(float scale)
|
|
{
|
|
// Scale all velosity and position parameters
|
|
if (PosRand) PosRand->Scale(scale);
|
|
BaseVel *= scale;
|
|
if (VelRand) VelRand->Scale(scale);
|
|
OutwardVel *= scale;
|
|
|
|
// Scale sizes of all particles
|
|
Buffer->Scale(scale);
|
|
}
|
|
|
|
// Put particle buffer in scene if this is the first time (clunky code
|
|
// - hopefully can be rewritten more cleanly in future)...
|
|
void ParticleEmitterClass::On_Frame_Update(void)
|
|
{
|
|
if (Active && !IsComplete) {
|
|
if (FirstTime) {
|
|
|
|
// The particle buffer doesn't have a valid Scene yet - the emitter
|
|
// finds out what scene it belongs to (goes up the container tree
|
|
// until it finds a non-NULL Scene), and then adds the particle
|
|
// buffer to it.
|
|
if ( BufferSceneNeeded ) {
|
|
|
|
if (Is_In_Scene()) {
|
|
Buffer->Add(Scene);
|
|
BufferSceneNeeded = false;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
}
|
|
BufferSceneNeeded = false;
|
|
|
|
// Initialize previous transform:
|
|
PrevQ = Build_Quaternion(Get_Transform());
|
|
PrevOrig = Get_Transform().Get_Translation();
|
|
|
|
FirstTime = false;
|
|
}
|
|
}
|
|
|
|
if (Is_Complete()) {
|
|
if (Is_In_Scene() && Is_Remove_On_Complete_Enabled()) {
|
|
Scene->Register(this,SceneClass::RELEASE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParticleEmitterClass::Reset(void)
|
|
{
|
|
// Note: This flag needs to be set first thing, otherwise
|
|
// getting the transform will result in an 'update_x' call
|
|
// which in turn results in a 'Set_Animation_Hidden' call, which
|
|
// in turn will cause the Update_Visibilty function to call
|
|
// Start(). This won't cause a stack overflow like in Start
|
|
// but it would do some extra work.
|
|
Active = true;
|
|
|
|
// Initialize previous transform:
|
|
PrevQ = Build_Quaternion(Get_Transform());
|
|
PrevOrig = Get_Transform().Get_Translation();
|
|
|
|
// Reset the number of particles to emit
|
|
ParticlesLeft = MaxParticles;
|
|
EmitRemain = 0;
|
|
IsComplete = false;
|
|
}
|
|
|
|
void ParticleEmitterClass::Start(void)
|
|
{
|
|
// Note: This flag needs to be set first thing, otherwise
|
|
// getting the transform will result in an 'update_x' call
|
|
// which in turn results in a 'Set_Animation_Hidden' call, which
|
|
// in turn will cause the Update_Visibilty function to call
|
|
// this method. And then... Stack Overflow! ;)
|
|
Active = true;
|
|
|
|
// Initialize previous transform:
|
|
PrevQ = Build_Quaternion(Get_Transform());
|
|
PrevOrig = Get_Transform().Get_Translation();
|
|
|
|
// Reset the number of particles to emit (if necessary)
|
|
if (IsComplete == true) {
|
|
ParticlesLeft = MaxParticles;
|
|
IsComplete = false;
|
|
}
|
|
}
|
|
|
|
|
|
void ParticleEmitterClass::Stop(void)
|
|
{
|
|
Active = false;
|
|
}
|
|
|
|
|
|
bool ParticleEmitterClass::Is_Stopped(void)
|
|
{
|
|
return (Active == false);
|
|
}
|
|
|
|
|
|
void ParticleEmitterClass::Set_Position_Randomizer(Vector3Randomizer *rand)
|
|
{
|
|
if (PosRand) {
|
|
delete PosRand;
|
|
PosRand =NULL;
|
|
}
|
|
PosRand = rand;
|
|
}
|
|
|
|
|
|
void ParticleEmitterClass::Set_Velocity_Randomizer(Vector3Randomizer *rand)
|
|
{
|
|
if (VelRand) {
|
|
delete VelRand;
|
|
VelRand =NULL;
|
|
}
|
|
VelRand = rand;
|
|
if (VelRand) {
|
|
VelRand->Scale(0.001f); // Convert from seconds to ms
|
|
}
|
|
}
|
|
|
|
|
|
Vector3Randomizer *ParticleEmitterClass::Get_Creation_Volume (void) const
|
|
{
|
|
Vector3Randomizer *randomizer = NULL;
|
|
if (PosRand != NULL) {
|
|
randomizer = PosRand->Clone ();
|
|
//randomizer->Scale (1000.0F);
|
|
}
|
|
return randomizer;
|
|
}
|
|
|
|
|
|
Vector3Randomizer *ParticleEmitterClass::Get_Velocity_Random (void) const
|
|
{
|
|
Vector3Randomizer *randomizer = NULL;
|
|
if (VelRand != NULL) {
|
|
randomizer = VelRand->Clone ();
|
|
randomizer->Scale (1000.0F);
|
|
}
|
|
return randomizer;
|
|
}
|
|
|
|
void ParticleEmitterClass::Set_Base_Velocity(const Vector3& base_vel)
|
|
{
|
|
BaseVel = base_vel * 0.001f; // Convert from seconds to ms
|
|
}
|
|
|
|
|
|
void ParticleEmitterClass::Set_Outwards_Velocity(float out_vel)
|
|
{
|
|
OutwardVel = out_vel * 0.001f; // Convert from seconds to ms
|
|
}
|
|
|
|
|
|
void ParticleEmitterClass::Set_Velocity_Inheritance_Factor(float inh_factor)
|
|
{
|
|
VelInheritFactor = inh_factor;
|
|
}
|
|
|
|
|
|
// Emit particles (put in particle buffer). This is called by the particle
|
|
// buffer On_Frame_Update() function to avoid order dependence.
|
|
void ParticleEmitterClass::Emit(void)
|
|
{
|
|
WWPROFILE("PartlicleEmitter::Emit");
|
|
#ifdef WWDEBUG
|
|
if (DebugDisable == true) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (Active && !IsComplete) {
|
|
Quaternion curr_quat; // Quaternion form of orientation.
|
|
Vector3 curr_orig; // Origin.
|
|
|
|
// Convert current matrix into quaternion + origin form.
|
|
curr_quat = Build_Quaternion(Get_Transform());
|
|
curr_orig = Get_Transform().Get_Translation();
|
|
|
|
Create_New_Particles(curr_quat, curr_orig);
|
|
|
|
PrevQ = curr_quat;
|
|
PrevOrig = curr_orig;
|
|
} else {
|
|
// These need to be updated each frame no matter what
|
|
PrevQ = Build_Quaternion(Get_Transform());
|
|
PrevOrig = Get_Transform().Get_Translation();
|
|
}
|
|
}
|
|
|
|
|
|
// Collision sphere is a point - emitter emits also when not visible, so this
|
|
// is only important to avoid affecting the collision spheres of composite
|
|
// objects into which the emitter is inserted.
|
|
void ParticleEmitterClass::Update_Cached_Bounding_Volumes(void) const
|
|
{
|
|
CachedBoundingSphere.Init(Get_Position(),0.0);
|
|
CachedBoundingBox.Center = Get_Position();
|
|
CachedBoundingBox.Extent.Set(0,0,0);
|
|
Validate_Cached_Bounding_Volumes();
|
|
}
|
|
|
|
|
|
// Note that creation location and velocity are in local coordinates, so new
|
|
// particles need to be transformed into worldspace. It is important to get
|
|
// the correct transform at the exact time of particle creation (for frame-
|
|
// rate independence), so the current emitter transform is calculated by
|
|
// time-based interpolation between the transforms at the beginning and end
|
|
// of the current frame. This interpolation is performed via quaternion-
|
|
// slerping the orientation and lerping the origin.
|
|
void ParticleEmitterClass::Create_New_Particles(const Quaternion & curr_quat, const Vector3 & curr_orig)
|
|
{
|
|
Quaternion quat;
|
|
Vector3 orig;
|
|
|
|
// The emit remainder from the previous interval (the time remaining in
|
|
// the previous interval when the last particle was emitted) is added to
|
|
// the size of the current frame to yield the time currently available
|
|
// for emitting particles.
|
|
unsigned int frametime = WW3D::Get_Frame_Time();
|
|
// Since the particles are written into a wraparound buffer, we can take the time modulo a time
|
|
// constant which represents the time it takes to fill up the entire buffer with new particles.
|
|
// We will do this so we don't run into performance problems with very large frame times.
|
|
if (frametime > 100 * EmitRate) { // If the loop will run over 100 times
|
|
unsigned int buf_size = Buffer->Get_Buffer_Size();
|
|
unsigned int gcd = Greatest_Common_Divisor(buf_size, BurstSize);
|
|
unsigned int bursts = buf_size / gcd;
|
|
unsigned int cycle_time = EmitRate * bursts;
|
|
if (cycle_time > 1) {
|
|
frametime = frametime % cycle_time;
|
|
} else {
|
|
frametime = 1;
|
|
}
|
|
}
|
|
|
|
EmitRemain += frametime;
|
|
|
|
// The interpolation factor (0: start of interval: 1: end of interval).
|
|
// Possibly negative at this point, but after the delta is added to it, it
|
|
// will be positive.
|
|
float fl_frametime = (float)frametime;
|
|
float alpha = 1 - ((float)EmitRemain / fl_frametime);
|
|
float d_alpha = (float)EmitRate / fl_frametime;
|
|
|
|
// Setup the slerp between the two quaternions.
|
|
SlerpInfoStruct slerp_info;
|
|
Slerp_Setup(PrevQ, curr_quat, &slerp_info);
|
|
|
|
// Find the velocity of the emitter (for velocity inheritance).
|
|
// InheritedWorldSpaceEmitterVel is a global variable which is only used
|
|
// to pass this into the following Initialize_Particle() calls without
|
|
// having to set it as an argument for each call.
|
|
if (VelInheritFactor) {
|
|
InheritedWorldSpaceEmitterVel = (curr_orig - PrevOrig) * (VelInheritFactor / fl_frametime);
|
|
} else {
|
|
InheritedWorldSpaceEmitterVel.Set(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
for (; EmitRemain > EmitRate;) {
|
|
|
|
// Calculate the new remainder.
|
|
EmitRemain -= EmitRate;
|
|
|
|
// Interpolate the start and end transforms to find the transform at
|
|
// the moment of particle creation.
|
|
alpha += d_alpha;
|
|
quat = Cached_Slerp(PrevQ, curr_quat, alpha, &slerp_info);
|
|
orig = Lerp(PrevOrig, curr_orig, alpha);
|
|
|
|
// Initialize BurstSize new particles with the given age and emitter
|
|
// transform (expressed as a quaternion and origin vector), and add it
|
|
// to the particle buffer's new particle vector.
|
|
unsigned int age = WW3D::Get_Sync_Time() - EmitRemain;
|
|
unsigned int burst_size = BurstSize;
|
|
if (OneTimeBurst) {
|
|
burst_size = OneTimeBurstSize;
|
|
OneTimeBurst = false;
|
|
}
|
|
|
|
if ( ParticlesLeft > 0 ) { // if we are counting,
|
|
if (burst_size > (unsigned int)ParticlesLeft) {
|
|
burst_size = (unsigned int)ParticlesLeft;
|
|
ParticlesLeft = 0;
|
|
} else {
|
|
ParticlesLeft -= burst_size;
|
|
}
|
|
if ( ParticlesLeft <= 0 ) { // count and if done
|
|
IsComplete = true; // stop
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < burst_size; i++) {
|
|
Initialize_Particle(Buffer->Add_Uninitialized_New_Particle(), age, quat, orig);
|
|
}
|
|
|
|
if (IsComplete) break;
|
|
}
|
|
}
|
|
|
|
|
|
// Initialize one new particle at the given NewParticleStruct address, with
|
|
// the given age and emitter transform (expressed as a quaternion and origin
|
|
// vector). (must check if address is NULL).
|
|
void ParticleEmitterClass::Initialize_Particle(NewParticleStruct * newpart,
|
|
unsigned int timestamp, const Quaternion & quat, const Vector3 & orig)
|
|
{
|
|
// Set time stamp.
|
|
newpart->TimeStamp = timestamp;
|
|
|
|
// Set starting (random) local position.
|
|
Vector3 rand_pos;
|
|
if (PosRand) {
|
|
PosRand->Get_Vector(rand_pos);
|
|
} else {
|
|
rand_pos.Set(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
// Transform position to worldspace, using the transform at moment of
|
|
// particle creation.
|
|
newpart->Position = quat.Rotate_Vector(rand_pos) + orig;
|
|
|
|
// Set (random) local velocity.
|
|
Vector3 rand_vel;
|
|
if (VelRand) {
|
|
VelRand->Get_Vector(rand_vel);
|
|
} else {
|
|
rand_vel.Set(0.0, 0.0, 0.0);
|
|
}
|
|
|
|
// Add outwards velocity to emitterspace velocity
|
|
if (OutwardVel) {
|
|
// Find vector pointing outwards (from origin to creation position)
|
|
Vector3 outwards;
|
|
float pos_l2 = rand_pos.Length2();
|
|
if (pos_l2) {
|
|
outwards = rand_pos * (OutwardVel * WWMath::Inv_Sqrt(pos_l2));
|
|
} else {
|
|
outwards.X = OutwardVel;
|
|
outwards.Y = 0.0f;
|
|
outwards.Z = 0.0f;
|
|
}
|
|
|
|
rand_vel += outwards;
|
|
}
|
|
|
|
// Add base velocity to emitterspace velocity
|
|
rand_vel += BaseVel;
|
|
|
|
// Rotate velocity to worldspace and add emitter's inherited velocity.
|
|
newpart->Velocity = InheritedWorldSpaceEmitterVel + quat.Rotate_Vector(rand_vel);
|
|
}
|
|
|
|
|
|
ParticleEmitterDefClass *
|
|
ParticleEmitterClass::Build_Definition (void) const
|
|
{
|
|
// Allocate a new emitter definition object
|
|
ParticleEmitterDefClass *pdefinition = new ParticleEmitterDefClass;
|
|
WWASSERT (pdefinition != NULL);
|
|
if (pdefinition != NULL) {
|
|
|
|
// Set the texture's filename
|
|
TextureClass *ptexture = Get_Texture ();
|
|
if (ptexture != NULL) {
|
|
pdefinition->Set_Texture_Filename (ptexture->Get_Texture_Name());
|
|
REF_PTR_RELEASE(ptexture);
|
|
}
|
|
|
|
// Now fill the definition with data from this emitter instance
|
|
pdefinition->Set_Render_Mode (Get_Render_Mode());
|
|
pdefinition->Set_Frame_Mode (Get_Frame_Mode());
|
|
pdefinition->Set_Name (Get_Name ());
|
|
pdefinition->Set_Lifetime (Get_Lifetime ());
|
|
pdefinition->Set_Emission_Rate (Get_Emission_Rate ());
|
|
pdefinition->Set_Max_Emissions (Get_Max_Particles ());
|
|
pdefinition->Set_Fade_Time (Get_Fade_Time ());
|
|
pdefinition->Set_Gravity (0);
|
|
pdefinition->Set_Elasticity (0);
|
|
pdefinition->Set_Velocity (Get_Start_Velocity ());
|
|
pdefinition->Set_Acceleration (Get_Acceleration ());
|
|
pdefinition->Set_Burst_Size (Get_Burst_Size ());
|
|
pdefinition->Set_Outward_Vel (Get_Outwards_Vel ());
|
|
pdefinition->Set_Vel_Inherit (Get_Velocity_Inherit ());
|
|
pdefinition->Set_Shader (Get_Shader ());
|
|
|
|
//
|
|
// Pass the creation volume onto the definition
|
|
//
|
|
Vector3Randomizer *randomizer = Get_Creation_Volume ();
|
|
pdefinition->Set_Creation_Volume (randomizer);
|
|
|
|
//
|
|
// Pass the velocity randomizer onto the definition
|
|
//
|
|
randomizer = Get_Velocity_Random ();
|
|
pdefinition->Set_Velocity_Random (randomizer);
|
|
|
|
//
|
|
// Pass the color keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<Vector3> colors;
|
|
Get_Color_Key_Frames (colors);
|
|
pdefinition->Set_Color_Keyframes (colors);
|
|
if (colors.KeyTimes != NULL) delete [] colors.KeyTimes;
|
|
if (colors.Values != NULL) delete [] colors.Values;
|
|
|
|
//
|
|
// Pass the opacity keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<float> opacities;
|
|
Get_Opacity_Key_Frames (opacities);
|
|
pdefinition->Set_Opacity_Keyframes (opacities);
|
|
if (opacities.KeyTimes != NULL) delete [] opacities.KeyTimes;
|
|
if (opacities.Values != NULL) delete [] opacities.Values;
|
|
|
|
//
|
|
// Pass the size keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<float> sizes;
|
|
Get_Size_Key_Frames (sizes);
|
|
pdefinition->Set_Size_Keyframes (sizes);
|
|
if (sizes.KeyTimes != NULL) delete [] sizes.KeyTimes;
|
|
if (sizes.Values != NULL) delete [] sizes.Values;
|
|
|
|
//
|
|
// Pass the rotation keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<float> rotations;
|
|
Get_Rotation_Key_Frames (rotations);
|
|
pdefinition->Set_Rotation_Keyframes (rotations, Get_Initial_Orientation_Random());
|
|
if (rotations.KeyTimes != NULL) delete [] rotations.KeyTimes;
|
|
if (rotations.Values != NULL) delete [] rotations.Values;
|
|
|
|
//
|
|
// Pass the frame keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<float> frames;
|
|
Get_Frame_Key_Frames (frames);
|
|
pdefinition->Set_Frame_Keyframes (frames);
|
|
if (frames.KeyTimes != NULL) delete [] frames.KeyTimes;
|
|
if (frames.Values != NULL) delete [] frames.Values;
|
|
|
|
//
|
|
// Pass the blur time keyframes onto the definition
|
|
//
|
|
ParticlePropertyStruct<float> blur_times;
|
|
Get_Blur_Time_Key_Frames (blur_times);
|
|
pdefinition->Set_Blur_Time_Keyframes (blur_times);
|
|
if (blur_times.KeyTimes != NULL) delete [] blur_times.KeyTimes;
|
|
if (blur_times.Values != NULL) delete [] blur_times.Values;
|
|
|
|
|
|
//
|
|
// Set up the line parameters
|
|
//
|
|
pdefinition->Set_Line_Texture_Mapping_Mode(Get_Line_Texture_Mapping_Mode());
|
|
pdefinition->Set_Merge_Intersections(Is_Merge_Intersections());
|
|
pdefinition->Set_Freeze_Random(Is_Freeze_Random());
|
|
pdefinition->Set_Disable_Sorting(Is_Sorting_Disabled());
|
|
pdefinition->Set_End_Caps(Are_End_Caps_Enabled());
|
|
pdefinition->Set_Subdivision_Level(Get_Subdivision_Level());
|
|
pdefinition->Set_Noise_Amplitude(Get_Noise_Amplitude());
|
|
pdefinition->Set_Merge_Abort_Factor(Get_Merge_Abort_Factor());
|
|
pdefinition->Set_Texture_Tile_Factor(Get_Texture_Tile_Factor());
|
|
pdefinition->Set_UV_Offset_Rate(Get_UV_Offset_Rate());
|
|
|
|
}
|
|
|
|
// Return a pointer to the new definition
|
|
return pdefinition;
|
|
}
|
|
|
|
|
|
WW3DErrorType
|
|
ParticleEmitterClass::Save (ChunkSaveClass &chunk_save) const
|
|
{
|
|
// Assume error
|
|
WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED;
|
|
|
|
// Build a definition from this emitter instance, and save it
|
|
// to the chunk.
|
|
ParticleEmitterDefClass *pdefinition = Build_Definition ();
|
|
if (pdefinition != NULL) {
|
|
ret_val = pdefinition->Save_W3D (chunk_save);
|
|
}
|
|
|
|
// Return the WW3DErrorType return code
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
void
|
|
ParticleEmitterClass::Set_Name (const char *pname)
|
|
{
|
|
// Free the old name if necessary
|
|
if (NameString != NULL) {
|
|
::free (NameString);
|
|
NameString = NULL;
|
|
}
|
|
|
|
// Copy the provided name
|
|
NameString = ::_strdup (pname);
|
|
return ;
|
|
}
|
|
|
|
|
|
void
|
|
ParticleEmitterClass::Update_On_Visibilty(void)
|
|
{
|
|
// Simply start or stop the emission based on
|
|
// the visibility state of the emitter.
|
|
if (Is_Not_Hidden_At_All () && Is_Stopped () && IsInScene) {
|
|
Start ();
|
|
} else if ((Is_Not_Hidden_At_All () == false) && (Is_Stopped () == false)) {
|
|
Stop ();
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
void
|
|
ParticleEmitterClass::Add_Dependencies_To_List
|
|
(
|
|
DynamicVectorClass<StringClass> &file_list,
|
|
bool textures_only
|
|
)
|
|
{
|
|
//
|
|
// Get the texture the emitter is using and add it to our list
|
|
//
|
|
TextureClass *texture = Get_Texture ();
|
|
if (texture != NULL) {
|
|
file_list.Add (texture->Get_Full_Path ());
|
|
REF_PTR_RELEASE(texture);
|
|
}
|
|
|
|
// Allow the base class to process this call (extremely important)
|
|
RenderObjClass::Add_Dependencies_To_List (file_list, textures_only);
|
|
return ;
|
|
}
|