Skip to main content

proof_engine/metaball/
templates.rs

1//! Entity templates — predefined source configurations for different entity types.
2
3use glam::{Vec3, Vec4};
4use super::entity_field::{MetaballEntity, FieldSource, FalloffType};
5use super::surface_material::SurfaceMaterial;
6
7/// Pre-built entity template.
8pub struct EntityTemplate;
9
10impl EntityTemplate {
11    /// Humanoid: 8-12 sources (head, torso, arms, hands, legs).
12    pub fn humanoid() -> MetaballEntity {
13        let mut e = MetaballEntity::new(0.5, 32).with_name("humanoid");
14        let body_color = Vec4::new(0.85, 0.75, 0.65, 1.0);
15        let dark_color = Vec4::new(0.6, 0.5, 0.45, 1.0);
16
17        // Head
18        e.add_source(FieldSource::new(Vec3::new(0.0, 1.8, 0.0), 0.9, 0.5)
19            .with_color(body_color).with_tag("head").with_breath(0.01, 0.0));
20        // Upper torso
21        e.add_source(FieldSource::new(Vec3::new(0.0, 1.2, 0.0), 1.0, 0.7)
22            .with_color(body_color).with_tag("torso_upper").with_breath(0.03, 0.0));
23        // Mid torso
24        e.add_source(FieldSource::new(Vec3::new(0.0, 0.8, 0.0), 0.95, 0.65)
25            .with_color(body_color).with_tag("torso_mid").with_breath(0.03, 0.1));
26        // Lower torso
27        e.add_source(FieldSource::new(Vec3::new(0.0, 0.4, 0.0), 0.85, 0.6)
28            .with_color(body_color).with_tag("torso_lower").with_breath(0.02, 0.2));
29        // Left arm
30        e.add_source(FieldSource::new(Vec3::new(-0.7, 1.1, 0.0), 0.6, 0.35)
31            .with_color(body_color).with_tag("left_arm").with_breath(0.015, 0.3));
32        // Right arm
33        e.add_source(FieldSource::new(Vec3::new(0.7, 1.1, 0.0), 0.6, 0.35)
34            .with_color(body_color).with_tag("right_arm").with_breath(0.015, 0.8));
35        // Left hand
36        e.add_source(FieldSource::new(Vec3::new(-1.0, 0.8, 0.0), 0.35, 0.25)
37            .with_color(dark_color).with_tag("left_hand"));
38        // Right hand
39        e.add_source(FieldSource::new(Vec3::new(1.0, 0.8, 0.0), 0.35, 0.25)
40            .with_color(dark_color).with_tag("right_hand"));
41        // Left leg
42        e.add_source(FieldSource::new(Vec3::new(-0.25, -0.2, 0.0), 0.7, 0.4)
43            .with_color(dark_color).with_tag("left_leg"));
44        // Right leg
45        e.add_source(FieldSource::new(Vec3::new(0.25, -0.2, 0.0), 0.7, 0.4)
46            .with_color(dark_color).with_tag("right_leg"));
47
48        e
49    }
50
51    /// Beast: 10-15 sources (body, legs, head, tail).
52    pub fn beast() -> MetaballEntity {
53        let mut e = MetaballEntity::new(0.45, 32).with_name("beast");
54        let fur_color = Vec4::new(0.5, 0.35, 0.2, 1.0);
55
56        // Head
57        e.add_source(FieldSource::new(Vec3::new(1.2, 0.8, 0.0), 0.8, 0.5)
58            .with_color(fur_color).with_tag("head").with_breath(0.01, 0.0));
59        // Neck
60        e.add_source(FieldSource::new(Vec3::new(0.8, 0.6, 0.0), 0.7, 0.4)
61            .with_color(fur_color).with_tag("neck"));
62        // Body front
63        e.add_source(FieldSource::new(Vec3::new(0.3, 0.4, 0.0), 1.0, 0.7)
64            .with_color(fur_color).with_tag("body_front").with_breath(0.03, 0.0));
65        // Body mid
66        e.add_source(FieldSource::new(Vec3::new(-0.2, 0.4, 0.0), 1.1, 0.75)
67            .with_color(fur_color).with_tag("body_mid").with_breath(0.03, 0.15));
68        // Body rear
69        e.add_source(FieldSource::new(Vec3::new(-0.7, 0.35, 0.0), 0.9, 0.65)
70            .with_color(fur_color).with_tag("body_rear").with_breath(0.025, 0.3));
71        // Front left leg
72        e.add_source(FieldSource::new(Vec3::new(0.5, -0.3, -0.3), 0.5, 0.3)
73            .with_color(fur_color).with_tag("fl_leg"));
74        // Front right leg
75        e.add_source(FieldSource::new(Vec3::new(0.5, -0.3, 0.3), 0.5, 0.3)
76            .with_color(fur_color).with_tag("fr_leg"));
77        // Rear left leg
78        e.add_source(FieldSource::new(Vec3::new(-0.6, -0.3, -0.3), 0.5, 0.3)
79            .with_color(fur_color).with_tag("rl_leg"));
80        // Rear right leg
81        e.add_source(FieldSource::new(Vec3::new(-0.6, -0.3, 0.3), 0.5, 0.3)
82            .with_color(fur_color).with_tag("rr_leg"));
83        // Tail segments
84        e.add_source(FieldSource::new(Vec3::new(-1.1, 0.4, 0.0), 0.4, 0.25)
85            .with_color(fur_color).with_tag("tail_1"));
86        e.add_source(FieldSource::new(Vec3::new(-1.4, 0.5, 0.0), 0.3, 0.2)
87            .with_color(fur_color).with_tag("tail_2"));
88
89        e
90    }
91
92    /// Amorphous: 20+ sources in random cluster with drifting positions.
93    pub fn amorphous(seed: u64) -> MetaballEntity {
94        let mut e = MetaballEntity::new(0.4, 32).with_name("amorphous");
95        let mut rng = seed;
96        let color = Vec4::new(0.3, 0.7, 0.5, 1.0);
97
98        for i in 0..24 {
99            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
100            let x = (rng >> 32) as f32 / u32::MAX as f32 * 2.0 - 1.0;
101            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
102            let y = (rng >> 32) as f32 / u32::MAX as f32 * 2.0 - 1.0;
103            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
104            let z = (rng >> 32) as f32 / u32::MAX as f32 * 2.0 - 1.0;
105
106            let strength = 0.4 + (rng >> 48) as f32 / u16::MAX as f32 * 0.6;
107            let radius = 0.3 + (rng >> 40) as f32 / (u32::MAX >> 8) as f32 * 0.5;
108
109            e.add_source(
110                FieldSource::new(Vec3::new(x, y, z), strength, radius)
111                    .with_color(color)
112                    .with_tag(&format!("blob_{i}"))
113                    .with_breath(0.04, i as f32 * 0.3)
114                    .with_emission(0.2)
115            );
116        }
117
118        e
119    }
120
121    /// Boss: 30+ sources forming massive complex shape with multiple dense cores.
122    pub fn boss(name: &str) -> MetaballEntity {
123        let mut e = MetaballEntity::new(0.4, 64).with_name(name);
124        let core_color = Vec4::new(0.8, 0.2, 0.1, 1.0);
125        let body_color = Vec4::new(0.4, 0.15, 0.05, 1.0);
126        let aura_color = Vec4::new(1.0, 0.5, 0.2, 1.0);
127
128        // Central core (dense, high strength)
129        e.add_source(FieldSource::new(Vec3::ZERO, 1.5, 1.2)
130            .with_color(core_color).with_emission(1.0).with_tag("core_main")
131            .with_breath(0.02, 0.0));
132
133        // Secondary cores
134        let core_offsets = [
135            Vec3::new(1.0, 0.5, 0.0),
136            Vec3::new(-1.0, 0.5, 0.0),
137            Vec3::new(0.0, 1.5, 0.0),
138            Vec3::new(0.0, -0.5, 0.8),
139        ];
140        for (i, &offset) in core_offsets.iter().enumerate() {
141            e.add_source(FieldSource::new(offset, 1.2, 0.9)
142                .with_color(core_color).with_emission(0.8)
143                .with_tag(&format!("core_{i}"))
144                .with_breath(0.025, i as f32 * 0.5));
145        }
146
147        // Body mass: 20 sources, as documented (the loop made only 16, so
148        // the boss had 29 sources instead of the intended 30+).
149        let angles = 20;
150        for i in 0..angles {
151            let theta = i as f32 / angles as f32 * std::f32::consts::TAU;
152            let r = 1.5 + (i % 3) as f32 * 0.3;
153            let y = (i as f32 * 0.7).sin() * 0.5;
154            let pos = Vec3::new(theta.cos() * r, y, theta.sin() * r);
155            e.add_source(FieldSource::new(pos, 0.7, 0.6)
156                .with_color(body_color).with_tag(&format!("body_{i}"))
157                .with_breath(0.02, i as f32 * 0.4));
158        }
159
160        // Aura sources (outer, low strength, high emission)
161        for i in 0..8 {
162            let theta = i as f32 / 8.0 * std::f32::consts::TAU;
163            let pos = Vec3::new(theta.cos() * 2.5, 0.0, theta.sin() * 2.5);
164            e.add_source(FieldSource::new(pos, 0.3, 1.0)
165                .with_color(aura_color).with_emission(1.5)
166                .with_falloff(FalloffType::Gaussian)
167                .with_tag(&format!("aura_{i}"))
168                .with_breath(0.05, i as f32 * 0.8));
169        }
170
171        e
172    }
173
174    /// Small slime: 3-5 sources, simple blob shape.
175    pub fn slime() -> MetaballEntity {
176        let mut e = MetaballEntity::new(0.5, 16).with_name("slime");
177        let color = Vec4::new(0.2, 0.8, 0.3, 0.9);
178
179        e.add_source(FieldSource::new(Vec3::ZERO, 1.0, 0.8)
180            .with_color(color).with_tag("center").with_breath(0.05, 0.0));
181        e.add_source(FieldSource::new(Vec3::new(0.3, 0.2, 0.0), 0.6, 0.5)
182            .with_color(color).with_tag("lobe_1").with_breath(0.04, 0.5));
183        e.add_source(FieldSource::new(Vec3::new(-0.3, 0.15, 0.0), 0.55, 0.45)
184            .with_color(color).with_tag("lobe_2").with_breath(0.04, 1.0));
185        e.add_source(FieldSource::new(Vec3::new(0.0, -0.2, 0.2), 0.5, 0.4)
186            .with_color(color).with_tag("lobe_3").with_breath(0.035, 1.5));
187
188        e
189    }
190
191    /// Spectral entity: translucent, low threshold, high emission.
192    pub fn spectral() -> MetaballEntity {
193        let mut e = MetaballEntity::new(0.3, 32).with_name("spectral");
194        let ghost_color = Vec4::new(0.5, 0.7, 1.0, 0.6);
195
196        for i in 0..12 {
197            let y = i as f32 * 0.2 - 0.5;
198            let wobble = (i as f32 * 1.5).sin() * 0.3;
199            e.add_source(FieldSource::new(Vec3::new(wobble, y, 0.0), 0.5, 0.6)
200                .with_color(ghost_color)
201                .with_emission(0.6)
202                .with_falloff(FalloffType::Gaussian)
203                .with_tag(&format!("wisp_{i}"))
204                .with_breath(0.06, i as f32 * 0.4));
205        }
206
207        e
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use super::super::marching_cubes::MarchingCubesExtractor;
215
216    #[test]
217    fn humanoid_has_expected_sources() {
218        let e = EntityTemplate::humanoid();
219        assert!(e.source_count() >= 8 && e.source_count() <= 12);
220        assert!(e.find_source("head").is_some());
221        assert!(e.find_source("torso_upper").is_some());
222        assert!(e.find_source("left_arm").is_some());
223    }
224
225    #[test]
226    fn beast_has_legs_and_tail() {
227        let e = EntityTemplate::beast();
228        assert!(e.find_source("fl_leg").is_some());
229        assert!(e.find_source("tail_1").is_some());
230    }
231
232    #[test]
233    fn amorphous_has_many_sources() {
234        let e = EntityTemplate::amorphous(42);
235        assert!(e.source_count() >= 20);
236    }
237
238    #[test]
239    fn boss_has_cores() {
240        let e = EntityTemplate::boss("TestBoss");
241        assert!(e.source_count() >= 30);
242        assert!(e.find_source("core_main").is_some());
243    }
244
245    #[test]
246    fn slime_extracts_mesh() {
247        let e = EntityTemplate::slime();
248        let mesh = MarchingCubesExtractor::extract_entity(&e);
249        assert!(!mesh.is_empty(), "Slime should produce a mesh");
250    }
251
252    #[test]
253    fn humanoid_extracts_mesh() {
254        let e = EntityTemplate::humanoid();
255        let mesh = MarchingCubesExtractor::extract_entity(&e);
256        assert!(!mesh.is_empty(), "Humanoid should produce mesh, got {} verts", mesh.vertex_count());
257    }
258
259    #[test]
260    fn spectral_has_high_emission() {
261        let e = EntityTemplate::spectral();
262        let max_emission: f32 = e.sources.iter().map(|s| s.emission).fold(0.0, f32::max);
263        assert!(max_emission > 0.3);
264    }
265}