/*************************************************************************/ /* particle_system_sw.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "particle_system_sw.h" #include "sort.h" ParticleSystemSW::ParticleSystemSW() { amount=8; emitting=true; for (int i=0;i<VS::PARTICLE_VAR_MAX;i++) { particle_randomness[i]=0.0; } particle_vars[VS::PARTICLE_LIFETIME]=2.0;// particle_vars[VS::PARTICLE_SPREAD]=0.2;// particle_vars[VS::PARTICLE_GRAVITY]=9.8;// particle_vars[VS::PARTICLE_LINEAR_VELOCITY]=0.2;// particle_vars[VS::PARTICLE_ANGULAR_VELOCITY]=0.0;// particle_vars[VS::PARTICLE_LINEAR_ACCELERATION]=0.0;// particle_vars[VS::PARTICLE_RADIAL_ACCELERATION]=0.0;// particle_vars[VS::PARTICLE_TANGENTIAL_ACCELERATION]=1.0;// particle_vars[VS::PARTICLE_DAMPING]=0.0;// particle_vars[VS::PARTICLE_INITIAL_SIZE]=1.0; particle_vars[VS::PARTICLE_FINAL_SIZE]=0.8; particle_vars[VS::PARTICLE_HEIGHT]=1; particle_vars[VS::PARTICLE_HEIGHT_SPEED_SCALE]=1; height_from_velocity=false; local_coordinates=false; particle_vars[VS::PARTICLE_INITIAL_ANGLE]=0.0;// gravity_normal=Vector3(0,-1.0,0); //emission_half_extents=Vector3(0.1,0.1,0.1); emission_half_extents=Vector3(1,1,1); color_phase_count=0; color_phases[0].pos=0.0; color_phases[0].color=Color(1.0,0.0,0.0); visibility_aabb=AABB(Vector3(-64,-64,-64),Vector3(128,128,128)); attractor_count=0; } ParticleSystemSW::~ParticleSystemSW() { } #define DEFAULT_SEED 1234567 _FORCE_INLINE_ static float _rand_from_seed(uint32_t *seed) { uint32_t k; uint32_t s = (*seed); if (s == 0) s = 0x12345987; k = s / 127773; s = 16807 * (s - k * 127773) - 2836 * k; if (s < 0) s += 2147483647; (*seed) = s; float v=((float)((*seed) & 0xFFFFF))/(float)0xFFFFF; v=v*2.0-1.0; return v; } _FORCE_INLINE_ static uint32_t _irand_from_seed(uint32_t *seed) { uint32_t k; uint32_t s = (*seed); if (s == 0) s = 0x12345987; k = s / 127773; s = 16807 * (s - k * 127773) - 2836 * k; if (s < 0) s += 2147483647; (*seed) = s; return s; } void ParticleSystemProcessSW::process(const ParticleSystemSW *p_system,const Transform& p_transform,float p_time) { valid=false; if (p_system->amount<=0) { ERR_EXPLAIN("Invalid amount of particles: "+itos(p_system->amount)); ERR_FAIL_COND(p_system->amount<=0); } if (p_system->attractor_count<0 || p_system->attractor_count>VS::MAX_PARTICLE_ATTRACTORS) { ERR_EXPLAIN("Invalid amount of particle attractors."); ERR_FAIL_COND(p_system->attractor_count<0 || p_system->attractor_count>VS::MAX_PARTICLE_ATTRACTORS); } float lifetime = p_system->particle_vars[VS::PARTICLE_LIFETIME]; if (lifetime<CMP_EPSILON) { ERR_EXPLAIN("Particle system lifetime too small."); ERR_FAIL_COND(lifetime<CMP_EPSILON); } valid=true; int particle_count=MIN(p_system->amount,ParticleSystemSW::MAX_PARTICLES);; int emission_point_count = p_system->emission_points.size(); DVector<Vector3>::Read r; if (emission_point_count) r=p_system->emission_points.read(); if (particle_count!=particle_data.size()) { //clear the whole system if particle amount changed particle_data.clear(); particle_data.resize(p_system->amount); particle_system_time=0; } float next_time = particle_system_time+p_time; if (next_time > lifetime) next_time=Math::fmod(next_time,lifetime); ParticleData *pdata=&particle_data[0]; Vector3 attractor_positions[VS::MAX_PARTICLE_ATTRACTORS]; for(int i=0;i<p_system->attractor_count;i++) { attractor_positions[i]=p_transform.xform(p_system->attractors[i].pos); } for(int i=0;i<particle_count;i++) { ParticleData &p=pdata[i]; float restart_time = (i * lifetime / p_system->amount); bool restart=false; if ( next_time < particle_system_time ) { if (restart_time > particle_system_time || restart_time < next_time ) restart=true; } else if (restart_time > particle_system_time && restart_time < next_time ) { restart=true; } if (restart) { if (p_system->emitting) { if (emission_point_count==0) { //use AABB if (p_system->local_coordinates) p.pos = p_system->emission_half_extents * Vector3( _rand_from_seed(&rand_seed), _rand_from_seed(&rand_seed), _rand_from_seed(&rand_seed) ); else p.pos = p_transform.xform( p_system->emission_half_extents * Vector3( _rand_from_seed(&rand_seed), _rand_from_seed(&rand_seed), _rand_from_seed(&rand_seed) ) ); } else { //use preset positions if (p_system->local_coordinates) p.pos = r[_irand_from_seed(&rand_seed)%emission_point_count]; else p.pos = p_transform.xform( r[_irand_from_seed(&rand_seed)%emission_point_count] ); } float angle1 = _rand_from_seed(&rand_seed)*p_system->particle_vars[VS::PARTICLE_SPREAD]*Math_PI; float angle2 = _rand_from_seed(&rand_seed)*20.0*Math_PI; // make it more random like Vector3 rot_xz=Vector3( Math::sin(angle1), 0.0, Math::cos(angle1) ); Vector3 rot = Vector3( Math::cos(angle2)*rot_xz.x,Math::sin(angle2)*rot_xz.x, rot_xz.z); p.vel=(rot*p_system->particle_vars[VS::PARTICLE_LINEAR_VELOCITY]+rot*p_system->particle_randomness[VS::PARTICLE_LINEAR_VELOCITY]*_rand_from_seed(&rand_seed)); if (!p_system->local_coordinates) p.vel=p_transform.basis.xform( p.vel ); p.vel+=p_system->emission_base_velocity; p.rot=p_system->particle_vars[VS::PARTICLE_INITIAL_ANGLE]+p_system->particle_randomness[VS::PARTICLE_INITIAL_ANGLE]*_rand_from_seed(&rand_seed); p.active=true; for(int r=0;r<PARTICLE_RANDOM_NUMBERS;r++) p.random[r]=_rand_from_seed(&rand_seed); } else { p.pos=Vector3(); p.rot=0; p.vel=Vector3(); p.active=false; } } else { if (!p.active) continue; Vector3 force; //apply gravity force=p_system->gravity_normal * (p_system->particle_vars[VS::PARTICLE_GRAVITY]+(p_system->particle_randomness[VS::PARTICLE_GRAVITY]*p.random[0])); //apply linear acceleration force+=p.vel.normalized() * (p_system->particle_vars[VS::PARTICLE_LINEAR_ACCELERATION]+p_system->particle_randomness[VS::PARTICLE_LINEAR_ACCELERATION]*p.random[1]); //apply radial acceleration Vector3 org; if (!p_system->local_coordinates) org=p_transform.origin; force+=(p.pos-org).normalized() * (p_system->particle_vars[VS::PARTICLE_RADIAL_ACCELERATION]+p_system->particle_randomness[VS::PARTICLE_RADIAL_ACCELERATION]*p.random[2]); //apply tangential acceleration force+=(p.pos-org).cross(p_system->gravity_normal).normalized() * (p_system->particle_vars[VS::PARTICLE_TANGENTIAL_ACCELERATION]+p_system->particle_randomness[VS::PARTICLE_TANGENTIAL_ACCELERATION]*p.random[3]); //apply attractor forces for(int a=0;a<p_system->attractor_count;a++) { force+=(p.pos-attractor_positions[a]).normalized() * p_system->attractors[a].force; } p.vel+=force * p_time; if (p_system->particle_vars[VS::PARTICLE_DAMPING]) { float v = p.vel.length(); float damp = p_system->particle_vars[VS::PARTICLE_DAMPING] + p_system->particle_vars[VS::PARTICLE_DAMPING] * p_system->particle_randomness[VS::PARTICLE_DAMPING]; v -= damp * p_time; if (v<0) { p.vel=Vector3(); } else { p.vel=p.vel.normalized() * v; } } p.rot+=(p_system->particle_vars[VS::PARTICLE_ANGULAR_VELOCITY]+p_system->particle_randomness[VS::PARTICLE_ANGULAR_VELOCITY]*p.random[4]) *p_time; p.pos+=p.vel * p_time; } } particle_system_time=Math::fmod( particle_system_time+p_time, lifetime ); } ParticleSystemProcessSW::ParticleSystemProcessSW() { particle_system_time=0; rand_seed=1234567; valid=false; } struct _ParticleSorterSW { _FORCE_INLINE_ bool operator()(const ParticleSystemDrawInfoSW::ParticleDrawInfo *p_a,const ParticleSystemDrawInfoSW::ParticleDrawInfo *p_b) const { return p_a->d > p_b->d; // draw from further away to closest } }; void ParticleSystemDrawInfoSW::prepare(const ParticleSystemSW *p_system,const ParticleSystemProcessSW *p_process,const Transform& p_system_transform,const Transform& p_camera_transform) { ERR_FAIL_COND(p_process->particle_data.size() != p_system->amount); ERR_FAIL_COND(p_system->amount<=0 || p_system->amount>=ParticleSystemSW::MAX_PARTICLES); const ParticleSystemProcessSW::ParticleData *pdata=&p_process->particle_data[0]; float time_pos=p_process->particle_system_time/p_system->particle_vars[VS::PARTICLE_LIFETIME]; ParticleSystemSW::ColorPhase cphase[VS::MAX_PARTICLE_COLOR_PHASES]; float last=-1; int col_count=0; for(int i=0;i<p_system->color_phase_count;i++) { if (p_system->color_phases[i].pos<=last) break; cphase[i]=p_system->color_phases[i]; col_count++; } Vector3 camera_z_axis = p_camera_transform.basis.get_axis(2); for(int i=0;i<p_system->amount;i++) { ParticleDrawInfo &pdi=draw_info[i]; pdi.data=&pdata[i]; pdi.transform.origin=pdi.data->pos; if (p_system->local_coordinates) pdi.transform.origin=p_system_transform.xform(pdi.transform.origin); pdi.d=-camera_z_axis.dot(pdi.transform.origin); // adjust particle size, color and rotation float time = ((float)i / p_system->amount); if (time<time_pos) time=time_pos-time; else time=(1.0-time)+time_pos; Vector3 up=p_camera_transform.basis.get_axis(1); // up determines the rotation float up_scale=1.0; if (p_system->height_from_velocity) { Vector3 veld = pdi.data->vel; Vector3 cam_z = camera_z_axis.normalized(); float vc = Math::abs(veld.normalized().dot(cam_z)); if (vc<(1.0-CMP_EPSILON)) { up = Plane(cam_z,0).project(veld).normalized(); float h = p_system->particle_vars[VS::PARTICLE_HEIGHT]+p_system->particle_randomness[VS::PARTICLE_HEIGHT]*pdi.data->random[7]; float velh = veld.length(); h+=velh*(p_system->particle_vars[VS::PARTICLE_HEIGHT_SPEED_SCALE]+p_system->particle_randomness[VS::PARTICLE_HEIGHT_SPEED_SCALE]*pdi.data->random[7]); up_scale=Math::lerp(1.0,h,(1.0-vc)); } } else if (pdi.data->rot) { up.rotate(camera_z_axis,pdi.data->rot); } { // matrix Vector3 v_z = (p_camera_transform.origin-pdi.transform.origin).normalized(); // Vector3 v_z = (p_camera_transform.origin-pdi.data->pos).normalized(); Vector3 v_y = up; Vector3 v_x = v_y.cross(v_z); v_y = v_z.cross(v_x); v_x.normalize(); v_y.normalize(); float initial_scale, final_scale; initial_scale = p_system->particle_vars[VS::PARTICLE_INITIAL_SIZE]+p_system->particle_randomness[VS::PARTICLE_INITIAL_SIZE]*pdi.data->random[5]; final_scale = p_system->particle_vars[VS::PARTICLE_FINAL_SIZE]+p_system->particle_randomness[VS::PARTICLE_FINAL_SIZE]*pdi.data->random[6]; float scale = initial_scale + time * (final_scale - initial_scale); pdi.transform.basis.set_axis(0,v_x * scale); pdi.transform.basis.set_axis(1,v_y * scale * up_scale); pdi.transform.basis.set_axis(2,v_z * scale); } int cpos=0; while(cpos<col_count) { if (cphase[cpos].pos > time) break; cpos++; } cpos--; if (cpos==-1) pdi.color=Color(1,1,1,1); else { if (cpos==col_count-1) pdi.color=cphase[cpos].color; else { float diff = (cphase[cpos+1].pos-cphase[cpos].pos); if (diff>0) pdi.color=cphase[cpos].color.linear_interpolate(cphase[cpos+1].color, (time - cphase[cpos].pos) / diff ); else pdi.color=cphase[cpos+1].color; } } draw_info_order[i]=&pdi; } SortArray<ParticleDrawInfo*,_ParticleSorterSW> particle_sort; particle_sort.sort(&draw_info_order[0],p_system->amount); }