1use glam::{Vec3, Vec4};
4use super::entity_field::{MetaballEntity, FieldSource, FalloffType};
5use super::surface_material::SurfaceMaterial;
6
7pub struct EntityTemplate;
9
10impl EntityTemplate {
11 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let angles = 16;
149 for i in 0..angles {
150 let theta = i as f32 / angles as f32 * std::f32::consts::TAU;
151 let r = 1.5 + (i % 3) as f32 * 0.3;
152 let y = (i as f32 * 0.7).sin() * 0.5;
153 let pos = Vec3::new(theta.cos() * r, y, theta.sin() * r);
154 e.add_source(FieldSource::new(pos, 0.7, 0.6)
155 .with_color(body_color).with_tag(&format!("body_{i}"))
156 .with_breath(0.02, i as f32 * 0.4));
157 }
158
159 for i in 0..8 {
161 let theta = i as f32 / 8.0 * std::f32::consts::TAU;
162 let pos = Vec3::new(theta.cos() * 2.5, 0.0, theta.sin() * 2.5);
163 e.add_source(FieldSource::new(pos, 0.3, 1.0)
164 .with_color(aura_color).with_emission(1.5)
165 .with_falloff(FalloffType::Gaussian)
166 .with_tag(&format!("aura_{i}"))
167 .with_breath(0.05, i as f32 * 0.8));
168 }
169
170 e
171 }
172
173 pub fn slime() -> MetaballEntity {
175 let mut e = MetaballEntity::new(0.5, 16).with_name("slime");
176 let color = Vec4::new(0.2, 0.8, 0.3, 0.9);
177
178 e.add_source(FieldSource::new(Vec3::ZERO, 1.0, 0.8)
179 .with_color(color).with_tag("center").with_breath(0.05, 0.0));
180 e.add_source(FieldSource::new(Vec3::new(0.3, 0.2, 0.0), 0.6, 0.5)
181 .with_color(color).with_tag("lobe_1").with_breath(0.04, 0.5));
182 e.add_source(FieldSource::new(Vec3::new(-0.3, 0.15, 0.0), 0.55, 0.45)
183 .with_color(color).with_tag("lobe_2").with_breath(0.04, 1.0));
184 e.add_source(FieldSource::new(Vec3::new(0.0, -0.2, 0.2), 0.5, 0.4)
185 .with_color(color).with_tag("lobe_3").with_breath(0.035, 1.5));
186
187 e
188 }
189
190 pub fn spectral() -> MetaballEntity {
192 let mut e = MetaballEntity::new(0.3, 32).with_name("spectral");
193 let ghost_color = Vec4::new(0.5, 0.7, 1.0, 0.6);
194
195 for i in 0..12 {
196 let y = i as f32 * 0.2 - 0.5;
197 let wobble = (i as f32 * 1.5).sin() * 0.3;
198 e.add_source(FieldSource::new(Vec3::new(wobble, y, 0.0), 0.5, 0.6)
199 .with_color(ghost_color)
200 .with_emission(0.6)
201 .with_falloff(FalloffType::Gaussian)
202 .with_tag(&format!("wisp_{i}"))
203 .with_breath(0.06, i as f32 * 0.4));
204 }
205
206 e
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use super::super::marching_cubes::MarchingCubesExtractor;
214
215 #[test]
216 fn humanoid_has_expected_sources() {
217 let e = EntityTemplate::humanoid();
218 assert!(e.source_count() >= 8 && e.source_count() <= 12);
219 assert!(e.find_source("head").is_some());
220 assert!(e.find_source("torso_upper").is_some());
221 assert!(e.find_source("left_arm").is_some());
222 }
223
224 #[test]
225 fn beast_has_legs_and_tail() {
226 let e = EntityTemplate::beast();
227 assert!(e.find_source("fl_leg").is_some());
228 assert!(e.find_source("tail_1").is_some());
229 }
230
231 #[test]
232 fn amorphous_has_many_sources() {
233 let e = EntityTemplate::amorphous(42);
234 assert!(e.source_count() >= 20);
235 }
236
237 #[test]
238 fn boss_has_cores() {
239 let e = EntityTemplate::boss("TestBoss");
240 assert!(e.source_count() >= 30);
241 assert!(e.find_source("core_main").is_some());
242 }
243
244 #[test]
245 fn slime_extracts_mesh() {
246 let e = EntityTemplate::slime();
247 let mesh = MarchingCubesExtractor::extract_entity(&e);
248 assert!(!mesh.is_empty(), "Slime should produce a mesh");
249 }
250
251 #[test]
252 fn humanoid_extracts_mesh() {
253 let e = EntityTemplate::humanoid();
254 let mesh = MarchingCubesExtractor::extract_entity(&e);
255 assert!(!mesh.is_empty(), "Humanoid should produce mesh, got {} verts", mesh.vertex_count());
256 }
257
258 #[test]
259 fn spectral_has_high_emission() {
260 let e = EntityTemplate::spectral();
261 let max_emission: f32 = e.sources.iter().map(|s| s.emission).fold(0.0, f32::max);
262 assert!(max_emission > 0.3);
263 }
264}