1use serde::Serialize;
19use serde_json::Value as Json;
20
21use crate::{Component, Entity, JsonError, Transform, World};
22
23mod motion;
24pub use motion::{MotionState, Mover};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
28pub enum Argument {
29 Constant,
31 Age,
33 ActiveTime,
35 EmissionTime,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
41pub enum Interpolation {
42 Step,
44 Linear,
46 Bezier,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
54pub struct Key {
55 pub at: f32,
57 pub value: [f32; 4],
59 pub handle_in: [f32; 4],
62 pub handle_out: [f32; 4],
64}
65
66#[derive(Clone, Debug, PartialEq, Serialize)]
68pub struct Curve {
69 pub width: u8,
71 pub interpolation: Interpolation,
73 pub looped: bool,
76 pub keys: Vec<Key>,
78}
79
80impl Curve {
81 pub fn constant(values: &[f32]) -> Curve {
83 let mut value = [0.0; 4];
84 for (slot, v) in value.iter_mut().zip(values) {
85 *slot = *v;
86 }
87 Curve {
88 width: values.len().clamp(1, 4) as u8,
89 interpolation: Interpolation::Linear,
90 looped: false,
91 keys: vec![Key {
92 at: 0.0,
93 value,
94 handle_in: [0.0; 4],
95 handle_out: [0.0; 4],
96 }],
97 }
98 }
99
100 pub fn value_at(&self, x: f32) -> [f32; 4] {
103 let keys = &self.keys;
104 let (Some(first), Some(last)) = (keys.first(), keys.last()) else {
105 return [0.0; 4];
106 };
107 let x = if self.looped {
108 if last.at <= 0.0 {
109 return first.value;
110 }
111 x.rem_euclid(last.at)
112 } else {
113 x
114 };
115 if keys.len() == 1 || x <= first.at {
116 return first.value;
117 }
118 if x >= last.at {
119 return last.value;
120 }
121 let b = keys.partition_point(|k| k.at <= x);
123 let (a, b) = (&keys[b - 1], &keys[b]);
124 let d = b.at - a.at;
125 if d <= 0.0 {
126 return b.value;
127 }
128 let u = (x - a.at) / d;
129 let mut out = [0.0; 4];
130 for (c, slot) in out
131 .iter_mut()
132 .enumerate()
133 .take(usize::from(self.width.clamp(1, 4)))
134 {
135 *slot = match self.interpolation {
136 Interpolation::Step => a.value[c],
137 Interpolation::Linear => a.value[c] + (b.value[c] - a.value[c]) * u,
138 Interpolation::Bezier => {
139 let (p0, p1, p2, p3) = (
140 a.value[c],
141 a.value[c] + a.handle_out[c],
142 b.value[c] - b.handle_in[c],
143 b.value[c],
144 );
145 let k = 1.0 - u;
146 k * (k * k * p0 + 3.0 * u * (k * p1 + u * p2)) + u * u * u * p3
147 }
148 };
149 }
150 out
151 }
152
153 fn check(&self, what: &str) -> Result<(), String> {
154 if !(1..=4).contains(&self.width) {
155 return Err(format!("{what}: {} components", self.width));
156 }
157 if self.keys.is_empty() {
158 return Err(format!("{what}: no keys"));
159 }
160 let mut previous = f32::NEG_INFINITY;
161 for (i, k) in self.keys.iter().enumerate() {
162 if !(k.at.is_finite()
163 && k.value
164 .iter()
165 .chain(&k.handle_in)
166 .chain(&k.handle_out)
167 .all(|v| v.is_finite()))
168 {
169 return Err(format!("{what}: key {i} is not finite"));
170 }
171 if k.at < previous {
172 return Err(format!("{what}: key {i} at {} goes back from {previous}", k.at));
173 }
174 previous = k.at;
175 }
176 Ok(())
177 }
178}
179
180#[derive(Clone, Debug, PartialEq, Serialize)]
183pub enum Variation {
184 Delta { max: [f32; 4], stream: u32 },
186 Fraction { max: [f32; 4], stream: u32 },
188 Range {
190 min: [f32; 4],
191 max: [f32; 4],
192 stream: u32,
193 },
194 Scaled { input: u32, factor: Curve },
196}
197
198#[derive(Clone, Debug, PartialEq, Serialize)]
200pub struct Value {
201 pub argument: Argument,
203 pub curve: Curve,
205 pub variation: Vec<Variation>,
207}
208
209impl Value {
210 pub fn constant(values: &[f32]) -> Value {
212 Value {
213 argument: Argument::Constant,
214 curve: Curve::constant(values),
215 variation: Vec::new(),
216 }
217 }
218
219 pub fn is_fixed(&self) -> bool {
221 self.variation.is_empty() && (self.argument == Argument::Constant || self.curve.keys.len() == 1)
222 }
223
224 fn check(&self, what: &str, inputs: usize) -> Result<(), String> {
225 self.curve.check(what)?;
226 for v in &self.variation {
227 match v {
228 Variation::Delta { max, .. } | Variation::Fraction { max, .. } => {
229 if !max.iter().all(|m| m.is_finite()) {
230 return Err(format!("{what}: a variation that is not finite"));
231 }
232 }
233 Variation::Range { min, max, .. } => {
234 if !min.iter().chain(max).all(|m| m.is_finite()) {
235 return Err(format!("{what}: a variation that is not finite"));
236 }
237 }
238 Variation::Scaled { input, factor } => {
239 if usize::try_from(*input).is_ok_and(|i| i >= inputs) {
240 return Err(format!("{what}: input {input} of {inputs}"));
241 }
242 factor.check(what)?;
243 }
244 }
245 }
246 Ok(())
247 }
248}
249
250pub fn unit(seed: u64, emitter: u32, scope: u64, stream: u32, component: u32) -> f32 {
254 fn mix(z: u64) -> u64 {
255 let z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
256 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
257 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
258 z ^ (z >> 31)
259 }
260 let mut x = mix(seed);
261 for v in [u64::from(emitter), scope, u64::from(stream), u64::from(component)] {
262 x = mix(x ^ v);
263 }
264 (x >> 40) as f32 / 16_777_216.0
266}
267
268impl Value {
269 pub fn eval(&self, x: f32, seed: u64, emitter: u32, scope: u64, input: &dyn Fn(u32) -> f32) -> [f32; 4] {
272 let at = if self.argument == Argument::Constant {
273 0.0
274 } else {
275 x
276 };
277 let mut v = self.curve.value_at(at);
278 let width = usize::from(self.curve.width.clamp(1, 4));
279 for var in &self.variation {
280 match var {
281 Variation::Delta { max, stream } => {
282 for (c, slot) in v.iter_mut().enumerate().take(width) {
283 let u = unit(seed, emitter, scope, *stream, c as u32);
284 *slot += (2.0 * u - 1.0) * max[c];
285 }
286 }
287 Variation::Fraction { max, stream } => {
288 for (c, slot) in v.iter_mut().enumerate().take(width) {
289 let u = unit(seed, emitter, scope, *stream, c as u32);
290 *slot += (2.0 * u - 1.0) * max[c] * *slot;
291 }
292 }
293 Variation::Range { min, max, stream } => {
294 for (c, slot) in v.iter_mut().enumerate().take(width) {
295 let u = unit(seed, emitter, scope, *stream, c as u32);
296 *slot += min[c] + u * (max[c] - min[c]);
297 }
298 }
299 Variation::Scaled { input: i, factor } => {
300 let f = factor.value_at(input(*i));
301 for (c, slot) in v.iter_mut().enumerate().take(width) {
302 *slot *= f[c.min(usize::from(factor.width.clamp(1, 4)) - 1)];
303 }
304 }
305 }
306 }
307 v
308 }
309}
310
311#[derive(Clone, Debug, PartialEq, Serialize)]
314pub struct Input {
315 pub name: String,
317 pub default: f32,
319}
320
321#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
323pub enum Follow {
324 None,
326 Frame,
328 Position,
330}
331
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
334pub enum Anchor {
335 Parent,
337 System,
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
343pub struct Attachment {
344 pub anchor: Anchor,
345 pub follow: Follow,
346}
347
348#[derive(Clone, Debug, PartialEq, Serialize)]
350pub enum Spawn {
351 Once,
353 Periodic {
355 interval: Value,
356 count: Value,
357 total: Option<u32>,
358 max_alive: Option<u32>,
359 },
360 Distance {
362 step: Value,
363 count: Value,
364 total: Option<u32>,
365 max_alive: Option<u32>,
366 },
367}
368
369#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
371pub enum Axis {
372 X,
373 Y,
374 Z,
375}
376
377#[derive(Clone, Debug, PartialEq, Serialize)]
379pub enum Shape {
380 Point,
382 Disk { radius: Value, concentration: Value },
385 Rectangle { size: [Value; 2], concentration: Value },
387 Sphere { radius: Value, volume: bool },
389 Box { size: [Value; 3], volume: bool },
391 Cylinder {
393 radius: Value,
394 height: Value,
395 volume: bool,
396 axis: Axis,
397 },
398}
399
400#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
402pub enum Heading {
403 Shape { facing: [f32; 3] },
407 World([f32; 3]),
409 Frame([f32; 3]),
411}
412
413#[derive(Clone, Debug, PartialEq, Serialize)]
415pub enum Spread {
416 None,
418 Cone { angle: Value, concentration: Value },
421 Ellipse {
423 x: Value,
424 y: Value,
425 concentration: Value,
426 },
427 Rectangle {
429 x: Value,
430 y: Value,
431 concentration: Value,
432 },
433}
434
435#[derive(Clone, Debug, PartialEq, Serialize)]
437pub enum Drive {
438 None,
440 Speed { speed: Value, multiplier: Value },
443 Acceleration { acceleration: Value, multiplier: Value },
446}
447
448#[derive(Clone, Debug, PartialEq, Serialize)]
450pub struct Turns {
451 pub interval: f32,
453 pub max_angle: Value,
455}
456
457#[derive(Clone, Debug, PartialEq, Serialize)]
459pub struct Drag {
460 pub factor: Value,
462 pub rotation: bool,
464}
465
466#[derive(Clone, Debug, PartialEq, Serialize)]
468pub struct Movement {
469 pub drive: Drive,
471 pub gravity: Value,
473 pub turns: Option<Turns>,
475 pub drag: Option<Drag>,
477 pub fields: Option<FieldResponse>,
479}
480
481#[derive(Clone, Debug, PartialEq, Serialize)]
485pub struct FieldResponse {
486 pub speed: Value,
487 pub speed_multiplier: Value,
488 pub acceleration: Value,
489 pub acceleration_multiplier: Value,
490}
491
492#[derive(Clone, Debug, PartialEq, Serialize)]
494pub struct Law {
495 pub lifetime: Option<Value>,
497 pub attachment: Attachment,
499 pub movement: Option<Movement>,
501 pub initial_speed: Value,
504 pub size_scale: [Value; 3],
507 pub tint: Value,
509}
510
511#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
513pub enum Blend {
514 Replace,
516 Alpha,
518 Additive,
520 Subtractive,
522 Multiply,
524}
525
526#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
528pub enum Orientation {
529 CameraPlane,
531 Camera,
533 CameraYaw,
535 FrameYaw,
537 World([f32; 3]),
539 Frame([f32; 3]),
541 Heading,
543}
544
545#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
547pub struct Map {
548 pub texture: Entity,
551}
552
553#[derive(Clone, Debug, PartialEq, Serialize)]
555pub struct Atlas {
556 pub columns: u32,
558 pub cells: u32,
560 pub cell: Value,
562 pub blend_cells: bool,
564}
565
566#[derive(Clone, Debug, PartialEq, Serialize)]
569pub struct Term {
570 pub value: Value,
571 pub clamp: bool,
572 pub channels: Channels,
573}
574
575#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
577pub enum Channels {
578 Rgba,
579 Rgb,
580 Alpha,
581}
582
583#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
587pub struct DistanceFade {
588 pub near: Option<[f32; 2]>,
589 pub far: Option<[f32; 2]>,
590 pub cut_near: Option<f32>,
591 pub cut_far: Option<f32>,
592}
593
594impl DistanceFade {
595 pub fn is_none(&self) -> bool {
597 *self == DistanceFade::default()
598 }
599}
600
601#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
603pub struct SoftEdge {
604 pub distance: f32,
605}
606
607#[derive(Clone, Debug, PartialEq, Serialize)]
609pub struct Quad {
610 pub map: Option<Map>,
612 pub blend: Blend,
613 pub orientation: Orientation,
614 pub size: [Value; 2],
616 pub uniform: bool,
618 pub size_variance: [f32; 2],
620 pub offset: [Value; 3],
622 pub rotation: [Value; 3],
624 pub spin: [Value; 3],
625 pub scale_before_rotation: bool,
627 pub atlas: Option<Atlas>,
628 pub tint: Vec<Term>,
630 pub alpha_fade_threshold: Value,
632 pub alpha_cutoff: Value,
634 pub soft: Option<SoftEdge>,
635 pub distance: Option<DistanceFade>,
636 pub octagonal: bool,
638 pub lit: Option<Lit>,
640 pub bloom: Option<[f32; 4]>,
642 pub premultiply_alpha: bool,
644 pub depth_bias: Value,
646}
647
648#[derive(Clone, Debug, PartialEq, Serialize)]
650pub struct Lit {
651 pub normal_map: Option<Map>,
652 pub specular_map: Option<Map>,
653 pub glossiness: f32,
654 pub specularity: f32,
655 pub shadow: f32,
657}
658
659#[derive(Clone, Debug, PartialEq, Serialize)]
663pub struct LayeredQuad {
664 pub quad: Quad,
667 pub base: Layer,
669 pub layers: Vec<LaidLayer>,
671}
672
673#[derive(Clone, Debug, PartialEq, Serialize)]
675pub struct Layer {
676 pub map: Option<Map>,
677 pub tint: Value,
678 pub scroll: [Value; 2],
680 pub offset: [Value; 2],
681 pub scale: [Value; 2],
682}
683
684#[derive(Clone, Debug, PartialEq, Serialize)]
686pub struct LaidLayer {
687 pub layer: Layer,
688 pub target: LayerTarget,
690 pub operation: LayerOperation,
692}
693
694#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
696pub enum LayerTarget {
697 Colour,
699 Opacity,
702}
703
704#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
707pub enum LayerOperation {
708 Multiply,
710 Add,
712 Overlay,
715}
716
717impl LayerOperation {
718 pub fn apply(self, b: f32, l: f32, w: f32) -> f32 {
720 match self {
721 LayerOperation::Multiply => b * (1.0 + (l - 1.0) * w),
722 LayerOperation::Add => b + l * w,
723 LayerOperation::Overlay => {
724 let overlay = if b < 0.5 {
725 2.0 * b * l
726 } else {
727 1.0 - 2.0 * (1.0 - b) * (1.0 - l)
728 };
729 b + (overlay - b) * w
730 }
731 }
732 }
733}
734
735pub fn compose_layers(
741 premultiply_alpha: bool,
742 base: [f32; 4],
743 laid: &[(LayerTarget, LayerOperation, [f32; 4])],
744) -> [f32; 4] {
745 let colour = |s: [f32; 4]| {
746 let k = if premultiply_alpha { s[3] } else { 1.0 };
747 [s[0] * k, s[1] * k, s[2] * k]
748 };
749 let [r, g, b] = colour(base);
750 let mut out = [r, g, b, base[3]];
751 for &(target, operation, sample) in laid {
752 let c = colour(sample);
753 let w = sample[3];
754 match target {
755 LayerTarget::Colour => {
756 for (o, l) in out.iter_mut().zip(c) {
757 *o = operation.apply(*o, l, w);
758 }
759 }
760 LayerTarget::Opacity => {
761 let brightness = ((c[0] + c[1] + c[2]) / 3.0).clamp(0.0, 1.0);
762 out[3] = operation.apply(out[3], brightness, w);
763 }
764 }
765 }
766 out
767}
768
769#[derive(Clone, Debug, PartialEq, Serialize)]
771pub struct Point {
772 pub map: Option<Map>,
773 pub blend: Blend,
774 pub size: Value,
776 pub tint: Vec<Term>,
777 pub alpha_cutoff: Value,
778 pub soft: Option<SoftEdge>,
779 pub distance: Option<DistanceFade>,
780 pub bloom: Option<[f32; 4]>,
781}
782
783#[derive(Clone, Debug, PartialEq, Serialize)]
785pub struct MeshParticle {
786 pub mesh: Entity,
788 pub clip: Option<MeshClip>,
790 pub blend: Blend,
791 pub orientation: MeshOrientation,
792 pub size: [Value; 3],
794 pub size_variance: [f32; 3],
795 pub uniform: bool,
796 pub rotation: [Value; 3],
797 pub spin: [Value; 3],
798 pub tint: Vec<Term>,
799 pub scroll: [Value; 2],
801 pub offset: [Value; 2],
802 pub atlas: Option<Atlas>,
803 pub soft: Option<SoftEdge>,
804 pub distance: Option<DistanceFade>,
805 pub lit: bool,
806}
807
808#[derive(Clone, Debug, PartialEq, Serialize)]
810pub struct MeshClip {
811 pub clip: Entity,
812 pub speed: f32,
813 pub looped: bool,
814}
815
816#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
818pub enum MeshOrientation {
819 Frame,
821 World,
823 Heading { forward: [f32; 3] },
828 CameraPlane,
830 CameraYaw,
832}
833
834#[derive(Clone, Debug, PartialEq, Serialize)]
836pub struct Trail {
837 pub map: Option<Map>,
838 pub blend: Blend,
839 pub orientation: TrailOrientation,
840 pub width: Value,
842 pub width_scale: Value,
843 pub segment_interval: f32,
846 pub segment_life: f32,
847 pub max_segments: u32,
848 pub subdivisions: u32,
849 pub texture_fraction: Value,
852 pub scroll_u: Value,
853 pub variance_u: f32,
854 pub variance_v: Value,
855 pub attached_uv: bool,
857 pub dynamic_opacity: bool,
859 pub fade_start: Value,
862 pub fade_end: Value,
863 pub alpha_fade_threshold: Value,
864 pub alpha_cutoff: Value,
865 pub atlas: Option<Atlas>,
866 pub tint: Vec<Term>,
867 pub fade_out: f32,
869 pub distortion: Option<TrailDistortion>,
871 pub lit: Option<Lit>,
872 pub premultiply_alpha: bool,
873 pub soft: Option<SoftEdge>,
874 pub distance: DistanceFade,
875 pub bloom: Option<[f32; 4]>,
876}
877
878#[derive(Clone, Debug, PartialEq, Serialize)]
880pub struct TrailDistortion {
881 pub normal_map: Option<Map>,
882 pub intensity: Value,
883}
884
885#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
887pub enum TrailOrientation {
888 Camera,
890 Frame,
893 World,
895}
896
897#[derive(Clone, Debug, PartialEq, Serialize)]
899pub struct Streak {
900 pub map: Option<Map>,
901 pub blend: Blend,
902 pub width: Option<Value>,
904 pub length: Value,
906 pub start_colour: Value,
908 pub end_colour: Value,
909 pub tint: Vec<Term>,
910 pub alpha_cutoff: Value,
911 pub soft: Option<SoftEdge>,
912 pub distance: DistanceFade,
913 pub bloom: Option<[f32; 4]>,
914}
915
916#[derive(Clone, Debug, PartialEq, Serialize)]
918pub struct Refraction {
919 pub mode: RefractionMode,
920 pub shape: RefractionShape,
921 pub orientation: Orientation,
922 pub normal_map: Option<Map>,
924 pub mask: Option<Map>,
925 pub offset: [Value; 3],
928 pub size: [Value; 3],
929 pub uniform: bool,
930 pub size_variance: [f32; 3],
931 pub intensity: Value,
937 pub stir_speed: Value,
938 pub radius: Value,
939 pub normal_offset: [Value; 2],
942 pub normal_scroll: [Value; 2],
943 pub normal_scale: [Value; 2],
944 pub tint: Vec<Term>,
945 pub blend: Blend,
946 pub alpha_cutoff: Value,
947 pub distance: DistanceFade,
948}
949
950#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
952pub enum RefractionMode {
953 Stir,
955 Push,
957 Twist,
959}
960
961#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
963pub enum RefractionShape {
964 Rectangle,
965 Hemiellipsoid,
967 Ellipsoid,
968}
969
970#[derive(Clone, Debug, PartialEq, Serialize)]
972pub struct RadialBlur {
973 pub mask: Option<Map>,
975 pub blend: Blend,
976 pub offset: [Value; 3],
979 pub size: [Value; 2],
980 pub uniform: bool,
981 pub radius: Value,
983 pub iterations: u32,
984 pub tint: Vec<Term>,
985 pub alpha_cutoff: Value,
986 pub distance: DistanceFade,
987}
988
989#[derive(Clone, Debug, PartialEq, Serialize)]
991pub struct LightDraw {
992 pub kind: LightKind,
993 pub colour: Value,
995 pub intensity: Value,
997 pub range: Value,
999 pub shadows: bool,
1000 pub fade_out: f32,
1002}
1003
1004#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
1006pub enum LightKind {
1007 Point,
1008 Spot {
1010 inner: f32,
1011 outer: f32,
1012 },
1013}
1014
1015#[derive(Clone, Debug, PartialEq, Serialize)]
1017pub struct Flare {
1018 pub layers: Vec<FlareLayer>,
1019 pub blend: Blend,
1020 pub source_size: f32,
1022 pub opacity_transition: f32,
1023}
1024
1025#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
1027pub enum Reflection {
1028 None,
1030 Radial,
1032 Horizontal,
1034}
1035
1036#[derive(Clone, Debug, PartialEq, Serialize)]
1038pub struct FlareLayer {
1039 pub map: Option<Map>,
1040 pub count: u32,
1041 pub width: Value,
1043 pub height: Value,
1044 pub uniform: bool,
1045 pub scale_variation: [f32; 2],
1046 pub colour: Value,
1048 pub colour_multiplier: [f32; 4],
1049 pub reflection: Reflection,
1050 pub offset: f32,
1052 pub offset_variation: f32,
1053 pub attenuation_radius: f32,
1055 pub bloom: Option<[f32; 4]>,
1056}
1057
1058#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
1060pub enum ForceKind {
1061 Wind,
1063 Gravity,
1065 Turbulence,
1067 Cancel,
1069}
1070
1071#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
1073pub enum ForceVolume {
1074 Boundless,
1076 Box {
1077 half: [f32; 3],
1078 },
1079 Sphere {
1080 radius: f32,
1081 },
1082 Cylinder {
1083 radius: f32,
1084 half_height: f32,
1085 },
1086}
1087
1088#[derive(Clone, Debug, PartialEq, Serialize)]
1090pub struct ForceField {
1091 pub kind: ForceKind,
1092 pub volume: ForceVolume,
1093 pub direction: [f32; 3],
1095 pub strength: Value,
1098 pub multiplier: f32,
1099 pub random_multiplier: [f32; 2],
1100 pub fade_out: f32,
1102}
1103
1104#[derive(Clone, Debug, PartialEq, Serialize)]
1107pub struct Ripples {
1108 pub map: Option<Map>,
1109 pub depth: f32,
1110 pub size: f32,
1111 pub descent: f32,
1112 pub duration: f32,
1113}
1114
1115#[derive(Clone, Debug, PartialEq, Serialize)]
1117pub enum Draw {
1118 Quad(Quad),
1119 LayeredQuad(Box<LayeredQuad>),
1120 Point(Point),
1121 Mesh(MeshParticle),
1122 Trail(Trail),
1123 Streak(Streak),
1124 Refraction(Refraction),
1125 RadialBlur(RadialBlur),
1126 Light(LightDraw),
1127 Flare(Flare),
1128 Field(ForceField),
1129 Ripples(Ripples),
1130}
1131
1132#[derive(Clone, Debug, PartialEq, Serialize)]
1134pub struct Emitter {
1135 pub name: Option<String>,
1137 pub parent: Option<u32>,
1139 pub placement: Transform,
1141 pub jitter_translation: [f32; 3],
1143 pub jitter_rotation: [f32; 3],
1144 pub attachment: Attachment,
1146 pub delay: f32,
1148 pub duration: Option<f32>,
1149 pub motion: Option<NodeMotion>,
1151 pub spawn: Spawn,
1152 pub shape: Shape,
1153 pub heading: Heading,
1154 pub spread: Spread,
1155 pub law: Law,
1156 pub draw: Option<Draw>,
1158 pub spawns: Vec<Entity>,
1161 pub states: Vec<u32>,
1163 pub order: f32,
1165 pub lod_range: Option<[f32; 2]>,
1168 pub copies: Option<Pick>,
1171}
1172
1173#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
1175pub enum Pick {
1176 All,
1178 One,
1180}
1181
1182#[derive(Clone, Debug, PartialEq, Serialize)]
1184pub struct NodeMotion {
1185 pub forward: [f32; 3],
1187 pub speed: Value,
1190 pub speed_multiplier: Value,
1191 pub acceleration: Value,
1192 pub acceleration_multiplier: Value,
1193 pub lift: Value,
1195 pub lift_in_world: bool,
1196 pub displacement: Option<Value>,
1198 pub spin: [Value; 3],
1200 pub spin_multiplier: [Value; 3],
1201 pub align: bool,
1203 pub turns: Option<Turns>,
1204 pub drag: Option<Drag>,
1205 pub min_speed: Option<f32>,
1208 pub max_speed: Option<f32>,
1209}
1210
1211#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
1213pub enum Termination {
1214 Natural,
1216 Fade(f32),
1218 Instant,
1220}
1221
1222#[derive(Clone, Debug, PartialEq, Serialize)]
1224pub struct ParticleSystem {
1225 pub seed: u64,
1227 pub emitters: Vec<Emitter>,
1229 pub inputs: Vec<Input>,
1231 pub states: u32,
1233 pub warmup: f32,
1235 pub time_scale: Option<Value>,
1237 pub max_view_distance: Option<f32>,
1239 pub termination: Termination,
1241 pub end: Option<f32>,
1244 pub lost: Vec<(String, u32)>,
1246}
1247
1248impl Component for ParticleSystem {
1249 const NAME: &'static str = "core.particles";
1250 const GEOMETRY: crate::Geometry = crate::Geometry::None;
1251 fn to_json(&self) -> Result<Option<Json>, JsonError> {
1252 crate::json::to_value(self).map(Some)
1253 }
1254 fn references(&self, out: &mut Vec<Entity>) {
1256 for e in &self.emitters {
1257 out.extend(e.spawns.iter().copied());
1258 if let Some(d) = &e.draw {
1259 d.references(out);
1260 }
1261 }
1262 }
1263 fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
1266 for (i, e) in self.emitters.iter().enumerate() {
1267 let at = |what: &str| format!("emitter {i}: {what}");
1268 if let Some(p) = e.parent
1269 && usize::try_from(p).map_or(true, |p| p >= i)
1270 {
1271 return Err(at(&format!("parent {p} is not an earlier emitter")));
1272 }
1273 let finite = e
1274 .placement
1275 .cols
1276 .iter()
1277 .flatten()
1278 .chain(&e.jitter_translation)
1279 .chain(&e.jitter_rotation);
1280 if !finite.chain([&e.delay, &e.order]).all(|v| v.is_finite()) {
1281 return Err(at("a placement that is not finite"));
1282 }
1283 if e.states.iter().any(|&s| s >= self.states.max(1)) {
1284 return Err(at("a state the system does not have"));
1285 }
1286 e.check(self.inputs.len()).map_err(|m| at(&m))?;
1287 }
1288 if self.end.is_some_and(|t| !t.is_finite() || t < 0.0) {
1289 return Err("an end that is not a finite time".into());
1290 }
1291 if !(self.warmup.is_finite() && self.warmup >= 0.0) {
1292 return Err(format!("a warm-up of {} seconds", self.warmup));
1293 }
1294 if self
1295 .max_view_distance
1296 .is_some_and(|d| !(d.is_finite() && d > 0.0))
1297 {
1298 return Err("a view distance that is not a positive number".into());
1299 }
1300 if let Some(v) = &self.time_scale {
1301 v.check("time scale", self.inputs.len())?;
1302 }
1303 if let Termination::Fade(t) = self.termination
1304 && !(t.is_finite() && t >= 0.0)
1305 {
1306 return Err(format!("a fade of {t} seconds"));
1307 }
1308 Ok(())
1309 }
1310}
1311
1312impl Emitter {
1313 fn check(&self, inputs: usize) -> Result<(), String> {
1314 let mut values: Vec<(&str, &Value)> = vec![
1315 ("initial speed", &self.law.initial_speed),
1316 ("tint", &self.law.tint),
1317 ("size scale x", &self.law.size_scale[0]),
1318 ("size scale y", &self.law.size_scale[1]),
1319 ("size scale z", &self.law.size_scale[2]),
1320 ];
1321 if let Some(v) = &self.law.lifetime {
1322 values.push(("lifetime", v));
1323 }
1324 match &self.spawn {
1325 Spawn::Once => {}
1326 Spawn::Periodic { interval, count, .. } => {
1327 values.extend([("interval", interval), ("count", count)])
1328 }
1329 Spawn::Distance { step, count, .. } => values.extend([("step", step), ("count", count)]),
1330 }
1331 match &self.shape {
1332 Shape::Point => {}
1333 Shape::Disk {
1334 radius,
1335 concentration,
1336 } => values.extend([("radius", radius), ("concentration", concentration)]),
1337 Shape::Rectangle { size, concentration } => {
1338 values.extend(size.iter().map(|v| ("size", v)));
1339 values.push(("concentration", concentration));
1340 }
1341 Shape::Sphere { radius, .. } => values.push(("radius", radius)),
1342 Shape::Box { size, .. } => values.extend(size.iter().map(|v| ("size", v))),
1343 Shape::Cylinder { radius, height, .. } => values.extend([("radius", radius), ("height", height)]),
1344 }
1345 match &self.spread {
1346 Spread::None => {}
1347 Spread::Cone { angle, concentration } => {
1348 values.extend([("angle", angle), ("concentration", concentration)])
1349 }
1350 Spread::Ellipse { x, y, concentration } | Spread::Rectangle { x, y, concentration } => {
1351 values.extend([("angle", x), ("angle", y), ("concentration", concentration)]);
1352 }
1353 }
1354 if let Some(m) = &self.law.movement {
1355 match &m.drive {
1356 Drive::None => {}
1357 Drive::Speed { speed, multiplier } => {
1358 values.extend([("speed", speed), ("speed multiplier", multiplier)])
1359 }
1360 Drive::Acceleration {
1361 acceleration,
1362 multiplier,
1363 } => {
1364 values.extend([
1365 ("acceleration", acceleration),
1366 ("acceleration multiplier", multiplier),
1367 ]);
1368 }
1369 }
1370 values.push(("gravity", &m.gravity));
1371 if let Some(t) = &m.turns {
1372 values.push(("turn angle", &t.max_angle));
1373 }
1374 if let Some(d) = &m.drag {
1375 values.push(("drag", &d.factor));
1376 }
1377 if let Some(f) = &m.fields {
1378 values.extend([
1379 ("field speed", &f.speed),
1380 ("field speed multiplier", &f.speed_multiplier),
1381 ("field acceleration", &f.acceleration),
1382 ("field acceleration multiplier", &f.acceleration_multiplier),
1383 ]);
1384 }
1385 }
1386 if let Some(n) = &self.motion {
1387 for (what, limit) in [("node min speed", n.min_speed), ("node max speed", n.max_speed)] {
1388 if limit.is_some_and(|x| !x.is_finite()) {
1389 return Err(format!("{what} {limit:?} is not finite"));
1390 }
1391 }
1392 if let (Some(min), Some(max)) = (n.min_speed, n.max_speed)
1393 && min > max
1394 {
1395 return Err(format!("node min speed {min} is above its max speed {max}"));
1396 }
1397 values.extend([
1398 ("node speed", &n.speed),
1399 ("node speed multiplier", &n.speed_multiplier),
1400 ("node acceleration", &n.acceleration),
1401 ("node acceleration multiplier", &n.acceleration_multiplier),
1402 ("node lift", &n.lift),
1403 ]);
1404 values.extend(n.displacement.iter().map(|v| ("node displacement", v)));
1405 values.extend(n.spin.iter().chain(&n.spin_multiplier).map(|v| ("node spin", v)));
1406 if let Some(t) = &n.turns {
1407 values.push(("node turn angle", &t.max_angle));
1408 }
1409 if let Some(d) = &n.drag {
1410 values.push(("node drag", &d.factor));
1411 }
1412 }
1413 for (what, v) in values {
1414 v.check(what, inputs)?;
1415 }
1416 if let Some(d) = &self.draw {
1417 d.check(inputs)?;
1418 }
1419 Ok(())
1420 }
1421}
1422
1423fn push_map(out: &mut Vec<Entity>, m: &Option<Map>) {
1424 out.extend(m.iter().map(|m| m.texture));
1425}
1426
1427impl Draw {
1428 fn references(&self, out: &mut Vec<Entity>) {
1429 match self {
1430 Draw::Quad(q) => {
1431 push_map(out, &q.map);
1432 if let Some(l) = &q.lit {
1433 push_map(out, &l.normal_map);
1434 push_map(out, &l.specular_map);
1435 }
1436 }
1437 Draw::LayeredQuad(q) => {
1438 push_map(out, &q.quad.map);
1439 if let Some(l) = &q.quad.lit {
1440 push_map(out, &l.normal_map);
1441 push_map(out, &l.specular_map);
1442 }
1443 push_map(out, &q.base.map);
1444 for l in &q.layers {
1445 push_map(out, &l.layer.map);
1446 }
1447 }
1448 Draw::Point(p) => push_map(out, &p.map),
1449 Draw::Mesh(m) => {
1450 out.push(m.mesh);
1451 out.extend(m.clip.iter().map(|c| c.clip));
1452 }
1453 Draw::Trail(t) => {
1454 push_map(out, &t.map);
1455 if let Some(d) = &t.distortion {
1456 push_map(out, &d.normal_map);
1457 }
1458 if let Some(l) = &t.lit {
1459 push_map(out, &l.normal_map);
1460 push_map(out, &l.specular_map);
1461 }
1462 }
1463 Draw::Streak(s) => push_map(out, &s.map),
1464 Draw::Refraction(r) => {
1465 push_map(out, &r.normal_map);
1466 push_map(out, &r.mask);
1467 }
1468 Draw::RadialBlur(b) => push_map(out, &b.mask),
1469 Draw::Flare(f) => {
1470 for l in &f.layers {
1471 push_map(out, &l.map);
1472 }
1473 }
1474 Draw::Ripples(r) => push_map(out, &r.map),
1475 Draw::Light(_) | Draw::Field(_) => {}
1476 }
1477 }
1478
1479 fn values<'a>(&'a self, out: &mut Vec<(&'static str, &'a Value)>) {
1481 fn terms<'a>(out: &mut Vec<(&'static str, &'a Value)>, t: &'a [Term]) {
1482 out.extend(t.iter().map(|t| ("tint", &t.value)));
1483 }
1484 fn atlas<'a>(out: &mut Vec<(&'static str, &'a Value)>, a: &'a Option<Atlas>) {
1485 out.extend(a.iter().map(|a| ("atlas cell", &a.cell)));
1486 }
1487 fn quad_values<'a>(out: &mut Vec<(&'static str, &'a Value)>, q: &'a Quad) {
1488 out.extend(q.size.iter().map(|v| ("size", v)));
1489 out.extend(q.offset.iter().map(|v| ("offset", v)));
1490 out.extend(q.rotation.iter().map(|v| ("rotation", v)));
1491 out.extend(q.spin.iter().map(|v| ("spin", v)));
1492 out.extend([
1493 ("alpha fade threshold", &q.alpha_fade_threshold),
1494 ("alpha cutoff", &q.alpha_cutoff),
1495 ]);
1496 out.push(("depth bias", &q.depth_bias));
1497 terms(out, &q.tint);
1498 atlas(out, &q.atlas);
1499 }
1500 match self {
1501 Draw::Quad(q) => quad_values(out, q),
1502 Draw::LayeredQuad(q) => {
1503 quad_values(out, &q.quad);
1504 for l in std::iter::once(&q.base).chain(q.layers.iter().map(|l| &l.layer)) {
1505 out.push(("layer tint", &l.tint));
1506 out.extend(l.scroll.iter().map(|v| ("layer scroll", v)));
1507 out.extend(l.offset.iter().map(|v| ("layer offset", v)));
1508 out.extend(l.scale.iter().map(|v| ("layer scale", v)));
1509 }
1510 }
1511 Draw::Point(p) => {
1512 out.extend([("size", &p.size), ("alpha cutoff", &p.alpha_cutoff)]);
1513 terms(out, &p.tint);
1514 }
1515 Draw::Mesh(m) => {
1516 out.extend(m.size.iter().map(|v| ("size", v)));
1517 out.extend(m.rotation.iter().map(|v| ("rotation", v)));
1518 out.extend(m.spin.iter().map(|v| ("spin", v)));
1519 out.extend(m.scroll.iter().map(|v| ("scroll", v)));
1520 out.extend(m.offset.iter().map(|v| ("offset", v)));
1521 terms(out, &m.tint);
1522 atlas(out, &m.atlas);
1523 }
1524 Draw::Trail(t) => {
1525 out.extend([
1526 ("width", &t.width),
1527 ("width scale", &t.width_scale),
1528 ("texture fraction", &t.texture_fraction),
1529 ("scroll", &t.scroll_u),
1530 ("variance", &t.variance_v),
1531 ("fade start", &t.fade_start),
1532 ("fade end", &t.fade_end),
1533 ("alpha fade threshold", &t.alpha_fade_threshold),
1534 ("alpha cutoff", &t.alpha_cutoff),
1535 ]);
1536 out.extend(t.distortion.iter().map(|d| ("distortion", &d.intensity)));
1537 terms(out, &t.tint);
1538 atlas(out, &t.atlas);
1539 }
1540 Draw::Streak(s) => {
1541 out.extend([
1542 ("length", &s.length),
1543 ("start colour", &s.start_colour),
1544 ("end colour", &s.end_colour),
1545 ("alpha cutoff", &s.alpha_cutoff),
1546 ]);
1547 out.extend(s.width.iter().map(|w| ("width", w)));
1548 terms(out, &s.tint);
1549 }
1550 Draw::Refraction(r) => {
1551 out.extend(r.offset.iter().map(|v| ("offset", v)));
1552 out.extend(r.size.iter().map(|v| ("size", v)));
1553 out.extend([
1554 ("intensity", &r.intensity),
1555 ("stir speed", &r.stir_speed),
1556 ("radius", &r.radius),
1557 ("alpha cutoff", &r.alpha_cutoff),
1558 ]);
1559 out.extend(
1560 r.normal_offset
1561 .iter()
1562 .chain(&r.normal_scroll)
1563 .chain(&r.normal_scale)
1564 .map(|v| ("normal map", v)),
1565 );
1566 terms(out, &r.tint);
1567 }
1568 Draw::RadialBlur(b) => {
1569 out.extend(b.offset.iter().map(|v| ("offset", v)));
1570 out.extend(b.size.iter().map(|v| ("size", v)));
1571 out.extend([("radius", &b.radius), ("alpha cutoff", &b.alpha_cutoff)]);
1572 terms(out, &b.tint);
1573 }
1574 Draw::Light(l) => out.extend([
1575 ("colour", &l.colour),
1576 ("intensity", &l.intensity),
1577 ("range", &l.range),
1578 ]),
1579 Draw::Flare(f) => {
1580 for l in &f.layers {
1581 out.extend([
1582 ("flare width", &l.width),
1583 ("flare height", &l.height),
1584 ("flare colour", &l.colour),
1585 ]);
1586 }
1587 }
1588 Draw::Field(f) => out.push(("force", &f.strength)),
1589 Draw::Ripples(_) => {}
1590 }
1591 }
1592
1593 fn check(&self, inputs: usize) -> Result<(), String> {
1594 let mut values = Vec::new();
1595 self.values(&mut values);
1596 for (what, v) in values {
1597 v.check(what, inputs)?;
1598 }
1599 let atlas = match self {
1600 Draw::Quad(q) => q.atlas.as_ref(),
1601 Draw::LayeredQuad(q) => q.quad.atlas.as_ref(),
1602 Draw::Mesh(m) => m.atlas.as_ref(),
1603 Draw::Trail(t) => t.atlas.as_ref(),
1604 _ => None,
1605 };
1606 if atlas.is_some_and(|a| a.columns == 0 || a.cells == 0) {
1607 return Err("an atlas of no cells".into());
1608 }
1609 if let Draw::Quad(q) = self
1610 && let Orientation::World(n) | Orientation::Frame(n) = q.orientation
1611 && !n.iter().all(|v| v.is_finite())
1612 {
1613 return Err("an orientation that is not finite".into());
1614 }
1615 if let Draw::Mesh(m) = self
1616 && let MeshOrientation::Heading { forward } = m.orientation
1617 && !(forward.iter().all(|v| v.is_finite())
1618 && (forward.iter().map(|v| v * v).sum::<f32>().sqrt() - 1.0).abs() <= 1e-4)
1619 {
1620 return Err("a mesh particle's forward direction that is not a unit vector".into());
1621 }
1622 if let Draw::Trail(t) = self
1623 && !(t.segment_interval.is_finite() && t.segment_life.is_finite() && t.fade_out.is_finite())
1624 {
1625 return Err("a trail time that is not finite".into());
1626 }
1627 if let Draw::LayeredQuad(q) = self
1628 && q.quad.map.is_some()
1629 {
1630 return Err("a layered quad with a texture of its own (its layers are its texture)".into());
1631 }
1632 Ok(())
1633 }
1634}
1635
1636#[cfg(test)]
1637mod tests {
1638 use super::*;
1639
1640 fn key(at: f32, v: f32) -> Key {
1641 Key {
1642 at,
1643 value: [v, 0.0, 0.0, 0.0],
1644 handle_in: [0.0; 4],
1645 handle_out: [0.0; 4],
1646 }
1647 }
1648
1649 fn curve(interpolation: Interpolation, keys: Vec<Key>) -> Curve {
1650 Curve {
1651 width: 1,
1652 interpolation,
1653 looped: false,
1654 keys,
1655 }
1656 }
1657
1658 #[test]
1659 fn curves_hold_run_and_loop() {
1660 let lin = curve(Interpolation::Linear, vec![key(0.0, 0.0), key(2.0, 10.0)]);
1661 assert_eq!(lin.value_at(-5.0)[0], 0.0);
1662 assert_eq!(lin.value_at(1.0)[0], 5.0);
1663 assert_eq!(lin.value_at(9.0)[0], 10.0);
1664 let step = curve(
1665 Interpolation::Step,
1666 vec![key(0.0, 1.0), key(2.0, 2.0), key(4.0, 3.0)],
1667 );
1668 assert_eq!(step.value_at(1.9)[0], 1.0);
1669 assert_eq!(step.value_at(2.0)[0], 2.0);
1670 let mut looped = lin.clone();
1671 looped.looped = true;
1672 assert_eq!(looped.value_at(3.0)[0], 5.0);
1673 let mut still = curve(Interpolation::Linear, vec![key(0.0, 7.0)]);
1674 still.looped = true;
1675 assert_eq!(still.value_at(100.0)[0], 7.0);
1676 }
1677
1678 #[test]
1679 fn a_bezier_segment_is_the_cubic_through_its_handles() {
1680 let mut a = key(0.0, 0.0);
1681 a.handle_out = [1.0, 0.0, 0.0, 0.0];
1682 let mut b = key(1.0, 1.0);
1683 b.handle_in = [1.0, 0.0, 0.0, 0.0];
1684 let c = curve(Interpolation::Bezier, vec![a, b]);
1685 let want = |u: f32| {
1687 let k = 1.0 - u;
1688 3.0 * k * k * u + u * u * u
1689 };
1690 for u in [0.0f32, 0.25, 0.5, 0.75, 1.0] {
1691 assert!((c.value_at(u)[0] - want(u)).abs() < 1e-6, "{u}");
1692 }
1693 }
1694
1695 #[test]
1696 fn draws_are_deterministic_uniform_and_keyed() {
1697 let a = unit(7, 1, 2, 3, 0);
1698 assert_eq!(a, unit(7, 1, 2, 3, 0));
1699 assert!((0.0..1.0).contains(&a));
1700 for other in [
1702 unit(8, 1, 2, 3, 0),
1703 unit(7, 2, 2, 3, 0),
1704 unit(7, 1, 3, 3, 0),
1705 unit(7, 1, 2, 4, 0),
1706 unit(7, 1, 2, 3, 1),
1707 ] {
1708 assert_ne!(a, other);
1709 }
1710 let n = 20_000;
1712 let mean = (0..n).map(|i| f64::from(unit(1, 0, i, 0, 0))).sum::<f64>() / n as f64;
1713 assert!((mean - 0.5).abs() < 0.01, "{mean}");
1714 }
1715
1716 #[test]
1717 fn variations_apply_in_order_within_their_bounds() {
1718 let mut v = Value::constant(&[10.0]);
1719 v.variation.push(Variation::Delta {
1720 max: [2.0; 4],
1721 stream: 0,
1722 });
1723 v.variation.push(Variation::Fraction {
1724 max: [0.1; 4],
1725 stream: 1,
1726 });
1727 let none = |_: u32| 0.0;
1728 for scope in 0..200 {
1729 let x = v.eval(0.0, 5, 0, scope, &none)[0];
1730 assert!((8.0 * 0.9..=12.0 * 1.1).contains(&x), "{x}");
1731 }
1732 assert_eq!(v.eval(0.0, 5, 0, 9, &none), v.eval(3.0, 5, 0, 9, &none));
1734 let mut scaled = Value::constant(&[4.0]);
1736 scaled.variation.push(Variation::Scaled {
1737 input: 0,
1738 factor: curve(Interpolation::Linear, vec![key(0.0, 0.0), key(1.0, 1.0)]),
1739 });
1740 assert_eq!(scaled.eval(0.0, 1, 0, 0, &|_| 0.25)[0], 1.0);
1741 }
1742
1743 #[test]
1744 fn a_constant_value_is_fixed() {
1745 let v = Value::constant(&[1.0, 2.0]);
1746 assert!(v.is_fixed());
1747 assert_eq!(v.curve.value_at(9.0)[..2], [1.0, 2.0]);
1748 let mut w = v.clone();
1749 w.variation.push(Variation::Delta {
1750 max: [0.1; 4],
1751 stream: 3,
1752 });
1753 assert!(!w.is_fixed());
1754 }
1755
1756 #[test]
1757 fn a_mesh_particle_turned_along_its_travel_names_a_unit_forward() {
1758 let zero = || Value::constant(&[0.0]);
1759 let mesh = |forward: [f32; 3]| {
1760 Draw::Mesh(MeshParticle {
1761 mesh: Entity::from_index(1),
1762 clip: None,
1763 blend: Blend::Alpha,
1764 orientation: MeshOrientation::Heading { forward },
1765 size: [zero(), zero(), zero()],
1766 size_variance: [1.0; 3],
1767 uniform: true,
1768 rotation: [zero(), zero(), zero()],
1769 spin: [zero(), zero(), zero()],
1770 tint: Vec::new(),
1771 scroll: [zero(), zero()],
1772 offset: [zero(), zero()],
1773 atlas: None,
1774 soft: None,
1775 distance: None,
1776 lit: false,
1777 })
1778 };
1779 assert!(mesh([0.0, 1.0, 0.0]).check(0).is_ok());
1780 assert!(mesh([0.0, 0.0, -1.0]).check(0).is_ok());
1781 for bad in [[0.0; 3], [0.0, 2.0, 0.0], [f32::NAN, 0.0, 0.0]] {
1782 assert!(mesh(bad).check(0).is_err(), "{bad:?}");
1783 }
1784 }
1785
1786 #[test]
1787 fn laid_layers_change_their_target_by_their_operation_at_their_opacity() {
1788 use LayerOperation::{Add, Multiply, Overlay};
1789 use LayerTarget::{Colour, Opacity};
1790 let base = [0.8, 0.4, 0.2, 0.5];
1791 let c = compose_layers(false, base, &[(Colour, Multiply, [0.5, 0.5, 0.5, 0.5])]);
1793 for (got, want) in c.iter().zip([0.6, 0.3, 0.15, 0.5]) {
1794 assert!((got - want).abs() < 1e-6, "{c:?}");
1795 }
1796 let c = compose_layers(false, base, &[(Colour, Add, [0.1, 0.2, 0.3, 1.0])]);
1798 assert!(
1799 (c[0] - 0.9).abs() < 1e-6
1800 && (c[1] - 0.6).abs() < 1e-6
1801 && (c[2] - 0.5).abs() < 1e-6
1802 && c[3] == 0.5
1803 );
1804 let c = compose_layers(false, base, &[(Colour, Overlay, [0.5, 0.5, 0.5, 1.0])]);
1806 assert!((c[0] - 0.8).abs() < 1e-6 && (c[1] - 0.4).abs() < 1e-6);
1807 let c = compose_layers(false, base, &[(Opacity, Multiply, [0.2, 0.4, 0.6, 1.0])]);
1809 assert_eq!(c[..3], base[..3]);
1810 assert!((c[3] - 0.2).abs() < 1e-6);
1811 let c = compose_layers(true, base, &[(Opacity, Add, [1.0, 1.0, 1.0, 0.5])]);
1813 assert!((c[0] - 0.4).abs() < 1e-6 && (c[3] - 0.75).abs() < 1e-6);
1814 let c = compose_layers(
1816 false,
1817 base,
1818 &[
1819 (Colour, Add, [0.2, 0.2, 0.2, 1.0]),
1820 (Colour, Multiply, [0.5, 0.5, 0.5, 1.0]),
1821 ],
1822 );
1823 assert!((c[0] - 0.5).abs() < 1e-6);
1824 }
1825}