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"]
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

View File

@ -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

View File

@ -23,13 +23,26 @@ pub(crate) mod syntax {
pub angle_rng: Option<f32>,
pub angvel: 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_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)]
@ -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 {

View File

@ -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<f32>,
parent_angle: f32,
parent_velocity: Vector2<f32>,
target_velocity: Vector2<f32>,
parent: RigidBodyHandle,
target: Option<RigidBodyHandle>,
) -> 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,17 +59,65 @@ impl PhysEffect {
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())
.rotation(angle)
.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 {
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)),
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)),
@ -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
pub fn step(&mut self, res: &PhysStepResources, wrapper: &mut PhysWrapper) {
if self.is_destroyed {

View File

@ -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,
));
}
}

View File

@ -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,
));
}
}

View File

@ -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,
));
}
}

View File

@ -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),
));
}
};

View File

@ -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<GenericJoint>,
) {
self.joint_set.insert(body1, body2, joint, false);
}
}