diff --git a/rust/src/boid/boid_3d.rs b/rust/src/boid/boid_3d.rs index e69de29..010271d 100644 --- a/rust/src/boid/boid_3d.rs +++ b/rust/src/boid/boid_3d.rs @@ -0,0 +1,101 @@ +use super::*; +use godot::prelude::*; + +use crate::{to_glam_vec, BoidProperties, Flock3D}; + +#[derive(GodotClass)] +#[class(init, base=Node3D)] +pub struct Boid3D { + #[export] + /// The properties of this boid. + /// Note: this cannot be changed in runtime, aside from removing and readding the node. + properties: Gd, + props: BoidProperties, + vel: Vec3, + flock_id: i64, + base: Base, +} + +#[godot_api] +impl Boid3D { + #[func] + #[inline(always)] + /// Get the current velocity of this boid. + fn get_velocity(&self) -> Vector3 { + Vector3::new(self.vel.x, self.vel.y, self.vel.z) + } + + #[func] + #[inline(always)] + /// Get the ID of this boid. + pub fn get_id(&self) -> i64 { + self.base().instance_id().to_i64() + } + + #[func] + #[inline(always)] + /// Get the flock ID of this boid. + pub fn get_flock_id(&self) -> i64 { + self.flock_id + } +} + +#[godot_api] +impl INode3D for Boid3D { + fn enter_tree(&mut self) { + let Some(mut flock) = self + .to_gd() + .get_parent() + .and_then(|gd| gd.try_cast::().ok()) + else { + let boid_id = self.get_id(); + godot_error!("[Boid3D:{boid_id}] boids parent isn't a Flock3D, or has no parent"); + return; + }; + let mut flock = flock.bind_mut(); + flock.register_boid(self.get_id()); + self.flock_id = flock.get_id(); + } + + fn ready(&mut self) { + self.props = self.properties.bind().clone(); + } + + fn exit_tree(&mut self) { + let mut flock = godot::global::instance_from_id(self.get_flock_id()) + .unwrap() + .cast::(); + flock.bind_mut().unregister_boid(self.get_id()); + } +} + +impl Boid for Boid3D { + #[inline(always)] + fn apply_force(&mut self, force: Vec3) { + self.vel += force; + let new_vel = self.vel.clamp_length_max(self.props.max_speed); + self.vel = new_vel; + self.base_mut() + .translate(Vector3::new(new_vel.x, new_vel.y, new_vel.z)); + } + + #[inline(always)] + fn get_boid_position(&self) -> Vec3 { + to_glam_vec(self.base().get_position()) + } + + #[inline(always)] + fn get_boid_velocity(&self) -> Vec3 { + self.vel + } + + #[inline(always)] + fn get_boid_properties(&self) -> &BoidProperties { + &self.props + } + + #[inline(always)] + fn get_flock_id(&self) -> i64 { + self.get_flock_id() + } +} diff --git a/rust/src/flock/flock_3d.rs b/rust/src/flock/flock_3d.rs new file mode 100644 index 0000000..be5b737 --- /dev/null +++ b/rust/src/flock/flock_3d.rs @@ -0,0 +1,104 @@ +use glam::*; +use godot::prelude::*; + +use crate::{ + get_singleton, to_glam_vec, Boid, Boid3D, BoidProperties, FlockProperties, FxIndexMap, +}; + +use super::Flock; + +#[derive(GodotClass)] +#[class(init, base=Node3D)] +pub struct Flock3D { + #[export] + /// Properties of this flock. + /// Note: this cannot be changed in runtime, aside from removing and readding the node. + properties: Gd, + props: FlockProperties, + #[export] + /// A target node for the flock to follow. + target: Option>, + pub boids: FxIndexMap>, + base: Base, +} + +impl Flock3D { + pub fn register_boid(&mut self, boid_id: i64) { + let boid: Gd = godot::global::instance_from_id(boid_id).unwrap().cast(); + self.boids.insert(boid_id, boid.clone()); + get_singleton().bind_mut().register_boid_3d(boid_id, boid); + let flock_id = self.get_id(); + godot_print!("[Flock3D:{flock_id}] boid {boid_id} registered"); + } + + pub fn unregister_boid(&mut self, boid_id: i64) { + self.boids.shift_remove(&boid_id); + get_singleton().bind_mut().unregister_boid_3d(boid_id); + let flock_id = self.get_id(); + godot_print!("[Flock3D:{flock_id}] boid {boid_id} unregistered"); + } +} + +#[godot_api] +impl INode3D for Flock3D { + fn enter_tree(&mut self) { + get_singleton().bind_mut().register_flock_3d(self.get_id()) + } + + fn ready(&mut self) { + self.props = self.properties.bind().clone(); + } + + fn exit_tree(&mut self) { + get_singleton() + .bind_mut() + .unregister_flock_3d(self.get_id()) + } +} + +#[godot_api] +impl Flock3D { + #[func] + #[inline(always)] + pub fn get_id(&self) -> i64 { + self.base().instance_id().to_i64() + } +} + +impl Flock for Flock3D { + #[inline(always)] + fn get_flock_properties(&self) -> &FlockProperties { + &self.props + } + + #[inline(always)] + fn get_target_position(&self) -> Option { + self.target.as_ref().map(|t| to_glam_vec(t.get_position())) + } + + #[inline(always)] + fn get_boids_posvel(&self) -> Vec<(Vec3, Vec3)> { + let boid_count = self.boids.len(); + let mut result = Vec::with_capacity(boid_count); + result.extend(self.boids.values().map(|b| { + let b = b.bind(); + (b.get_boid_position(), b.get_boid_velocity()) + })); + result + } + + #[inline(always)] + fn get_boids(&self) -> impl Iterator { + self.boids.iter().map(|(id, boid)| { + let boid = boid.bind(); + ( + id, + ( + boid.get_boid_position(), + boid.get_boid_velocity(), + boid.get_boid_properties().clone(), + ), + ) + }) + } +} diff --git a/rust/src/flock/mod.rs b/rust/src/flock/mod.rs index e7e9a1f..0ee1a90 100644 --- a/rust/src/flock/mod.rs +++ b/rust/src/flock/mod.rs @@ -3,6 +3,7 @@ use crate::{BoidProperties, FlockProperties}; use glam::*; pub mod flock_2d; +pub mod flock_3d; pub trait Flock { fn get_flock_properties(&self) -> &FlockProperties; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7e9a5cc..7956328 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -16,7 +16,7 @@ mod flock_properties; pub use boid::{boid_2d::*, boid_3d::*, Boid}; pub use boid_properties::BoidProperties; -pub use flock::{flock_2d::*, Flock}; +pub use flock::{flock_2d::*, flock_3d::*, Flock}; pub use flock_properties::FlockProperties; use rustc_hash::FxBuildHasher; @@ -115,8 +115,13 @@ impl INode for BoidsProcess { #[inline(always)] fn physics_process(&mut self, _: f64) { if self.get_engine_singleton().get_physics_frames() % (self.process_per_tick as u64) == 0 { - if self.process_2d { - self.get_boids_singleton().bind_mut().process_boids_2d(); + let (process_2d, process_3d) = (self.process_2d, self.process_3d); + let mut s = self.get_boids_singleton().bind_mut(); + if process_2d { + s.process_boids_2d(); + } + if process_3d { + s.process_boids_3d(); } } } @@ -128,6 +133,8 @@ impl INode for BoidsProcess { struct Boids { flocks2d: FxIndexMap>, boids2d: FxIndexMap>, + flocks3d: FxIndexMap>, + boids3d: FxIndexMap>, base: Base, } @@ -152,6 +159,27 @@ impl Boids { fn unregister_boid_2d(&mut self, boid_id: i64) { self.boids2d.shift_remove(&boid_id); } + + fn register_flock_3d(&mut self, flock_id: i64) { + let flock = godot::global::instance_from_id(flock_id).unwrap().cast(); + self.flocks3d.insert(flock_id, flock); + godot_print!("[Boids] flock {flock_id} registered"); + } + + fn unregister_flock_3d(&mut self, flock_id: i64) { + self.flocks3d.shift_remove(&flock_id); + godot_print!("[Boids] flock {flock_id} unregistered"); + } + + #[inline(always)] + fn register_boid_3d(&mut self, boid_id: i64, boid: Gd) { + self.boids3d.insert(boid_id, boid); + } + + #[inline(always)] + fn unregister_boid_3d(&mut self, boid_id: i64) { + self.boids3d.shift_remove(&boid_id); + } } #[godot_api] @@ -164,6 +192,14 @@ impl Boids { process_boids(&mut self.boids2d, &self.flocks2d) } + #[func] + #[inline(always)] + /// Process all 3D boids once. + /// NOTE: This function is not intended to be manually called. Prefer using `BoidsProcess` as an autoload singleton where possible. + fn process_boids_3d(&mut self) { + process_boids(&mut self.boids3d, &self.flocks3d) + } + #[func] #[inline(always)] /// Gets the total 2D boid count. @@ -177,6 +213,20 @@ impl Boids { fn get_total_flock_2d_count(&self) -> i64 { self.flocks2d.len() as i64 } + + #[func] + #[inline(always)] + /// Gets the total 3D boid count. + fn get_total_boid_3d_count(&self) -> i64 { + self.boids3d.len() as i64 + } + + #[func] + #[inline(always)] + /// Gets the total 3D flock count. + fn get_total_flock_3d_count(&self) -> i64 { + self.flocks3d.len() as i64 + } } #[inline(always)]