refactor(mesh): stockage CPU Arc<Geometry> (Étape 8, 8.1-8.5)
- geometry: ajoute le champ optionnel colors + constructeur new() et builder fluent (.with_normals/.with_uvs/.with_colors/.with_indices) + validate() et GeometryError (longueurs des tableaux optionnels + bornes des indices). - geometry: ajoute to_vertices()/try_into_vertices() -> Vec<Vertex> (zip avec défauts : normale [0,0,1], UV [0,0], couleur blanche) (D6). - mesh: remplace new()/with_material() par from_geometry(device, Arc<Geometry>, material); garde geometry: Arc<Geometry> (rétention CPU+GPU, D5) + nouveaux accesseurs geometry()/material()/set_material() (D4). - scene: create_mesh(id, Geometry, Option<&str>) déclare un mesh depuis une Geometry; expose Geometry via math (D2) et un ré-export de convenance resources. - exemples (cube/simple/manual) réécrits pour construire une Geometry.
This commit is contained in:
+17
-24
@@ -4,12 +4,14 @@
|
|||||||
//! On suit le workflow déclaratif (comme `simple`) : `AppBuilder` + scène automatique, **sans importer
|
//! On suit le workflow déclaratif (comme `simple`) : `AppBuilder` + scène automatique, **sans importer
|
||||||
//! wgpu**. Depuis l'Étape 7 la scène possède son `PipelineCache` : on passe par `register_shader` +
|
//! wgpu**. Depuis l'Étape 7 la scène possède son `PipelineCache` : on passe par `register_shader` +
|
||||||
//! `add_material_shader` + `create_mesh` + `add_entity` (le matériau est lié au mesh, plus de material_id).
|
//! `add_material_shader` + `create_mesh` + `add_entity` (le matériau est lié au mesh, plus de material_id).
|
||||||
|
//! Depuis l'Étape 8 (DRAFT 8.4/8.5) le mesh est déclaré à partir d'une **`Geometry`** (positions, normales,
|
||||||
|
//! indices) plutôt que d'un `&[Vertex]` brut : `Mesh` dérive ses buffers internes via `to_vertices()`.
|
||||||
//! La caméra active par défaut (`Scene::default`, position (0,0,3), fov 45°) cadre le cube, et
|
//! La caméra active par défaut (`Scene::default`, position (0,0,3), fov 45°) cadre le cube, et
|
||||||
//! `AppHandler::update` fait tourner l'entité via `set_entity_transform` chaque frame.
|
//! `AppHandler::update` fait tourner l'entité via `set_entity_transform` chaque frame.
|
||||||
use glam::Quat;
|
use glam::Quat;
|
||||||
use wsg_lib::AppHandler;
|
use wsg_lib::AppHandler;
|
||||||
use wsg_lib::app::AppBuilder;
|
use wsg_lib::app::AppBuilder;
|
||||||
use wsg_lib::resources::Vertex;
|
use wsg_lib::resources::Geometry;
|
||||||
use wsg_lib::utils::WsgError;
|
use wsg_lib::utils::WsgError;
|
||||||
|
|
||||||
/// Handler de démonstration : fait tourner le cube dans `update`.
|
/// Handler de démonstration : fait tourner le cube dans `update`.
|
||||||
@@ -18,12 +20,11 @@ struct Cube {
|
|||||||
angle: f32,
|
angle: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Génère les sommets d'un cube unitaire centré à l'origine (arête de 1), une normale par face.
|
/// Construit la `Geometry` d'un cube unitaire centré à l'origine (arête de 1), une normale par face.
|
||||||
/// 24 sommets (4 par face) + 36 indices ; la couleur est blanche, l'UV est laissé à zéro (inutilisé
|
/// 24 sommets (4 par face) + 36 indices ; la couleur est absente (défaut blanc opaque via
|
||||||
/// par `standard` pour un matériau sans texture).
|
/// `Geometry::to_vertices`), l'UV est laissé à zéro (inutilisé par `standard` pour un matériau sans texture).
|
||||||
fn cube_vertices() -> Vec<Vertex> {
|
fn cube_geometry() -> Geometry {
|
||||||
let s = 0.5; // demi-arête
|
let s = 0.5; // demi-arête
|
||||||
let color = [1.0, 1.0, 1.0, 1.0];
|
|
||||||
// Chaque face : (normale sortante, 4 coins). Le culling est désactivé par défaut (PrimitiveState
|
// Chaque face : (normale sortante, 4 coins). Le culling est désactivé par défaut (PrimitiveState
|
||||||
// par défaut), donc l'ordre d'enroulement n'affecte pas la visibilité ; seules les normales comptent
|
// par défaut), donc l'ordre d'enroulement n'affecte pas la visibilité ; seules les normales comptent
|
||||||
// pour l'éclairage.
|
// pour l'éclairage.
|
||||||
@@ -54,28 +55,25 @@ fn cube_vertices() -> Vec<Vertex> {
|
|||||||
), // -Y
|
), // -Y
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut verts = Vec::with_capacity(24);
|
let mut positions = Vec::with_capacity(24);
|
||||||
|
let mut normals = Vec::with_capacity(24);
|
||||||
for (normal, corners) in faces {
|
for (normal, corners) in faces {
|
||||||
for corner in corners {
|
for corner in corners {
|
||||||
verts.push(Vertex {
|
positions.push(corner);
|
||||||
position: corner,
|
normals.push(normal);
|
||||||
normal,
|
|
||||||
uv: [0.0, 0.0],
|
|
||||||
color,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verts
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Génère les indices d'un cube à partir de ses 24 sommets (2 triangles par face, 36 indices).
|
// 2 triangles par face, 36 indices.
|
||||||
fn cube_indices() -> Vec<u16> {
|
|
||||||
let mut indices = Vec::with_capacity(36);
|
let mut indices = Vec::with_capacity(36);
|
||||||
for face in 0..6u16 {
|
for face in 0..6u16 {
|
||||||
let b = face * 4;
|
let b = face * 4;
|
||||||
indices.extend_from_slice(&[b, b + 1, b + 2, b, b + 2, b + 3]);
|
indices.extend_from_slice(&[b, b + 1, b + 2, b, b + 2, b + 3]);
|
||||||
}
|
}
|
||||||
indices
|
|
||||||
|
Geometry::new(positions)
|
||||||
|
.with_normals(normals)
|
||||||
|
.with_indices(indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppHandler for Cube {
|
impl AppHandler for Cube {
|
||||||
@@ -90,12 +88,7 @@ impl AppHandler for Cube {
|
|||||||
.add_material_shader("cube_material", "standard")
|
.add_material_shader("cube_material", "standard")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
app.scene
|
app.scene
|
||||||
.create_mesh(
|
.create_mesh("cube_mesh", cube_geometry(), Some("cube_material"))
|
||||||
"cube_mesh",
|
|
||||||
&cube_vertices(),
|
|
||||||
Some(&cube_indices()),
|
|
||||||
Some("cube_material"),
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
app.scene.add_entity("cube", "cube_mesh").unwrap();
|
app.scene.add_entity("cube", "cube_mesh").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-34
@@ -1,7 +1,9 @@
|
|||||||
//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et
|
//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et
|
||||||
//! `Material`, contournant la façade `App`. Rendu d'un quad plat (shader `standard` **unlit**) via la
|
//! `Material`, contournant la façade `App`. Rendu d'un quad plat (shader `standard` **unlit**) via la
|
||||||
//! boucle winit 0.30 (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés
|
//! boucle winit 0.30 (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés
|
||||||
//! dans `resumed()`, comme l'exigent winit 0.30 et la migration faite dans `app.rs`.
|
//! dans `resumed()`, comme l'exigent winit 0.30 et la migration faite dans `app.rs`. Depuis l'Étape 8
|
||||||
|
//! (DRAFT 8.5) le mesh est construit via `Mesh::from_geometry(device, Arc<Geometry>, None)` à partir
|
||||||
|
//! d'une `Geometry` (positions + couleurs par sommet) au lieu de `Mesh::new(device, &[Vertex], ..)`.
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
@@ -12,9 +14,7 @@ use wsg_lib::core::Context;
|
|||||||
use wsg_lib::core::Frame;
|
use wsg_lib::core::Frame;
|
||||||
use wsg_lib::core::Renderer;
|
use wsg_lib::core::Renderer;
|
||||||
use wsg_lib::pipeline::PipelineCache;
|
use wsg_lib::pipeline::PipelineCache;
|
||||||
use wsg_lib::resources::Material;
|
use wsg_lib::resources::{Geometry, Material, Mesh};
|
||||||
use wsg_lib::resources::Mesh;
|
|
||||||
use wsg_lib::resources::Vertex;
|
|
||||||
use wsg_lib::utils;
|
use wsg_lib::utils;
|
||||||
|
|
||||||
/// Application bas-niveau : détient les objets GPU + window, tous créés dans `resumed`.
|
/// Application bas-niveau : détient les objets GPU + window, tous créés dans `resumed`.
|
||||||
@@ -70,36 +70,24 @@ impl ApplicationHandler for App {
|
|||||||
// 3. Material : On utilise renderer.device() et renderer.format()
|
// 3. Material : On utilise renderer.device() et renderer.format()
|
||||||
let material = Material::new(renderer.format(), "standard", &mut cache);
|
let material = Material::new(renderer.format(), "standard", &mut cache);
|
||||||
|
|
||||||
// Mesh : On utilise le device du renderer
|
// Mesh : on utilise le device du renderer. Depuis l'Étape 8 le mesh est construit depuis une
|
||||||
let vertices = [
|
// `Geometry` (positions + couleurs par sommet) via `Mesh::from_geometry` — le mesh garde aussi
|
||||||
// Position (x,y,z) | Normale (x,y,z) | UV (u,v) | Couleur (r,g,b,a)
|
// l'`Arc<Geometry>` côté CPU (rétention D5).
|
||||||
Vertex {
|
let geometry = Geometry::new(vec![
|
||||||
position: [-0.5, 0.5, 0.0],
|
// Position (x,y,z) | Couleur (r,g,b,a) — normales/UV par défaut via to_vertices
|
||||||
normal: [0.0, 0.0, 1.0],
|
[-0.5, 0.5, 0.0],
|
||||||
uv: [0.0, 0.0],
|
[0.5, 0.5, 0.0],
|
||||||
color: [1.0, 0.0, 0.0, 1.0],
|
[0.5, -0.5, 0.0],
|
||||||
}, // Haut-Gauche (Rouge)
|
[-0.5, -0.5, 0.0],
|
||||||
Vertex {
|
])
|
||||||
position: [0.5, 0.5, 0.0],
|
.with_colors(vec![
|
||||||
normal: [0.0, 0.0, 1.0],
|
[1.0, 0.0, 0.0, 1.0], // Haut-Gauche (Rouge)
|
||||||
uv: [1.0, 0.0],
|
[0.0, 1.0, 0.0, 1.0], // Haut-Droite (Vert)
|
||||||
color: [0.0, 1.0, 0.0, 1.0],
|
[0.0, 0.0, 1.0, 1.0], // Bas-Droite (Bleu)
|
||||||
}, // Haut-Droite (Vert)
|
[1.0, 1.0, 0.0, 1.0], // Bas-Gauche (Jaune)
|
||||||
Vertex {
|
])
|
||||||
position: [0.5, -0.5, 0.0],
|
.with_indices(vec![0, 1, 2, 0, 2, 3]);
|
||||||
normal: [0.0, 0.0, 1.0],
|
let mesh = Mesh::from_geometry(renderer.device(), Arc::new(geometry), None);
|
||||||
uv: [1.0, 1.0],
|
|
||||||
color: [0.0, 0.0, 1.0, 1.0],
|
|
||||||
}, // Bas-Droite (Bleu)
|
|
||||||
Vertex {
|
|
||||||
position: [-0.5, -0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [0.0, 1.0],
|
|
||||||
color: [1.0, 1.0, 0.0, 1.0],
|
|
||||||
}, // Bas-Gauche (Jaune)
|
|
||||||
];
|
|
||||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
|
||||||
let mesh = Mesh::new(renderer.device(), &vertices, Some(&indices));
|
|
||||||
|
|
||||||
self.window = Some(window);
|
self.window = Some(window);
|
||||||
self.context = Some(context);
|
self.context = Some(context);
|
||||||
|
|||||||
+23
-41
@@ -4,11 +4,13 @@
|
|||||||
//! qu'après `resumed` : c'est pourquoi l'enregistrement shader + la création mesh/matériau/entité vivent
|
//! qu'après `resumed` : c'est pourquoi l'enregistrement shader + la création mesh/matériau/entité vivent
|
||||||
//! dans le hook `AppHandler::setup`, appelé une fois le contexte prêt. Depuis l'Étape 7 le PipelineCache
|
//! dans le hook `AppHandler::setup`, appelé une fois le contexte prêt. Depuis l'Étape 7 le PipelineCache
|
||||||
//! vit dans la scène (`Scene::init_gpu`, appelé dans `resumed`) : on passe par `register_shader` +
|
//! vit dans la scène (`Scene::init_gpu`, appelé dans `resumed`) : on passe par `register_shader` +
|
||||||
//! `add_material_shader` + `create_mesh` + `add_entity`, le matériau étant lié au mesh. La scène se rend
|
//! `add_material_shader` + `create_mesh` + `add_entity`, le matériau étant lié au mesh. Depuis l'Étape 8
|
||||||
//! automatiquement : la méthode `render()` par défaut appelle `app.render_scene(frame.view())`.
|
//! (DRAFT 8.4/8.5) le mesh est déclaré à partir d'une **`Geometry`** : positions + couleurs par sommet
|
||||||
|
//! pour le quad unlit. La scène se rend automatiquement : la méthode `render()` par défaut appelle
|
||||||
|
//! `app.render_scene(frame.view())`.
|
||||||
use wsg_lib::AppHandler;
|
use wsg_lib::AppHandler;
|
||||||
use wsg_lib::app::AppBuilder;
|
use wsg_lib::app::AppBuilder;
|
||||||
use wsg_lib::resources::Vertex;
|
use wsg_lib::resources::Geometry;
|
||||||
use wsg_lib::utils::WsgError;
|
use wsg_lib::utils::WsgError;
|
||||||
|
|
||||||
struct MonQuad;
|
struct MonQuad;
|
||||||
@@ -21,45 +23,25 @@ impl AppHandler for MonQuad {
|
|||||||
app.scene
|
app.scene
|
||||||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vertices = [
|
|
||||||
Vertex {
|
|
||||||
position: [-0.5, 0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [0.0, 0.0],
|
|
||||||
color: [1.0, 0.0, 0.0, 1.0],
|
|
||||||
}, // Haut-Gauche (Rouge)
|
|
||||||
Vertex {
|
|
||||||
position: [0.5, 0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [1.0, 0.0],
|
|
||||||
color: [0.0, 1.0, 0.0, 1.0],
|
|
||||||
}, // Haut-Droite (Vert)
|
|
||||||
Vertex {
|
|
||||||
position: [0.5, -0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [1.0, 1.0],
|
|
||||||
color: [0.0, 0.0, 1.0, 1.0],
|
|
||||||
}, // Bas-Droite (Bleu)
|
|
||||||
Vertex {
|
|
||||||
position: [-0.5, -0.5, 0.0],
|
|
||||||
normal: [0.0, 0.0, 1.0],
|
|
||||||
uv: [0.0, 1.0],
|
|
||||||
color: [1.0, 1.0, 0.0, 1.0],
|
|
||||||
}, // Bas-Gauche (Jaune)
|
|
||||||
];
|
|
||||||
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
|
||||||
|
|
||||||
app.scene
|
let geometry = Geometry::new(vec![
|
||||||
.add_material_shader("standard_material", "standard")
|
[-0.5, 0.5, 0.0], // Haut-Gauche
|
||||||
.unwrap();
|
[0.5, 0.5, 0.0], // Haut-Droite
|
||||||
app.scene
|
[0.5, -0.5, 0.0], // Bas-Droite
|
||||||
.create_mesh(
|
[-0.5, -0.5, 0.0], // Bas-Gauche
|
||||||
"quad_mesh",
|
])
|
||||||
&vertices,
|
.with_normals(vec![[0.0, 0.0, 1.0]; 4])
|
||||||
Some(&indices),
|
.with_colors(vec![
|
||||||
Some("standard_material"),
|
[1.0, 0.0, 0.0, 1.0], // Haut-Gauche (Rouge)
|
||||||
)
|
[0.0, 1.0, 0.0, 1.0], // Haut-Droite (Vert)
|
||||||
.unwrap();
|
[0.0, 0.0, 1.0, 1.0], // Bas-Droite (Bleu)
|
||||||
|
[1.0, 1.0, 0.0, 1.0], // Bas-Gauche (Jaune)
|
||||||
|
])
|
||||||
|
.with_indices(vec![0, 1, 2, 0, 2, 3]);
|
||||||
|
|
||||||
|
// Material par défaut : `None` laisse la Scene injecter son `standard` au rendu
|
||||||
|
// (`Scene::default_material`, DRAFT Étape 7.3.5) — on vérifie le chemin par défaut.
|
||||||
|
app.scene.create_mesh("quad_mesh", geometry, None).unwrap();
|
||||||
app.scene.add_entity("quad", "quad_mesh").unwrap();
|
app.scene.add_entity("quad", "quad_mesh").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+251
-8
@@ -4,25 +4,268 @@
|
|||||||
//! This module handles the core geometric representation used by meshes.
|
//! This module handles the core geometric representation used by meshes.
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
//! - Stores vertex attributes (positions, normals, UVs)
|
//! - Stores vertex attributes (positions, normals, UVs, colors)
|
||||||
//! - Used by `Mesh` to define its vertex data
|
//! - Used by `Mesh` to define its vertex data
|
||||||
//! - Passed to shaders for rendering
|
//! - Converted to `resources::Vertex` buffers via `to_vertices()`
|
||||||
//!
|
//!
|
||||||
//! ## Related Types
|
//! ## Related Types
|
||||||
//! - `Geometry`: Main struct for vertex data storage
|
//! - `Geometry`: Main struct for vertex data storage
|
||||||
//! - Fields: positions, normals, uvs, indices
|
//! - `GeometryError`: Validation errors for inconsistent per-vertex arrays
|
||||||
|
//! - Fields: positions, normals, uvs, colors, indices
|
||||||
|
//!
|
||||||
|
//! ## Invariants
|
||||||
|
//! `positions` is required and non-empty. The optional per-vertex attributes
|
||||||
|
//! (`normals`, `uvs`, `colors`), when present, must have exactly the same length
|
||||||
|
//! as `positions`. `indices`, when present, must reference valid vertex indices.
|
||||||
|
//! Call `Geometry::validate()` (or `try_into_vertices()`) to enforce these before
|
||||||
|
//! uploading to the GPU.
|
||||||
|
|
||||||
|
use crate::resources::Vertex;
|
||||||
|
|
||||||
|
/// Validation error produced when a `Geometry` is inconsistent, i.e. its optional
|
||||||
|
/// per-vertex arrays (`normals`, `uvs`, `colors`) have a length different from
|
||||||
|
/// `positions`, or an index is out of bounds.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum GeometryError {
|
||||||
|
/// No positions defined; a valid geometry must have at least one vertex.
|
||||||
|
EmptyPositions,
|
||||||
|
/// The normals array length differs from the positions array length.
|
||||||
|
NormalCountMismatch {
|
||||||
|
/// Number of positions.
|
||||||
|
positions: usize,
|
||||||
|
/// Number of normals.
|
||||||
|
normals: usize,
|
||||||
|
},
|
||||||
|
/// The UV array length differs from the positions array length.
|
||||||
|
UvCountMismatch {
|
||||||
|
/// Number of positions.
|
||||||
|
positions: usize,
|
||||||
|
/// Number of UVs.
|
||||||
|
uvs: usize,
|
||||||
|
},
|
||||||
|
/// The color array length differs from the positions array length.
|
||||||
|
ColorCountMismatch {
|
||||||
|
/// Number of positions.
|
||||||
|
positions: usize,
|
||||||
|
/// Number of colors.
|
||||||
|
colors: usize,
|
||||||
|
},
|
||||||
|
/// An index references a vertex that does not exist.
|
||||||
|
IndexOutOfBounds {
|
||||||
|
/// The offending index value.
|
||||||
|
index: u16,
|
||||||
|
/// Number of vertices in the geometry.
|
||||||
|
vertex_count: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for GeometryError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
GeometryError::EmptyPositions => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"geometry has no positions; at least one vertex is required"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GeometryError::NormalCountMismatch { positions, normals } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"normal count ({normals}) does not match position count ({positions})"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GeometryError::UvCountMismatch { positions, uvs } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"UV count ({uvs}) does not match position count ({positions})"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GeometryError::ColorCountMismatch { positions, colors } => write!(
|
||||||
|
f,
|
||||||
|
"color count ({colors}) does not match position count ({positions})"
|
||||||
|
),
|
||||||
|
GeometryError::IndexOutOfBounds {
|
||||||
|
index,
|
||||||
|
vertex_count,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"index {index} is out of bounds (vertex count is {vertex_count})"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for GeometryError {}
|
||||||
|
|
||||||
/// Represents the geometric data of a 3D mesh.
|
/// Represents the geometric data of a 3D mesh.
|
||||||
///
|
///
|
||||||
/// This struct stores the core vertex attributes that define a mesh's shape.
|
/// This struct stores the core vertex attributes that define a mesh's shape as
|
||||||
|
/// *scattered per-attribute arrays* (one Vec per attribute), as opposed to the
|
||||||
|
/// interleaved `resources::Vertex` layout used for GPU upload.
|
||||||
|
///
|
||||||
|
/// Construct a `Geometry` ergonomically with [`Geometry::new`] plus the fluent
|
||||||
|
/// builder methods ([`Geometry::with_normals`], [`Geometry::with_uvs`],
|
||||||
|
/// [`Geometry::with_colors`], [`Geometry::with_indices`]). Validate it with
|
||||||
|
/// [`Geometry::validate`], then produce GPU-ready vertices via
|
||||||
|
/// [`Geometry::to_vertices`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Geometry {
|
pub struct Geometry {
|
||||||
/// Vertex positions as an array of 3D coordinates
|
/// Vertex positions as an array of 3D coordinates. Required, non-empty.
|
||||||
pub positions: Vec<[f32; 3]>,
|
pub positions: Vec<[f32; 3]>,
|
||||||
/// Optional vertex normals for lighting calculations
|
/// Optional vertex normals for lighting calculations. When present, must have
|
||||||
|
/// the same length as `positions`.
|
||||||
pub normals: Option<Vec<[f32; 3]>>,
|
pub normals: Option<Vec<[f32; 3]>>,
|
||||||
/// Optional texture coordinates for UV mapping
|
/// Optional texture coordinates for UV mapping. When present, must have the
|
||||||
|
/// same length as `positions`.
|
||||||
pub uvs: Option<Vec<[f32; 2]>>,
|
pub uvs: Option<Vec<[f32; 2]>>,
|
||||||
/// Optional indices for indexed rendering
|
/// Optional per-vertex colors (RGBA). When present, must have the same length
|
||||||
|
/// as `positions`. Absent colors default to opaque white.
|
||||||
|
pub colors: Option<Vec<[f32; 4]>>,
|
||||||
|
/// Optional indices for indexed rendering. Each index must be `< positions.len()`.
|
||||||
pub indices: Option<Vec<u16>>,
|
pub indices: Option<Vec<u16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Geometry {
|
||||||
|
/// Creates a geometry from vertex positions only; all optional attributes
|
||||||
|
/// (normals, UVs, colors, indices) start as `None`.
|
||||||
|
/// Chain builder methods to populate them:
|
||||||
|
/// ```
|
||||||
|
/// # use wsg_lib::math::Geometry;
|
||||||
|
/// let geo = Geometry::new(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
|
||||||
|
/// .with_normals(vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]])
|
||||||
|
/// .with_indices(vec![0, 1]);
|
||||||
|
/// ```
|
||||||
|
pub fn new(positions: Vec<[f32; 3]>) -> Self {
|
||||||
|
Self {
|
||||||
|
positions,
|
||||||
|
normals: None,
|
||||||
|
uvs: None,
|
||||||
|
colors: None,
|
||||||
|
indices: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the vertex normals and returns the geometry for chaining.
|
||||||
|
pub fn with_normals(mut self, normals: Vec<[f32; 3]>) -> Self {
|
||||||
|
self.normals = Some(normals);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the texture coordinates and returns the geometry for chaining.
|
||||||
|
pub fn with_uvs(mut self, uvs: Vec<[f32; 2]>) -> Self {
|
||||||
|
self.uvs = Some(uvs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the per-vertex colors (RGBA) and returns the geometry for chaining.
|
||||||
|
pub fn with_colors(mut self, colors: Vec<[f32; 4]>) -> Self {
|
||||||
|
self.colors = Some(colors);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the index buffer and returns the geometry for chaining.
|
||||||
|
pub fn with_indices(mut self, indices: Vec<u16>) -> Self {
|
||||||
|
self.indices = Some(indices);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the geometry invariants:
|
||||||
|
/// - `positions` is non-empty;
|
||||||
|
/// - each optional per-vertex array, when present, has the same length as `positions`;
|
||||||
|
/// - each index, when present, references an existing vertex.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` or a [`GeometryError`] describing the first inconsistency found.
|
||||||
|
pub fn validate(&self) -> Result<(), GeometryError> {
|
||||||
|
let count = self.positions.len();
|
||||||
|
if count == 0 {
|
||||||
|
return Err(GeometryError::EmptyPositions);
|
||||||
|
}
|
||||||
|
if let Some(normals) = &self.normals {
|
||||||
|
if normals.len() != count {
|
||||||
|
return Err(GeometryError::NormalCountMismatch {
|
||||||
|
positions: count,
|
||||||
|
normals: normals.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(uvs) = &self.uvs {
|
||||||
|
if uvs.len() != count {
|
||||||
|
return Err(GeometryError::UvCountMismatch {
|
||||||
|
positions: count,
|
||||||
|
uvs: uvs.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(colors) = &self.colors {
|
||||||
|
if colors.len() != count {
|
||||||
|
return Err(GeometryError::ColorCountMismatch {
|
||||||
|
positions: count,
|
||||||
|
colors: colors.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(indices) = &self.indices {
|
||||||
|
for &index in indices {
|
||||||
|
if index as usize >= count {
|
||||||
|
return Err(GeometryError::IndexOutOfBounds {
|
||||||
|
index,
|
||||||
|
vertex_count: count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional index slice, when present.
|
||||||
|
/// Helper used by `Mesh` to upload the index buffer.
|
||||||
|
pub fn indices(&self) -> Option<&[u16]> {
|
||||||
|
self.indices.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the CPU vertices in interleaved `resources::Vertex` layout, suitable
|
||||||
|
/// for GPU upload. Assumes the geometry is valid; missing per-vertex attributes
|
||||||
|
/// are filled with defaults:
|
||||||
|
/// - position: required (must be present);
|
||||||
|
/// - normal: `[0, 0, 1]` (a geometry without normals lit by Phong will appear flat);
|
||||||
|
/// - uv: `[0, 0]`;
|
||||||
|
/// - color: `[1, 1, 1, 1]` (opaque white).
|
||||||
|
///
|
||||||
|
/// For a checked conversion that rejects inconsistent arrays, use
|
||||||
|
/// [`Geometry::try_into_vertices`].
|
||||||
|
pub fn to_vertices(&self) -> Vec<Vertex> {
|
||||||
|
self.positions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &position)| Vertex {
|
||||||
|
position,
|
||||||
|
normal: self
|
||||||
|
.normals
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|n| n.get(i))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or([0.0, 0.0, 1.0]),
|
||||||
|
uv: self
|
||||||
|
.uvs
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|u| u.get(i))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or([0.0, 0.0]),
|
||||||
|
color: self
|
||||||
|
.colors
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.get(i))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the geometry and, on success, returns the interleaved CPU vertices
|
||||||
|
/// (see [`Geometry::to_vertices`]). Returns a [`GeometryError`] describing the
|
||||||
|
/// first inconsistency if the geometry is invalid.
|
||||||
|
pub fn try_into_vertices(&self) -> Result<Vec<Vertex>, GeometryError> {
|
||||||
|
self.validate()?;
|
||||||
|
Ok(self.to_vertices())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,5 +19,5 @@ pub mod geometry;
|
|||||||
pub mod transform;
|
pub mod transform;
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
pub use geometry::Geometry;
|
pub use geometry::{Geometry, GeometryError};
|
||||||
pub use transform::Transform;
|
pub use transform::Transform;
|
||||||
|
|||||||
+43
-36
@@ -1,25 +1,38 @@
|
|||||||
//! # Mesh Module — Persistent GPU Geometry Container
|
//! # Mesh Module — Persistent GPU Geometry Container
|
||||||
//!
|
//!
|
||||||
//! Defines `Mesh`, a persistent GPU geometry container. Mesh data is uploaded to the GPU once at creation time
|
//! Defines `Mesh`, a persistent GPU geometry container. Mesh data is uploaded to the GPU once at creation time
|
||||||
//! and remains valid across all frames until dropped. Since Étape 7 (DRAFT Étape 7.2), a Mesh may also hold a
|
//! and remains valid across all frames until dropped. Since Étape 8 (DRAFT Étape 8.3), a Mesh also retains the
|
||||||
//! reference to the `Material` that draws it — the appearance lives on the Mesh rather than on the `Entity`.
|
//! CPU geometry it was built from (`geometry: Arc<Geometry>`), giving meshes a shared, readable source of truth
|
||||||
|
//! for phases such as bounding-box culling and UV access. Since Étape 7, a Mesh may also hold a reference to the
|
||||||
|
//! `Material` that draws it — the appearance lives on the Mesh rather than on the `Entity`.
|
||||||
//!
|
//!
|
||||||
//! ## Architecture Notes (per ARCHI_APP.md)
|
//! ## Architecture Notes (per ARCHI_APP.md)
|
||||||
//! - **Identifiants**: Each Mesh is registered in Scene by string identifier, enabling dynamic access
|
//! - **Identifiants**: Each Mesh is registered in Scene by string identifier, enabling dynamic access
|
||||||
//! during the render loop without borrow checker issues. The identifier serves as the `Handle<T>` key.
|
//! during the render loop without borrow checker issues. The identifier serves as the `Handle<T>` key.
|
||||||
//! - **Phase de Déclaration**: Meshes are instantiated once in the declarative phase before the render loop begins.
|
//! - **Phase de Déclaration**: Meshes are instantiated once in the declarative phase before the render loop begins.
|
||||||
//! - **Performance**: Multiple entities can reference the same Mesh, reducing memory footprint for repeated geometry.
|
//! - **Performance**: Multiple entities can reference the same Mesh, reducing memory footprint for repeated geometry.
|
||||||
|
//! - **Rétention CPU+GPU (DRAFT Étape 8, D5)**: `geometry` (CPU) and the vertex/index buffers (GPU) coexist.
|
||||||
|
//! The GPU buffers are uploaded once at creation; the `Arc<Geometry>` is kept for CPU-side computations
|
||||||
|
//! without re-uploading per frame.
|
||||||
|
//!
|
||||||
|
//! ## Construction (DRAFT Étape 8, D4)
|
||||||
|
//! The single canonical constructor is [`Mesh::from_geometry`]. The former `Mesh::new`/`Mesh::with_material`
|
||||||
|
//! (which took raw `&[Vertex]`) were removed in Étape 8: the `Scene` declares meshes from a `Geometry`, and
|
||||||
|
//! `Mesh` derives its interleaved vertices internally via `Geometry::to_vertices()`.
|
||||||
|
|
||||||
|
use crate::math::Geometry;
|
||||||
use crate::resources::Material;
|
use crate::resources::Material;
|
||||||
use crate::resources::vertex::Vertex;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
/// Persistent GPU geometry: vertex positions, optional indices, and draw call counters.
|
/// Persistent GPU geometry: vertex positions, optional indices, draw call counters, and the retained
|
||||||
/// Created once via `Mesh::new()` during scene setup; referenced by Renderer for every frame.
|
/// CPU `Geometry` (Étape 8). Created once via `Mesh::from_geometry()` during scene setup; referenced by
|
||||||
/// A Mesh optionally references the `Material` used to render it (`Option<Arc<Material>>`).
|
/// Renderer for every frame. A Mesh optionally references the `Material` used to render it (`Option<Arc<Material>>`).
|
||||||
/// When `material()` is `None`, the `Scene` supplies its default material at draw time (DRAFT Étape 7.3.5).
|
/// When `material()` is `None`, the `Scene` supplies its default material at draw time (DRAFT Étape 7.3.5).
|
||||||
pub struct Mesh {
|
pub struct Mesh {
|
||||||
|
/// Shared CPU geometry this mesh was built from (Étape 8, D5). Retained for CPU-side computation
|
||||||
|
/// (bounding boxes, UV access, normal queries) and shared across meshes with identical geometry.
|
||||||
|
geometry: Arc<Geometry>,
|
||||||
/// GPU buffer containing vertex attribute data (position, UV, color).
|
/// GPU buffer containing vertex attribute data (position, UV, color).
|
||||||
pub vertex_buffer: wgpu::Buffer,
|
pub vertex_buffer: wgpu::Buffer,
|
||||||
/// Optional GPU buffer for indexed drawing. Present when the mesh uses index-based rendering instead of simple vertex iteration.
|
/// Optional GPU buffer for indexed drawing. Present when the mesh uses index-based rendering instead of simple vertex iteration.
|
||||||
@@ -34,44 +47,30 @@ pub struct Mesh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mesh {
|
impl Mesh {
|
||||||
/// Creates a new Mesh by uploading vertex and optional index data to GPU buffers.
|
/// Canonical constructor (Étape 8, D4): builds GPU buffers from a shared CPU `Geometry`.
|
||||||
/// Inputs: device (GPU command source for buffer creation), vertices (CPU-side vertex array to upload),
|
///
|
||||||
/// indices (optional CPU-side index array for indexed drawing).
|
/// Inputs: device (GPU command source for buffer creation), geometry (shared CPU vertex data to
|
||||||
/// Returns a Mesh with two GPU buffers ready for rendering, without a material (falls back to the
|
/// upload), material (optional appearance; `None` falls back to the Scene default at draw time).
|
||||||
/// Scene default at draw time). Called at scene initialization time only.
|
///
|
||||||
/// Internal steps: 1) create_buffer_init for vertex data →
|
/// Internal steps: 1) derive interleaved `Vertex` array via `geometry.to_vertices()`; 2) create the
|
||||||
/// 2) if indices provided: create_buffer_init for index data and set num_indices = len →
|
/// vertex buffer (one `Vertex` per position); 3) if the geometry has indices, create the index buffer
|
||||||
/// else: set index_buffer = None and num_indices = 0.
|
/// and set `num_indices`, else leave it `None`.
|
||||||
pub fn new(device: &wgpu::Device, vertices: &[Vertex], indices: Option<&[u16]>) -> Self {
|
///
|
||||||
Self::new_inner(device, vertices, indices, None)
|
/// The provided `geometry` is retained on the mesh (`geometry` accessor) alongside the uploaded GPU
|
||||||
}
|
/// buffers, so the CPU data remains readable for later phases without re-uploading each frame (DRAFT Étape 8, D5).
|
||||||
|
pub fn from_geometry(
|
||||||
/// Creates a new Mesh (as `Mesh::new`) and immediately attaches a Material reference.
|
|
||||||
/// Convenience helper for the declarative workflow where geometry and appearance are declared together.
|
|
||||||
pub fn with_material(
|
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
vertices: &[Vertex],
|
geometry: Arc<Geometry>,
|
||||||
indices: Option<&[u16]>,
|
|
||||||
material: Arc<Material>,
|
|
||||||
) -> Self {
|
|
||||||
Self::new_inner(device, vertices, indices, Some(material))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared construction helper for `Mesh::new` / `Mesh::with_material`.
|
|
||||||
fn new_inner(
|
|
||||||
device: &wgpu::Device,
|
|
||||||
vertices: &[Vertex],
|
|
||||||
indices: Option<&[u16]>,
|
|
||||||
material: Option<Arc<Material>>,
|
material: Option<Arc<Material>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let vertices = geometry.to_vertices();
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("Mesh Vertex Buffer"),
|
label: Some("Mesh Vertex Buffer"),
|
||||||
contents: bytemuck::cast_slice(vertices),
|
contents: bytemuck::cast_slice(&vertices),
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
usage: wgpu::BufferUsages::VERTEX,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create optional index buffer and count indices if provided
|
let (index_buffer, num_indices) = if let Some(data) = geometry.indices() {
|
||||||
let (index_buffer, num_indices) = if let Some(data) = indices {
|
|
||||||
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("Mesh Index Buffer"),
|
label: Some("Mesh Index Buffer"),
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
usage: wgpu::BufferUsages::INDEX,
|
||||||
@@ -81,7 +80,9 @@ impl Mesh {
|
|||||||
} else {
|
} else {
|
||||||
(None, 0)
|
(None, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
geometry,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
index_buffer,
|
index_buffer,
|
||||||
num_vertices: vertices.len() as u32,
|
num_vertices: vertices.len() as u32,
|
||||||
@@ -90,6 +91,12 @@ impl Mesh {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the shared CPU geometry this mesh was built from (Étape 8, D5).
|
||||||
|
/// Read-only accessor for CPU-side queries (bounding boxes, UVs, normals).
|
||||||
|
pub fn geometry(&self) -> &Arc<Geometry> {
|
||||||
|
&self.geometry
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a reference to the Material attached to this mesh, if any.
|
/// Returns a reference to the Material attached to this mesh, if any.
|
||||||
/// When `None`, the Renderer falls back to the Scene's default material at draw time.
|
/// When `None`, the Renderer falls back to the Scene's default material at draw time.
|
||||||
pub fn material(&self) -> Option<&Arc<Material>> {
|
pub fn material(&self) -> Option<&Arc<Material>> {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
//! # Resources Module — Data Types
|
//! # Resources Module — Data Types
|
||||||
//!
|
//!
|
||||||
//! Defines the three core data types that flow through the rendering pipeline: **Vertex** (CPU-side per-attribute
|
//! Defines the core data types that flow through the rendering pipeline: **Geometry** (CPU-side scattered
|
||||||
//! tuple), **Mesh** (GPU geometry container with vertex/index buffers), and **Material** (appearance descriptor
|
//! vertex data, source of truth — re-exported here from `math` for convenience), **Vertex** (interleaved
|
||||||
//! pairing shader ID with a compiled RenderPipeline). These are immutable after creation and consumed by Renderer
|
//! CPU-side per-attribute tuple, the GPU upload contract), **Mesh** (GPU geometry container with vertex/index
|
||||||
//! for draw calls.
|
//! buffers), and **Material** (appearance descriptor pairing shader ID with a compiled RenderPipeline).
|
||||||
|
//! These are immutable after creation and consumed by Renderer for draw calls.
|
||||||
//!
|
//!
|
||||||
//! ## Interaction with Other Modules
|
//! ## Interaction with Other Modules
|
||||||
//! - `pipeline_cache::build_pipeline()` reads Vertex field offsets to construct the vertex buffer layout.
|
//! - `pipeline_cache::build_pipeline()` reads Vertex field offsets to construct the vertex buffer layout.
|
||||||
//! - `mesh::new()` uploads Vertex arrays from CPU memory into GPU vertex buffers via DeviceExt::create_buffer_init().
|
//! - `mesh::from_geometry()` derives `Vertex` arrays from a `Geometry` and uploads them into GPU vertex
|
||||||
|
//! buffers via DeviceExt::create_buffer_init().
|
||||||
//! - `material::new()` requests RenderPipelines from PipelineCache during scene initialization.
|
//! - `material::new()` requests RenderPipelines from PipelineCache during scene initialization.
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
@@ -22,3 +24,7 @@ pub use material::Material;
|
|||||||
pub use mesh::Mesh;
|
pub use mesh::Mesh;
|
||||||
pub use uniform::{FrameUniforms, ObjectUniform};
|
pub use uniform::{FrameUniforms, ObjectUniform};
|
||||||
pub use vertex::Vertex;
|
pub use vertex::Vertex;
|
||||||
|
|
||||||
|
// Convenience re-export of `math::Geometry` (Étape 8, D2) so examples can build meshes
|
||||||
|
// from `wsg_lib::resources::Geometry` without importing `math` separately.
|
||||||
|
pub use crate::math::Geometry;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
//! instead of `App`. It can therefore build materials and meshes itself (`add_material_shader`, `create_mesh`) and inject
|
//! instead of `App`. It can therefore build materials and meshes itself (`add_material_shader`, `create_mesh`) and inject
|
||||||
//! a default material for meshes that carry none (`default_material`).
|
//! a default material for meshes that carry none (`default_material`).
|
||||||
|
|
||||||
use crate::math::Transform;
|
use crate::math::{Geometry, Transform};
|
||||||
use crate::pipeline::PipelineCache;
|
use crate::pipeline::PipelineCache;
|
||||||
use crate::resources::{Camera, Material, Mesh, Vertex};
|
use crate::resources::{Camera, Material, Mesh};
|
||||||
use crate::scene::Entity;
|
use crate::scene::Entity;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -134,21 +134,22 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds, (optionally) links to a Material, and registers a Mesh in one declarative call.
|
/// Builds, (optionally) links to a Material, and registers a Mesh in one declarative call.
|
||||||
/// Creates the GPU buffers via `Mesh::new(device, ...)`, then — if `material` is `Some(name)` —
|
/// Since Étape 8 the mesh is declared from a CPU `Geometry` (DRAFT Étape 8, D4) instead of raw
|
||||||
/// resolves that material id and attaches it to the mesh (`Mesh::set_material`). When `material`
|
/// `&[Vertex]`. This builds the shared `Arc<Geometry>` and creates the GPU buffers via
|
||||||
/// is `None`, the mesh carries no material and the Scene's `default_material` is used at draw time.
|
/// `Mesh::from_geometry(device, arc, ...)`, then — if `material` is `Some(name)` — resolves that
|
||||||
|
/// material id and attaches it to the mesh (`Mesh::set_material`). When `material` is `None`, the
|
||||||
|
/// mesh carries no material and the Scene's `default_material` is used at draw time.
|
||||||
/// Returns Ok(id) or Err(String) if the id exists or the named material does not.
|
/// Returns Ok(id) or Err(String) if the id exists or the named material does not.
|
||||||
pub fn create_mesh(
|
pub fn create_mesh(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &str,
|
id: &str,
|
||||||
vertices: &[Vertex],
|
geometry: Geometry,
|
||||||
indices: Option<&[u16]>,
|
|
||||||
material: Option<&str>,
|
material: Option<&str>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
if self.meshes.contains_key(id) {
|
if self.meshes.contains_key(id) {
|
||||||
return Err(format!("Mesh ID '{}' already exists.", id));
|
return Err(format!("Mesh ID '{}' already exists.", id));
|
||||||
}
|
}
|
||||||
let mut mesh = Mesh::new(self.device(), vertices, indices);
|
let mut mesh = Mesh::from_geometry(self.device(), Arc::new(geometry), None);
|
||||||
if let Some(name) = material {
|
if let Some(name) = material {
|
||||||
let mat = self
|
let mat = self
|
||||||
.materials
|
.materials
|
||||||
|
|||||||
Reference in New Issue
Block a user