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