use std::collections::HashMap;
use crate::camera::Camera;
use crate::editor::{EditorEvent, EditorState, InspectorData};
use crate::mesh::{MeshData, MeshRegistry};
use crate::pipeline::Pipeline;
use crate::world::World;
use crate::objects::Object;
use crate::transform::Transform;
use crate::vtr::{self, VtrError};
pub struct TextureEntry {
#[allow(dead_code)]
pub texture: wgpu::Texture,
pub bind_group: wgpu::BindGroup,
}
pub struct Scene {
pub pipeline: Pipeline,
pub mesh_registry: MeshRegistry,
pub camera: Camera,
pub world: World,
pub editor: Option<EditorState>,
pub textures: HashMap<String, TextureEntry>,
}
impl Scene {
pub fn spawn(&mut self, object: Object, parent_id: Option<usize>) -> usize {
self.world.spawn_object(object, parent_id)
}
pub fn load_texture_from_rgba(
&mut self,
path_key: &str,
width: u32,
height: u32,
rgba_data: &[u8],
) {
let (texture, bind_group) = self.pipeline
.create_texture_bind_group_from_rgba(path_key, width, height, rgba_data);
self.textures.insert(path_key.to_string(), TextureEntry { texture, bind_group });
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_texture(&mut self, path: &str) -> Result<(), String> {
use image::GenericImageView;
let img = image::open(path).map_err(|e| format!("load_texture(\"{path}\"): {e}"))?;
let rgba = img.to_rgba8();
let (width, height) = img.dimensions();
self.load_texture_from_rgba(path, width, height, &rgba);
Ok(())
}
pub fn unload_texture(&mut self, path_key: &str) -> bool {
self.textures.remove(path_key).is_some()
}
pub fn has_texture(&self, path_key: &str) -> bool {
self.textures.contains_key(path_key)
}
pub fn draw_world(&mut self) {
let mut groups: HashMap<Option<String>, MeshData> = HashMap::new();
let identity = Transform::default();
for &root_id in &self.world.roots {
collect_by_texture(&self.world, root_id, &identity, &mut groups);
}
let baked_groups: Vec<(Option<String>, crate::mesh::BakedMesh)> = groups
.into_iter()
.map(|(key, mesh_data)| (key, mesh_data.bake(&self.pipeline)))
.collect();
let world_batches: Vec<(&crate::mesh::BakedMesh, &wgpu::BindGroup)> = baked_groups
.iter()
.map(|(key, baked)| {
let bg: &wgpu::BindGroup = key
.as_ref()
.and_then(|p| self.textures.get(p))
.map(|e| &e.bind_group)
.unwrap_or(&self.pipeline.default_texture_bind_group);
(baked, bg)
})
.collect();
let overlay_baked = self.editor.as_ref()
.and_then(|ed| ed.gizmo_overlay_for_selection(&self.world, &self.camera))
.map(|(v, i)| self.pipeline.create_baked_mesh(&v, &i));
let camera = &self.camera;
let skybox = self.editor.as_ref().and_then(|ed| ed.skybox.as_ref());
self.pipeline.render_scene(camera, &world_batches, skybox, overlay_baked.as_ref());
}
pub fn enable_editor_mode(&mut self) {
let w = self.pipeline.surface_config.width as f32;
let h = self.pipeline.surface_config.height as f32;
let mut ed = EditorState::new(w, h);
ed.spawn_gizmos(&mut self.world);
let (sky_v, sky_i) = crate::editor::build_skybox_mesh();
ed.skybox = Some(self.pipeline.create_baked_mesh(&sky_v, &sky_i));
ed.pivot = self.camera.target;
self.editor = Some(ed);
}
pub fn disable_editor_mode(&mut self) {
self.editor = None;
}
pub fn update_editor(&mut self, dt: f32) {
if let Some(ed) = &mut self.editor {
ed.update(&mut self.camera, dt);
}
}
pub fn handle_editor_event(&mut self, event: EditorEvent) {
if self.editor.is_none() { return; }
if matches!(&event, EditorEvent::KeyPressed(winit::keyboard::KeyCode::Escape)) {
self.editor = None;
return;
}
if let Some(ed) = &mut self.editor {
ed.process(&mut self.camera, &mut self.world, event);
}
}
pub fn inspector(&self) -> Option<&InspectorData> {
self.editor.as_ref()?.inspector.selected.as_ref()
}
pub fn save_vtr_file(&self, path: &std::path::Path) -> Result<(), VtrError> {
vtr::write_to_file(path, &self.camera, &self.world)
}
pub fn load_vtr_file(&mut self, path: &std::path::Path) -> Result<(), VtrError> {
let data = vtr::read_from_file(path)?;
self.camera = data.camera;
self.world = data.world;
Ok(())
}
}
fn collect_by_texture(
world: &World,
object_id: usize,
parent_transform: &Transform,
groups: &mut HashMap<Option<String>, MeshData>,
) {
if let Some(obj) = world.objects.get(&object_id) {
let world_transform = parent_transform.combine(&obj.transform);
if let Some(geo) = &obj.geometry {
let entry = groups
.entry(obj.texture_path.clone())
.or_insert_with(MeshData::new);
geo.generate_mesh_data(entry, &world_transform, obj.color);
}
for &child_id in &obj.children {
collect_by_texture(world, child_id, &world_transform, groups);
}
}
}