1use glam::{Vec2, Vec3};
15use std::f32::consts::{PI, TAU};
16
17#[derive(Debug, Clone)]
23pub enum UVMode {
24 Scroll {
26 velocity: Vec2,
27 },
28 Rotate {
30 pivot: Vec2,
31 speed: f32,
32 },
33 Scale {
35 center: Vec2,
36 amplitude: Vec2,
37 frequency: f32,
38 },
39 SineWarp {
41 amplitude: Vec2,
42 frequency: Vec2,
43 speed: f32,
44 },
45 RadialWarp {
47 center: Vec2,
48 amplitude: f32,
49 frequency: f32,
50 speed: f32,
51 },
52 Turbulence {
54 amplitude: f32,
55 frequency: f32,
56 speed: f32,
57 octaves: u32,
58 },
59 Custom {
61 label: String,
62 },
63}
64
65pub struct UVAnimator {
67 pub mode: UVMode,
68 pub time: f32,
69 custom_fn: Option<Box<dyn Fn(Vec2, f32) -> Vec2 + Send + Sync>>,
71}
72
73impl UVAnimator {
74 pub fn new(mode: UVMode) -> Self {
76 Self {
77 mode,
78 time: 0.0,
79 custom_fn: None,
80 }
81 }
82
83 pub fn scroll(velocity: Vec2) -> Self {
85 Self::new(UVMode::Scroll { velocity })
86 }
87
88 pub fn rotate(pivot: Vec2, speed: f32) -> Self {
90 Self::new(UVMode::Rotate { pivot, speed })
91 }
92
93 pub fn scale(center: Vec2, amplitude: Vec2, frequency: f32) -> Self {
95 Self::new(UVMode::Scale { center, amplitude, frequency })
96 }
97
98 pub fn sine_warp(amplitude: Vec2, frequency: Vec2, speed: f32) -> Self {
100 Self::new(UVMode::SineWarp { amplitude, frequency, speed })
101 }
102
103 pub fn custom<F>(label: &str, func: F) -> Self
105 where
106 F: Fn(Vec2, f32) -> Vec2 + Send + Sync + 'static,
107 {
108 Self {
109 mode: UVMode::Custom { label: label.to_string() },
110 time: 0.0,
111 custom_fn: Some(Box::new(func)),
112 }
113 }
114
115 pub fn tick(&mut self, dt: f32) {
117 self.time += dt;
118 }
119
120 pub fn reset(&mut self) {
122 self.time = 0.0;
123 }
124
125 pub fn transform(&self, uv: Vec2) -> Vec2 {
127 self.transform_at(uv, self.time)
128 }
129
130 pub fn transform_at(&self, uv: Vec2, time: f32) -> Vec2 {
132 match &self.mode {
133 UVMode::Scroll { velocity } => {
134 let offset = *velocity * time;
135 fract_vec2(uv + offset)
136 }
137 UVMode::Rotate { pivot, speed } => {
138 let angle = time * speed;
139 let cos_a = angle.cos();
140 let sin_a = angle.sin();
141 let centered = uv - *pivot;
142 let rotated = Vec2::new(
143 centered.x * cos_a - centered.y * sin_a,
144 centered.x * sin_a + centered.y * cos_a,
145 );
146 rotated + *pivot
147 }
148 UVMode::Scale { center, amplitude, frequency } => {
149 let scale = Vec2::ONE + *amplitude * (time * frequency * TAU).sin();
150 let centered = uv - *center;
151 centered * scale + *center
152 }
153 UVMode::SineWarp { amplitude, frequency, speed } => {
154 let offset_x = (uv.y * frequency.y * TAU + time * speed).sin() * amplitude.x;
155 let offset_y = (uv.x * frequency.x * TAU + time * speed).sin() * amplitude.y;
156 Vec2::new(uv.x + offset_x, uv.y + offset_y)
157 }
158 UVMode::RadialWarp { center, amplitude, frequency, speed } => {
159 let dir = uv - *center;
160 let dist = dir.length();
161 if dist < 1e-6 {
162 return uv;
163 }
164 let wave = (dist * *frequency * TAU - time * speed).sin() * amplitude;
165 let offset = dir.normalize() * wave;
166 uv + offset
167 }
168 UVMode::Turbulence { amplitude, frequency, speed, octaves } => {
169 let mut dx = 0.0_f32;
170 let mut dy = 0.0_f32;
171 let mut freq = *frequency;
172 let mut amp = *amplitude;
173 for _ in 0..*octaves {
174 dx += ((uv.x * freq + time * speed) * TAU).sin() * amp;
175 dy += ((uv.y * freq + time * speed * 1.3) * TAU).sin() * amp;
176 freq *= 2.0;
177 amp *= 0.5;
178 }
179 Vec2::new(uv.x + dx, uv.y + dy)
180 }
181 UVMode::Custom { .. } => {
182 if let Some(ref func) = self.custom_fn {
183 func(uv, time)
184 } else {
185 uv
186 }
187 }
188 }
189 }
190
191 pub fn transform_array(&self, uvs: &mut [[f32; 2]]) {
193 for uv_pair in uvs.iter_mut() {
194 let uv = Vec2::new(uv_pair[0], uv_pair[1]);
195 let result = self.transform(uv);
196 uv_pair[0] = result.x;
197 uv_pair[1] = result.y;
198 }
199 }
200
201 pub fn transform_array_new(&self, uvs: &[[f32; 2]]) -> Vec<[f32; 2]> {
203 uvs.iter().map(|&uv_pair| {
204 let uv = Vec2::new(uv_pair[0], uv_pair[1]);
205 let result = self.transform(uv);
206 [result.x, result.y]
207 }).collect()
208 }
209}
210
211pub struct UVAnimatorChain {
217 pub animators: Vec<UVAnimator>,
218}
219
220impl UVAnimatorChain {
221 pub fn new() -> Self {
222 Self { animators: Vec::new() }
223 }
224
225 pub fn push(&mut self, animator: UVAnimator) {
226 self.animators.push(animator);
227 }
228
229 pub fn tick(&mut self, dt: f32) {
230 for anim in &mut self.animators {
231 anim.tick(dt);
232 }
233 }
234
235 pub fn transform(&self, mut uv: Vec2) -> Vec2 {
236 for anim in &self.animators {
237 uv = anim.transform(uv);
238 }
239 uv
240 }
241}
242
243impl Default for UVAnimatorChain {
244 fn default() -> Self { Self::new() }
245}
246
247pub struct FlowMap {
256 pub vectors: Vec<Vec2>,
258 pub width: usize,
260 pub height: usize,
262 pub speed: f32,
264 pub strength: f32,
266 pub time: f32,
268 pub cycle_duration: f32,
270}
271
272impl FlowMap {
273 pub fn new(width: usize, height: usize) -> Self {
275 Self {
276 vectors: vec![Vec2::ZERO; width * height],
277 width,
278 height,
279 speed: 1.0,
280 strength: 0.1,
281 time: 0.0,
282 cycle_duration: 2.0,
283 }
284 }
285
286 pub fn uniform(width: usize, height: usize, direction: Vec2) -> Self {
288 let mut fm = Self::new(width, height);
289 fm.vectors.fill(direction);
290 fm
291 }
292
293 pub fn vortex(width: usize, height: usize, strength: f32) -> Self {
295 let mut fm = Self::new(width, height);
296 let cx = width as f32 * 0.5;
297 let cy = height as f32 * 0.5;
298 for y in 0..height {
299 for x in 0..width {
300 let dx = x as f32 - cx;
301 let dy = y as f32 - cy;
302 let dist = (dx * dx + dy * dy).sqrt().max(0.01);
303 let falloff = 1.0 / (1.0 + dist * 0.1);
304 fm.vectors[y * width + x] = Vec2::new(-dy, dx).normalize_or_zero() * strength * falloff;
305 }
306 }
307 fm
308 }
309
310 pub fn divergent(width: usize, height: usize, strength: f32) -> Self {
312 let mut fm = Self::new(width, height);
313 let cx = width as f32 * 0.5;
314 let cy = height as f32 * 0.5;
315 for y in 0..height {
316 for x in 0..width {
317 let dx = x as f32 - cx;
318 let dy = y as f32 - cy;
319 fm.vectors[y * width + x] = Vec2::new(dx, dy).normalize_or_zero() * strength;
320 }
321 }
322 fm
323 }
324
325 pub fn with_speed(mut self, speed: f32) -> Self {
327 self.speed = speed;
328 self
329 }
330
331 pub fn with_strength(mut self, strength: f32) -> Self {
333 self.strength = strength;
334 self
335 }
336
337 pub fn with_cycle(mut self, duration: f32) -> Self {
339 self.cycle_duration = duration;
340 self
341 }
342
343 pub fn tick(&mut self, dt: f32) {
345 self.time += dt * self.speed;
346 }
347
348 pub fn sample(&self, uv: Vec2) -> Vec2 {
350 let fx = (uv.x.fract() + 1.0).fract() * (self.width - 1) as f32;
351 let fy = (uv.y.fract() + 1.0).fract() * (self.height - 1) as f32;
352
353 let ix = fx as usize;
354 let iy = fy as usize;
355 let sx = fx - ix as f32;
356 let sy = fy - iy as f32;
357
358 let ix1 = (ix + 1) % self.width;
359 let iy1 = (iy + 1) % self.height;
360
361 let v00 = self.vectors[iy * self.width + ix];
362 let v10 = self.vectors[iy * self.width + ix1];
363 let v01 = self.vectors[iy1 * self.width + ix];
364 let v11 = self.vectors[iy1 * self.width + ix1];
365
366 let top = v00 * (1.0 - sx) + v10 * sx;
367 let bottom = v01 * (1.0 - sx) + v11 * sx;
368 top * (1.0 - sy) + bottom * sy
369 }
370
371 pub fn distort(&self, uv: Vec2) -> Vec2 {
376 let flow = self.sample(uv) * self.strength;
377 let cycle = self.cycle_duration;
378 if cycle < 1e-6 {
379 return uv + flow * self.time;
380 }
381
382 let phase0 = (self.time / cycle).fract();
383 let phase1 = ((self.time / cycle) + 0.5).fract();
384 let blend = (phase0 * 2.0 - 1.0).abs(); let uv0 = uv - flow * phase0;
387 let uv1 = uv - flow * phase1;
388
389 uv0 * (1.0 - blend) + uv1 * blend
390 }
391
392 pub fn distort_array(&self, uvs: &mut [[f32; 2]]) {
394 for uv_pair in uvs.iter_mut() {
395 let uv = Vec2::new(uv_pair[0], uv_pair[1]);
396 let result = self.distort(uv);
397 uv_pair[0] = result.x;
398 uv_pair[1] = result.y;
399 }
400 }
401}
402
403#[derive(Debug, Clone)]
409pub struct ParallaxLayer {
410 pub speed_factor: f32,
412 pub offset: Vec2,
414 pub scale: Vec2,
416 pub opacity: f32,
418 pub depth: f32,
420}
421
422impl ParallaxLayer {
423 pub fn new(speed_factor: f32, depth: f32) -> Self {
424 Self {
425 speed_factor,
426 offset: Vec2::ZERO,
427 scale: Vec2::ONE,
428 opacity: 1.0,
429 depth,
430 }
431 }
432
433 pub fn with_scale(mut self, scale: Vec2) -> Self {
434 self.scale = scale;
435 self
436 }
437
438 pub fn with_opacity(mut self, opacity: f32) -> Self {
439 self.opacity = opacity;
440 self
441 }
442}
443
444pub struct ParallaxScroller {
446 pub layers: Vec<ParallaxLayer>,
447 pub scroll_position: Vec2,
448}
449
450impl ParallaxScroller {
451 pub fn new() -> Self {
452 Self {
453 layers: Vec::new(),
454 scroll_position: Vec2::ZERO,
455 }
456 }
457
458 pub fn add_layer(&mut self, layer: ParallaxLayer) -> usize {
460 let idx = self.layers.len();
461 self.layers.push(layer);
462 self.layers.sort_by(|a, b| b.depth.partial_cmp(&a.depth).unwrap_or(std::cmp::Ordering::Equal));
464 idx
465 }
466
467 pub fn standard_layers(num_layers: usize) -> Self {
469 let mut scroller = Self::new();
470 for i in 0..num_layers {
471 let depth = (i + 1) as f32;
472 let speed = 1.0 / depth;
473 let opacity = 1.0 - (i as f32 * 0.15).min(0.8);
474 scroller.add_layer(
475 ParallaxLayer::new(speed, depth)
476 .with_opacity(opacity),
477 );
478 }
479 scroller
480 }
481
482 pub fn scroll(&mut self, delta: Vec2) {
484 self.scroll_position += delta;
485 }
486
487 pub fn set_position(&mut self, position: Vec2) {
489 self.scroll_position = position;
490 }
491
492 pub fn layer_uv(&self, layer_index: usize, base_uv: Vec2) -> Vec2 {
494 if let Some(layer) = self.layers.get(layer_index) {
495 let offset = self.scroll_position * layer.speed_factor + layer.offset;
496 fract_vec2((base_uv + offset) * layer.scale)
497 } else {
498 base_uv
499 }
500 }
501
502 pub fn all_layer_uvs(&self, base_uv: Vec2) -> Vec<(Vec2, f32)> {
504 self.layers.iter().map(|layer| {
505 let offset = self.scroll_position * layer.speed_factor + layer.offset;
506 let uv = fract_vec2((base_uv + offset) * layer.scale);
507 (uv, layer.opacity)
508 }).collect()
509 }
510}
511
512impl Default for ParallaxScroller {
513 fn default() -> Self { Self::new() }
514}
515
516pub struct SpriteSheetAnimator {
524 pub columns: usize,
526 pub rows: usize,
528 pub total_frames: usize,
530 pub current_frame: usize,
532 pub fps: f32,
534 pub looping: bool,
536 time_accumulator: f32,
538 pub playing: bool,
540}
541
542impl SpriteSheetAnimator {
543 pub fn new(columns: usize, rows: usize, fps: f32) -> Self {
545 Self {
546 columns: columns.max(1),
547 rows: rows.max(1),
548 total_frames: columns * rows,
549 current_frame: 0,
550 fps,
551 looping: true,
552 time_accumulator: 0.0,
553 playing: true,
554 }
555 }
556
557 pub fn with_total_frames(mut self, total: usize) -> Self {
559 self.total_frames = total.min(self.columns * self.rows);
560 self
561 }
562
563 pub fn with_looping(mut self, looping: bool) -> Self {
565 self.looping = looping;
566 self
567 }
568
569 pub fn tick(&mut self, dt: f32) {
571 if !self.playing || self.total_frames == 0 {
572 return;
573 }
574
575 self.time_accumulator += dt;
576 let frame_duration = 1.0 / self.fps;
577
578 while self.time_accumulator >= frame_duration {
579 self.time_accumulator -= frame_duration;
580 self.current_frame += 1;
581
582 if self.current_frame >= self.total_frames {
583 if self.looping {
584 self.current_frame = 0;
585 } else {
586 self.current_frame = self.total_frames - 1;
587 self.playing = false;
588 return;
589 }
590 }
591 }
592 }
593
594 pub fn set_frame(&mut self, frame: usize) {
596 self.current_frame = frame.min(self.total_frames.saturating_sub(1));
597 }
598
599 pub fn current_uv_rect(&self) -> (Vec2, Vec2) {
601 self.frame_uv_rect(self.current_frame)
602 }
603
604 pub fn frame_uv_rect(&self, frame: usize) -> (Vec2, Vec2) {
606 let col = frame % self.columns;
607 let row = frame / self.columns;
608 let cell_w = 1.0 / self.columns as f32;
609 let cell_h = 1.0 / self.rows as f32;
610 let uv_min = Vec2::new(col as f32 * cell_w, row as f32 * cell_h);
611 let uv_max = Vec2::new((col + 1) as f32 * cell_w, (row + 1) as f32 * cell_h);
612 (uv_min, uv_max)
613 }
614
615 pub fn map_uv(&self, local_uv: Vec2) -> Vec2 {
617 let (uv_min, uv_max) = self.current_uv_rect();
618 Vec2::new(
619 uv_min.x + local_uv.x * (uv_max.x - uv_min.x),
620 uv_min.y + local_uv.y * (uv_max.y - uv_min.y),
621 )
622 }
623
624 pub fn current_cell(&self) -> (usize, usize) {
626 (self.current_frame / self.columns, self.current_frame % self.columns)
627 }
628
629 pub fn play(&mut self) {
631 self.playing = true;
632 }
633
634 pub fn pause(&mut self) {
636 self.playing = false;
637 }
638
639 pub fn reset(&mut self) {
641 self.current_frame = 0;
642 self.time_accumulator = 0.0;
643 self.playing = true;
644 }
645
646 pub fn is_finished(&self) -> bool {
648 !self.looping && self.current_frame >= self.total_frames.saturating_sub(1)
649 }
650}
651
652pub struct AnimatedNormalMap {
660 pub width: usize,
662 pub height: usize,
664 pub normals: Vec<[f32; 3]>,
666 pub time: f32,
668 pub waves: Vec<NormalMapWave>,
670}
671
672#[derive(Debug, Clone, Copy)]
674pub struct NormalMapWave {
675 pub direction: Vec2,
676 pub frequency: f32,
677 pub amplitude: f32,
678 pub speed: f32,
679}
680
681impl NormalMapWave {
682 pub fn new(direction: Vec2, frequency: f32, amplitude: f32, speed: f32) -> Self {
683 Self { direction: direction.normalize_or_zero(), frequency, amplitude, speed }
684 }
685}
686
687impl AnimatedNormalMap {
688 pub fn new(width: usize, height: usize) -> Self {
690 Self {
691 width,
692 height,
693 normals: vec![[0.5, 0.5, 1.0]; width * height],
694 time: 0.0,
695 waves: Vec::new(),
696 }
697 }
698
699 pub fn add_wave(&mut self, wave: NormalMapWave) {
701 self.waves.push(wave);
702 }
703
704 pub fn water(width: usize, height: usize) -> Self {
706 let mut nm = Self::new(width, height);
707 nm.add_wave(NormalMapWave::new(Vec2::new(1.0, 0.3), 4.0, 0.3, 0.5));
708 nm.add_wave(NormalMapWave::new(Vec2::new(-0.5, 1.0), 6.0, 0.2, 0.7));
709 nm.add_wave(NormalMapWave::new(Vec2::new(0.7, -0.7), 10.0, 0.1, 1.2));
710 nm
711 }
712
713 pub fn tick(&mut self, dt: f32) {
715 self.time += dt;
716 self.regenerate();
717 }
718
719 pub fn regenerate(&mut self) {
721 for y in 0..self.height {
722 for x in 0..self.width {
723 let uv = Vec2::new(
724 x as f32 / self.width as f32,
725 y as f32 / self.height as f32,
726 );
727
728 let mut dx = 0.0_f32;
729 let mut dy = 0.0_f32;
730
731 for wave in &self.waves {
732 let phase = uv.dot(wave.direction) * wave.frequency - self.time * wave.speed;
733 let deriv = (phase * TAU).cos() * wave.amplitude * wave.frequency * TAU;
734 dx += wave.direction.x * deriv;
735 dy += wave.direction.y * deriv;
736 }
737
738 let normal = Vec3::new(-dx, -dy, 1.0).normalize();
740 let idx = y * self.width + x;
742 self.normals[idx] = [
743 normal.x * 0.5 + 0.5,
744 normal.y * 0.5 + 0.5,
745 normal.z * 0.5 + 0.5,
746 ];
747 }
748 }
749 }
750
751 pub fn sample(&self, uv: Vec2) -> Vec3 {
753 let fx = (uv.x.fract() + 1.0).fract() * (self.width - 1) as f32;
754 let fy = (uv.y.fract() + 1.0).fract() * (self.height - 1) as f32;
755
756 let ix = (fx as usize).min(self.width - 1);
757 let iy = (fy as usize).min(self.height - 1);
758
759 let n = self.normals[iy * self.width + ix];
760 Vec3::new(n[0] * 2.0 - 1.0, n[1] * 2.0 - 1.0, n[2] * 2.0 - 1.0).normalize()
761 }
762}
763
764pub struct TriplanarProjector {
773 pub scale: f32,
775 pub sharpness: f32,
777 pub axis_scale: Vec3,
779 pub axis_offset: Vec3,
781}
782
783impl TriplanarProjector {
784 pub fn new(scale: f32) -> Self {
785 Self {
786 scale,
787 sharpness: 1.0,
788 axis_scale: Vec3::ONE,
789 axis_offset: Vec3::ZERO,
790 }
791 }
792
793 pub fn with_sharpness(mut self, sharpness: f32) -> Self {
794 self.sharpness = sharpness;
795 self
796 }
797
798 pub fn with_axis_scale(mut self, axis_scale: Vec3) -> Self {
799 self.axis_scale = axis_scale;
800 self
801 }
802
803 pub fn with_offset(mut self, offset: Vec3) -> Self {
804 self.axis_offset = offset;
805 self
806 }
807
808 pub fn blend_weights(&self, normal: Vec3) -> Vec3 {
810 let n = normal.abs();
811 let mut w = Vec3::new(
813 n.x.powf(self.sharpness),
814 n.y.powf(self.sharpness),
815 n.z.powf(self.sharpness),
816 );
817 let sum = w.x + w.y + w.z;
818 if sum > 1e-6 {
819 w /= sum;
820 }
821 w
822 }
823
824 pub fn project(&self, position: Vec3, normal: Vec3) -> TriplanarUVs {
828 let p = (position + self.axis_offset) * self.scale;
829 let weights = self.blend_weights(normal);
830
831 TriplanarUVs {
832 uv_xy: Vec2::new(p.x * self.axis_scale.x, p.y * self.axis_scale.y),
833 uv_xz: Vec2::new(p.x * self.axis_scale.x, p.z * self.axis_scale.z),
834 uv_yz: Vec2::new(p.y * self.axis_scale.y, p.z * self.axis_scale.z),
835 weight_xy: weights.z, weight_xz: weights.y, weight_yz: weights.x, }
839 }
840
841 pub fn project_single(&self, position: Vec3, normal: Vec3) -> Vec2 {
843 let uvs = self.project(position, normal);
844 uvs.uv_xy * uvs.weight_xy
845 + uvs.uv_xz * uvs.weight_xz
846 + uvs.uv_yz * uvs.weight_yz
847 }
848}
849
850#[derive(Debug, Clone, Copy)]
852pub struct TriplanarUVs {
853 pub uv_xy: Vec2,
855 pub uv_xz: Vec2,
857 pub uv_yz: Vec2,
859 pub weight_xy: f32,
861 pub weight_xz: f32,
863 pub weight_yz: f32,
865}
866
867impl TriplanarUVs {
868 pub fn blend_f32(&self, val_xy: f32, val_xz: f32, val_yz: f32) -> f32 {
870 val_xy * self.weight_xy + val_xz * self.weight_xz + val_yz * self.weight_yz
871 }
872
873 pub fn blend_vec3(&self, val_xy: Vec3, val_xz: Vec3, val_yz: Vec3) -> Vec3 {
875 val_xy * self.weight_xy + val_xz * self.weight_xz + val_yz * self.weight_yz
876 }
877}
878
879pub struct UVUnwrap;
885
886impl UVUnwrap {
887 pub fn cylindrical(position: Vec3, height: f32) -> [f32; 2] {
891 let u = (position.z.atan2(position.x) / TAU + 0.5).fract();
892 let v = (position.y / height + 0.5).clamp(0.0, 1.0);
893 [u, v]
894 }
895
896 pub fn spherical(position: Vec3) -> [f32; 2] {
900 let len = position.length();
901 if len < 1e-6 {
902 return [0.5, 0.5];
903 }
904 let normalized = position / len;
905 let u = (normalized.z.atan2(normalized.x) / TAU + 0.5).fract();
906 let v = (normalized.y.asin() / PI + 0.5).clamp(0.0, 1.0);
907 [u, v]
908 }
909
910 pub fn box_projection(position: Vec3, normal: Vec3) -> [f32; 2] {
912 let abs_n = normal.abs();
913 if abs_n.x >= abs_n.y && abs_n.x >= abs_n.z {
914 [position.z.fract().abs(), position.y.fract().abs()]
916 } else if abs_n.y >= abs_n.z {
917 [position.x.fract().abs(), position.z.fract().abs()]
919 } else {
920 [position.x.fract().abs(), position.y.fract().abs()]
922 }
923 }
924
925 pub fn planar(
927 position: Vec3,
928 origin: Vec3,
929 u_axis: Vec3,
930 v_axis: Vec3,
931 ) -> [f32; 2] {
932 let relative = position - origin;
933 let u = relative.dot(u_axis) / u_axis.length_squared();
934 let v = relative.dot(v_axis) / v_axis.length_squared();
935 [u, v]
936 }
937
938 pub fn camera_projection(
940 position: Vec3,
941 camera_pos: Vec3,
942 camera_forward: Vec3,
943 camera_up: Vec3,
944 fov: f32,
945 ) -> [f32; 2] {
946 let right = camera_forward.cross(camera_up).normalize_or_zero();
947 let up = right.cross(camera_forward).normalize_or_zero();
948 let to_point = position - camera_pos;
949 let depth = to_point.dot(camera_forward);
950 if depth < 1e-6 {
951 return [0.5, 0.5];
952 }
953 let scale = 1.0 / (fov * 0.5).tan() / depth;
954 let u = to_point.dot(right) * scale * 0.5 + 0.5;
955 let v = to_point.dot(up) * scale * 0.5 + 0.5;
956 [u.clamp(0.0, 1.0), v.clamp(0.0, 1.0)]
957 }
958
959 pub fn cylindrical_array(positions: &[Vec3], height: f32) -> Vec<[f32; 2]> {
961 positions.iter().map(|&p| Self::cylindrical(p, height)).collect()
962 }
963
964 pub fn spherical_array(positions: &[Vec3]) -> Vec<[f32; 2]> {
966 positions.iter().map(|&p| Self::spherical(p)).collect()
967 }
968
969 pub fn box_projection_array(positions: &[Vec3], normals: &[Vec3]) -> Vec<[f32; 2]> {
971 positions.iter().zip(normals.iter()).map(|(&p, &n)| {
972 Self::box_projection(p, n)
973 }).collect()
974 }
975
976 pub fn fix_seams(uvs: &mut [[f32; 2]], indices: &[[u32; 3]]) {
981 for tri in indices {
982 let u0 = uvs[tri[0] as usize][0];
983 let u1 = uvs[tri[1] as usize][0];
984 let u2 = uvs[tri[2] as usize][0];
985
986 let max_u = u0.max(u1).max(u2);
988 let min_u = u0.min(u1).min(u2);
989 if max_u - min_u > 0.5 {
990 for &idx in tri {
992 if uvs[idx as usize][0] < 0.5 {
993 uvs[idx as usize][0] += 1.0;
994 }
995 }
996 }
997 }
998 }
999}
1000
1001fn fract_vec2(v: Vec2) -> Vec2 {
1007 Vec2::new(
1008 (v.x.fract() + 1.0).fract(),
1009 (v.y.fract() + 1.0).fract(),
1010 )
1011}
1012
1013#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1022 fn uv_scroll() {
1023 let mut anim = UVAnimator::scroll(Vec2::new(1.0, 0.0));
1024 anim.tick(0.5);
1025 let uv = anim.transform(Vec2::new(0.0, 0.0));
1026 assert!((uv.x - 0.5).abs() < 1e-5);
1027 assert!(uv.y.abs() < 1e-5);
1028 }
1029
1030 #[test]
1031 fn uv_rotate() {
1032 let anim = UVAnimator::rotate(Vec2::new(0.5, 0.5), PI);
1033 let uv = anim.transform_at(Vec2::new(1.0, 0.5), 0.0);
1034 assert!((uv.x - 1.0).abs() < 1e-4);
1036 }
1037
1038 #[test]
1039 fn uv_sine_warp() {
1040 let anim = UVAnimator::sine_warp(Vec2::new(0.1, 0.1), Vec2::new(2.0, 2.0), 1.0);
1041 let uv = anim.transform_at(Vec2::new(0.5, 0.5), 0.0);
1042 assert!(uv.x.is_finite());
1044 assert!(uv.y.is_finite());
1045 }
1046
1047 #[test]
1048 fn flow_map_uniform() {
1049 let mut fm = FlowMap::uniform(8, 8, Vec2::new(1.0, 0.0))
1050 .with_strength(0.1);
1051 fm.tick(0.5);
1052 let uv = fm.distort(Vec2::new(0.5, 0.5));
1053 assert!(uv.x.is_finite());
1054 }
1055
1056 #[test]
1057 fn flow_map_vortex() {
1058 let fm = FlowMap::vortex(16, 16, 1.0);
1059 let center = fm.sample(Vec2::new(0.5, 0.5));
1060 assert!(center.length() < 5.0);
1062 }
1063
1064 #[test]
1065 fn sprite_sheet() {
1066 let mut ss = SpriteSheetAnimator::new(4, 4, 10.0);
1067 assert_eq!(ss.total_frames, 16);
1068 assert_eq!(ss.current_frame, 0);
1069
1070 ss.tick(0.1); assert_eq!(ss.current_frame, 1);
1072
1073 let (uv_min, uv_max) = ss.current_uv_rect();
1074 assert!((uv_min.x - 0.25).abs() < 1e-5);
1075 assert!((uv_max.x - 0.5).abs() < 1e-5);
1076 }
1077
1078 #[test]
1079 fn sprite_sheet_looping() {
1080 let mut ss = SpriteSheetAnimator::new(2, 2, 10.0);
1081 ss.tick(0.5);
1083 assert!(ss.current_frame < 4);
1084 }
1085
1086 #[test]
1087 fn triplanar_projection() {
1088 let proj = TriplanarProjector::new(1.0).with_sharpness(2.0);
1089
1090 let uvs = proj.project(Vec3::new(1.0, 2.0, 3.0), Vec3::Y);
1092 assert!(uvs.weight_xz > 0.9);
1093 }
1094
1095 #[test]
1096 fn triplanar_blend_weights() {
1097 let proj = TriplanarProjector::new(1.0);
1098 let weights = proj.blend_weights(Vec3::new(0.0, 1.0, 0.0));
1099 assert!((weights.x + weights.y + weights.z - 1.0).abs() < 1e-5);
1100 }
1101
1102 #[test]
1103 fn cylindrical_unwrap() {
1104 let uv = UVUnwrap::cylindrical(Vec3::new(1.0, 0.0, 0.0), 2.0);
1105 assert!(uv[0] >= 0.0 && uv[0] <= 1.0);
1106 assert!(uv[1] >= 0.0 && uv[1] <= 1.0);
1107 }
1108
1109 #[test]
1110 fn spherical_unwrap() {
1111 let uv = UVUnwrap::spherical(Vec3::new(0.0, 1.0, 0.0));
1112 assert!((uv[1] - 1.0).abs() < 1e-5); }
1114
1115 #[test]
1116 fn box_projection_unwrap() {
1117 let uv = UVUnwrap::box_projection(
1118 Vec3::new(0.5, 0.3, 0.7),
1119 Vec3::new(0.0, 1.0, 0.0),
1120 );
1121 assert!(uv[0].is_finite());
1123 assert!(uv[1].is_finite());
1124 }
1125
1126 #[test]
1127 fn parallax_layers() {
1128 let mut scroller = ParallaxScroller::standard_layers(4);
1129 assert_eq!(scroller.layers.len(), 4);
1130
1131 scroller.scroll(Vec2::new(1.0, 0.0));
1132 let uvs = scroller.all_layer_uvs(Vec2::new(0.5, 0.5));
1133 assert_eq!(uvs.len(), 4);
1134 }
1136
1137 #[test]
1138 fn animated_normal_map() {
1139 let mut nm = AnimatedNormalMap::water(32, 32);
1140 nm.tick(0.016);
1141 let normal = nm.sample(Vec2::new(0.5, 0.5));
1142 assert!(normal.z > 0.0); }
1144
1145 #[test]
1146 fn uv_chain() {
1147 let mut chain = UVAnimatorChain::new();
1148 chain.push(UVAnimator::scroll(Vec2::new(0.1, 0.0)));
1149 chain.push(UVAnimator::scroll(Vec2::new(0.0, 0.1)));
1150 chain.tick(1.0);
1151 let uv = chain.transform(Vec2::ZERO);
1152 assert!((uv.x - 0.1).abs() < 1e-4);
1153 assert!((uv.y - 0.1).abs() < 1e-4);
1154 }
1155
1156 #[test]
1157 fn planar_unwrap() {
1158 let uv = UVUnwrap::planar(
1159 Vec3::new(1.0, 2.0, 3.0),
1160 Vec3::ZERO,
1161 Vec3::X,
1162 Vec3::Y,
1163 );
1164 assert!((uv[0] - 1.0).abs() < 1e-5);
1165 assert!((uv[1] - 2.0).abs() < 1e-5);
1166 }
1167}