Skip to main content

proof_engine/particle/
shape_templates.rs

1//! Shape templates for density entities.
2
3use glam::{Vec2, Vec3, Vec4};
4use super::density_entity::{DensityEntity, ShapeField, ShapeBone};
5
6pub struct DensityTemplates;
7
8impl DensityTemplates {
9    /// Humanoid shape: torso, head, two arms, two legs.
10    pub fn humanoid(name: &str, position: Vec3, color: Vec4, particle_count: u32) -> DensityEntity {
11        let shape = ShapeField::new(vec![
12            ShapeBone::new(Vec2::new(0.0, -0.2), Vec2::new(0.0, 0.6), 0.25, 1.5).named("torso"),
13            ShapeBone::new(Vec2::new(0.0, 0.6), Vec2::new(0.0, 0.9), 0.18, 2.0).named("head"),
14            ShapeBone::new(Vec2::new(-0.25, 0.5), Vec2::new(-0.7, 0.2), 0.1, 0.8).named("left_arm"),
15            ShapeBone::new(Vec2::new(0.25, 0.5), Vec2::new(0.7, 0.2), 0.1, 0.8).named("right_arm"),
16            ShapeBone::new(Vec2::new(-0.1, -0.2), Vec2::new(-0.2, -0.9), 0.1, 0.9).named("left_leg"),
17            ShapeBone::new(Vec2::new(0.1, -0.2), Vec2::new(0.2, -0.9), 0.1, 0.9).named("right_leg"),
18        ]);
19        DensityEntity::new(name, position, shape, particle_count, color)
20    }
21
22    /// Beast: long body, four legs, tail.
23    pub fn beast(name: &str, position: Vec3, color: Vec4, particle_count: u32) -> DensityEntity {
24        let shape = ShapeField::new(vec![
25            ShapeBone::new(Vec2::new(-0.6, 0.0), Vec2::new(0.6, 0.0), 0.3, 1.8).named("body"),
26            ShapeBone::new(Vec2::new(0.6, 0.0), Vec2::new(0.8, 0.3), 0.2, 1.5).named("head"),
27            ShapeBone::new(Vec2::new(-0.4, 0.0), Vec2::new(-0.5, -0.6), 0.08, 0.7).named("front_left"),
28            ShapeBone::new(Vec2::new(-0.2, 0.0), Vec2::new(-0.3, -0.6), 0.08, 0.7).named("front_right"),
29            ShapeBone::new(Vec2::new(0.3, 0.0), Vec2::new(0.2, -0.6), 0.08, 0.7).named("back_left"),
30            ShapeBone::new(Vec2::new(0.5, 0.0), Vec2::new(0.4, -0.6), 0.08, 0.7).named("back_right"),
31            ShapeBone::new(Vec2::new(-0.6, 0.0), Vec2::new(-1.0, 0.2), 0.06, 0.5).named("tail1"),
32            ShapeBone::new(Vec2::new(-1.0, 0.2), Vec2::new(-1.3, 0.4), 0.04, 0.4).named("tail2"),
33        ]);
34        DensityEntity::new(name, position, shape, particle_count, color)
35    }
36
37    /// Swarm: no bones, loose cluster around center.
38    pub fn swarm(name: &str, position: Vec3, color: Vec4, particle_count: u32) -> DensityEntity {
39        let shape = ShapeField::new(vec![
40            ShapeBone::new(Vec2::ZERO, Vec2::new(0.01, 0.0), 1.5, 0.5).named("center"),
41        ]);
42        let mut ent = DensityEntity::new(name, position, shape, particle_count, color);
43        ent.jitter = 0.5; // high jitter for swarming look
44        ent.binding_strength = 5.0;
45        ent.base_binding = 5.0;
46        ent
47    }
48
49    /// Boss: massive frame, high particle count.
50    pub fn boss(name: &str, position: Vec3, color: Vec4, particle_count: u32) -> DensityEntity {
51        let shape = ShapeField::new(vec![
52            // Core body (very dense)
53            ShapeBone::new(Vec2::new(0.0, -0.3), Vec2::new(0.0, 0.8), 0.4, 2.5).named("torso"),
54            // Head
55            ShapeBone::new(Vec2::new(0.0, 0.8), Vec2::new(0.0, 1.2), 0.25, 2.0).named("head"),
56            // Horns
57            ShapeBone::new(Vec2::new(-0.2, 1.1), Vec2::new(-0.5, 1.5), 0.08, 1.0).named("left_horn"),
58            ShapeBone::new(Vec2::new(0.2, 1.1), Vec2::new(0.5, 1.5), 0.08, 1.0).named("right_horn"),
59            // Arms (thick)
60            ShapeBone::new(Vec2::new(-0.4, 0.6), Vec2::new(-0.9, 0.3), 0.15, 1.0).named("left_upper_arm"),
61            ShapeBone::new(Vec2::new(-0.9, 0.3), Vec2::new(-1.2, 0.0), 0.12, 0.8).named("left_forearm"),
62            ShapeBone::new(Vec2::new(0.4, 0.6), Vec2::new(0.9, 0.3), 0.15, 1.0).named("right_upper_arm"),
63            ShapeBone::new(Vec2::new(0.9, 0.3), Vec2::new(1.2, 0.0), 0.12, 0.8).named("right_forearm"),
64            // Legs
65            ShapeBone::new(Vec2::new(-0.15, -0.3), Vec2::new(-0.25, -1.0), 0.13, 1.0).named("left_leg"),
66            ShapeBone::new(Vec2::new(0.15, -0.3), Vec2::new(0.25, -1.0), 0.13, 1.0).named("right_leg"),
67            // Shoulder plates
68            ShapeBone::new(Vec2::new(-0.5, 0.7), Vec2::new(-0.6, 0.5), 0.15, 0.8).named("left_plate"),
69            ShapeBone::new(Vec2::new(0.5, 0.7), Vec2::new(0.6, 0.5), 0.15, 0.8).named("right_plate"),
70        ]);
71        let mut ent = DensityEntity::new(name, position, shape, particle_count, color);
72        ent.breath_amplitude = 0.03;
73        ent.breath_rate = 0.2;
74        ent
75    }
76
77    /// Player mage: humanoid with staff detail.
78    pub fn mage(position: Vec3) -> DensityEntity {
79        let mut ent = Self::humanoid("Mage", position, Vec4::new(0.3, 0.5, 1.0, 0.9), 2000);
80        // Add staff bone
81        ent.shape_field.bones.push(ShapeBone::new(Vec2::new(0.7, 0.2), Vec2::new(0.8, 1.0), 0.04, 0.6).named("staff"));
82        ent.shape_field.bones.push(ShapeBone::new(Vec2::new(0.8, 1.0), Vec2::new(0.8, 1.1), 0.08, 1.2).named("staff_orb"));
83        ent
84    }
85
86    /// Enemy warrior.
87    pub fn warrior(position: Vec3) -> DensityEntity {
88        let mut ent = Self::humanoid("Warrior", position, Vec4::new(0.9, 0.2, 0.3, 0.9), 1500);
89        ent.binding_strength = 25.0;
90        ent.base_binding = 25.0;
91        ent
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_humanoid() {
101        let ent = DensityTemplates::humanoid("test", Vec3::ZERO, Vec4::ONE, 500);
102        assert_eq!(ent.particles.len(), 500);
103        assert!(ent.shape_field.bones.len() >= 6);
104    }
105
106    #[test]
107    fn test_boss() {
108        let ent = DensityTemplates::boss("boss", Vec3::ZERO, Vec4::ONE, 3000);
109        assert_eq!(ent.particles.len(), 3000);
110        assert!(ent.shape_field.bones.len() >= 10);
111    }
112
113    #[test]
114    fn test_mage() {
115        let ent = DensityTemplates::mage(Vec3::ZERO);
116        assert!(ent.particles.len() >= 2000);
117        assert!(ent.shape_field.bones.iter().any(|b| b.name == "staff"));
118    }
119}