Added sticky particles

master
Mark 2024-01-23 21:21:15 -08:00
parent a2b88af375
commit 98b460aba9
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
9 changed files with 235 additions and 132 deletions

View File

@ -1,75 +1,71 @@
[effect."small explosion"] [effect."small explosion"]
sprite = "effect::explosion::small" sprite = "effect::explosion::small"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target"
size = 8.0 size = 8.0
size_rng = 1.6 size_rng = 1.6
angle_rng = 360 angle_rng = 360
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
[effect."large explosion"] [effect."large explosion"]
sprite = "effect::explosion::large" sprite = "effect::explosion::large"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target"
size = 25.0 size = 25.0
size_rng = 5.0 size_rng = 5.0
angle_rng = 360 angle_rng = 360
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
[effect."huge explosion"] [effect."huge explosion"]
sprite = "effect::explosion::huge" sprite = "effect::explosion::huge"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "target"
size = 50.0 size = 50.0
size_rng = 10.0 size_rng = 10.0
angle_rng = 360 angle_rng = 360
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
[effect."blue spark"] [effect."blue spark"]
sprite = "effect::spark::blue" sprite = "effect::spark::blue"
lifetime = 0.5 lifetime = 0.5
lifetime_rng = 0.5 lifetime_rng = 0.5
inherit_velocity = "parent"
size = 4.0 size = 4.0
size_rng = 2.0 size_rng = 2.0
angle_rng = 360 angle_rng = 360
angvel_rng = 0.0 angvel_rng = 0.0
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
[effect."yellow spark"] [effect."yellow spark"]
sprite = "effect::spark::yellow" sprite = "effect::spark::yellow"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "parent"
size = 4.0 size = 4.0
size_rng = 2.0 size_rng = 2.0
angle_rng = 360 angle_rng = 360
angvel_rng = 0.0 angvel_rng = 0.0
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
[effect."red spark"] [effect."red spark"]
sprite = "effect::spark::red" sprite = "effect::spark::red"
lifetime = "inherit" lifetime = "inherit"
inherit_velocity = "parent"
size = 4.0 size = 4.0
size_rng = 1.0 size_rng = 1.0
angle_rng = 360 angle_rng = 360
angvel_rng = 0.0 angvel_rng = 0.0
velocity_scale_parent = 1.0
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1
velocity.sticky = "parent"
# Every effect has a parent, some effects have a target # Every effect has a parent, some effects have a target
@ -90,12 +86,11 @@ angvel = 0.0 # Angular velocity at creation
# Total velocity is sum of parent + target velocities with scale applied # Total velocity is sum of parent + target velocities with scale applied
velocity_scale_parent = 0.0 # Multiply velocity by this value velocity.scale_parent = 0.0 # Multiply velocity by this value
velocity_scale_parent_rng = 0.0 # random variation of scale velocity.scale_parent_rng = 0.0 # random variation of scale
velocity_scale_target = 1.0 velocity.scale_target = 1.0
velocity_scale_target_rng = 1.0 velocity.scale_target_rng = 1.0
velocity.direction_rng = 0.0 # Random variation of travel direction, in degrees, applied to velocity vector (/2 each side?)
direction_rng = 0.0 # Random variation of travel direction, in degrees, applied to velocity vector (/2 each side?)
fade = 0.2 fade = 0.2
fade_rng = 0.1 fade_rng = 0.1

View File

@ -54,4 +54,4 @@ gun.projectile.impact_effect = "blaster impact"
gun.projectile.expire_effect.sprite = "effect::blaster" gun.projectile.expire_effect.sprite = "effect::blaster"
gun.projectile.expire_effect.lifetime = "inherit" gun.projectile.expire_effect.lifetime = "inherit"
gun.projectile.expire_effect.size = 3.0 gun.projectile.expire_effect.size = 3.0
gun.projectile.expire_effect.velocity_scale_parent = 1.0 gun.projectile.expire_effect.velocity.scale_parent = 1.0

View File

@ -23,13 +23,26 @@ pub(crate) mod syntax {
pub angle_rng: Option<f32>, pub angle_rng: Option<f32>,
pub angvel: Option<f32>, pub angvel: Option<f32>,
pub angvel_rng: Option<f32>, pub angvel_rng: Option<f32>,
pub velocity_scale_parent: Option<f32>,
pub velocity_scale_parent_rng: Option<f32>,
pub velocity_scale_target: Option<f32>,
pub velocity_scale_target_rng: Option<f32>,
pub direction_rng: Option<f32>,
pub fade: Option<f32>, pub fade: Option<f32>,
pub fade_rng: Option<f32>, pub fade_rng: Option<f32>,
pub velocity: EffectVelocity,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EffectVelocity {
Sticky {
sticky: String,
},
Explicit {
scale_parent: Option<f32>,
scale_parent_rng: Option<f32>,
scale_target: Option<f32>,
scale_target_rng: Option<f32>,
direction_rng: Option<f32>,
},
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -69,12 +82,38 @@ pub(crate) mod syntax {
} }
}; };
let velocity = match self.velocity {
EffectVelocity::Explicit {
scale_parent,
scale_parent_rng,
scale_target,
scale_target_rng,
direction_rng,
} => super::EffectVelocity::Explicit {
scale_parent: scale_parent.unwrap_or(0.0),
scale_parent_rng: scale_parent_rng.unwrap_or(0.0),
scale_target: scale_target.unwrap_or(0.0),
scale_target_rng: scale_target_rng.unwrap_or(0.0),
direction_rng: direction_rng.unwrap_or(0.0) / 2.0,
},
EffectVelocity::Sticky { sticky } => {
if sticky == "parent" {
super::EffectVelocity::StickyParent
} else if sticky == "target" {
super::EffectVelocity::StickyTarget
} else {
bail!("bad sticky specification `{}`", sticky);
}
}
};
let handle = EffectHandle { let handle = EffectHandle {
index: content.effects.len(), index: content.effects.len(),
}; };
content.effects.push(super::Effect { content.effects.push(super::Effect {
handle, handle,
sprite, sprite,
velocity,
size: self.size, size: self.size,
size_rng: self.size_rng.unwrap_or(0.0), size_rng: self.size_rng.unwrap_or(0.0),
lifetime, lifetime,
@ -83,11 +122,6 @@ pub(crate) mod syntax {
angle_rng: to_radians(self.angle_rng.unwrap_or(0.0) / 2.0), angle_rng: to_radians(self.angle_rng.unwrap_or(0.0) / 2.0),
angvel: to_radians(self.angvel.unwrap_or(0.0)), angvel: to_radians(self.angvel.unwrap_or(0.0)),
angvel_rng: to_radians(self.angvel_rng.unwrap_or(0.0)), angvel_rng: to_radians(self.angvel_rng.unwrap_or(0.0)),
velocity_scale_parent: self.velocity_scale_parent.unwrap_or(0.0),
velocity_scale_parent_rng: self.velocity_scale_parent_rng.unwrap_or(0.0),
velocity_scale_target: self.velocity_scale_target.unwrap_or(0.0),
velocity_scale_target_rng: self.velocity_scale_target_rng.unwrap_or(0.0),
direction_rng: self.direction_rng.unwrap_or(0.0) / 2.0,
fade: self.fade.unwrap_or(0.0), fade: self.fade.unwrap_or(0.0),
fade_rng: self.fade_rng.unwrap_or(0.0), fade_rng: self.fade_rng.unwrap_or(0.0),
}); });
@ -157,27 +191,43 @@ pub struct Effect {
/// Random angvel variation /// Random angvel variation
pub angvel_rng: f32, pub angvel_rng: f32,
/// The amount of this effect's parent's velocity to inherit
pub velocity_scale_parent: f32,
/// Parent velocity random variation
pub velocity_scale_parent_rng: f32,
/// The amount of this effect's parent's target velocity to inherit.
/// If there is no target, this is zero.
pub velocity_scale_target: f32,
/// Target velocity random variation
pub velocity_scale_target_rng: f32,
/// Travel direction random variation
pub direction_rng: f32,
/// Fade this effect out over this many seconds as it ends /// Fade this effect out over this many seconds as it ends
pub fade: f32, pub fade: f32,
/// Random fade ariation /// Random fade variation
pub fade_rng: f32, pub fade_rng: f32,
/// How to compute this effect's velocity
pub velocity: EffectVelocity,
}
/// How to compute an effect's velocity
#[derive(Debug, Clone)]
pub enum EffectVelocity {
/// Stick to parent
StickyParent,
/// Stick to target.
/// Zero velocity if no target is given.
StickyTarget,
/// Compute velocity from parent and target
Explicit {
/// How much of the parent's velocity to inherit
scale_parent: f32,
/// Random variation of scale_parent
scale_parent_rng: f32,
/// How much of the target's velocity to keep
scale_target: f32,
/// Random variation of scale_target
scale_target_rng: f32,
/// Random variation of travel direction
direction_rng: f32,
},
} }
impl crate::Build for Effect { impl crate::Build for Effect {

View File

@ -1,7 +1,7 @@
use galactica_content::{Content, EffectHandle, SpriteAutomaton}; use galactica_content::{Content, EffectHandle, EffectVelocity, SpriteAutomaton};
use nalgebra::{Rotation2, Vector2}; use nalgebra::{Point2, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
use rapier2d::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType}; use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
use crate::phys::{PhysStepResources, PhysWrapper}; use crate::phys::{PhysStepResources, PhysWrapper};
@ -35,25 +35,18 @@ impl PhysEffect {
ct: &Content, ct: &Content,
wrapper: &mut PhysWrapper, wrapper: &mut PhysWrapper,
effect: EffectHandle, effect: EffectHandle,
// Where to spawn the particle, in world space.
pos: Vector2<f32>, pos: Vector2<f32>,
parent_angle: f32, parent: RigidBodyHandle,
parent_velocity: Vector2<f32>, target: Option<RigidBodyHandle>,
target_velocity: Vector2<f32>,
) -> Self { ) -> Self {
let effect = ct.get_effect(effect); let effect = ct.get_effect(effect);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let parent_body = wrapper.get_rigid_body(parent).unwrap();
let velocity = { let parent_angle = parent_body.rotation().angle();
let a = let parent_pos = *parent_body.translation();
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng); let parent_velocity = parent_body.velocity_at_point(parent_body.center_of_mass());
let b =
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
+ ((effect.velocity_scale_target + b) * target_velocity);
Rotation2::new(rng.gen_range(-effect.direction_rng..=effect.direction_rng)) * velocity
};
let angvel = if effect.angvel_rng == 0.0 { let angvel = if effect.angvel_rng == 0.0 {
effect.angvel effect.angvel
@ -66,17 +59,65 @@ impl PhysEffect {
parent_angle + effect.angle + rng.gen_range(-effect.angle_rng..=effect.angle_rng) parent_angle + effect.angle + rng.gen_range(-effect.angle_rng..=effect.angle_rng)
}; };
let rb = RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased) let target_velocity = {
if let Some(target) = target {
let target_body = wrapper.get_rigid_body(target).unwrap();
target_body.velocity_at_point(&Point2::new(pos.x, pos.y))
} else {
Vector2::new(0.0, 0.0)
}
};
match effect.velocity {
EffectVelocity::StickyTarget | EffectVelocity::StickyParent => {
let rigid_body = wrapper.insert_rigid_body(
RigidBodyBuilder::new(RigidBodyType::Dynamic)
.additional_mass(f32::MIN_POSITIVE)
.position(pos.into()) .position(pos.into())
.rotation(angle) .rotation(angle)
.angvel(angvel) .angvel(angvel)
.linvel(velocity); .build(),
);
match effect.velocity {
EffectVelocity::StickyParent => {
let d = Rotation2::new(-parent_angle) * (pos - parent_pos);
wrapper.add_joint(
rigid_body,
parent,
RevoluteJointBuilder::new()
.local_anchor1(Point2::new(0.0, 0.0))
.local_anchor2(Point2::new(d.x, d.y)),
)
}
EffectVelocity::StickyTarget => {
if target.is_some() {
let target_body = wrapper.get_rigid_body(target.unwrap()).unwrap();
// Correct for rotation, since joint coordinates are relative
// and input coordinates are in world space.
let d = Rotation2::new(-target_body.rotation().angle())
* (pos - target_body.translation());
wrapper.add_joint(
rigid_body,
target.unwrap(),
RevoluteJointBuilder::new()
.local_anchor1(Point2::new(0.0, 0.0))
.local_anchor2(Point2::new(d.x, d.y)),
)
}
}
_ => unreachable!("lol what?"),
};
PhysEffect { PhysEffect {
anim: SpriteAutomaton::new(ct, effect.sprite), anim: SpriteAutomaton::new(ct, effect.sprite),
rigid_body: wrapper.insert_rigid_body(rb.build()), rigid_body,
lifetime: 0f32 lifetime: 0f32.max(
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)), effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
),
// Make sure size isn't negative. This check should be on EVERY rng! // Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)), size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
@ -85,6 +126,48 @@ impl PhysEffect {
} }
} }
EffectVelocity::Explicit {
scale_parent,
scale_parent_rng,
scale_target,
scale_target_rng,
direction_rng,
} => {
let velocity = {
let a = rng.gen_range(-scale_parent_rng..=scale_parent_rng);
let b = rng.gen_range(-scale_target_rng..=scale_target_rng);
let velocity = ((scale_parent + a) * parent_velocity)
+ ((scale_target + b) * target_velocity);
Rotation2::new(rng.gen_range(-direction_rng..=direction_rng)) * velocity
};
let rigid_body = wrapper.insert_rigid_body(
RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased)
.position(pos.into())
.rotation(angle)
.angvel(angvel)
.linvel(velocity)
.build(),
);
PhysEffect {
anim: SpriteAutomaton::new(ct, effect.sprite),
rigid_body,
lifetime: 0f32.max(
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
),
// Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
is_destroyed: false,
}
}
}
}
/// Step this effect's state by `t` seconds /// Step this effect's state by `t` seconds
pub fn step(&mut self, res: &PhysStepResources, wrapper: &mut PhysWrapper) { pub fn step(&mut self, res: &PhysStepResources, wrapper: &mut PhysWrapper) {
if self.is_destroyed { if self.is_destroyed {

View File

@ -1,5 +1,4 @@
use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton}; use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton};
use nalgebra::Vector2;
use rand::Rng; use rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
@ -97,35 +96,18 @@ impl PhysProjectile {
return; return;
} }
let rigid_body = wrapper.remove_rigid_body(self.rigid_body).unwrap(); let rb = wrapper.get_rigid_body(self.rigid_body).unwrap();
let mut rng = rand::thread_rng();
if expire { if expire {
match &self.content.expire_effect { match &self.content.expire_effect {
None => {} None => {}
Some(handle) => { Some(handle) => {
let x = res.ct.get_effect(*handle);
let pos = *rigid_body.translation();
let vel = rigid_body.velocity_at_point(rigid_body.center_of_mass());
let angle = rigid_body.rotation().angle();
let velocity = {
let a = rng
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng);
let velocity = (x.velocity_scale_parent + a) * vel;
velocity
};
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
res.ct, res.ct,
wrapper, wrapper,
*handle, *handle,
pos, *rb.translation(),
angle, self.rigid_body,
velocity, None,
Vector2::new(0.0, 0.0),
)); ));
} }
} }

View File

@ -88,17 +88,13 @@ impl ShipCollapseSequence {
}; };
let pos = ship_pos + (ship_rot * pos); let pos = ship_pos + (ship_rot * pos);
let velocity =
rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
res.ct, res.ct,
wrapper, wrapper,
spawner.effect, spawner.effect,
pos, pos,
0.0, rigid_body_handle,
velocity, None,
Vector2::new(0.0, 0.0),
)); ));
} }
} }
@ -133,16 +129,13 @@ impl ShipCollapseSequence {
// Position, adjusted for ship rotation // Position, adjusted for ship rotation
let pos = ship_pos + (ship_rot * pos); let pos = ship_pos + (ship_rot * pos);
let vel = rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
res.ct, res.ct,
wrapper, wrapper,
spawner.effect, spawner.effect,
pos, pos,
0.0, rigid_body_handle,
vel, None,
Vector2::new(0.0, 0.0),
)); ));
} }
} }

View File

@ -453,16 +453,14 @@ impl PhysShip {
}; };
let pos = ship_pos + (Rotation2::new(ship_ang) * pos); let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
let velocity = rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
res.ct, res.ct,
wrapper, wrapper,
e.effect, e.effect,
pos.into(), pos,
0.0, self.rigid_body,
velocity, None,
Vector2::new(0.0, 0.0),
)); ));
} }
} }

View File

@ -126,25 +126,17 @@ impl PhysSim {
// Borrow again, we can only have one at a time // Borrow again, we can only have one at a time
let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap(); let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap();
let pos = *pr.translation(); let pos = *pr.translation();
let angle = pr.rotation().angle();
match &projectile.content.impact_effect { match &projectile.content.impact_effect {
None => {} None => {}
Some(x) => { Some(x) => {
let r = ship.rigid_body;
let sr = self.wrapper.get_rigid_body(r).unwrap();
let parent_velocity = pr.velocity_at_point(pr.center_of_mass());
let target_velocity =
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
self.effects.push(PhysEffect::new( self.effects.push(PhysEffect::new(
res.ct, res.ct,
&mut self.wrapper, &mut self.wrapper,
*x, *x,
pos, pos,
angle, projectile.rigid_body,
parent_velocity, Some(ship.rigid_body),
target_velocity,
)); ));
} }
}; };

View File

@ -1,8 +1,8 @@
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use rapier2d::{ use rapier2d::{
dynamics::{ dynamics::{
CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, CCDSolver, GenericJoint, ImpulseJointSet, IntegrationParameters, IslandManager,
RigidBody, RigidBodyHandle, RigidBodySet, MultibodyJointSet, RigidBody, RigidBodyHandle, RigidBodySet,
}, },
geometry::{BroadPhase, Collider, ColliderHandle, ColliderSet, CollisionEvent, NarrowPhase}, geometry::{BroadPhase, Collider, ColliderHandle, ColliderSet, CollisionEvent, NarrowPhase},
na::vector, na::vector,
@ -16,12 +16,12 @@ pub struct PhysWrapper {
im: IslandManager, im: IslandManager,
bp: BroadPhase, bp: BroadPhase,
np: NarrowPhase, np: NarrowPhase,
ij: ImpulseJointSet,
mj: MultibodyJointSet, mj: MultibodyJointSet,
ccd: CCDSolver, ccd: CCDSolver,
rigid_body_set: RigidBodySet, rigid_body_set: RigidBodySet,
collider_set: ColliderSet, collider_set: ColliderSet,
joint_set: ImpulseJointSet,
collision_handler: ChannelEventCollector, collision_handler: ChannelEventCollector,
@ -42,7 +42,6 @@ impl PhysWrapper {
im: IslandManager::new(), im: IslandManager::new(),
bp: BroadPhase::new(), bp: BroadPhase::new(),
np: NarrowPhase::new(), np: NarrowPhase::new(),
ij: ImpulseJointSet::new(),
mj: MultibodyJointSet::new(), mj: MultibodyJointSet::new(),
ccd: CCDSolver::new(), ccd: CCDSolver::new(),
collision_queue, collision_queue,
@ -50,6 +49,7 @@ impl PhysWrapper {
rigid_body_set: RigidBodySet::new(), rigid_body_set: RigidBodySet::new(),
collider_set: ColliderSet::new(), collider_set: ColliderSet::new(),
joint_set: ImpulseJointSet::new(),
} }
} }
@ -64,7 +64,7 @@ impl PhysWrapper {
&mut self.np, &mut self.np,
&mut self.rigid_body_set, &mut self.rigid_body_set,
&mut self.collider_set, &mut self.collider_set,
&mut self.ij, &mut self.joint_set,
&mut self.mj, &mut self.mj,
&mut self.ccd, &mut self.ccd,
None, None,
@ -79,7 +79,7 @@ impl PhysWrapper {
body, body,
&mut self.im, &mut self.im,
&mut self.collider_set, &mut self.collider_set,
&mut self.ij, &mut self.joint_set,
&mut self.mj, &mut self.mj,
true, true,
); );
@ -121,4 +121,14 @@ impl PhysWrapper {
self.collider_set self.collider_set
.insert_with_parent(collider, parent_handle, &mut self.rigid_body_set) .insert_with_parent(collider, parent_handle, &mut self.rigid_body_set)
} }
/// Add an impulse joint between two bodies
pub fn add_joint(
&mut self,
body1: RigidBodyHandle,
body2: RigidBodyHandle,
joint: impl Into<GenericJoint>,
) {
self.joint_set.insert(body1, body2, joint, false);
}
} }