feat(lib): expose Camera and Entity with Transform (data foundation)

Étape 1 du plan 3D+Phong : fondations de données sans toucher au rendu.

- resources: exporte Camera (fichier auparavant orphelin), migre les fonctions
  glam dépréciées look_at_rh/perspective_rh_gl vers glam::camera::rh::* (induit
  par la compilation de camera.rs, sinon 2 warnings).
- scene: nouveau type Entity { mesh_id, material_id, transform } (scene/entity.rs);
  Scene::entities passe de HashMap<String,(String,String)> à HashMap<String,Entity>.
- API: add_entity conserve sa signature (transform identité par défaut), ajout de
  add_entity_with_transform, entity_transform, set_entity_transform; iter_entities
  rend désormais aussi le &Transform. renderer et examples inchangés a posteriori.
- Validation: cargo check --workspace et examples 0 warning, cargo doc 0 warning, fmt OK.
This commit is contained in:
Jérôme Bousquié
2026-09-16 15:26:16 +02:00
parent 91007853d9
commit 252db88980
7 changed files with 139 additions and 24 deletions
+10 -6
View File
@@ -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<String, (String, String)>` par
`HashMap<String, Entity>`. 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`
+1 -1
View File
@@ -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);
}
}
+8 -4
View File
@@ -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)
}
}
+2
View File
@@ -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;
+64
View File
@@ -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<String, Entity>` 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<String>,
material_id: impl Into<String>,
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;
}
}
+2
View File
@@ -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;
+51 -12
View File
@@ -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<Mesh>` instances. Populated via `add_mesh()`.
meshes: HashMap<String, Arc<Mesh>>,
/// Map of material identifiers to owned `Arc<Material>` instances. Populated via `add_material()`.
materials: HashMap<String, Arc<Material>>,
/// Map of entity labels to (mesh_id, material_id) associations. Populated via `add_entity()`.
entities: HashMap<String, (String, String)>,
/// Map of entity labels to `Entity` associations. Populated via `add_entity()` / `add_entity_with_transform()`.
entities: HashMap<String, Entity>,
}
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<String, String> {
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<String, String> {
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<Item = (&str, &Arc<Mesh>, &Arc<Material>)> + '_ {
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<Item = (&str, &Arc<Mesh>, &Arc<Material>, &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.