diff --git a/content/effects.toml b/content/effects.toml index d0004d3..aa0a1cf 100644 --- a/content/effects.toml +++ b/content/effects.toml @@ -1,75 +1,71 @@ [effect."small explosion"] sprite = "effect::explosion::small" lifetime = "inherit" -inherit_velocity = "target" size = 8.0 size_rng = 1.6 angle_rng = 360 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" [effect."large explosion"] sprite = "effect::explosion::large" lifetime = "inherit" -inherit_velocity = "target" size = 25.0 size_rng = 5.0 angle_rng = 360 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" [effect."huge explosion"] sprite = "effect::explosion::huge" lifetime = "inherit" -inherit_velocity = "target" size = 50.0 size_rng = 10.0 angle_rng = 360 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" [effect."blue spark"] sprite = "effect::spark::blue" lifetime = 0.5 lifetime_rng = 0.5 -inherit_velocity = "parent" size = 4.0 size_rng = 2.0 angle_rng = 360 angvel_rng = 0.0 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" + [effect."yellow spark"] sprite = "effect::spark::yellow" lifetime = "inherit" -inherit_velocity = "parent" size = 4.0 size_rng = 2.0 angle_rng = 360 angvel_rng = 0.0 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" + [effect."red spark"] sprite = "effect::spark::red" lifetime = "inherit" -inherit_velocity = "parent" size = 4.0 size_rng = 1.0 angle_rng = 360 angvel_rng = 0.0 -velocity_scale_parent = 1.0 fade = 0.2 fade_rng = 0.1 +velocity.sticky = "parent" # 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 -velocity_scale_parent = 0.0 # Multiply velocity by this value -velocity_scale_parent_rng = 0.0 # random variation of scale -velocity_scale_target = 1.0 -velocity_scale_target_rng = 1.0 - -direction_rng = 0.0 # Random variation of travel direction, in degrees, applied to velocity vector (/2 each side?) +velocity.scale_parent = 0.0 # Multiply velocity by this value +velocity.scale_parent_rng = 0.0 # random variation of scale +velocity.scale_target = 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?) fade = 0.2 fade_rng = 0.1 diff --git a/content/outfits.toml b/content/outfits.toml index 90e08ee..c466b5a 100644 --- a/content/outfits.toml +++ b/content/outfits.toml @@ -54,4 +54,4 @@ gun.projectile.impact_effect = "blaster impact" gun.projectile.expire_effect.sprite = "effect::blaster" gun.projectile.expire_effect.lifetime = "inherit" 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 diff --git a/crates/content/src/part/effect.rs b/crates/content/src/part/effect.rs index 81dccfb..dffd7d0 100644 --- a/crates/content/src/part/effect.rs +++ b/crates/content/src/part/effect.rs @@ -23,13 +23,26 @@ pub(crate) mod syntax { pub angle_rng: Option, pub angvel: Option, pub angvel_rng: Option, - pub velocity_scale_parent: Option, - pub velocity_scale_parent_rng: Option, - pub velocity_scale_target: Option, - pub velocity_scale_target_rng: Option, - pub direction_rng: Option, + pub fade: Option, pub fade_rng: Option, + + pub velocity: EffectVelocity, + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + pub enum EffectVelocity { + Sticky { + sticky: String, + }, + Explicit { + scale_parent: Option, + scale_parent_rng: Option, + scale_target: Option, + scale_target_rng: Option, + direction_rng: Option, + }, } #[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 { index: content.effects.len(), }; content.effects.push(super::Effect { handle, sprite, + velocity, size: self.size, size_rng: self.size_rng.unwrap_or(0.0), lifetime, @@ -83,11 +122,6 @@ pub(crate) mod syntax { angle_rng: to_radians(self.angle_rng.unwrap_or(0.0) / 2.0), angvel: to_radians(self.angvel.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_rng: self.fade_rng.unwrap_or(0.0), }); @@ -157,27 +191,43 @@ pub struct Effect { /// Random angvel variation 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 pub fade: f32, - /// Random fade ariation + /// Random fade variation 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 { diff --git a/crates/system/src/phys/objects/effect.rs b/crates/system/src/phys/objects/effect.rs index a905074..cc157b4 100644 --- a/crates/system/src/phys/objects/effect.rs +++ b/crates/system/src/phys/objects/effect.rs @@ -1,7 +1,7 @@ -use galactica_content::{Content, EffectHandle, SpriteAutomaton}; -use nalgebra::{Rotation2, Vector2}; +use galactica_content::{Content, EffectHandle, EffectVelocity, SpriteAutomaton}; +use nalgebra::{Point2, Rotation2, Vector2}; use rand::Rng; -use rapier2d::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType}; +use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType}; use crate::phys::{PhysStepResources, PhysWrapper}; @@ -35,25 +35,18 @@ impl PhysEffect { ct: &Content, wrapper: &mut PhysWrapper, effect: EffectHandle, + // Where to spawn the particle, in world space. pos: Vector2, - parent_angle: f32, - parent_velocity: Vector2, - target_velocity: Vector2, + parent: RigidBodyHandle, + target: Option, ) -> Self { let effect = ct.get_effect(effect); + let mut rng = rand::thread_rng(); - - let velocity = { - let a = - rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng); - 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 parent_body = wrapper.get_rigid_body(parent).unwrap(); + let parent_angle = parent_body.rotation().angle(); + let parent_pos = *parent_body.translation(); + let parent_velocity = parent_body.velocity_at_point(parent_body.center_of_mass()); let angvel = if effect.angvel_rng == 0.0 { effect.angvel @@ -66,22 +59,112 @@ impl PhysEffect { parent_angle + effect.angle + rng.gen_range(-effect.angle_rng..=effect.angle_rng) }; - let rb = RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased) - .position(pos.into()) - .rotation(angle) - .angvel(angvel) - .linvel(velocity); + 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) + } + }; - PhysEffect { - anim: SpriteAutomaton::new(ct, effect.sprite), - rigid_body: wrapper.insert_rigid_body(rb.build()), - lifetime: 0f32 - .max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)), + 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()) + .rotation(angle) + .angvel(angvel) + .build(), + ); - // 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, + 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 { + 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, + } + } + + 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, + } + } } } diff --git a/crates/system/src/phys/objects/projectile.rs b/crates/system/src/phys/objects/projectile.rs index cd0ddc9..0e257ae 100644 --- a/crates/system/src/phys/objects/projectile.rs +++ b/crates/system/src/phys/objects/projectile.rs @@ -1,5 +1,4 @@ use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton}; -use nalgebra::Vector2; use rand::Rng; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; @@ -97,35 +96,18 @@ impl PhysProjectile { return; } - let rigid_body = wrapper.remove_rigid_body(self.rigid_body).unwrap(); - let mut rng = rand::thread_rng(); - + let rb = wrapper.get_rigid_body(self.rigid_body).unwrap(); if expire { match &self.content.expire_effect { None => {} 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( res.ct, wrapper, *handle, - pos, - angle, - velocity, - Vector2::new(0.0, 0.0), + *rb.translation(), + self.rigid_body, + None, )); } } diff --git a/crates/system/src/phys/objects/ship/collapse.rs b/crates/system/src/phys/objects/ship/collapse.rs index 5350d0d..e909026 100644 --- a/crates/system/src/phys/objects/ship/collapse.rs +++ b/crates/system/src/phys/objects/ship/collapse.rs @@ -88,17 +88,13 @@ impl ShipCollapseSequence { }; 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( res.ct, wrapper, spawner.effect, pos, - 0.0, - velocity, - Vector2::new(0.0, 0.0), + rigid_body_handle, + None, )); } } @@ -133,16 +129,13 @@ impl ShipCollapseSequence { // Position, adjusted for ship rotation 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( res.ct, wrapper, spawner.effect, pos, - 0.0, - vel, - Vector2::new(0.0, 0.0), + rigid_body_handle, + None, )); } } diff --git a/crates/system/src/phys/objects/ship/ship.rs b/crates/system/src/phys/objects/ship/ship.rs index 75c1c4e..8f322ba 100644 --- a/crates/system/src/phys/objects/ship/ship.rs +++ b/crates/system/src/phys/objects/ship/ship.rs @@ -453,16 +453,14 @@ impl PhysShip { }; 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( res.ct, wrapper, e.effect, - pos.into(), - 0.0, - velocity, - Vector2::new(0.0, 0.0), + pos, + self.rigid_body, + None, )); } } diff --git a/crates/system/src/phys/physsim.rs b/crates/system/src/phys/physsim.rs index 82b6e7a..fe68751 100644 --- a/crates/system/src/phys/physsim.rs +++ b/crates/system/src/phys/physsim.rs @@ -126,25 +126,17 @@ impl PhysSim { // Borrow again, we can only have one at a time let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap(); let pos = *pr.translation(); - let angle = pr.rotation().angle(); match &projectile.content.impact_effect { None => {} 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( res.ct, &mut self.wrapper, *x, pos, - angle, - parent_velocity, - target_velocity, + projectile.rigid_body, + Some(ship.rigid_body), )); } }; diff --git a/crates/system/src/phys/physwrapper.rs b/crates/system/src/phys/physwrapper.rs index 9d39ff4..72c6563 100644 --- a/crates/system/src/phys/physwrapper.rs +++ b/crates/system/src/phys/physwrapper.rs @@ -1,8 +1,8 @@ use crossbeam::channel::Receiver; use rapier2d::{ dynamics::{ - CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, - RigidBody, RigidBodyHandle, RigidBodySet, + CCDSolver, GenericJoint, ImpulseJointSet, IntegrationParameters, IslandManager, + MultibodyJointSet, RigidBody, RigidBodyHandle, RigidBodySet, }, geometry::{BroadPhase, Collider, ColliderHandle, ColliderSet, CollisionEvent, NarrowPhase}, na::vector, @@ -16,12 +16,12 @@ pub struct PhysWrapper { im: IslandManager, bp: BroadPhase, np: NarrowPhase, - ij: ImpulseJointSet, mj: MultibodyJointSet, ccd: CCDSolver, rigid_body_set: RigidBodySet, collider_set: ColliderSet, + joint_set: ImpulseJointSet, collision_handler: ChannelEventCollector, @@ -42,7 +42,6 @@ impl PhysWrapper { im: IslandManager::new(), bp: BroadPhase::new(), np: NarrowPhase::new(), - ij: ImpulseJointSet::new(), mj: MultibodyJointSet::new(), ccd: CCDSolver::new(), collision_queue, @@ -50,6 +49,7 @@ impl PhysWrapper { rigid_body_set: RigidBodySet::new(), collider_set: ColliderSet::new(), + joint_set: ImpulseJointSet::new(), } } @@ -64,7 +64,7 @@ impl PhysWrapper { &mut self.np, &mut self.rigid_body_set, &mut self.collider_set, - &mut self.ij, + &mut self.joint_set, &mut self.mj, &mut self.ccd, None, @@ -79,7 +79,7 @@ impl PhysWrapper { body, &mut self.im, &mut self.collider_set, - &mut self.ij, + &mut self.joint_set, &mut self.mj, true, ); @@ -121,4 +121,14 @@ impl PhysWrapper { self.collider_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, + ) { + self.joint_set.insert(body1, body2, joint, false); + } }