use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
#[cfg(feature = "audio")]
use std::str::FromStr;
use freecs::Entity;
use crate::ecs::audio::components::AudioSource;
#[cfg(feature = "audio")]
use crate::ecs::audio::systems::load_sound_from_cursor;
use crate::ecs::bounding_volume::components::{BoundingVolume, OrientedBoundingBox};
use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::lines::components::Lines;
use crate::ecs::material::resources::material_registry_insert;
use crate::ecs::mesh::components::InstancedMesh;
use crate::ecs::prefab::resources::mesh_cache_insert;
use crate::ecs::prefab::{import_gltf_from_path, spawn_prefab};
use crate::ecs::world::Vec3;
use crate::ecs::world::commands::WorldCommand;
use crate::ecs::world::components::{
CastsShadow, GlobalTransform, LocalTransformDirty, MaterialRef, Name, Parent, RenderMesh,
Visibility,
};
use crate::ecs::world::{
ANIMATION_PLAYER, AUDIO_SOURCE, BOUNDING_VOLUME, CAMERA, CASTS_SHADOW, DECAL, GLOBAL_TRANSFORM,
GRASS_INTERACTOR, GRASS_REGION, INSTANCED_MESH, LIGHT, LINES, LOCAL_TRANSFORM,
LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, NAME, NAVMESH_AGENT, PARENT, PARTICLE_EMITTER,
RENDER_LAYER, RENDER_MESH, SCRIPT, TEXT, VISIBILITY, WATER, World,
};
use crate::render::wgpu::texture_cache::texture_cache_add_reference;
use super::asset_uuid::AssetUuid;
use super::audio::ScenePrefabInstance;
use super::components::{Scene, SceneComponents, SceneEntity, SceneHdrSkybox};
use super::lighting::SceneLight;
use super::material::SceneMaterial;
use super::registry::AssetRegistry;
#[derive(Debug, thiserror::Error)]
pub enum SceneError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Binary error: {0}")]
Binary(#[from] bincode::Error),
#[error("Asset not found: {0}")]
AssetNotFound(String),
#[error("Invalid scene structure")]
InvalidStructure,
#[error("Cyclic parent reference detected")]
CyclicReference,
}
pub fn save_scene_json(scene: &mut Scene, path: &Path) -> Result<(), SceneError> {
scene.compute_spawn_order();
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, scene)?;
Ok(())
}
pub fn load_scene_json(path: &Path) -> Result<Scene, SceneError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut scene: Scene = serde_json::from_reader(reader)?;
scene.rebuild_uuid_index();
Ok(scene)
}
pub fn save_scene_binary(scene: &mut Scene, path: &Path) -> Result<(), SceneError> {
scene.compute_spawn_order();
let data = bincode::serialize(scene)?;
let compressed = lz4_flex::compress_prepend_size(&data);
std::fs::write(path, compressed)?;
Ok(())
}
pub fn load_scene_binary(path: &Path) -> Result<Scene, SceneError> {
let compressed = std::fs::read(path)?;
let data = lz4_flex::decompress_size_prepended(&compressed)
.map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))?;
let mut scene: Scene = bincode::deserialize(&data)?;
scene.rebuild_uuid_index();
Ok(scene)
}
pub fn load_scene_binary_from_bytes(bytes: &[u8]) -> Result<Scene, SceneError> {
let data = lz4_flex::decompress_size_prepended(bytes)
.map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))?;
let mut scene: Scene = bincode::deserialize(&data)?;
scene.rebuild_uuid_index();
Ok(scene)
}
pub fn save_scene(scene: &mut Scene, path: &Path) -> Result<(), SceneError> {
match path.extension().and_then(|extension| extension.to_str()) {
Some("bin") => save_scene_binary(scene, path),
_ => save_scene_json(scene, path),
}
}
pub fn load_scene(path: &Path) -> Result<Scene, SceneError> {
match path.extension().and_then(|extension| extension.to_str()) {
Some("bin") => load_scene_binary(path),
_ => load_scene_json(path),
}
}
pub struct SpawnSceneResult {
pub uuid_to_entity: HashMap<AssetUuid, Entity>,
pub root_entities: Vec<Entity>,
pub warnings: Vec<String>,
}
pub fn spawn_scene(
world: &mut World,
scene: &Scene,
asset_registry: Option<&AssetRegistry>,
) -> Result<SpawnSceneResult, SceneError> {
let mut uuid_to_entity: HashMap<AssetUuid, Entity> = HashMap::new();
let mut root_entities: Vec<Entity> = Vec::new();
let mut warnings: Vec<String> = Vec::new();
world.resources.graphics.atmosphere = scene.atmosphere;
apply_scene_settings(world, &scene.settings);
if let Some(hdr_skybox) = &scene.hdr_skybox {
match hdr_skybox {
SceneHdrSkybox::Embedded { data } => {
world.queue_command(WorldCommand::LoadHdrSkybox {
hdr_data: data.clone(),
});
}
SceneHdrSkybox::Reference { path } => {
world.queue_command(WorldCommand::LoadHdrSkyboxFromPath {
path: std::path::PathBuf::from(path),
});
}
SceneHdrSkybox::Asset { uuid } => {
if let Some(registry) = asset_registry {
if let Some(path) = registry.resolve_uuid(*uuid) {
world.queue_command(WorldCommand::LoadHdrSkyboxFromPath { path });
} else {
warnings.push(format!("HDR skybox asset not found: {}", uuid));
}
} else {
warnings.push(format!(
"Asset registry required to resolve HDR skybox UUID: {}",
uuid
));
}
}
}
}
for (uuid, embedded_texture) in &scene.embedded_textures {
if let Some((rgba_data, width, height)) = embedded_texture.to_rgba() {
world.queue_command(WorldCommand::LoadTexture {
name: uuid.to_string(),
rgba_data,
width,
height,
});
}
}
let spawn_order = if !scene.spawn_order.is_empty() {
scene.spawn_order.clone()
} else {
compute_spawn_order(&scene.entities)?
};
for scene_entity_uuid in spawn_order {
let scene_entity = scene
.find_entity(scene_entity_uuid)
.ok_or(SceneError::InvalidStructure)?;
let parent_entity = scene_entity
.parent
.and_then(|parent_uuid| uuid_to_entity.get(&parent_uuid).copied());
let entity = spawn_scene_entity(
world,
scene,
scene_entity,
parent_entity,
asset_registry,
&mut warnings,
);
if scene_entity.parent.is_none() {
root_entities.push(entity);
}
uuid_to_entity.insert(scene_entity.uuid, entity);
}
if let Some(navmesh) = &scene.navmesh {
world.resources.navmesh = navmesh.to_navmesh_world();
}
#[cfg(feature = "physics")]
super::commands_physics::spawn_scene_joints(
world,
&scene.joints,
&uuid_to_entity,
&mut warnings,
);
Ok(SpawnSceneResult {
uuid_to_entity,
root_entities,
warnings,
})
}
fn compute_spawn_order(entities: &[SceneEntity]) -> Result<Vec<AssetUuid>, SceneError> {
let uuid_set: std::collections::HashSet<AssetUuid> = entities.iter().map(|e| e.uuid).collect();
let mut result: Vec<AssetUuid> = Vec::with_capacity(entities.len());
let mut visited: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();
let mut in_stack: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();
let entity_map: HashMap<AssetUuid, &SceneEntity> =
entities.iter().map(|e| (e.uuid, e)).collect();
fn visit(
uuid: AssetUuid,
entity_map: &HashMap<AssetUuid, &SceneEntity>,
uuid_set: &std::collections::HashSet<AssetUuid>,
visited: &mut std::collections::HashSet<AssetUuid>,
in_stack: &mut std::collections::HashSet<AssetUuid>,
result: &mut Vec<AssetUuid>,
) -> Result<(), SceneError> {
if visited.contains(&uuid) {
return Ok(());
}
if in_stack.contains(&uuid) {
return Err(SceneError::CyclicReference);
}
in_stack.insert(uuid);
if let Some(entity) = entity_map.get(&uuid)
&& let Some(parent_uuid) = entity.parent
&& uuid_set.contains(&parent_uuid)
{
visit(parent_uuid, entity_map, uuid_set, visited, in_stack, result)?;
}
in_stack.remove(&uuid);
visited.insert(uuid);
result.push(uuid);
Ok(())
}
for entity in entities {
visit(
entity.uuid,
&entity_map,
&uuid_set,
&mut visited,
&mut in_stack,
&mut result,
)?;
}
Ok(result)
}
fn spawn_scene_entity(
world: &mut World,
scene: &Scene,
scene_entity: &SceneEntity,
parent_entity: Option<Entity>,
asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) -> Entity {
let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;
if scene_entity.name.is_some() {
component_mask |= NAME;
}
if parent_entity.is_some() {
component_mask |= PARENT;
}
if scene_entity.components.visible {
component_mask |= VISIBILITY;
}
let entity = world.spawn_entities(component_mask, 1)[0];
world
.core
.set_local_transform(entity, scene_entity.transform);
world
.core
.set_global_transform(entity, GlobalTransform::default());
world
.core
.set_local_transform_dirty(entity, LocalTransformDirty);
if let Some(name) = &scene_entity.name {
world.core.set_name(entity, Name(name.clone()));
world.resources.entity_names.insert(name.clone(), entity);
}
if !scene_entity.components.tags.is_empty() {
world
.resources
.entity_tags
.insert(entity, scene_entity.components.tags.clone());
}
if !scene_entity.components.metadata.is_empty() {
world
.resources
.entity_metadata
.insert(entity, scene_entity.components.metadata.clone());
}
if let Some(parent) = parent_entity {
world.core.set_parent(entity, Parent(Some(parent)));
world
.resources
.children_cache
.entry(parent)
.or_default()
.push(entity);
}
if scene_entity.components.visible {
world
.core
.set_visibility(entity, Visibility { visible: true });
}
apply_scene_components(
world,
scene,
entity,
&scene_entity.components,
asset_registry,
warnings,
);
entity
}
fn apply_scene_components(
world: &mut World,
scene: &Scene,
entity: Entity,
components: &SceneComponents,
asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) {
if let Some(mesh) = &components.mesh {
apply_mesh_component(world, entity, mesh, asset_registry, warnings);
}
if let Some(instanced_mesh) = &components.instanced_mesh {
apply_instanced_mesh_component(world, entity, instanced_mesh, asset_registry, warnings);
}
if !components.lines.is_empty() {
world.core.add_components(entity, LINES);
world.core.set_lines(
entity,
Lines {
lines: components.lines.clone(),
version: 0,
},
);
}
if let Some(light) = &components.light {
world.core.add_components(entity, LIGHT);
world.core.set_light(entity, light.to_light());
}
if let Some(camera) = &components.camera {
world.core.add_components(entity, CAMERA);
world.core.set_camera(entity, camera.to_camera());
}
#[cfg(feature = "physics")]
if let Some(physics) = &components.physics {
super::commands_physics::apply_physics_component(world, entity, physics);
}
if let Some(script) = &components.script {
world.core.add_components(entity, SCRIPT);
world.core.set_script(entity, script.clone());
}
if let Some(audio) = &components.audio {
apply_audio_component(world, scene, entity, audio, asset_registry, warnings);
}
if let Some(prefab) = &components.prefab {
apply_prefab_component(world, entity, prefab, asset_registry, warnings);
}
if let Some(bounding_volume) = &components.bounding_volume {
world.core.add_components(entity, BOUNDING_VOLUME);
world.core.set_bounding_volume(entity, *bounding_volume);
}
if let Some(animation_player) = &components.animation_player {
world.core.add_components(entity, ANIMATION_PLAYER);
let player = if animation_player.clip_refs.is_empty() {
animation_player.to_animation_player()
} else {
animation_player.to_animation_player_with_resolved_clips(asset_registry, warnings)
};
world.core.set_animation_player(entity, player);
}
if components.casts_shadow {
world.core.add_components(entity, CASTS_SHADOW);
world.core.set_casts_shadow(entity, CastsShadow);
}
if let Some(scene_emitter) = &components.particle_emitter {
world.core.add_components(entity, PARTICLE_EMITTER);
world
.core
.set_particle_emitter(entity, scene_emitter.to_particle_emitter());
}
if let Some(decal) = &components.decal {
world.core.add_components(entity, DECAL);
world.core.set_decal(entity, decal.clone());
}
if let Some(water) = &components.water {
world.core.add_components(entity, WATER);
world.core.set_water(entity, water.clone());
}
if let Some(grass_region) = &components.grass_region {
world.core.add_components(entity, GRASS_REGION);
world.core.set_grass_region(entity, grass_region.clone());
}
if let Some(grass_interactor) = &components.grass_interactor {
world.core.add_components(entity, GRASS_INTERACTOR);
world
.core
.set_grass_interactor(entity, grass_interactor.clone());
}
if let Some(render_layer) = &components.render_layer {
world.core.add_components(entity, RENDER_LAYER);
world.core.set_render_layer(entity, *render_layer);
}
if let Some(text) = &components.text {
world.core.add_components(entity, TEXT);
world.core.set_text(entity, text.clone());
}
#[cfg(feature = "physics")]
if let Some(scene_cc) = &components.character_controller {
super::commands_physics::apply_character_controller(world, entity, scene_cc);
}
if let Some(navmesh_agent) = &components.navmesh_agent {
world.core.add_components(entity, NAVMESH_AGENT);
world.core.set_navmesh_agent(entity, navmesh_agent.clone());
}
}
fn apply_scene_settings(world: &mut World, settings: &super::settings::SceneSettings) {
#[cfg(feature = "physics")]
super::commands_physics::apply_physics_settings(world, settings);
let graphics = &mut world.resources.graphics;
graphics.ambient_light = settings.ambient_light;
graphics.clear_color = settings.clear_color;
graphics.fog = settings.fog;
graphics.bloom_enabled = settings.bloom_enabled;
graphics.bloom_intensity = settings.bloom_intensity;
graphics.bloom_threshold = settings.bloom_threshold;
graphics.color_grading = settings.color_grading;
graphics.depth_of_field = settings.depth_of_field;
graphics.ssao_enabled = settings.ssao_enabled;
graphics.ssao_radius = settings.ssao_radius;
graphics.ssao_bias = settings.ssao_bias;
graphics.ssao_intensity = settings.ssao_intensity;
graphics.ssao_sample_count = settings.ssao_sample_count;
graphics.ssgi_enabled = settings.ssgi_enabled;
graphics.ssgi_radius = settings.ssgi_radius;
graphics.ssgi_intensity = settings.ssgi_intensity;
graphics.ssgi_max_steps = settings.ssgi_max_steps;
graphics.ssr_enabled = settings.ssr_enabled;
graphics.ssr_max_steps = settings.ssr_max_steps;
graphics.ssr_thickness = settings.ssr_thickness;
graphics.ssr_max_distance = settings.ssr_max_distance;
graphics.ssr_stride = settings.ssr_stride;
graphics.ssr_fade_start = settings.ssr_fade_start;
graphics.ssr_fade_end = settings.ssr_fade_end;
graphics.ssr_intensity = settings.ssr_intensity;
graphics.vertex_snap = settings.vertex_snap;
graphics.affine_texture_mapping = settings.affine_texture_mapping;
if let Some(hour) = settings.day_night_hour {
graphics.day_night.hour = hour;
}
}
fn capture_scene_settings(world: &World) -> super::settings::SceneSettings {
let graphics = &world.resources.graphics;
#[cfg(feature = "physics")]
let (gravity, physics_timestep, physics_max_substeps) =
super::commands_physics::capture_physics_settings(world);
#[cfg(not(feature = "physics"))]
let (gravity, physics_timestep, physics_max_substeps) = ([0.0, -9.81, 0.0], 1.0 / 60.0, 4);
let day_night_hour = if matches!(
graphics.atmosphere,
crate::ecs::graphics::resources::Atmosphere::DayNight
) {
Some(graphics.day_night.hour)
} else {
None
};
super::settings::SceneSettings {
gravity,
physics_timestep,
physics_max_substeps,
ambient_light: graphics.ambient_light,
clear_color: graphics.clear_color,
fog: graphics.fog,
bloom_enabled: graphics.bloom_enabled,
bloom_intensity: graphics.bloom_intensity,
bloom_threshold: graphics.bloom_threshold,
color_grading: graphics.color_grading,
depth_of_field: graphics.depth_of_field,
ssao_enabled: graphics.ssao_enabled,
ssao_radius: graphics.ssao_radius,
ssao_bias: graphics.ssao_bias,
ssao_intensity: graphics.ssao_intensity,
ssao_sample_count: graphics.ssao_sample_count,
ssgi_enabled: graphics.ssgi_enabled,
ssgi_radius: graphics.ssgi_radius,
ssgi_intensity: graphics.ssgi_intensity,
ssgi_max_steps: graphics.ssgi_max_steps,
ssr_enabled: graphics.ssr_enabled,
ssr_max_steps: graphics.ssr_max_steps,
ssr_thickness: graphics.ssr_thickness,
ssr_max_distance: graphics.ssr_max_distance,
ssr_stride: graphics.ssr_stride,
ssr_fade_start: graphics.ssr_fade_start,
ssr_fade_end: graphics.ssr_fade_end,
ssr_intensity: graphics.ssr_intensity,
vertex_snap: graphics.vertex_snap,
affine_texture_mapping: graphics.affine_texture_mapping,
day_night_hour,
}
}
fn apply_mesh_component(
world: &mut World,
entity: Entity,
mesh: &super::components::SceneMesh,
asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) {
let mesh_name = if let Some(uuid) = mesh.mesh_uuid {
if let Some(registry) = asset_registry {
if let Some(entry) = registry.get_entry(uuid) {
entry.name.clone().unwrap_or_else(|| uuid.to_string())
} else {
warnings.push(format!("Mesh asset not found: {}", uuid));
return;
}
} else {
uuid.to_string()
}
} else if let Some(name) = &mesh.mesh_name {
name.clone()
} else {
warnings.push("Mesh component has neither UUID nor name".to_string());
return;
};
world.core.add_components(
entity,
RENDER_MESH | MATERIAL_REF | CASTS_SHADOW | BOUNDING_VOLUME,
);
let render_mesh = RenderMesh::new(mesh_name.clone());
world
.core
.set_bounding_volume(entity, BoundingVolume::from_mesh_type(&mesh_name));
if let Some(&index) = world
.resources
.mesh_cache
.registry
.name_to_index
.get(&render_mesh.name)
{
world.resources.mesh_cache.registry.add_reference(index);
}
world.core.set_render_mesh(entity, render_mesh);
world.resources.mesh_render_state.mark_entity_added(entity);
let material_name = if let Some(scene_material) = &mesh.material {
let mat = scene_material.to_material();
let name = format!("Scene_{}_{}", mesh_name, entity.id);
material_registry_insert(&mut world.resources.material_registry, name.clone(), mat);
name
} else {
"Default".to_string()
};
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
if let Some(mat) =
registry_entry_by_name(&world.resources.material_registry.registry, &material_name)
{
if let Some(base_texture) = &mat.base_texture {
texture_cache_add_reference(&mut world.resources.texture_cache, base_texture);
}
if let Some(emissive_texture) = &mat.emissive_texture {
texture_cache_add_reference(&mut world.resources.texture_cache, emissive_texture);
}
}
world
.core
.set_material_ref(entity, MaterialRef::new(material_name));
world.core.set_casts_shadow(entity, CastsShadow);
}
fn compute_instanced_mesh_bounding_volume(
mesh_name: &str,
instances: &[super::mesh::SceneMeshInstance],
) -> BoundingVolume {
if instances.is_empty() {
return BoundingVolume::from_mesh_type(mesh_name);
}
let base_bv = BoundingVolume::from_mesh_type(mesh_name);
let base_obb = &base_bv.obb;
let mut min_corner = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
let mut max_corner = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
for instance in instances {
let local_transform = instance.to_local_transform();
let instance_matrix = nalgebra_glm::translation(&local_transform.translation)
* nalgebra_glm::quat_to_mat4(&local_transform.rotation)
* nalgebra_glm::scaling(&local_transform.scale);
let transformed_obb = base_obb.transform(&instance_matrix);
let corners = transformed_obb.get_corners();
for corner in &corners {
min_corner.x = min_corner.x.min(corner.x);
min_corner.y = min_corner.y.min(corner.y);
min_corner.z = min_corner.z.min(corner.z);
max_corner.x = max_corner.x.max(corner.x);
max_corner.y = max_corner.y.max(corner.y);
max_corner.z = max_corner.z.max(corner.z);
}
}
let combined_obb = OrientedBoundingBox::from_aabb(min_corner, max_corner);
let sphere_radius = nalgebra_glm::length(&combined_obb.half_extents);
BoundingVolume::new(combined_obb, sphere_radius)
}
fn apply_instanced_mesh_component(
world: &mut World,
entity: Entity,
instanced_mesh: &super::mesh::SceneInstancedMesh,
asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) {
use crate::ecs::mesh::components::{InstanceCustomData, InstanceTransform};
let mesh_name = if let Some(uuid) = instanced_mesh.mesh_uuid {
if let Some(registry) = asset_registry {
if let Some(entry) = registry.get_entry(uuid) {
entry.name.clone().unwrap_or_else(|| uuid.to_string())
} else {
warnings.push(format!("Instanced mesh asset not found: {}", uuid));
return;
}
} else {
uuid.to_string()
}
} else if let Some(name) = &instanced_mesh.mesh_name {
name.clone()
} else {
warnings.push("Instanced mesh component has neither UUID nor name".to_string());
return;
};
world.core.add_components(
entity,
INSTANCED_MESH | MATERIAL_REF | CASTS_SHADOW | BOUNDING_VOLUME,
);
let bounding_volume =
compute_instanced_mesh_bounding_volume(&mesh_name, &instanced_mesh.instances);
world.core.set_bounding_volume(entity, bounding_volume);
let transforms: Vec<InstanceTransform> = instanced_mesh
.instances
.iter()
.map(|i| {
let local = i.to_local_transform();
InstanceTransform::new(local.translation, local.rotation, local.scale)
})
.collect();
let mut component = InstancedMesh::with_instances(&mesh_name, transforms);
for (index, instance) in instanced_mesh.instances.iter().enumerate() {
if let Some(color) = instance.color {
component.custom_data[index] = InstanceCustomData { tint: color };
}
}
world.core.set_instanced_mesh(entity, component);
let material_name = if let Some(scene_material) = &instanced_mesh.material {
let mat = scene_material.to_material();
let name = format!("SceneInstanced_{}_{}", mesh_name, entity.id);
material_registry_insert(&mut world.resources.material_registry, name.clone(), mat);
name
} else {
"Default".to_string()
};
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world
.core
.set_material_ref(entity, MaterialRef::new(material_name));
world.core.set_casts_shadow(entity, CastsShadow);
}
fn apply_audio_component(
world: &mut World,
scene: &Scene,
entity: Entity,
audio: &super::audio::SceneAudioSource,
_asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) {
world.core.add_components(entity, AUDIO_SOURCE);
let audio_ref = if let Some(name) = &audio.audio_name {
name.clone()
} else if let Some(uuid) = audio.audio_uuid {
uuid.to_string()
} else {
warnings.push("Audio component has neither UUID nor name".to_string());
return;
};
#[cfg(feature = "audio")]
{
if let Some(embedded_audio) = scene.embedded_audio.get(
&audio
.audio_uuid
.unwrap_or_else(|| AssetUuid::from_str(&audio_ref).unwrap_or(AssetUuid::NIL)),
) {
if let Ok(sound_data) = load_sound_from_cursor(embedded_audio.data.clone()) {
world
.resources
.audio
.load_sound(audio_ref.clone(), sound_data);
}
} else {
warnings.push(format!("Audio '{}' not found in scene", audio_ref));
}
}
#[cfg(not(feature = "audio"))]
{
let _ = scene;
warnings.push("Audio feature is not enabled - audio source will not play".to_string());
}
world.core.set_audio_source(
entity,
AudioSource {
audio_ref: Some(audio_ref),
volume: audio.volume,
looping: audio.looping,
playing: audio.playing,
spatial: audio.spatial,
reverb: audio.reverb,
},
);
}
fn apply_prefab_component(
world: &mut World,
entity: Entity,
prefab_instance: &ScenePrefabInstance,
asset_registry: Option<&AssetRegistry>,
warnings: &mut Vec<String>,
) {
let prefab_path = if let Some(uuid) = prefab_instance.prefab_uuid {
if let Some(registry) = asset_registry {
registry.resolve_uuid(uuid)
} else {
warnings.push(format!(
"Asset registry required to resolve prefab UUID: {}",
uuid
));
return;
}
} else if let Some(path) = &prefab_instance.prefab_path {
Some(std::path::PathBuf::from(path))
} else {
warnings.push("Prefab instance has neither UUID nor path".to_string());
return;
};
if let Some(path) = prefab_path {
match import_gltf_from_path(&path) {
Ok(result) => {
for (mesh_name, mesh) in result.meshes {
mesh_cache_insert(&mut world.resources.mesh_cache, mesh_name, mesh);
}
for (texture_name, (rgba_data, width, height)) in result.textures {
world.queue_command(WorldCommand::LoadTexture {
name: texture_name,
rgba_data,
width,
height,
});
}
if let Some(prefab) = result.prefabs.into_iter().next() {
let transform = world
.core
.get_local_transform(entity)
.copied()
.unwrap_or_default();
let prefab_root = spawn_prefab(world, &prefab, transform.translation);
world.core.add_components(prefab_root, PARENT);
world.core.set_parent(prefab_root, Parent(Some(entity)));
world
.resources
.children_cache
.entry(entity)
.or_default()
.push(prefab_root);
} else {
warnings.push(format!("No prefabs found in file '{}'", path.display()));
}
}
Err(error) => {
warnings.push(format!(
"Failed to load prefab from '{}': {}",
path.display(),
error
));
}
}
}
}
pub fn world_to_scene(world: &World, name: impl Into<String>) -> Scene {
let mut scene = Scene::new(name);
scene.atmosphere = world.resources.graphics.atmosphere;
scene.settings = capture_scene_settings(world);
let mut entity_to_uuid: HashMap<Entity, AssetUuid> = HashMap::new();
let entities: Vec<Entity> = world.core.query_entities(LOCAL_TRANSFORM).collect();
for entity in &entities {
let uuid = AssetUuid::new();
entity_to_uuid.insert(*entity, uuid);
}
for entity in &entities {
let entity = *entity;
let uuid = entity_to_uuid[&entity];
let parent_uuid = world
.core
.get_parent(entity)
.and_then(|p| p.0)
.and_then(|parent_entity| entity_to_uuid.get(&parent_entity).copied());
let transform = world
.core
.get_local_transform(entity)
.copied()
.unwrap_or_default();
let name = world.core.get_name(entity).map(|n| n.0.clone());
let mut scene_entity = SceneEntity {
uuid,
parent: parent_uuid,
name,
transform,
layer: None,
chunk_id: None,
components: SceneComponents::default(),
};
if let Some(tags) = world.resources.entity_tags.get(&entity) {
scene_entity.components.tags = tags.clone();
}
if let Some(metadata) = world.resources.entity_metadata.get(&entity) {
scene_entity.components.metadata = metadata.clone();
}
if let Some(render_mesh) = world.core.get_render_mesh(entity) {
let material = world
.core
.get_material_ref(entity)
.and_then(|mat_ref| {
registry_entry_by_name(
&world.resources.material_registry.registry,
&mat_ref.name,
)
})
.map(SceneMaterial::from);
scene_entity.components.mesh = Some(super::components::SceneMesh {
mesh_uuid: None,
mesh_name: Some(render_mesh.name.clone()),
material,
});
}
if let Some(instanced_mesh) = world.core.get_instanced_mesh(entity) {
let material = world
.core
.get_material_ref(entity)
.and_then(|mat_ref| {
registry_entry_by_name(
&world.resources.material_registry.registry,
&mat_ref.name,
)
})
.map(SceneMaterial::from);
let instances: Vec<super::mesh::SceneMeshInstance> = instanced_mesh
.instances
.iter()
.enumerate()
.map(|(index, transform)| {
let tint = instanced_mesh
.custom_data
.get(index)
.map(|cd| cd.tint)
.filter(|t| *t != [1.0, 1.0, 1.0, 1.0]);
super::mesh::SceneMeshInstance {
translation: [
transform.translation.x,
transform.translation.y,
transform.translation.z,
],
rotation: [
transform.rotation.i,
transform.rotation.j,
transform.rotation.k,
transform.rotation.w,
],
scale: [transform.scale.x, transform.scale.y, transform.scale.z],
color: tint,
}
})
.collect();
scene_entity.components.instanced_mesh = Some(super::mesh::SceneInstancedMesh {
mesh_uuid: None,
mesh_name: Some(instanced_mesh.mesh_name.clone()),
instances,
material,
});
}
if let Some(light) = world.core.get_light(entity) {
scene_entity.components.light = Some(SceneLight::from(light));
}
if let Some(camera) = world.core.get_camera(entity) {
scene_entity.components.camera = Some(super::lighting::SceneCamera::from(camera));
}
#[cfg(feature = "physics")]
super::commands_physics::export_entity_physics(world, entity, &mut scene_entity.components);
if let Some(script) = world.core.get_script(entity) {
scene_entity.components.script = Some(script.clone());
}
if let Some(audio_source) = world.core.get_audio_source(entity) {
scene_entity.components.audio = Some(super::audio::SceneAudioSource {
audio_uuid: None,
audio_name: audio_source.audio_ref.clone(),
volume: audio_source.volume,
looping: audio_source.looping,
playing: audio_source.playing,
spatial: audio_source.spatial,
reverb: audio_source.reverb,
});
}
if let Some(bounding_volume) = world.core.get_bounding_volume(entity) {
scene_entity.components.bounding_volume = Some(*bounding_volume);
}
if let Some(animation_player) = world.core.get_animation_player(entity) {
scene_entity.components.animation_player = Some(
super::animation::SceneAnimationPlayer::from(animation_player),
);
}
scene_entity.components.casts_shadow = world.core.get_casts_shadow(entity).is_some();
if let Some(visibility) = world.core.get_visibility(entity) {
scene_entity.components.visible = visibility.visible;
}
if let Some(emitter) = world.core.get_particle_emitter(entity) {
scene_entity.components.particle_emitter =
Some(super::particles::SceneParticleEmitter::from(emitter));
}
if let Some(decal) = world.core.get_decal(entity) {
scene_entity.components.decal = Some(decal.clone());
}
if let Some(water) = world.core.get_water(entity) {
scene_entity.components.water = Some(water.clone());
}
if let Some(grass_region) = world.core.get_grass_region(entity) {
scene_entity.components.grass_region = Some(grass_region.clone());
}
if let Some(grass_interactor) = world.core.get_grass_interactor(entity) {
scene_entity.components.grass_interactor = Some(grass_interactor.clone());
}
if let Some(render_layer) = world.core.get_render_layer(entity) {
scene_entity.components.render_layer = Some(*render_layer);
}
if let Some(text) = world.core.get_text(entity) {
scene_entity.components.text = Some(text.clone());
}
if let Some(navmesh_agent) = world.core.get_navmesh_agent(entity) {
scene_entity.components.navmesh_agent = Some(navmesh_agent.clone());
}
scene.add_entity(scene_entity);
}
#[cfg(feature = "physics")]
super::commands_physics::export_scene_joints(world, &entity_to_uuid, &mut scene);
if !world.resources.navmesh.triangles.is_empty() {
scene.navmesh = Some(super::navigation::SceneNavMesh::from_navmesh_world(
&world.resources.navmesh,
));
}
scene
}