Skip to main content

gizmo_engine/
spawner.rs

1use crate::color::Color;
2/// Bevy benzeri Command API — setup closure içinde tek satırla nesne spawn etmek için.
3///
4/// # Örnek
5/// ```rust,ignore
6/// .set_setup(|world, renderer| {
7///     let mut cmd = Commands::new(world, renderer);
8///     cmd.spawn_cube(Vec3::new(0.0, 0.0, -10.0), Color::RED).with_name("Oyuncu");
9///     cmd.spawn_camera(Vec3::new(0.0, 2.0, 5.0));
10/// })
11/// ```
12use gizmo_core::{Entity, EntityName, World};
13use gizmo_math::{Quat, Vec3};
14use gizmo_physics_core::{Collider, Transform};
15use gizmo_physics_rigid::components::{RigidBody, Velocity};
16use gizmo_renderer::{
17    asset::AssetManager,
18    components::{Camera, DirectionalLight, Material, MeshRenderer, PointLight},
19    Renderer,
20};
21
22// ─── Hata Tipleri ───────────────────────────────────────────────────────────
23
24/// GLTF/GLB spawn işlemlerinde oluşabilecek hatalar.
25///
26/// 1.0 hata kontratı: stringly-typed hata yerine somut, `match` edilebilir tip.
27///
28/// `#[non_exhaustive]`: alt katmandaki yükleyici (`gizmo-renderer`) somut hata
29/// tipine geçtikçe burada yeni varyantlar (Io, Parse, GpuUpload, …) eklenebilir;
30/// bu yüzden tüketiciler `_ =>` kolu bulundurmalıdır.
31#[derive(Debug)]
32#[non_exhaustive]
33pub enum GltfLoadError {
34    /// Alttaki yükleyici (`AssetManager::load_gltf_scene` /
35    /// `load_gltf_from_import`) bir hata döndürdü. `path`, yüklenmeye çalışılan
36    /// dosya yolu; `source`, alt katmanın hata açıklamasıdır.
37    ///
38    /// Not: alt katman henüz `Result<_, String>` döndürdüğü için kaynak burada
39    /// bir `String` olarak taşınır; alt katman somut bir `Error` tipine
40    /// geçtiğinde bu varyant `#[source]` ile zincirlenecek şekilde
41    /// güncellenecektir.
42    Load {
43        /// Yüklenmeye çalışılan dosya yolu.
44        path: String,
45        /// Alt katmandan gelen hata açıklaması.
46        source: String,
47    },
48}
49
50impl std::fmt::Display for GltfLoadError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            GltfLoadError::Load { path, source } => {
54                write!(f, "GLTF '{}' yuklenemedi: {}", path, source)
55            }
56        }
57    }
58}
59
60impl std::error::Error for GltfLoadError {}
61
62// ─── Commands ─────────────────────────────────────────────────────────────────
63
64pub struct Commands<'a> {
65    pub world: &'a mut World,
66    pub renderer: &'a Renderer,
67    pub asset_manager: Option<AssetManager>,
68}
69
70impl<'a> Drop for Commands<'a> {
71    fn drop(&mut self) {
72        if let Some(am) = self.asset_manager.take() {
73            self.world.insert_resource(am);
74        }
75    }
76}
77
78impl<'a> Commands<'a> {
79    pub fn new(world: &'a mut World, renderer: &'a Renderer) -> Self {
80        let am = world.remove_resource::<AssetManager>().unwrap_or_default();
81        Self {
82            world,
83            renderer,
84            asset_manager: Some(am),
85        }
86    }
87
88    // ── Primitifler ────────────────────────────────────────────────────────────
89
90    /// Tek satırda renkli bir küp spawn eder. Builder zinciriyle `.with_name()` eklenebilir.
91    pub fn spawn_cube(&mut self, pos: Vec3, color: Color) -> EntityBuilder<'_, 'a> {
92        let mesh = AssetManager::create_cube(&self.renderer.device);
93        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
94            &self.renderer.device,
95            &self.renderer.queue,
96            &self.renderer.scene.texture_bind_group_layout,
97        );
98        let mat = Material::new(bg).with_unlit(color.to_vec4());
99        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
100        EntityBuilder {
101            commands: self,
102            entity: id,
103        }
104    }
105
106    /// Tek satırda renkli bir küre spawn eder.
107    pub fn spawn_sphere(&mut self, pos: Vec3, radius: f32, color: Color) -> EntityBuilder<'_, 'a> {
108        let mesh = AssetManager::create_sphere(&self.renderer.device, radius, 20, 20);
109        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
110            &self.renderer.device,
111            &self.renderer.queue,
112            &self.renderer.scene.texture_bind_group_layout,
113        );
114        let mat = Material::new(bg).with_unlit(color.to_vec4());
115        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
116        EntityBuilder {
117            commands: self,
118            entity: id,
119        }
120    }
121
122    /// Tek satırda düzlemsel bir zemin spawn eder.
123    pub fn spawn_plane(&mut self, pos: Vec3, size: f32, color: Color) -> EntityBuilder<'_, 'a> {
124        let mesh = AssetManager::create_plane(&self.renderer.device, size);
125        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
126            &self.renderer.device,
127            &self.renderer.queue,
128            &self.renderer.scene.texture_bind_group_layout,
129        );
130        let mat = Material::new(bg).with_unlit(color.to_vec4());
131        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
132        EntityBuilder {
133            commands: self,
134            entity: id,
135        }
136    }
137
138    /// Diskten bir .obj modeli yükler ve spawn eder.
139    pub fn spawn_model(&mut self, pos: Vec3, path: &str) -> EntityBuilder<'_, 'a> {
140        let mesh = self
141            .asset_manager
142            .as_mut()
143            .unwrap()
144            .load_obj(&self.renderer.device, path);
145        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
146            &self.renderer.device,
147            &self.renderer.queue,
148            &self.renderer.scene.texture_bind_group_layout,
149        );
150        let mat = Material::new(bg);
151        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
152        EntityBuilder {
153            commands: self,
154            entity: id,
155        }
156    }
157
158    // ── Kamera ────────────────────────────────────────────────────────────────
159
160    /// Birincil (primary) 3D perspektif kamera spawn eder.
161    /// `yaw = -π/2` (−X'e bakıyor), `pitch = 0` (düz).
162    pub fn spawn_camera(&mut self, pos: Vec3) -> EntityBuilder<'_, 'a> {
163        if let Some(mut cameras) = self.world.query_mut::<gizmo_core::prelude::Mut<Camera>>() {
164            for (_, mut c) in cameras.iter_mut() {
165                c.primary = false;
166            }
167        }
168        let id = self.world.spawn();
169        let trans = Transform::new(pos);
170
171        self.world.add_component(id, trans);
172        self.world.add_component(
173            id,
174            Camera {
175                fov: 60.0_f32.to_radians(),
176                near: 0.1,
177                far: 1000.0,
178                yaw: -std::f32::consts::FRAC_PI_2,
179                pitch: 0.0,
180                exposure: 1.0,
181                primary: true,
182                projection: Default::default(),
183            },
184        );
185        EntityBuilder {
186            commands: self,
187            entity: id,
188        }
189    }
190
191    /// `fov` (derece), `near`, `far` özelleştirilebilir kamera.
192    pub fn spawn_camera_with(
193        &mut self,
194        pos: Vec3,
195        fov_deg: f32,
196        near: f32,
197        far: f32,
198    ) -> EntityBuilder<'_, 'a> {
199        if let Some(mut cameras) = self.world.query_mut::<gizmo_core::prelude::Mut<Camera>>() {
200            for (_, mut c) in cameras.iter_mut() {
201                c.primary = false;
202            }
203        }
204        let id = self.world.spawn();
205        let trans = Transform::new(pos);
206
207        self.world.add_component(id, trans);
208        self.world.add_component(
209            id,
210            Camera {
211                fov: fov_deg.to_radians(),
212                near,
213                far,
214                yaw: -std::f32::consts::FRAC_PI_2,
215                pitch: 0.0,
216                exposure: 1.0,
217                primary: true,
218                projection: Default::default(),
219            },
220        );
221        EntityBuilder {
222            commands: self,
223            entity: id,
224        }
225    }
226
227    // ── Işıklar ─────────────────────────────────────────────────────────────────────────
228
229    /// Point light (nokta ışık) spawn eder.
230    pub fn spawn_point_light(
231        &mut self,
232        pos: Vec3,
233        color: Color,
234        intensity: f32,
235    ) -> EntityBuilder<'_, 'a> {
236        let id = self.world.spawn();
237        let trans = Transform::new(pos);
238
239        self.world.add_component(id, trans);
240        self.world.add_component(
241            id,
242            PointLight::new(
243                gizmo_math::Vec3::new(color.0.x, color.0.y, color.0.z),
244                intensity,
245                10.0,
246            ),
247        );
248        EntityBuilder {
249            commands: self,
250            entity: id,
251        }
252    }
253
254    /// Directional light (güneş/ay) spawn eder.
255    /// `direction`: normallenmiş ışık yönü (aşağı bakan = negatif Y).
256    pub fn spawn_sun(
257        &mut self,
258        _direction: Vec3,
259        color: Color,
260        intensity: f32,
261    ) -> EntityBuilder<'_, 'a> {
262        let id = self.world.spawn();
263        let pos = Vec3::ZERO; // DirectionalLight position is largely irrelevant
264        let trans = Transform::new(pos);
265
266        self.world.add_component(id, trans);
267        self.world.add_component(
268            id,
269            DirectionalLight {
270                color: Vec3::new(color.0.x, color.0.y, color.0.z),
271                intensity,
272                role: crate::renderer::components::LightRole::Sun,
273            },
274        );
275        EntityBuilder {
276            commands: self,
277            entity: id,
278        }
279    }
280
281    // ── Sahne Yardımcıları ────────────────────────────────────────────────────────────
282
283    /// Skybox spawn eder (ters yüzlü çok büyük küp). Renk arka plan rengini belirler.
284    pub fn spawn_skybox(&mut self, color: Color) -> EntityBuilder<'_, 'a> {
285        // Skip existing check since is_skybox is removed
286
287        // Wait, best approach for skybox is ignoring the duplication request if exists, but we must return an EntityBuilder...
288        let mesh = AssetManager::create_inverted_cube(&self.renderer.device);
289        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
290            &self.renderer.device,
291            &self.renderer.queue,
292            &self.renderer.scene.texture_bind_group_layout,
293        );
294        let mat = Material::new(bg).with_unlit(color.to_vec4()).with_skybox();
295        let id = self.world.spawn();
296        let mut trans = Transform::new(Vec3::ZERO);
297        trans.scale = Vec3::new(500.0, 500.0, 500.0);
298        trans.update_local_matrix();
299
300        self.world.add_component(id, trans);
301        self.world.add_component(id, mesh);
302        self.world.add_component(id, mat);
303        self.world.add_component(id, MeshRenderer::new());
304        EntityBuilder {
305            commands: self,
306            entity: id,
307        }
308    }
309
310    // ── Fizik Spawn ─────────────────────────────────────────────────────────────────────────
311
312    /// Fizik simulasyonuna katılan dinamik bir küp spawn eder.
313    /// `half_extents`: Her eksende yarı boyut. `mass`: kg cinsinden kütle (0 = statik).
314    pub fn spawn_rigid_cube(
315        &mut self,
316        pos: Vec3,
317        half_extents: Vec3,
318        color: Color,
319        mass: f32,
320    ) -> EntityBuilder<'_, 'a> {
321        let mesh = AssetManager::create_cube(&self.renderer.device);
322        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
323            &self.renderer.device,
324            &self.renderer.queue,
325            &self.renderer.scene.texture_bind_group_layout,
326        );
327        let mat = Material::new(bg).with_unlit(color.to_vec4());
328        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
329        // Scale'i half_extents ile eşleştir
330        {
331            let mut trans_store = self.world.borrow_mut::<Transform>();
332            if let Some(mut trans) = trans_store.get_mut(id.id()) {
333                trans.scale = half_extents * 2.0;
334                trans.update_local_matrix();
335            }
336        }
337        let mut rb = if mass > 0.0 {
338            RigidBody::new(mass, true)
339        } else {
340            RigidBody::new_static()
341        };
342        let col = Collider::box_collider(half_extents);
343        rb.update_inertia_from_collider(&col);
344        self.world.add_component(id, rb);
345        if mass > 0.0 {
346            self.world.add_component(id, Velocity::new(Vec3::ZERO));
347        }
348        self.world.add_component(id, col);
349        EntityBuilder {
350            commands: self,
351            entity: id,
352        }
353    }
354
355    /// Fizik simulasyonuna katılan dinamik bir küre spawn eder.
356    pub fn spawn_rigid_sphere(
357        &mut self,
358        pos: Vec3,
359        radius: f32,
360        color: Color,
361        mass: f32,
362    ) -> EntityBuilder<'_, 'a> {
363        let mesh = AssetManager::create_sphere(&self.renderer.device, radius, 16, 16);
364        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
365            &self.renderer.device,
366            &self.renderer.queue,
367            &self.renderer.scene.texture_bind_group_layout,
368        );
369        let mat = Material::new(bg).with_unlit(color.to_vec4());
370        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
371        let mut rb = if mass > 0.0 {
372            RigidBody::new(mass, true)
373        } else {
374            RigidBody::new_static()
375        };
376        let col = Collider::sphere(radius);
377        rb.update_inertia_from_collider(&col);
378        self.world.add_component(id, rb);
379        if mass > 0.0 {
380            self.world.add_component(id, Velocity::new(Vec3::ZERO));
381        }
382        self.world.add_component(id, col);
383        EntityBuilder {
384            commands: self,
385            entity: id,
386        }
387    }
388
389    /// Statik (hareket etmeyen) zemin düzlemi spawn eder.
390    pub fn spawn_static_plane(
391        &mut self,
392        pos: Vec3,
393        size: f32,
394        color: Color,
395    ) -> EntityBuilder<'_, 'a> {
396        let mesh = AssetManager::create_plane(&self.renderer.device, size);
397        let bg = self.asset_manager.as_mut().unwrap().create_white_texture(
398            &self.renderer.device,
399            &self.renderer.queue,
400            &self.renderer.scene.texture_bind_group_layout,
401        );
402        let mat = Material::new(bg).with_pbr(color.to_vec4(), 0.9, 0.0);
403        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
404        self.world.add_component(id, RigidBody::new_static());
405        self.world.add_component(
406            id,
407            Collider::box_collider(Vec3::new(size / 2.0, 0.05, size / 2.0)),
408        );
409        EntityBuilder {
410            commands: self,
411            entity: id,
412        }
413    }
414
415    // ── Görsel Yardımcılar ──────────────────────────────────────────────────────────────────────
416
417    /// Textureli bir materyal yükler ve bir küpe uygular.
418    pub fn spawn_textured_cube(&mut self, pos: Vec3, texture_path: &str) -> EntityBuilder<'_, 'a> {
419        let mesh = AssetManager::create_cube(&self.renderer.device);
420        let bg = self
421            .asset_manager
422            .as_mut()
423            .unwrap()
424            .load_material_texture(
425                &self.renderer.device,
426                &self.renderer.queue,
427                &self.renderer.scene.texture_bind_group_layout,
428                texture_path,
429            )
430            .unwrap_or_else(|_| {
431                self.asset_manager.as_mut().unwrap().create_white_texture(
432                    &self.renderer.device,
433                    &self.renderer.queue,
434                    &self.renderer.scene.texture_bind_group_layout,
435                )
436            });
437        let mat = Material::new(bg);
438        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
439        EntityBuilder {
440            commands: self,
441            entity: id,
442        }
443    }
444
445    /// Textureli bir materyal yükler ve bir düzleme uygular.
446    pub fn spawn_textured_plane(
447        &mut self,
448        pos: Vec3,
449        size: f32,
450        texture_path: &str,
451    ) -> EntityBuilder<'_, 'a> {
452        let mesh = AssetManager::create_plane(&self.renderer.device, size);
453        let bg = self
454            .asset_manager
455            .as_mut()
456            .unwrap()
457            .load_material_texture(
458                &self.renderer.device,
459                &self.renderer.queue,
460                &self.renderer.scene.texture_bind_group_layout,
461                texture_path,
462            )
463            .unwrap_or_else(|_| {
464                self.asset_manager.as_mut().unwrap().create_white_texture(
465                    &self.renderer.device,
466                    &self.renderer.queue,
467                    &self.renderer.scene.texture_bind_group_layout,
468                )
469            });
470        let mat = Material::new(bg);
471        let id = spawn_mesh_entity(self.world, pos, mesh, mat);
472        EntityBuilder {
473            commands: self,
474            entity: id,
475        }
476    }
477
478    // ── GLTF Yükleme ─────────────────────────────────────────────────────────────────────────────
479
480    /// GLTF/GLB dosyasını yükler ve dünya içinde spawn eder.
481    /// Animasyon ve iskelet hiyerarşisi otomatik oluşturulur.
482    pub fn spawn_gltf(
483        &mut self,
484        pos: Vec3,
485        path: &str,
486        attach_colliders: bool,
487    ) -> Result<EntityBuilder<'_, 'a>, GltfLoadError> {
488        let default_bg = self.asset_manager.as_mut().unwrap().create_white_texture(
489            &self.renderer.device,
490            &self.renderer.queue,
491            &self.renderer.scene.texture_bind_group_layout,
492        );
493        let default_mat = Material::new(default_bg.clone());
494
495        match self.asset_manager.as_mut().unwrap().load_gltf_scene(
496            &self.renderer.device,
497            &self.renderer.queue,
498            &self.renderer.scene.texture_bind_group_layout,
499            default_bg,
500            path,
501        ) {
502            Ok(asset) => {
503                let root = self.world.spawn();
504                let mut trans = Transform::new(pos);
505                trans.update_local_matrix();
506
507                self.world.add_component(root, trans);
508                self.world
509                    .add_component(root, gizmo_physics_core::components::GlobalTransform::default());
510                self.world
511                    .add_component(root, EntityName(format!("GLTF: {}", path)));
512                self.world
513                    .add_component(root, gizmo_core::component::Children(Vec::new()));
514
515                let mut skeletons = Vec::new();
516                for skel_data in &asset.skeletons {
517                    skeletons.push(self.renderer.create_skeleton(std::sync::Arc::new(skel_data.clone())));
518                }
519
520                for node in &asset.roots {
521                    spawn_gltf_node_flat(
522                        self.world,
523                        node,
524                        root.id(),
525                        default_mat.clone(),
526                        attach_colliders,
527                        &skeletons,
528                    );
529                }
530
531                if !skeletons.is_empty() {
532                    self.world.add_component(root, skeletons[0].clone());
533                }
534
535                if !asset.animations.is_empty() {
536                    self.world.add_component(
537                        root,
538                        gizmo_renderer::components::AnimationPlayer {
539                            active_animation: 0,
540                            current_time: 0.0,
541                            loop_anim: true,
542                            speed: 1.0,
543                            animations: std::sync::Arc::from(
544                                asset.animations.clone().into_boxed_slice(),
545                            ),
546                            ..Default::default()
547                        },
548                    );
549                }
550
551                Ok(EntityBuilder {
552                    commands: self,
553                    entity: root,
554                })
555            }
556            Err(e) => Err(GltfLoadError::Load {
557                path: path.to_string(),
558                source: e.to_string(),
559            }),
560        }
561    }
562
563    /// Asenkron GLTF yükleme tamamlandığında çağrılacak metot.
564    pub fn spawn_gltf_async_completed(
565        &mut self,
566        completion: gizmo_renderer::async_assets::GltfImportCompletion,
567        pos: Vec3,
568        attach_colliders: bool,
569    ) -> Result<EntityBuilder<'_, 'a>, GltfLoadError> {
570        let default_bg = self.asset_manager.as_mut().unwrap().create_white_texture(
571            &self.renderer.device,
572            &self.renderer.queue,
573            &self.renderer.scene.texture_bind_group_layout,
574        );
575        let default_mat = Material::new(default_bg.clone());
576
577        match self.asset_manager.as_mut().unwrap().load_gltf_from_import(
578            &self.renderer.device,
579            &self.renderer.queue,
580            &self.renderer.scene.texture_bind_group_layout,
581            default_bg,
582            &completion.path,
583            completion.document,
584            completion.buffers,
585            completion.images,
586        ) {
587            Ok(asset) => {
588                let root = self.world.spawn();
589                let mut trans = Transform::new(pos);
590                trans.update_local_matrix();
591
592                self.world.add_component(root, trans);
593                self.world
594                    .add_component(root, gizmo_physics_core::components::GlobalTransform::default());
595                self.world
596                    .add_component(root, EntityName(format!("GLTF: {}", completion.path)));
597                self.world
598                    .add_component(root, gizmo_core::component::Children(Vec::new()));
599
600                let mut skeletons = Vec::new();
601                for skel_data in &asset.skeletons {
602                    skeletons.push(self.renderer.create_skeleton(std::sync::Arc::new(skel_data.clone())));
603                }
604
605                for node in &asset.roots {
606                    spawn_gltf_node_flat(
607                        self.world,
608                        node,
609                        root.id(),
610                        default_mat.clone(),
611                        attach_colliders,
612                        &skeletons,
613                    );
614                }
615
616                if !skeletons.is_empty() {
617                    self.world.add_component(root, skeletons[0].clone());
618                }
619
620                if !asset.animations.is_empty() {
621                    self.world.add_component(
622                        root,
623                        gizmo_renderer::components::AnimationPlayer {
624                            active_animation: 0,
625                            current_time: 0.0,
626                            loop_anim: true,
627                            speed: 1.0,
628                            animations: std::sync::Arc::from(
629                                asset.animations.clone().into_boxed_slice(),
630                            ),
631                            ..Default::default()
632                        },
633                    );
634                }
635
636                Ok(EntityBuilder {
637                    commands: self,
638                    entity: root,
639                })
640            }
641            Err(e) => Err(GltfLoadError::Load {
642                path: completion.path.clone(),
643                source: e.to_string(),
644            }),
645        }
646    }
647}
648
649// ─── EntityBuilder — Zincir API ───────────────────────────────────────────────
650
651/// Spawn edilen entity'e ek bileşenler eklemek için zincir builder.
652pub struct EntityBuilder<'b, 'a> {
653    commands: &'b mut Commands<'a>,
654    entity: Entity,
655}
656
657impl<'b, 'a> EntityBuilder<'b, 'a> {
658    /// Entity'e bir isim (tag) ata. Update içinde `world.entity_named("...")` ile bulunabilir.
659    pub fn with_name(self, name: &str) -> Self {
660        self.commands
661            .world
662            .add_component(self.entity, EntityName(name.to_string()));
663        self
664    }
665
666    /// Herhangi bir ek bileşen ekle.
667    pub fn with<C: gizmo_core::Component + 'static>(self, component: C) -> Self {
668        self.commands.world.add_component(self.entity, component);
669        self
670    }
671
672    /// Entity ID'sini tüket ve döndür.
673    pub fn id(self) -> Entity {
674        self.entity
675    }
676}
677
678impl<'b, 'a> From<EntityBuilder<'b, 'a>> for Entity {
679    fn from(b: EntityBuilder<'b, 'a>) -> Entity {
680        b.entity
681    }
682}
683
684// ─── Yardımcı: Mesh entity oluştur ────────────────────────────────────────────────────────────────
685
686fn spawn_mesh_entity(
687    world: &mut World,
688    pos: Vec3,
689    mesh: gizmo_renderer::components::Mesh,
690    mat: Material,
691) -> Entity {
692    let id = world.spawn();
693    let mut trans = Transform::new(pos);
694    trans.update_local_matrix();
695
696    world.add_component(id, trans);
697    world.add_component(id, gizmo_physics_core::components::GlobalTransform::default());
698    world.add_component(id, mesh);
699    world.add_component(id, mat);
700    world.add_component(id, MeshRenderer::new());
701    id
702}
703
704/// GLTF hiyerarşisini düz (flat) olarak spawn eder — parent/child olmadan.
705fn spawn_gltf_node_flat(
706    world: &mut World,
707    node: &gizmo_renderer::asset::GltfNodeData,
708    parent_id: u32,
709    default_mat: Material,
710    attach_colliders: bool,
711    skeletons: &[gizmo_renderer::components::Skeleton],
712) {
713    use gizmo_core::component::{Children, Parent};
714    let entity = world.spawn();
715    let name = node.name.clone().unwrap_or_else(|| "GLTF_Node".to_string());
716    world.add_component(entity, EntityName(name));
717    world.add_component(entity, Parent(parent_id));
718    world.add_component(entity, Children(Vec::new()));
719
720    {
721        let mut ch_store = world.borrow_mut::<Children>();
722        // Safe to push since entity just spawned and didn't trigger any complex re-borrow updates
723        if let Some(mut parent_ch) = ch_store.get_mut(parent_id) {
724            parent_ch.0.push(entity.id());
725        }
726    }
727
728    let raw_rot = Quat::from_xyzw(
729        node.rotation[0],
730        node.rotation[1],
731        node.rotation[2],
732        node.rotation[3],
733    );
734    let rot = if raw_rot.is_nan() || raw_rot.length() < 0.0001 {
735        Quat::IDENTITY
736    } else {
737        raw_rot.normalize()
738    };
739
740    let mut t = Transform::new(Vec3::new(
741        node.translation[0],
742        node.translation[1],
743        node.translation[2],
744    ))
745    .with_rotation(rot)
746    .with_scale(Vec3::new(node.scale[0], node.scale[1], node.scale[2]));
747    t.update_local_matrix();
748
749    if node.skin_index.is_some() || node.name.as_deref() == Some("Armature") {
750        t = Transform::default();
751        t.update_local_matrix();
752    }
753    
754    world.add_component(entity, t);
755    world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
756
757    tracing::debug!("SPAWN GLTF NODE: name={:?}, num_primitives={}", node.name, node.primitives.len());
758    let mut newly_added_prims = Vec::new();
759    for (mesh, mat_opt) in node.primitives.iter() {
760        tracing::debug!("  SPAWN PRIM: mesh_source='{}', bounds_min={:?}, bounds_max={:?}", mesh.source, mesh.bounds.min, mesh.bounds.max);
761        let prim = world.spawn();
762        let mut prim_t = Transform::new(Vec3::ZERO);
763        prim_t.update_local_matrix();
764        world.add_component(prim, prim_t);
765        world.add_component(prim, gizmo_physics_core::components::GlobalTransform::default());
766        world.add_component(prim, Parent(entity.id()));
767        world.add_component(prim, Children(Vec::new()));
768
769        newly_added_prims.push(prim.id());
770
771        world.add_component(prim, mesh.clone());
772        world.add_component(prim, mat_opt.clone().unwrap_or_else(|| default_mat.clone()));
773        world.add_component(prim, MeshRenderer::new());
774
775        if let Some(skin_idx) = node.skin_index {
776            if skin_idx < skeletons.len() {
777                world.add_component(prim, skeletons[skin_idx].clone());
778            }
779        }
780
781        if attach_colliders {
782            let extents = (mesh.bounds.max - mesh.bounds.min) / 2.0;
783            let center_offset = (mesh.bounds.max + mesh.bounds.min) / 2.0;
784            let cx = extents.x.max(0.01);
785            let cy = extents.y.max(0.01);
786            let cz = extents.z.max(0.01);
787            
788            // Eğer merkeze tam oturmuyorsa Compound(offset_box) kullanarak hizala
789            if center_offset.length_squared() > 0.0001 {
790                world.add_component(
791                    prim,
792                    gizmo_physics_core::Collider::offset_box(center_offset.into(), gizmo_math::Vec3::new(cx, cy, cz)),
793                );
794            } else {
795                world.add_component(prim, gizmo_physics_core::Collider::new_aabb(cx, cy, cz));
796            }
797        }
798    }
799
800    // Pulling borrow_mut OUTSIDE the loop avoiding multiple overlapping mutable queries
801    if !newly_added_prims.is_empty() {
802        {
803            let mut ch_store = world.borrow_mut::<Children>();
804            if let Some(mut parent_ch) = ch_store.get_mut(entity.id()) {
805                parent_ch.0.extend(newly_added_prims);
806            }
807        }
808    }
809
810    for child_node in &node.children {
811        spawn_gltf_node_flat(
812            world,
813            child_node,
814            entity.id(),
815            default_mat.clone(),
816            attach_colliders,
817            skeletons,
818        );
819    }
820}
821
822// ─── WorldExt Trait — Update içinde kısa sorgular ─────────────────────────────
823
824/// World üzerine eklenen kolaylık metodları.
825/// `use gizmo::prelude::*;` ile otomatik içeri alınır.
826pub trait WorldExt {
827    /// İsme göre Entity ID'sini (u32) bul.
828    fn entity_named(&self, name: &str) -> Option<u32>;
829
830    /// İsme göre entity'nin Transform'unu değiştir. Transform matrisi otomatik güncellenir.
831    fn move_entity_named<F: FnMut(&mut gizmo_physics_core::Transform)>(&mut self, name: &str, f: F);
832
833    /// İsme göre entity'nin dünya pozisyonunu al.
834    fn position_of(&self, name: &str) -> Option<Vec3>;
835
836    /// İsme göre herhangi bir bileşeni değiştir.
837    ///
838    /// # Örnek
839    /// ```rust,ignore
840    /// world.modify::<Camera>("Kamera", |cam| { cam.fov = 90.0_f32.to_radians(); });
841    /// world.modify::<Material>("Top", |mat| { mat.albedo = Color::BLUE.to_vec4(); });
842    /// ```
843    fn modify<T: gizmo_core::Component + 'static, F: FnMut(&mut T)>(&mut self, name: &str, f: F);
844}
845
846impl WorldExt for World {
847    fn entity_named(&self, name: &str) -> Option<u32> {
848        let mut names = self.query::<&EntityName>()?;
849        for (id, n) in names.iter_mut() {
850            if n.0 == name {
851                return Some(id);
852            }
853        }
854        None
855    }
856
857    fn move_entity_named<F: FnMut(&mut gizmo_physics_core::Transform)>(&mut self, name: &str, mut f: F) {
858        let target: Option<u32> = {
859            if let Some(mut names) = self.query::<&EntityName>() {
860                let mut found = None;
861                for (id, n) in names.iter_mut() {
862                    if n.0 == name {
863                        found = Some(id);
864                        break;
865                    }
866                }
867                found
868            } else {
869                None
870            }
871        };
872        if let Some(target_id) = target {
873            if let Some(mut transforms) =
874                self.query_mut::<gizmo_core::prelude::Mut<gizmo_physics_core::Transform>>()
875            {
876                for (tid, mut trans) in transforms.iter_mut() {
877                    if tid == target_id {
878                        f(&mut trans);
879                        trans.update_local_matrix();
880                    }
881                }
882            }
883        }
884    }
885
886    fn position_of(&self, name: &str) -> Option<Vec3> {
887        let target_id = self.entity_named(name)?;
888        let transforms = self.borrow::<gizmo_physics_core::Transform>();
889        transforms.get(target_id).map(|t| t.position)
890    }
891
892    fn modify<T: gizmo_core::Component + 'static, F: FnMut(&mut T)>(
893        &mut self,
894        name: &str,
895        mut f: F,
896    ) {
897        let target: Option<u32> = {
898            if let Some(mut names) = self.query::<&EntityName>() {
899                let mut found = None;
900                for (id, n) in names.iter_mut() {
901                    if n.0 == name {
902                        found = Some(id);
903                        break;
904                    }
905                }
906                found
907            } else {
908                None
909            }
910        };
911        if let Some(target_id) = target {
912            {
913                let mut storage = self.borrow_mut::<T>();
914                if let Some(mut comp) = storage.get_mut(target_id) {
915                    f(&mut *comp);
916                }
917            }
918        }
919    }
920}
921
922// ─── InputExt Trait — KeyCode doğrudan kabul eden kısaltmalar ─────────────────
923// gizmo-core'da winit bağımlılığı olmadığı için bu trait gizmo crate'inde tanımlıdır.
924
925/// `Input` üzerine eklenen ergonomik metodlar.
926/// `use gizmo::prelude::*;` ile otomatik içeri alınır.
927///
928/// # Örnek
929/// ```rust,ignore
930/// if input.pressed(Key::KeyW) { trans.position.z -= 5.0 * dt; }
931/// if input.just_pressed(Key::Space) { player.jump(); }
932/// ```
933pub trait InputExt {
934    /// Tuş basılı mı? `Key::KeyW`, `Key::Space` gibi `KeyCode` varyantlarını doğrudan alır.
935    fn pressed(&self, keycode: winit::keyboard::KeyCode) -> bool;
936
937    /// Tuş bu frame'de ilk kez mi basıldı? (tek seferlik tetikleme)
938    fn just_pressed(&self, keycode: winit::keyboard::KeyCode) -> bool;
939
940    /// Tuş bu frame'de mi bırakıldı?
941    fn just_released(&self, keycode: winit::keyboard::KeyCode) -> bool;
942}
943
944impl InputExt for gizmo_core::input::Input {
945    #[inline]
946    fn pressed(&self, keycode: winit::keyboard::KeyCode) -> bool {
947        self.is_key_pressed(keycode as u32)
948    }
949    #[inline]
950    fn just_pressed(&self, keycode: winit::keyboard::KeyCode) -> bool {
951        self.is_key_just_pressed(keycode as u32)
952    }
953    #[inline]
954    fn just_released(&self, keycode: winit::keyboard::KeyCode) -> bool {
955        self.is_key_just_released(keycode as u32)
956    }
957}