1use glam::{Vec2, Vec3, Vec4};
13use crate::glyph::{Glyph, GlyphId, RenderLayer, BlendMode};
14use crate::particle::density_entity::{DensityEntity, DensityParticle, ShapeField, ShapeBone};
15use crate::curves::entity_curves::{CurveEntity, EntityCurve, CurveType};
16use crate::curves::tessellate::tessellate_curve;
17use crate::curves::curve_renderer::render_curve_entity;
18use crate::math::{MathFunction, ForceField};
19use std::f32::consts::TAU;
20
21#[derive(Debug, Clone, Copy)]
27pub struct LayerVisibility {
28 pub density_opacity: f32,
30 pub metaball_opacity: f32,
32 pub curve_opacity: f32,
34 pub glyph_opacity: f32,
36}
37
38impl LayerVisibility {
39 pub fn from_hp(hp_ratio: f32) -> Self {
41 let hp = hp_ratio.clamp(0.0, 1.0);
42
43 if hp > 0.75 {
44 let t = (hp - 0.75) / 0.25; Self {
47 density_opacity: 0.8 + t * 0.2,
48 metaball_opacity: 0.8 + t * 0.2,
49 curve_opacity: 0.0,
50 glyph_opacity: 0.0,
51 }
52 } else if hp > 0.5 {
53 let t = (hp - 0.5) / 0.25; Self {
56 density_opacity: 0.5 + t * 0.3,
57 metaball_opacity: 0.5 + t * 0.3,
58 curve_opacity: (1.0 - t) * 0.3,
59 glyph_opacity: 0.0,
60 }
61 } else if hp > 0.25 {
62 let t = (hp - 0.25) / 0.25; Self {
65 density_opacity: 0.2 + t * 0.3,
66 metaball_opacity: 0.2 + t * 0.3,
67 curve_opacity: 0.4 + (1.0 - t) * 0.3,
68 glyph_opacity: (1.0 - t) * 0.3,
69 }
70 } else {
71 let t = hp / 0.25; Self {
74 density_opacity: t * 0.2,
75 metaball_opacity: t * 0.2,
76 curve_opacity: 0.5 + (1.0 - t) * 0.5,
77 glyph_opacity: 0.5 + (1.0 - t) * 0.3,
78 }
79 }
80 }
81
82 pub fn zero() -> Self {
84 Self { density_opacity: 0.0, metaball_opacity: 0.0, curve_opacity: 0.0, glyph_opacity: 0.0 }
85 }
86
87 pub fn full() -> Self {
89 Self { density_opacity: 1.0, metaball_opacity: 1.0, curve_opacity: 1.0, glyph_opacity: 1.0 }
90 }
91}
92
93#[derive(Debug, Clone)]
99pub struct Glyph3DInstance {
100 pub character: char,
101 pub offset: Vec2,
102 pub base_offset: Vec2,
103 pub color: Vec4,
104 pub emission: f32,
105 pub scale: f32,
106 pub velocity: Vec2,
107 pub rotation: f32,
108 pub angular_velocity: f32,
109 pub alive: bool,
110}
111
112impl Glyph3DInstance {
113 pub fn new(character: char, offset: Vec2, color: Vec4) -> Self {
114 Self {
115 character, offset, base_offset: offset,
116 color, emission: 1.5, scale: 0.4,
117 velocity: Vec2::ZERO, rotation: 0.0, angular_velocity: 0.0,
118 alive: true,
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
129pub struct DissolutionState {
130 pub active: bool,
131 pub frame: u32,
132 pub elapsed: f32,
133 pub density_started: bool,
135 pub metaball_started: bool,
136 pub curves_started: bool,
137 pub glyphs_started: bool,
138 pub density_done: bool,
140 pub metaball_done: bool,
141 pub curves_done: bool,
142 pub glyphs_done: bool,
143}
144
145impl DissolutionState {
146 pub fn inactive() -> Self {
147 Self {
148 active: false, frame: 0, elapsed: 0.0,
149 density_started: false, metaball_started: false,
150 curves_started: false, glyphs_started: false,
151 density_done: false, metaball_done: false,
152 curves_done: false, glyphs_done: false,
153 }
154 }
155
156 pub fn is_complete(&self) -> bool {
157 self.density_done && self.metaball_done && self.curves_done && self.glyphs_done
158 }
159}
160
161pub struct LayeredEntity {
167 pub density_layer: Option<DensityEntity>,
169 pub metaball_opacity: f32,
173 pub metaball_emission: f32,
174 pub curve_layer: Option<CurveEntity>,
176 pub glyph_layer: Vec<Glyph3DInstance>,
178
179 pub position: Vec3,
181 pub hp: f32,
183 pub max_hp: f32,
185 pub layer_visibility: LayerVisibility,
187 pub dissolution: DissolutionState,
189 pub alive: bool,
191 pub name: String,
193 pub id: u32,
195 pub time: f32,
197 pub base_color: Vec4,
199}
200
201impl LayeredEntity {
202 pub fn new(name: &str, position: Vec3, max_hp: f32) -> Self {
207 Self {
208 density_layer: None,
209 metaball_opacity: 1.0,
210 metaball_emission: 1.0,
211 curve_layer: None,
212 glyph_layer: Vec::new(),
213 position,
214 hp: max_hp,
215 max_hp,
216 layer_visibility: LayerVisibility::from_hp(1.0),
217 dissolution: DissolutionState::inactive(),
218 alive: true,
219 name: name.to_string(),
220 id: 0,
221 time: 0.0,
222 base_color: Vec4::new(0.5, 0.7, 1.0, 1.0),
223 }
224 }
225
226 pub fn with_density(mut self, density: DensityEntity) -> Self {
228 self.density_layer = Some(density);
229 self
230 }
231
232 pub fn with_curves(mut self, curves: CurveEntity) -> Self {
234 self.curve_layer = Some(curves);
235 self
236 }
237
238 pub fn with_glyph(mut self, glyph: Glyph3DInstance) -> Self {
240 self.glyph_layer.push(glyph);
241 self
242 }
243
244 pub fn with_glyphs(mut self, glyphs: Vec<Glyph3DInstance>) -> Self {
246 self.glyph_layer = glyphs;
247 self
248 }
249
250 pub fn with_color(mut self, color: Vec4) -> Self {
251 self.base_color = color;
252 self
253 }
254
255 pub fn update(&mut self, dt: f32) {
261 self.time += dt;
262
263 if self.dissolution.active {
264 self.dissolve_update(dt);
265 return;
266 }
267
268 let hp_ratio = (self.hp / self.max_hp).clamp(0.0, 1.0);
270 self.layer_visibility = LayerVisibility::from_hp(hp_ratio);
271
272 if let Some(ref mut density) = self.density_layer {
274 density.hp_ratio = hp_ratio;
275 density.tick(dt);
276 let density_em = self.layer_visibility.density_opacity;
278 for p in &mut density.particles {
279 p.emission = p.emission * density_em;
280 }
281 }
282
283 if let Some(ref mut curves) = self.curve_layer {
285 curves.hp_ratio = hp_ratio;
286 curves.emission_mult = self.layer_visibility.curve_opacity;
287 curves.tick(dt);
288 }
289
290 self.metaball_opacity = self.layer_visibility.metaball_opacity;
292 self.metaball_emission = self.layer_visibility.metaball_opacity * 1.5;
293
294 let glyph_vis = self.layer_visibility.glyph_opacity;
296 let jitter = (1.0 - hp_ratio) * 0.1;
297 for glyph in &mut self.glyph_layer {
298 if !glyph.alive { continue; }
299 let to_base = glyph.base_offset - glyph.offset;
301 glyph.velocity += to_base * 5.0 * dt;
302 glyph.velocity *= 0.9; glyph.offset += glyph.velocity * dt;
304 glyph.offset.x += hash_noise(self.time + glyph.base_offset.x * 10.0) * jitter;
306 glyph.offset.y += hash_noise(self.time + glyph.base_offset.y * 10.0 + 50.0) * jitter;
307 glyph.rotation += glyph.angular_velocity * dt;
309 }
310
311 if self.hp <= 0.0 && self.alive {
313 self.begin_dissolution();
314 }
315 }
316
317 pub fn take_damage(&mut self, amount: f32, impact_point: Vec2, impact_direction: Vec2) {
323 self.hp = (self.hp - amount).max(0.0);
324
325 if let Some(ref mut density) = self.density_layer {
328 density.apply_hit(impact_point, amount, 2.0);
329 }
330
331 if let Some(ref mut curves) = self.curve_layer {
333 for curve in &mut curves.curves {
334 curve.apply_hit_recoil(impact_direction, amount * 0.3);
335 }
336 }
337
338 for glyph in &mut self.glyph_layer {
340 let to_glyph = glyph.offset - impact_point;
341 let dist = to_glyph.length();
342 if dist < 2.0 {
343 let impulse = to_glyph.normalize_or_zero() * amount * 0.02 / (dist + 0.1);
344 glyph.velocity += impulse;
345 glyph.angular_velocity += (hash_noise(glyph.offset.x * 7.0) - 0.5) * amount * 0.05;
346 }
347 }
348
349 if self.hp <= 0.0 && self.alive {
350 self.begin_dissolution();
351 }
352 }
353
354 pub fn take_crit(&mut self, amount: f32, impact_point: Vec2, impact_direction: Vec2) {
356 self.take_damage(amount, impact_point, impact_direction);
357
358 if let Some(ref mut density) = self.density_layer {
360 density.apply_crit(impact_point, amount);
361 }
362
363 if let Some(ref mut curves) = self.curve_layer {
365 curves.break_random_curve(self.time as u32);
366 }
367 }
368
369 pub fn apply_force(&mut self, force: Vec2) {
375 if let Some(ref mut density) = self.density_layer {
376 for p in &mut density.particles {
377 if p.alive { p.velocity += force * 0.1; }
378 }
379 }
380 if let Some(ref mut curves) = self.curve_layer {
381 for curve in &mut curves.curves {
382 curve.apply_force(force);
383 }
384 }
385 for glyph in &mut self.glyph_layer {
386 glyph.velocity += force * 0.05;
387 }
388 }
389
390 fn begin_dissolution(&mut self) {
395 self.alive = false;
396 self.dissolution.active = true;
397 self.dissolution.frame = 0;
398 self.dissolution.elapsed = 0.0;
399 }
400
401 fn dissolve_update(&mut self, dt: f32) {
402 self.dissolution.elapsed += dt;
403 self.dissolution.frame += 1;
404 let frame = self.dissolution.frame;
405
406 if frame <= 30 && !self.dissolution.density_started {
408 self.dissolution.density_started = true;
409 if let Some(ref mut density) = self.density_layer {
410 density.die();
411 }
412 }
413 if frame > 30 {
414 if let Some(ref mut density) = self.density_layer {
415 density.tick(dt);
416 if density.is_dissolved() { self.dissolution.density_done = true; }
417 } else {
418 self.dissolution.density_done = true;
419 }
420 }
421
422 if frame >= 15 && !self.dissolution.metaball_started {
424 self.dissolution.metaball_started = true;
425 }
426 if frame >= 15 {
427 let t = ((frame - 15) as f32 / 45.0).min(1.0);
428 self.metaball_opacity = (1.0 - t).max(0.0);
429 self.metaball_emission = (1.0 - t) * 2.0; if frame >= 60 { self.dissolution.metaball_done = true; }
431 }
432
433 if frame >= 30 && !self.dissolution.curves_started {
435 self.dissolution.curves_started = true;
436 if let Some(ref mut curves) = self.curve_layer {
437 curves.die();
438 }
439 }
440 if frame >= 30 {
441 if let Some(ref mut curves) = self.curve_layer {
442 curves.tick(dt);
443 if curves.is_dissolved() { self.dissolution.curves_done = true; }
444 } else {
445 self.dissolution.curves_done = true;
446 }
447 }
448
449 if frame >= 45 && !self.dissolution.glyphs_started {
451 self.dissolution.glyphs_started = true;
452 for glyph in &mut self.glyph_layer {
453 let angle = hash_noise(glyph.offset.x * 3.0 + glyph.offset.y * 7.0) * TAU;
454 glyph.velocity += Vec2::new(angle.cos(), angle.sin()) * 2.0;
455 glyph.angular_velocity = (hash_noise(glyph.offset.x * 5.0) - 0.5) * 10.0;
456 }
457 }
458 if frame >= 45 {
459 let glyph_t = ((frame - 45) as f32 / 75.0).min(1.0);
460 for glyph in &mut self.glyph_layer {
461 glyph.velocity *= 0.98;
462 glyph.offset += glyph.velocity * dt;
463 glyph.rotation += glyph.angular_velocity * dt;
464 glyph.color.w = (1.0 - glyph_t).max(0.0);
466 glyph.emission = (1.0 - glyph_t) * 2.0;
467 }
468 if frame >= 120 { self.dissolution.glyphs_done = true; }
469 }
470
471 if frame >= 180 {
473 self.dissolution.density_done = true;
474 self.dissolution.metaball_done = true;
475 self.dissolution.curves_done = true;
476 self.dissolution.glyphs_done = true;
477 }
478
479 self.layer_visibility = LayerVisibility {
481 density_opacity: if self.dissolution.density_done { 0.0 } else { (1.0 - self.dissolution.elapsed / 1.0).max(0.0) },
482 metaball_opacity: self.metaball_opacity,
483 curve_opacity: if self.dissolution.curves_done { 0.0 } else { (1.0 - (self.dissolution.elapsed - 0.5).max(0.0) / 1.5).max(0.0) },
484 glyph_opacity: if self.dissolution.glyphs_done { 0.0 } else { (1.0 - (self.dissolution.elapsed - 0.75).max(0.0) / 1.5).max(0.0) },
485 };
486 }
487
488 pub fn render(&self, spawn_fn: &mut dyn FnMut(Glyph) -> GlyphId, dt: f32) -> usize {
494 let mut count = 0;
495 let vis = &self.layer_visibility;
496
497 if vis.density_opacity > 0.01 {
499 if let Some(ref density) = self.density_layer {
500 for p in &density.particles {
501 if !p.alive { continue; }
502 let alpha = p.color.w * vis.density_opacity;
503 if alpha < 0.005 { continue; }
504 spawn_fn(Glyph {
505 character: '.', scale: Vec2::splat(p.size * 5.0),
506 position: Vec3::new(self.position.x + p.position.x, self.position.y + p.position.y, self.position.z - 0.2),
507 color: Vec4::new(p.color.x, p.color.y, p.color.z, alpha),
508 emission: p.emission * vis.density_opacity,
509 glow_color: Vec3::new(p.color.x, p.color.y, p.color.z),
510 glow_radius: p.emission * 0.3 * vis.density_opacity,
511 mass: 0.0, lifetime: dt * 1.5,
512 layer: RenderLayer::Particle, blend_mode: BlendMode::Additive,
513 ..Default::default()
514 });
515 count += 1;
516 }
517 }
518 }
519
520 if vis.metaball_opacity > 0.01 {
522 let mb_count = 20;
524 for i in 0..mb_count {
525 let angle = (i as f32 / mb_count as f32) * TAU;
526 let r = 0.3 * vis.metaball_opacity;
527 let x = r * angle.cos();
528 let y = r * angle.sin() + 0.3;
529 spawn_fn(Glyph {
530 character: '#', scale: Vec2::splat(0.35 * vis.metaball_opacity),
531 position: Vec3::new(self.position.x + x, self.position.y + y, self.position.z - 0.1),
532 color: Vec4::new(self.base_color.x, self.base_color.y, self.base_color.z, vis.metaball_opacity * 0.4),
533 emission: self.metaball_emission * 0.5,
534 mass: 0.0, lifetime: dt * 1.5,
535 layer: RenderLayer::Entity, blend_mode: BlendMode::Additive,
536 ..Default::default()
537 });
538 count += 1;
539 }
540 }
541
542 if vis.curve_opacity > 0.01 {
544 if let Some(ref curves) = self.curve_layer {
545 for curve in &curves.curves {
546 if !curve.alive && curve.kinetic_energy() < 0.001 { continue; }
547 let polyline = tessellate_curve(curve);
548 for (i, pt) in polyline.iter().enumerate() {
549 let t = i as f32 / polyline.len().max(1) as f32;
550 let alpha = curve.color.w * vis.curve_opacity;
551 if alpha < 0.005 { continue; }
552 let ch = if curve.thickness > 0.03 { '*' } else { '.' };
553 spawn_fn(Glyph {
554 character: ch, scale: Vec2::splat(curve.thickness * 4.0),
555 position: Vec3::new(
556 self.position.x + curves.position.x + pt.x,
557 self.position.y + curves.position.y + pt.y,
558 self.position.z,
559 ),
560 color: Vec4::new(curve.color.x, curve.color.y, curve.color.z, alpha),
561 emission: curve.emission * vis.curve_opacity * curves.emission_mult,
562 mass: 0.0, lifetime: dt * 1.5,
563 layer: RenderLayer::Entity, blend_mode: BlendMode::Additive,
564 ..Default::default()
565 });
566 count += 1;
567 }
568 }
569 }
570 }
571
572 if vis.glyph_opacity > 0.01 {
574 for glyph in &self.glyph_layer {
575 if !glyph.alive { continue; }
576 let alpha = glyph.color.w * vis.glyph_opacity;
577 if alpha < 0.005 { continue; }
578 spawn_fn(Glyph {
579 character: glyph.character, scale: Vec2::splat(glyph.scale),
580 position: Vec3::new(
581 self.position.x + glyph.offset.x,
582 self.position.y + glyph.offset.y,
583 self.position.z + 0.1,
584 ),
585 rotation: glyph.rotation,
586 color: Vec4::new(glyph.color.x, glyph.color.y, glyph.color.z, alpha),
587 emission: glyph.emission * vis.glyph_opacity,
588 glow_color: Vec3::new(glyph.color.x, glyph.color.y, glyph.color.z),
589 glow_radius: glyph.emission * 0.5 * vis.glyph_opacity,
590 mass: 0.0, lifetime: dt * 1.5,
591 layer: RenderLayer::Entity, blend_mode: BlendMode::Additive,
592 ..Default::default()
593 });
594 count += 1;
595 }
596 }
597
598 count
599 }
600
601 pub fn hp_ratio(&self) -> f32 { (self.hp / self.max_hp).clamp(0.0, 1.0) }
606
607 pub fn is_dissolved(&self) -> bool {
608 !self.alive && self.dissolution.is_complete()
609 }
610
611 pub fn get_bounds(&self) -> (Vec2, Vec2) {
613 let mut min = Vec2::splat(f32::MAX);
614 let mut max = Vec2::splat(f32::MIN);
615
616 if let Some(ref density) = self.density_layer {
617 let (dmin, dmax) = density.bounds();
618 min = min.min(dmin); max = max.max(dmax);
619 }
620 if let Some(ref curves) = self.curve_layer {
621 let (cmin, cmax) = curves.bounding_box();
622 min = min.min(cmin); max = max.max(cmax);
623 }
624 for glyph in &self.glyph_layer {
625 min = min.min(glyph.offset - Vec2::splat(glyph.scale));
626 max = max.max(glyph.offset + Vec2::splat(glyph.scale));
627 }
628
629 (min, max)
630 }
631
632 pub fn total_particle_count(&self) -> u32 {
634 let density_count = self.density_layer.as_ref().map(|d| d.alive_count()).unwrap_or(0);
635 let curve_count = self.curve_layer.as_ref().map(|c| c.alive_curve_count() as u32).unwrap_or(0);
636 let glyph_count = self.glyph_layer.iter().filter(|g| g.alive).count() as u32;
637 density_count + curve_count + glyph_count
638 }
639}
640
641pub struct LayeredEntityBuilder;
647
648impl LayeredEntityBuilder {
649 pub fn mage(position: Vec3) -> LayeredEntity {
651 use crate::particle::density_entity::*;
652 use crate::particle::shape_templates::DensityTemplates;
653 use crate::curves::templates::CurveTemplates;
654
655 let density = DensityTemplates::mage(position);
656 let curves = CurveTemplates::mage(position);
657 let glyphs = vec![
658 Glyph3DInstance::new('@', Vec2::new(0.0, 0.9), Vec4::new(0.5, 0.7, 1.0, 0.9)),
659 Glyph3DInstance::new('#', Vec2::new(0.0, 0.4), Vec4::new(0.4, 0.6, 0.9, 0.8)),
660 Glyph3DInstance::new('*', Vec2::new(0.7, 1.0), Vec4::new(0.8, 0.8, 1.0, 0.95)),
661 Glyph3DInstance::new('<', Vec2::new(-0.6, 0.4), Vec4::new(0.35, 0.55, 1.0, 0.7)),
662 Glyph3DInstance::new('>', Vec2::new(0.6, 0.4), Vec4::new(0.35, 0.55, 1.0, 0.7)),
663 ];
664
665 LayeredEntity::new("Mage", position, 100.0)
666 .with_density(density)
667 .with_curves(curves)
668 .with_glyphs(glyphs)
669 .with_color(Vec4::new(0.3, 0.5, 1.0, 1.0))
670 }
671
672 pub fn boss(position: Vec3) -> LayeredEntity {
674 use crate::particle::density_entity::*;
675 use crate::particle::shape_templates::DensityTemplates;
676 use crate::curves::templates::CurveTemplates;
677
678 let density = DensityTemplates::boss("Chaos Lord", position, Vec4::new(0.9, 0.15, 0.3, 0.9), 3000);
679 let curves = CurveTemplates::boss(position);
680 let glyphs = vec![
681 Glyph3DInstance::new('X', Vec2::new(-0.25, 0.9), Vec4::new(1.0, 0.9, 0.1, 1.0)),
682 Glyph3DInstance::new('X', Vec2::new(0.25, 0.9), Vec4::new(1.0, 0.9, 0.1, 1.0)),
683 Glyph3DInstance::new('H', Vec2::new(0.0, 0.0), Vec4::new(0.8, 0.1, 0.2, 0.9)),
684 Glyph3DInstance::new('^', Vec2::new(-0.4, 1.3), Vec4::new(0.9, 0.3, 0.6, 0.8)),
685 Glyph3DInstance::new('^', Vec2::new(0.4, 1.3), Vec4::new(0.9, 0.3, 0.6, 0.8)),
686 Glyph3DInstance::new('v', Vec2::new(0.0, -0.7), Vec4::new(0.7, 0.15, 0.2, 0.7)),
687 ];
688
689 LayeredEntity::new("Chaos Lord", position, 500.0)
690 .with_density(density)
691 .with_curves(curves)
692 .with_glyphs(glyphs)
693 .with_color(Vec4::new(0.9, 0.15, 0.3, 1.0))
694 }
695}
696
697fn hash_noise(x: f32) -> f32 {
702 let n = (x * 374761.393) as i32;
703 let n = (n as u32) ^ ((n as u32) >> 13);
704 let n = n.wrapping_mul(0x5851F42D);
705 (n & 0x00FF_FFFF) as f32 / 0x0080_0000 as f32 - 1.0
706}
707
708#[cfg(test)]
713mod tests {
714 use super::*;
715
716 #[test]
717 fn test_layer_visibility_full_hp() {
718 let vis = LayerVisibility::from_hp(1.0);
719 assert!(vis.density_opacity > 0.9);
720 assert!(vis.metaball_opacity > 0.9);
721 assert!(vis.curve_opacity < 0.01);
722 assert!(vis.glyph_opacity < 0.01);
723 }
724
725 #[test]
726 fn test_layer_visibility_half_hp() {
727 let vis = LayerVisibility::from_hp(0.5);
728 assert!(vis.density_opacity > 0.3);
729 assert!(vis.curve_opacity > 0.1);
730 }
731
732 #[test]
733 fn test_layer_visibility_critical() {
734 let vis = LayerVisibility::from_hp(0.1);
735 assert!(vis.density_opacity < 0.15);
736 assert!(vis.curve_opacity > 0.7);
737 assert!(vis.glyph_opacity > 0.5);
738 }
739
740 #[test]
741 fn test_take_damage() {
742 let mut ent = LayeredEntity::new("test", Vec3::ZERO, 100.0);
743 ent.take_damage(30.0, Vec2::ZERO, Vec2::X);
744 assert!((ent.hp - 70.0).abs() < 0.01);
745 }
746
747 #[test]
748 fn test_death_triggers_dissolution() {
749 let mut ent = LayeredEntity::new("test", Vec3::ZERO, 100.0);
750 ent.take_damage(100.0, Vec2::ZERO, Vec2::X);
751 assert!(!ent.alive);
752 assert!(ent.dissolution.active);
753 }
754
755 #[test]
756 fn test_dissolution_completes() {
757 let mut ent = LayeredEntity::new("test", Vec3::ZERO, 100.0);
758 ent.take_damage(100.0, Vec2::ZERO, Vec2::X);
759 for _ in 0..300 { ent.update(1.0 / 60.0); }
760 assert!(ent.is_dissolved());
761 }
762
763 #[test]
764 fn test_builder_mage() {
765 let mage = LayeredEntityBuilder::mage(Vec3::ZERO);
766 assert!(mage.density_layer.is_some());
767 assert!(mage.curve_layer.is_some());
768 assert!(!mage.glyph_layer.is_empty());
769 assert_eq!(mage.hp, 100.0);
770 }
771
772 #[test]
773 fn test_builder_boss() {
774 let boss = LayeredEntityBuilder::boss(Vec3::ZERO);
775 assert!(boss.density_layer.is_some());
776 assert!(boss.curve_layer.is_some());
777 assert!(boss.glyph_layer.len() >= 5);
778 assert_eq!(boss.hp, 500.0);
779 }
780}