gizmo_engine/systems/spin.rs
1//! Genel amaçlı GÖRSEL DÖNME bileşeni + sistemi.
2//!
3//! Bir mesh'i (ECS `Transform`) sabit bir eksende, sabit bir açısal hızla döndürür.
4//! Tekerlek, pervane, fan, türbin, gezegen, dönen platform... hepsi için tek çözüm —
5//! artık demoların her frame elle `transform.rotation = ...` yazması GEREKMEZ. Bileşeni
6//! ekle, [`SpinPlugin`]'i (veya doğrudan [`SpinSystem`]) çalıştır, motor döndürsün.
7//!
8//! ```ignore
9//! world.add_component(wheel_mesh, Spin::new(Vec3::X, 30.0)); // 30 rad/s yuvarlanma
10//! app.add_plugin(SpinPlugin); // otomatik döner
11//! ```
12
13use gizmo_core::world::World;
14use gizmo_math::{Quat, Vec3};
15use gizmo_physics_core::Transform;
16
17/// Bir `Transform`'u `axis` ekseninde `angular_velocity` (rad/s) hızıyla döndürür.
18/// Dönüş `rest_rotation`'ın (modelin yazar-duruşu) ÜZERİNE biner. `angular_velocity`
19/// her frame değiştirilebilir (ör. tekerlek hızını araç hızına bağla).
20#[derive(Debug, Clone, Copy)]
21pub struct Spin {
22 /// Dönme ekseni (gövde-yerel), normalize edilir.
23 pub axis: Vec3,
24 /// Açısal hız, rad/s. Runtime'da değiştirilebilir.
25 pub angular_velocity: f32,
26 /// Modelin dönmeden önceki (yazar) rotasyonu — dönüş bunun üzerine uygulanır.
27 pub rest_rotation: Quat,
28 /// Biriken açı (rad) — sistem tarafından yönetilir.
29 pub angle: f32,
30}
31
32impl Spin {
33 /// `axis` ekseninde `angular_velocity` (rad/s) ile dönen bileşen.
34 pub fn new(axis: Vec3, angular_velocity: f32) -> Self {
35 let axis = if axis.length_squared() > 1e-9 {
36 axis.normalize()
37 } else {
38 Vec3::X
39 };
40 Self {
41 axis,
42 angular_velocity,
43 rest_rotation: Quat::IDENTITY,
44 angle: 0.0,
45 }
46 }
47
48 /// Modelin yazar-duruş rotasyonunu koru (GLTF tekerleği gibi önceden döndürülmüş
49 /// mesh'lerde şart — yoksa duruş bozulur). Zincirlenebilir.
50 pub fn with_rest_rotation(mut self, rest: Quat) -> Self {
51 self.rest_rotation = rest;
52 self
53 }
54}
55
56gizmo_core::impl_component!(Spin);
57
58/// Her frame tüm [`Spin`]'leri ilerletip `Transform.rotation`'a uygular. [`SpinPlugin`]
59/// bunu schedule'a ekler; el ile `SpinSystem.run(world, dt)` da çağrılabilir.
60pub struct SpinSystem;
61
62impl gizmo_core::system::System for SpinSystem {
63 fn access_info(&self) -> gizmo_core::system::AccessInfo {
64 let mut info = gizmo_core::system::AccessInfo::new();
65 info.is_exclusive = true; // Spin + Transform'a mutable erişir
66 info
67 }
68
69 fn run(&mut self, world: &World, dt: f32) {
70 // SAFETY: exclusive sistem; Spin ve Transform ayrı bileşen tipleri (disjoint),
71 // scheduler bu çalışırken başka mutable alias vermez.
72 if let Some(mut q) = unsafe {
73 world.query_unchecked::<(
74 gizmo_core::query::Mut<Spin>,
75 gizmo_core::query::Mut<Transform>,
76 )>()
77 } {
78 for (_id, (mut spin, mut t)) in q.iter_mut() {
79 spin.angle += spin.angular_velocity * dt;
80 t.rotation = spin.rest_rotation * Quat::from_axis_angle(spin.axis, spin.angle);
81 t.update_local_matrix();
82 }
83 }
84 }
85}
86
87/// [`SpinSystem`]'i uygulamanın schedule'ına ekler → [`Spin`] bileşenli her mesh
88/// otomatik döner.
89pub struct SpinPlugin;
90
91impl<State: 'static> crate::app::Plugin<State> for SpinPlugin {
92 fn build(&self, app: &mut crate::app::App<State>) {
93 app.schedule.add_di_system(
94 gizmo_core::system::SystemConfig::new(Box::new(SpinSystem)).label("spin"),
95 );
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use gizmo_core::system::System;
103 use gizmo_core::world::World;
104
105 #[test]
106 fn spin_system_rotates_transform_over_rest() {
107 let mut world = World::new();
108 let e = world.spawn();
109 let rest = Quat::from_rotation_y(0.5);
110 world.add_component(e, Transform::new(Vec3::ZERO));
111 // 2 rad/s, X ekseni, yazar-duruş korunur.
112 world.add_component(e, Spin::new(Vec3::X, 2.0).with_rest_rotation(rest));
113
114 let mut sys = SpinSystem;
115 // 1 s topla (dt=1/60 × 60).
116 for _ in 0..60 {
117 sys.run(&world, 1.0 / 60.0);
118 }
119
120 let t = world.borrow::<Transform>();
121 let rot = t.get(e.id()).unwrap().rotation;
122 // 1 s'de ~2 rad dönmüş olmalı, rest'in üzerine.
123 let expected = rest * Quat::from_axis_angle(Vec3::X, 2.0);
124 assert!(
125 rot.dot(expected).abs() > 0.9999,
126 "Spin rest'in üzerine ~2 rad döndürmeli"
127 );
128 // Spin bileşeninin biriken açısı da ~2.
129 let spins = world.borrow::<Spin>();
130 assert!((spins.get(e.id()).unwrap().angle - 2.0).abs() < 1e-3);
131 }
132}