Skip to main content

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}