diff --git a/docs/DRAFT.md b/docs/DRAFT.md index 8625471..017c53d 100644 --- a/docs/DRAFT.md +++ b/docs/DRAFT.md @@ -25,17 +25,21 @@ **But** : donner à chaque entité un `Transform` et rendre `Camera` utilisable via l'API publique, **sans** toucher au rendu (pure façade de données, validable par compilation). -- [ ] 1.1 **Exporter `Camera`** : dans `lib/src/resources/mod.rs`, ajouter - `pub mod camera;` et `pub use camera::Camera;` (aujourd'hui fichier orphelin non compilé). -- [ ] 1.2 **Type `Entity` + transform** : nouvelle struct +- [X] 1.1 **Exporter `Camera`** : dans `lib/src/resources/mod.rs`, ajouter + `pub mod camera;` et `pub use camera::Camera;` (aujourd'hui fichier orphelin non compilé). *(fait — 2026-09-16)* +- [X] 1.2 **Type `Entity` + transform** : nouvelle struct `Entity { mesh_id: String, material_id: String, transform: Transform }` (module `scene` ou `resources`). Remplacer `Scene::entities: HashMap` par `HashMap`. Sérialiser `iter_entities()` pour rendre le `&Transform`. -- [ ] 1.3 **Compat API** : garder `add_entity(label, mesh_id, material_id)` (transform identité par défaut) + *(fait — `lib/src/scene/entity.rs`)* +- [X] 1.3 **Compat API** : garder `add_entity(label, mesh_id, material_id)` (transform identité par défaut) + ajouter `add_entity_with_transform(label, mesh_id, material_id, transform)`. Ajouter `entity_transform(label) -> Option<&Transform>` et `set_entity_transform(label, transform)`. -- [ ] **Validation** : `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; les exemples - `simple`/`manual` compilent inchangés (défaut : identité ⇒ même rendu). + *(fait — 2026-09-16)* +- [X] **Validation** : `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; les exemples + `simple`/`manual` compilent inchangés (défaut : identité ⇒ même rendu). *(fait — 0 warning. Au passage, + `camera.rs` étant désormais compilée, les fonctions glam dépréciées `look_at_rh`/`perspective_rh_gl` ont été + migrées vers `glam::camera::rh::view::look_at_mat4` / `glam::camera::rh::proj::opengl::perspective`.)* ## Étape 2 — Shader Phong `standard_shader.wgsl` diff --git a/lib/src/core/renderer.rs b/lib/src/core/renderer.rs index 4febc6a..5fbe940 100644 --- a/lib/src/core/renderer.rs +++ b/lib/src/core/renderer.rs @@ -113,7 +113,7 @@ impl Renderer { ..Default::default() }); - for (_label, mesh, material) in scene.iter_entities() { + for (_label, mesh, material, _transform) in scene.iter_entities() { draw_entity(&mut render_pass, mesh, material); } } diff --git a/lib/src/resources/camera.rs b/lib/src/resources/camera.rs index 61727d4..cf839ce 100644 --- a/lib/src/resources/camera.rs +++ b/lib/src/resources/camera.rs @@ -13,7 +13,7 @@ //! - `view_matrix()`: Computes the view matrix //! - `projection_matrix()`: Computes the projection matrix -use glam::{Vec3, Mat4}; +use glam::{Mat4, Vec3}; /// Represents a 3D camera for viewing the scene. /// @@ -31,7 +31,11 @@ pub struct Camera { impl Camera { /// Creates a new camera with specified position, target, and up vector. pub fn new(position: Vec3, target: Vec3, up: Vec3) -> Self { - Self { position, target, up } + Self { + position, + target, + up, + } } /// Computes the view matrix for this camera. @@ -39,7 +43,7 @@ impl Camera { /// # Returns /// A `Mat4` representing the view transformation matrix pub fn view_matrix(&self) -> Mat4 { - Mat4::look_at_rh(self.position, self.target, self.up) + glam::camera::rh::view::look_at_mat4(self.position, self.target, self.up) } /// Computes the projection matrix for this camera. @@ -53,6 +57,6 @@ impl Camera { /// # Returns /// A `Mat4` representing the projection transformation matrix pub fn projection_matrix(&self, fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 { - Mat4::perspective_rh_gl(fov, aspect, near, far) + glam::camera::rh::proj::opengl::perspective(fov, aspect, near, far) } -} \ No newline at end of file +} diff --git a/lib/src/resources/mod.rs b/lib/src/resources/mod.rs index 6be9a02..de76c9a 100644 --- a/lib/src/resources/mod.rs +++ b/lib/src/resources/mod.rs @@ -10,11 +10,13 @@ //! - `mesh::new()` uploads Vertex arrays from CPU memory into GPU vertex buffers via DeviceExt::create_buffer_init(). //! - `material::new()` requests RenderPipelines from PipelineCache during scene initialization. +pub mod camera; pub mod material; pub mod mesh; pub mod vertex; // Re-exports +pub use camera::Camera; pub use material::Material; pub use mesh::Mesh; pub use vertex::Vertex; diff --git a/lib/src/scene/entity.rs b/lib/src/scene/entity.rs new file mode 100644 index 0000000..191de65 --- /dev/null +++ b/lib/src/scene/entity.rs @@ -0,0 +1,64 @@ +//! # Entity Module +//! +//! Defines `Entity`, the renderable association between a Mesh and a Material together with its +//! own world-space `Transform`. Each entry of `Scene::entities` is an `Entity`: it references the +//! resource by identifier while carrying the per-entity placement data. +//! +//! ## Interaction with Other Modules +//! - `scene::Scene` stores entities in a `HashMap` keyed by label. +//! - `math::Transform` provides the placement (translation / rotation / scale) converted to a +//! matrix during rendering. +//! - `resources::{Mesh, Material}` are the referenced render resources, resolved by `Scene`. + +use crate::math::Transform; + +/// A renderable entity: a mesh + material pair with its own world-space transform. +/// +/// Entities are created through [`crate::scene::Scene::add_entity`] (identity transform) or +/// [`crate::scene::Scene::add_entity_with_transform`]. Fields are exposed via accessors. +#[derive(Debug, Clone)] +pub struct Entity { + /// Identifier of the referenced Mesh resource. + mesh_id: String, + /// Identifier of the referenced Material resource. + material_id: String, + /// World-space placement of this entity. + transform: Transform, +} + +impl Entity { + /// Creates a new entity associating a mesh and a material under the given transform. + /// Called internally by `Scene::add_entity*` after resource existence is validated. + pub fn new( + mesh_id: impl Into, + material_id: impl Into, + transform: Transform, + ) -> Self { + Self { + mesh_id: mesh_id.into(), + material_id: material_id.into(), + transform, + } + } + + /// Returns the identifier of the referenced Mesh resource. + pub fn mesh_id(&self) -> &str { + &self.mesh_id + } + + /// Returns the identifier of the referenced Material resource. + pub fn material_id(&self) -> &str { + &self.material_id + } + + /// Returns a reference to this entity's world-space transform. + pub fn transform(&self) -> &Transform { + &self.transform + } + + /// Sets this entity's world-space transform. + /// Called by `Scene::set_entity_transform` during dynamic updates. + pub fn set_transform(&mut self, transform: Transform) { + self.transform = transform; + } +} diff --git a/lib/src/scene/mod.rs b/lib/src/scene/mod.rs index 688167d..f9ba1bf 100644 --- a/lib/src/scene/mod.rs +++ b/lib/src/scene/mod.rs @@ -16,7 +16,9 @@ //! all resources are declared before the render loop begins, while keeping the freedom to build the engine //! "brick by brick" through direct Context/PipelineCache/Renderer manipulation. +pub mod entity; pub mod scene; // Re-export +pub use entity::Entity; pub use scene::Scene; diff --git a/lib/src/scene/scene.rs b/lib/src/scene/scene.rs index 23ed115..05738ca 100644 --- a/lib/src/scene/scene.rs +++ b/lib/src/scene/scene.rs @@ -10,20 +10,22 @@ //! and avoiding borrow checker issues during dynamic updates. //! - **Ergonomie**: Users interact only with entity-level operations (add/remove/get) rather than wgpu buffers/pipelines directly. +use crate::math::Transform; use crate::resources::{Material, Mesh}; +use crate::scene::Entity; use std::collections::HashMap; use std::sync::Arc; /// Resource depot and entity graph. Stores Meshes and Materials keyed by identifier strings, -/// and maps entity labels to their associated mesh+material pairs for rendering iteration. +/// and maps entity labels to their associated `Entity` (mesh + material + transform) for rendering iteration. /// Created once during application setup; entities are added before the render loop starts. pub struct Scene { /// Map of mesh identifiers to owned `Arc` instances. Populated via `add_mesh()`. meshes: HashMap>, /// Map of material identifiers to owned `Arc` instances. Populated via `add_material()`. materials: HashMap>, - /// Map of entity labels to (mesh_id, material_id) associations. Populated via `add_entity()`. - entities: HashMap, + /// Map of entity labels to `Entity` associations. Populated via `add_entity()` / `add_entity_with_transform()`. + entities: HashMap, } impl Scene { @@ -59,17 +61,33 @@ impl Scene { Ok(id.to_string()) } - /// Associates an entity label with a mesh and material pair for rendering iteration. + /// Associates an entity label with a mesh and material pair for rendering iteration, using an identity transform. /// Inputs: label (entity identifier string), mesh_id (key into meshes map), material_id (key into materials map). /// Returns Ok(label) on success or Err(String) if either referenced resource does not exist. /// Called during scene initialization to build the renderable entity graph. /// Internal steps: 1) validate mesh_id exists → 2) validate material_id exists → - /// 3) insert association into entities HashMap. + /// 3) insert an `Entity` with identity transform into the entities HashMap. pub fn add_entity( &mut self, label: &str, mesh_id: &str, material_id: &str, + ) -> Result { + self.add_entity_with_transform(label, mesh_id, material_id, Transform::identity()) + } + + /// Associates an entity label with a mesh and material pair together with an explicit world-space transform. + /// Inputs: label (entity identifier string), mesh_id (key into meshes map), material_id (key into materials map), + /// transform (world-space placement). Returns Ok(label) on success or Err(String) if either referenced resource does not exist. + /// Called during scene initialization to build the renderable entity graph. + /// Internal steps: 1) validate mesh_id exists → 2) validate material_id exists → + /// 3) insert the `Entity` into the entities HashMap. + pub fn add_entity_with_transform( + &mut self, + label: &str, + mesh_id: &str, + material_id: &str, + transform: Transform, ) -> Result { if !self.meshes.contains_key(mesh_id) { return Err(format!("Mesh '{}' does not exist.", mesh_id)); @@ -79,7 +97,7 @@ impl Scene { } self.entities.insert( label.to_string(), - (mesh_id.to_string(), material_id.to_string()), + Entity::new(mesh_id, material_id, transform), ); Ok(label.to_string()) } @@ -96,16 +114,37 @@ impl Scene { self.materials.get(id) } - /// Iterates all entity associations, yielding (label, mesh_ref, material_ref) triples. + /// Iterates all entity associations, yielding (label, mesh_ref, material_ref, transform_ref) tuples. /// Called by the orchestrator during each render pass to draw every entity in order. - pub fn iter_entities(&self) -> impl Iterator, &Arc)> + '_ { - self.entities.iter().map(|(label, (mesh_id, mat_id))| { - let mesh = self.meshes.get(mesh_id).unwrap(); // safe: add_entity validates existence - let mat = self.materials.get(mat_id).unwrap(); // same invariant - (label.as_str(), mesh, mat) + pub fn iter_entities( + &self, + ) -> impl Iterator, &Arc, &Transform)> + '_ { + self.entities.iter().map(|(label, entity)| { + let mesh = self.meshes.get(entity.mesh_id()).unwrap(); // safe: add_entity validates existence + let mat = self.materials.get(entity.material_id()).unwrap(); // same invariant + (label.as_str(), mesh, mat, entity.transform()) }) } + /// Returns a reference to the transform of the entity with the given label, if it exists. + /// Called by the user to read an entity's current placement during updates. + pub fn entity_transform(&self, label: &str) -> Option<&Transform> { + self.entities.get(label).map(|e| e.transform()) + } + + /// Overwrites the transform of the entity with the given label. + /// Returns true if the entity existed and was updated, false otherwise. + /// Called by the user to move/rotate/scale an entity during `AppHandler::update`. + pub fn set_entity_transform(&mut self, label: &str, transform: Transform) -> bool { + match self.entities.get_mut(label) { + Some(entity) => { + entity.set_transform(transform); + true + } + None => false, + } + } + /// Removes an entity from the graph without freeing its underlying resources. /// The referenced Mesh and Material remain registered; only the association is dropped. /// Called during dynamic updates when an entity should be hidden or removed temporarily.