Skip to main content

gizmo_scene/
scene.rs

1use gizmo_core::{EntityName, World};
2use gizmo_math::Vec4;
3use gizmo_renderer::asset::AssetManager;
4use gizmo_renderer::components::{Material, Mesh, MeshRenderer};
5use serde::{Deserialize, Serialize};
6use std::fs;
7use std::sync::Arc;
8
9use gizmo_core::component::{Children, Parent};
10use std::collections::HashMap;
11
12/// Tam sahne verisi — tüm entity'ler ve bileşenleri
13#[derive(Serialize, Deserialize, Clone)]
14pub struct SceneData {
15    pub entities: Vec<EntityData>,
16    #[serde(default)]
17    pub joints: Vec<gizmo_physics::joints::Joint>,
18}
19
20/// Prefab verisi — Tıpkı SceneData gibi ama kök entity'si var
21#[derive(Serialize, Deserialize, Clone)]
22pub struct PrefabData {
23    pub root_id: u32,
24    pub entities: Vec<EntityData>,
25    #[serde(default)]
26    pub joints: Vec<gizmo_physics::joints::Joint>,
27}
28
29/// Tek bir entity'nin serileştirilebilir verisi
30#[derive(Serialize, Deserialize, Clone)]
31pub struct EntityData {
32    pub original_id: u32,
33    pub name: Option<String>,
34    pub mesh_source: Option<String>,
35    pub material_source: Option<MaterialData>,
36    #[serde(default)]
37    pub parent_id: Option<u32>,
38    #[serde(default)]
39    pub components: std::collections::BTreeMap<String, ron::Value>,
40}
41
42/// Material serileştirme verisi (GPU bind group'u diske yazılamaz)
43#[derive(Serialize, Deserialize, Clone)]
44pub struct MaterialData {
45    pub albedo: Vec4,
46    pub roughness: f32,
47    pub metallic: f32,
48    pub unlit: f32,
49    pub texture_source: Option<String>,
50}
51
52impl SceneData {
53    /// Mevcut World durumunu JSON dosyası olarak diske kaydeder
54    pub fn save(
55        world: &World,
56        file_path: &str,
57        registry: &crate::registry::SceneRegistry,
58    ) -> Result<(), String> {
59        if let Some(parent) = std::path::Path::new(file_path).parent() {
60            let _ = fs::create_dir_all(parent);
61        }
62        let entities_data = Self::serialize_entities(
63            world,
64            world
65                .iter_alive_entities()
66                .into_iter()
67                .map(|e| e.id())
68                .collect(),
69            registry,
70        );
71
72        let mut joints = Vec::new();
73        if let Ok(physics_world) = world.try_get_resource::<gizmo_physics::world::PhysicsWorld>() {
74            joints = physics_world.joints.clone();
75        }
76
77        let scene = SceneData {
78            entities: entities_data,
79            joints,
80        };
81
82        let string_data = ron::ser::to_string_pretty(&scene, ron::ser::PrettyConfig::default())
83            .map_err(|e| format!("[SceneData::save] Serileştirme hatası: {}", e))?;
84
85        fs::write(file_path, string_data)
86            .map_err(|e| format!("[SceneData::save] Dosya yazma hatası: {}", e))?;
87
88        tracing::info!("✅ Sahne kaydedildi → {}", file_path);
89        Ok(())
90    }
91
92    /// Belirtilen entity ID'lerini serileştirir
93    pub fn serialize_entities(
94        world: &World,
95        entity_ids: Vec<u32>,
96        registry: &crate::registry::SceneRegistry,
97    ) -> Vec<EntityData> {
98        let mut entities_data = Vec::new();
99        let names = world.borrow::<EntityName>();
100        let meshes = world.borrow::<Mesh>();
101        let materials = world.borrow::<Material>();
102        let parents = world.borrow::<Parent>();
103
104        for &id in &entity_ids {
105            let name = names.get(id).map(|n| n.0.clone());
106
107            // Gizmo Studio'nun içsel araçlarını kaydetme (Gizmo kurguları, Highlight box, grid vs.)
108            if let Some(ref n) = name {
109                if n.starts_with("Editor ") || n == "Highlight Box" {
110                    continue;
111                }
112            }
113
114            let mesh_source = meshes.get(id).map(|m| m.source.clone());
115            let material_source = materials.get(id).map(|m| MaterialData {
116                albedo: m.albedo,
117                roughness: m.roughness,
118                metallic: m.metallic,
119                unlit: match m.material_type {
120                    gizmo_renderer::components::MaterialType::Skybox => 2.0,
121                    gizmo_renderer::components::MaterialType::Unlit => 1.0,
122                    _ => 0.0,
123                },
124                texture_source: m.texture_source.clone(),
125            });
126            let parent_id = parents.get(id).map(|p| p.0);
127
128            // Dinamik bileşenleri Registry üzerinden tarayarak JSON AST'sine (ron::Value) dönüştür
129            let mut dynamic_components = std::collections::BTreeMap::new();
130            for comp_name in registry.all_components() {
131                if let Some(serializer) = registry.get_serializer(comp_name) {
132                    if let Some(comp_value) = serializer(world, id) {
133                        dynamic_components.insert(comp_name.clone(), comp_value);
134                    }
135                }
136            }
137
138            if name.is_some()
139                || mesh_source.is_some()
140                || material_source.is_some()
141                || parent_id.is_some()
142                || !dynamic_components.is_empty()
143            {
144                entities_data.push(EntityData {
145                    original_id: id,
146                    name,
147                    mesh_source,
148                    material_source,
149                    parent_id,
150                    components: dynamic_components,
151                });
152            }
153        }
154        entities_data
155    }
156
157    /// JSON sahne dosyasını okuyup World'e entity olarak yükler
158    pub fn load_into(
159        file_path: &str,
160        world: &mut World,
161        device: &wgpu::Device,
162        queue: &wgpu::Queue,
163        texture_bind_group_layout: &wgpu::BindGroupLayout,
164        asset_manager: &mut AssetManager,
165        default_texture_bind_group: Arc<wgpu::BindGroup>,
166        registry: &crate::registry::SceneRegistry,
167    ) -> bool {
168        let string_data = match fs::read_to_string(file_path) {
169            Ok(content) => content,
170            Err(_) => return false,
171        };
172
173        let scene: SceneData = match ron::from_str(&string_data) {
174            Ok(s) => s,
175            Err(e) => {
176                tracing::info!("❌ Sahne dosyası geçersiz ({}): {}", file_path, e);
177                return false;
178            }
179        };
180
181        let id_map = Self::instantiate_entities(
182            scene.entities,
183            None,
184            world,
185            device,
186            queue,
187            texture_bind_group_layout,
188            asset_manager,
189            &default_texture_bind_group,
190            registry,
191        );
192
193        if let Ok(mut physics_world) = world.try_get_resource_mut::<gizmo_physics::world::PhysicsWorld>() {
194            for mut joint in scene.joints {
195                if let (Some(&new_a), Some(&new_b)) = (id_map.get(&joint.entity_a.id()), id_map.get(&joint.entity_b.id())) {
196                    joint.entity_a = gizmo_core::entity::Entity::new(new_a, 0);
197                    joint.entity_b = gizmo_core::entity::Entity::new(new_b, 0);
198                    physics_world.joints.push(joint);
199                }
200            }
201        }
202
203        tracing::info!("✅ Sahne yüklendi ← {}", file_path);
204        true
205    }
206
207    /// Verilen entity listesini instantiate eder, id eşleştirmelerini yapar ve gerekirse root bir parent'a bağlar
208    pub fn instantiate_entities(
209        entities: Vec<EntityData>,
210        root_parent: Option<u32>,
211        world: &mut World,
212        device: &wgpu::Device,
213        queue: &wgpu::Queue,
214        texture_bind_group_layout: &wgpu::BindGroupLayout,
215        asset_manager: &mut AssetManager,
216        default_texture_bind_group: &Arc<wgpu::BindGroup>,
217        registry: &crate::registry::SceneRegistry,
218    ) -> HashMap<u32, u32> {
219        let mut id_map = HashMap::new(); // original_id -> new_entity_id
220        let mut entity_structs = HashMap::new();
221
222        // Entity'leri oluştur ve id haritasını çıkar
223        for data in &entities {
224            let root_ent = world.spawn();
225            id_map.insert(data.original_id, root_ent.id());
226            entity_structs.insert(root_ent.id(), root_ent);
227        }
228
229        // Parent-Child ilişkilerini toplayacağımız geçici yapı (new_parent -> [new_children])
230        let mut children_map: HashMap<u32, Vec<u32>> = HashMap::new();
231
232        for data in entities {
233            let new_id = id_map[&data.original_id];
234            let entity = entity_structs[&new_id];
235
236            if let Some(n) = data.name {
237                world.add_component(entity, EntityName::new(&n));
238            }
239            // Dinamik bileşenleri Registry üzerinden yükle
240            for (comp_name, comp_val) in &data.components {
241                if let Some(deserializer) = registry.get_deserializer(comp_name) {
242                    deserializer(world, new_id, comp_val);
243                }
244            }
245
246            if let Some(mesh_src) = data.mesh_source {
247                let mesh = if mesh_src == "inverted_cube" {
248                    AssetManager::create_inverted_cube(device)
249                } else if mesh_src == "plane" {
250                    AssetManager::create_plane(device, 200.0)
251                } else if mesh_src == "standard_cube" {
252                    AssetManager::create_cube(device)
253                } else if mesh_src == "sphere" {
254                    AssetManager::create_sphere(device, 1.0, 16, 16)
255                } else if mesh_src == "sprite_quad" {
256                    AssetManager::create_sprite_quad(device, 1.0, 1.0)
257                } else if mesh_src.starts_with("gltf_mesh_") {
258                    if let Some(cached) = asset_manager.get_cached_mesh(&mesh_src) {
259                        cached
260                    } else {
261                        // Eğer GLTF RAM'de yoksa, path'i çıkar ve gizlice parse edip Cache'e yükle.
262                        let file_path = if let Some(idx) = mesh_src.find(".glb") {
263                            Some(&mesh_src["gltf_mesh_".len()..idx + 4])
264                        } else {
265                            mesh_src
266                                .find(".gltf")
267                                .map(|idx| &mesh_src["gltf_mesh_".len()..idx + 5])
268                        };
269
270                        if let Some(path) = file_path {
271                            let _ = asset_manager.load_gltf_scene(
272                                device,
273                                queue,
274                                texture_bind_group_layout,
275                                default_texture_bind_group.clone(),
276                                path,
277                            );
278                            if let Some(cached) = asset_manager.get_cached_mesh(&mesh_src) {
279                                cached
280                            } else {
281                                asset_manager.loading_placeholder_mesh(device)
282                            }
283                        } else {
284                            asset_manager.loading_placeholder_mesh(device)
285                        }
286                    }
287                } else if mesh_src.starts_with("obj:") {
288                    let path = mesh_src.trim_start_matches("obj:");
289                    asset_manager.load_obj(device, path)
290                } else {
291                    // Fail-safe obj loading
292                    asset_manager.load_obj(device, &mesh_src)
293                };
294                world.add_component(entity, mesh);
295            }
296
297            if let Some(mat_data) = data.material_source {
298                let bind_group = if let Some(tex_path) = &mat_data.texture_source {
299                    asset_manager
300                        .load_material_texture(device, queue, texture_bind_group_layout, tex_path)
301                        .unwrap_or_else(|e| {
302                            tracing::info!("Scene Texture error: {}", e);
303                            default_texture_bind_group.clone()
304                        })
305                } else {
306                    default_texture_bind_group.clone()
307                };
308
309                let mut mat = Material::new(bind_group);
310                mat.albedo = mat_data.albedo;
311                mat.roughness = mat_data.roughness;
312                mat.metallic = mat_data.metallic;
313                mat.material_type = if mat_data.unlit > 1.5 {
314                    gizmo_renderer::components::MaterialType::Skybox
315                } else if mat_data.unlit > 0.5 {
316                    gizmo_renderer::components::MaterialType::Unlit
317                } else {
318                    gizmo_renderer::components::MaterialType::Pbr
319                };
320                mat.texture_source = mat_data.texture_source;
321                world.add_component(entity, mat);
322                world.add_component(entity, MeshRenderer::new());
323            }
324
325            // Hiyerarşi Bağlantıları
326            let mut resolved_parent = None;
327            if let Some(orig_parent) = data.parent_id {
328                // Kendi içerisinde (bu sahnede/prefabda) kaydedilmiş bir parent varsa ona bağla
329                if let Some(&p_id) = id_map.get(&orig_parent) {
330                    resolved_parent = Some(p_id);
331                }
332            } else {
333                // Eğer entity'nin kendi parent'ı yoksa, root_parent verilmişse ona tak
334                resolved_parent = root_parent;
335            }
336
337            if let Some(p_id) = resolved_parent {
338                world.add_component(entity, Parent(p_id));
339                children_map.entry(p_id).or_default().push(new_id);
340            }
341        }
342
343        // Tüm Children bileşenlerini ilgili parent'lara ekle
344        for (p_id, mut c_list) in children_map {
345            if let Some(&p_ent) = entity_structs.get(&p_id) {
346                world.add_component(p_ent, Children(c_list));
347            } else if let Some(p_ent) = world.get_entity(p_id) {
348                // External parent (e.g. root_parent passed in) — merge with existing children.
349                let existing: Vec<u32> = world
350                    .borrow::<Children>()
351                    .get(p_id)
352                    .map(|c| c.0.clone())
353                    .unwrap_or_default();
354                let mut merged = existing;
355                merged.append(&mut c_list);
356                world.add_component(p_ent, Children(merged));
357            }
358        }
359
360        id_map
361    }
362
363    /// Prefab kaydet (Verilen entity ve tüm alt çocukları)
364    pub fn save_prefab(
365        world: &World,
366        root_entity_id: u32,
367        file_path: &str,
368        registry: &crate::registry::SceneRegistry,
369    ) -> Result<(), String> {
370        if let Some(parent) = std::path::Path::new(file_path).parent() {
371            let _ = fs::create_dir_all(parent);
372        }
373
374        let mut ids_to_save = vec![root_entity_id];
375        let children_storage = world.borrow::<Children>();
376
377        let mut i = 0;
378        while i < ids_to_save.len() {
379            let current = ids_to_save[i];
380            if let Some(children_comp) = children_storage.get(current) {
381                for &child_id in &children_comp.0 {
382                    ids_to_save.push(child_id);
383                }
384            }
385            i += 1;
386        }
387
388        let mut entities_data = Self::serialize_entities(world, ids_to_save.clone(), registry);
389
390        // Prefab'ın root entity'sinin parent'ını kopar ki bağımsız yüklensin
391        if let Some(root_data) = entities_data
392            .iter_mut()
393            .find(|d| d.original_id == root_entity_id)
394        {
395            root_data.parent_id = None;
396        }
397
398        let mut joints = Vec::new();
399        if let Ok(physics_world) = world.try_get_resource::<gizmo_physics::world::PhysicsWorld>() {
400            for joint in &physics_world.joints {
401                if ids_to_save.contains(&joint.entity_a.id()) && ids_to_save.contains(&joint.entity_b.id()) {
402                    joints.push(joint.clone());
403                }
404            }
405        }
406
407        let prefab = PrefabData {
408            root_id: root_entity_id,
409            entities: entities_data,
410            joints,
411        };
412
413        let string_data = ron::ser::to_string_pretty(&prefab, ron::ser::PrettyConfig::default())
414            .map_err(|e| format!("[SceneData::save_prefab] Serileştirme hatası: {}", e))?;
415
416        fs::write(file_path, string_data)
417            .map_err(|e| format!("[SceneData::save_prefab] Dosya yazma hatası: {}", e))?;
418
419        tracing::info!("✅ Prefab kaydedildi → {}", file_path);
420        Ok(())
421    }
422
423    /// Prefab yükle
424    pub fn load_prefab(
425        file_path: &str,
426        parent_entity: Option<u32>,
427        world: &mut World,
428        device: &wgpu::Device,
429        queue: &wgpu::Queue,
430        texture_bind_group_layout: &wgpu::BindGroupLayout,
431        asset_manager: &mut AssetManager,
432        default_texture_bind_group: Arc<wgpu::BindGroup>,
433        registry: &crate::registry::SceneRegistry,
434    ) -> Option<u32> {
435        let string_data = match fs::read_to_string(file_path) {
436            Ok(content) => content,
437            Err(_) => return None,
438        };
439
440        let prefab: PrefabData = match ron::from_str(&string_data) {
441            Ok(p) => p,
442            Err(e) => {
443                tracing::info!("❌ Prefab dosyası geçersiz ({}): {}", file_path, e);
444                return None;
445            }
446        };
447
448        let id_map = Self::instantiate_entities(
449            prefab.entities,
450            parent_entity,
451            world,
452            device,
453            queue,
454            texture_bind_group_layout,
455            asset_manager,
456            &default_texture_bind_group,
457            registry,
458        );
459
460        let new_root_id = id_map.get(&prefab.root_id).copied();
461
462        if let (Some(new_r), Some(p_id)) = (new_root_id, parent_entity) {
463            if let Some(p_ent) = world.get_entity(p_id) {
464                let mut children_list = world
465                    .borrow::<Children>()
466                    .get(p_id)
467                    .map(|c| c.0.clone())
468                    .unwrap_or_default();
469                children_list.push(new_r);
470                world.add_component(p_ent, Children(children_list));
471            }
472        }
473
474        if let Ok(mut physics_world) = world.try_get_resource_mut::<gizmo_physics::world::PhysicsWorld>() {
475            for mut joint in prefab.joints {
476                if let (Some(&new_a), Some(&new_b)) = (id_map.get(&joint.entity_a.id()), id_map.get(&joint.entity_b.id())) {
477                    joint.entity_a = gizmo_core::entity::Entity::new(new_a, 0);
478                    joint.entity_b = gizmo_core::entity::Entity::new(new_b, 0);
479                    physics_world.joints.push(joint);
480                }
481            }
482        }
483
484        tracing::info!("✅ Prefab yüklendi ← {}", file_path);
485        new_root_id
486    }
487
488    /// Entity listesini döndürür (Lua API'si için)
489    pub fn get_entity_names(world: &World) -> Vec<(u32, String)> {
490        let mut result = Vec::new();
491        let names = world.borrow::<EntityName>();
492        for (entity_id, _) in names.iter() {
493            if let Some(name) = names.get(entity_id) {
494                result.push((entity_id, name.0.clone()));
495            }
496        }
497        result
498    }
499
500    /// İsme göre entity bul
501    pub fn find_entity_by_name(world: &World, target_name: &str) -> Option<u32> {
502        let names = world.borrow::<EntityName>();
503        for (entity_id, _) in names.iter() {
504            if let Some(name) = names.get(entity_id) {
505                if name.0 == target_name {
506                    return Some(entity_id);
507                }
508            }
509        }
510        None
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517    use gizmo_core::World;
518    use gizmo_physics::joints::data::{Joint, JointType};
519
520    #[test]
521    fn test_prefab_joint_serialization() {
522        let mut world = World::new();
523        let ent1 = world.spawn();
524        let ent2 = world.spawn();
525
526        let joint = Joint {
527            entity_a: ent1,
528            entity_b: ent2,
529            local_anchor_a: gizmo_math::Vec3::ZERO,
530            local_anchor_b: gizmo_math::Vec3::ZERO,
531            break_force: 1000.0,
532            break_torque: 1000.0,
533            is_broken: false,
534            collision_enabled: false,
535            data: gizmo_physics::joints::data::JointData::Fixed,
536        };
537
538        let prefab_data = PrefabData {
539            root_id: ent1.id(),
540            entities: vec![],
541            joints: vec![joint.clone()],
542        };
543
544        let serialized = ron::ser::to_string(&prefab_data).unwrap();
545        assert!(serialized.contains("Fixed"));
546
547        let deserialized: PrefabData = ron::from_str(&serialized).unwrap();
548        assert_eq!(deserialized.joints.len(), 1);
549        assert!(matches!(deserialized.joints[0].data, gizmo_physics::joints::data::JointData::Fixed));
550    }
551}