pub mod formation;
pub mod cohesion;
pub mod ai;
pub mod layered_entity;
use crate::glyph::GlyphId;
use crate::math::ForceField;
use glam::{Vec3, Vec4};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EntityId(pub u32);
#[derive(Clone)]
pub struct AmorphousEntity {
pub name: String,
pub position: Vec3,
pub binding_field: ForceField,
pub glyph_ids: Vec<GlyphId>,
pub formation: Vec<Vec3>,
pub formation_chars: Vec<char>,
pub formation_colors: Vec<Vec4>,
pub hp: f32,
pub max_hp: f32,
pub entity_mass: f32,
pub entity_temperature: f32,
pub entity_entropy: f32,
pub cohesion: f32,
pub pulse_rate: f32, pub pulse_depth: f32,
pub age: f32,
pub tags: Vec<String>,
pub despawn_requested: bool,
#[doc(hidden)]
pub cohesion_system: Option<cohesion::CohesionManager>,
}
impl AmorphousEntity {
pub fn new(name: impl Into<String>, position: Vec3) -> Self {
Self {
name: name.into(),
position,
binding_field: ForceField::Gravity {
center: position,
strength: 5.0,
falloff: crate::math::Falloff::InverseSquare,
},
glyph_ids: Vec::new(),
formation: Vec::new(),
formation_chars: Vec::new(),
formation_colors: Vec::new(),
hp: 100.0,
max_hp: 100.0,
entity_mass: 10.0,
entity_temperature: 0.5,
entity_entropy: 0.1,
cohesion: 1.0,
pulse_rate: 1.0,
pulse_depth: 0.05,
age: 0.0,
tags: Vec::new(),
despawn_requested: false,
cohesion_system: None,
}
}
pub fn hp_frac(&self) -> f32 {
(self.hp / self.max_hp.max(0.001)).clamp(0.0, 1.0)
}
pub fn update_cohesion(&mut self) {
self.cohesion = self.hp_frac().sqrt();
}
pub fn tick(&mut self, dt: f32, _time: f32) -> bool {
self.age += dt;
self.update_cohesion();
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
cs.cohesion = self.cohesion;
}
self.hp <= 0.0
}
pub fn rebuild_cohesion(&mut self) {
let world: Vec<Vec3> = self.formation.iter().map(|o| self.position + *o).collect();
self.cohesion_system = Some(cohesion::CohesionManager::new(&world, self.cohesion));
}
pub fn step_glyph_physics(&mut self, dt: f32) -> Vec<Vec3> {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
let pulse = 1.0 + (self.age * self.pulse_rate * std::f32::consts::TAU).sin()
* self.pulse_depth;
let (position, formation, temperature, entropy, age) = (
self.position,
&self.formation,
self.entity_temperature,
self.entity_entropy,
self.age,
);
let Some(cs) = self.cohesion_system.as_mut() else {
return Vec::new();
};
cs.cohesion = self.cohesion;
for (i, slot) in formation.iter().enumerate() {
let Some(g) = cs.glyphs.get_mut(i) else { break };
let wander = if entropy > 0.0 {
let a = age * 0.7 + i as f32 * 1.618;
Vec3::new(a.sin(), (a * 1.3).cos(), (a * 0.6).sin()) * entropy * 0.35
} else {
Vec3::ZERO
};
let target = position + *slot * pulse + wander;
g.spring.x.target = target.x;
g.spring.y.target = target.y;
g.spring.z.target = target.z;
g.temperature = temperature;
}
cs.tick(dt)
}
pub fn apply_impulse(&mut self, impulse: Vec3) {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
for g in &mut cs.glyphs {
g.apply_impulse(impulse);
}
}
}
pub fn dissolve(&mut self) {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
cs.damage_cohesion(1.0);
}
self.cohesion = 0.0;
}
pub fn reform(&mut self, cohesion: f32) {
self.cohesion = cohesion.clamp(0.0, 1.0);
self.hp = self.max_hp * self.cohesion * self.cohesion;
if let Some(cs) = self.cohesion_system.as_mut() {
cs.end_dissolution(self.cohesion);
}
}
pub fn reshape(&mut self, formation: &[Vec3]) {
if formation.is_empty() {
return;
}
let n = self.formation.len().max(formation.len());
self.formation = (0..n).map(|i| formation[i % formation.len()]).collect();
}
pub fn set_bind(&mut self, stiffness_scale: f32, damping_scale: f32) {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
cs.set_bind(stiffness_scale, damping_scale);
}
}
pub fn set_bind_each(&mut self, springs: &[(f32, f32)]) {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
cs.set_bind_each(springs);
}
}
pub fn set_floor(&mut self, y: Option<f32>) {
if self.cohesion_system.is_none() {
self.rebuild_cohesion();
}
if let Some(cs) = self.cohesion_system.as_mut() {
cs.set_floor(y);
}
}
pub fn take_damage(&mut self, amount: f32) {
self.hp = (self.hp - amount).max(0.0);
}
pub fn is_dead(&self) -> bool { self.hp <= 0.0 }
}
impl Default for AmorphousEntity {
fn default() -> Self { Self::new("", Vec3::ZERO) }
}