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