126 lines
4.9 KiB
Markdown
126 lines
4.9 KiB
Markdown
# Meshes: geometries, entities and transforms
|
|
|
|
A displayed object in WSG goes through three levels:
|
|
|
|
```
|
|
Geometry (CPU, source of truth) ──► Mesh (GPU: vertex/index buffers) ──► Entity (placement in the scene)
|
|
```
|
|
|
|
- **`Geometry`**: raw CPU-side data — positions + optional normals/UVs/colors/indices.
|
|
- **`Mesh`**: GPU container (buffers uploaded once). It **retains** its `Arc<Geometry>` on the
|
|
CPU side, along with its material.
|
|
- **`Entity`**: a `mesh + Transform` association. This is the unit the engine draws. The same
|
|
`Mesh` can be shared by several entities (each with its own `Transform`).
|
|
|
|
## 1. Procedural primitives (the shortest path)
|
|
|
|
The `math::primitives` module provides ready-to-use `Geometry` generators
|
|
(positions + normals + UVs + indices):
|
|
|
|
| Function | Parameters | Result |
|
|
|----------|-----------|--------|
|
|
| `cube(size)` | side length | origin-centered cube, per-face normals |
|
|
| `plane(width, depth, seg_x, seg_z)` | dimensions + subdivisions | horizontal plane (Y-up), UVs |
|
|
| `uv_sphere(radius, sectors, stacks)` | radius + resolution | UV sphere (seam visible) |
|
|
| `icosphere(radius, subdivisions)` | radius + subdivisions | smooth sphere (normalized, seam-free) |
|
|
| `cylinder(radius, height, sectors)` | radius, height, resolution | centered cylinder |
|
|
| `cone(radius, height, sectors)` | radius, height, resolution | cone (base at the bottom when translated in Y) |
|
|
| `torus(major, minor, major_segments, minor_segments)` | radii + resolution | torus |
|
|
|
|
```rust
|
|
use wsg_lib::math::{cube, icosphere, torus};
|
|
|
|
app.scene.create_mesh("cube_mesh", cube(0.8), Some("solid_mat")).unwrap();
|
|
app.scene.create_mesh("sphere_mesh", icosphere(0.5, 2), Some("solid_mat")).unwrap();
|
|
```
|
|
|
|
## 2. Custom `Geometry` (your own mesh)
|
|
|
|
`Geometry` is a builder: positions are mandatory, everything else is optional
|
|
(sensible defaults are applied at upload — e.g. normal `[0,0,1]`, white color).
|
|
|
|
```rust
|
|
use wsg_lib::resources::Geometry;
|
|
|
|
let geometry = Geometry::new(vec![
|
|
[-0.5, 0.5, 0.0],
|
|
[ 0.5, 0.5, 0.0],
|
|
[ 0.5, -0.5, 0.0],
|
|
[-0.5, -0.5, 0.0],
|
|
])
|
|
.with_normals(vec![[0.0, 0.0, 1.0]; 4]) // required for lighting (Phong)
|
|
.with_colors(vec![
|
|
[1.0, 0.0, 0.0, 1.0],
|
|
[0.0, 1.0, 0.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[1.0, 1.0, 0.0, 1.0],
|
|
])
|
|
.with_indices(vec![0, 1, 2, 0, 2, 3]); // triangulation (without indices: triangle list)
|
|
```
|
|
|
|
Other attributes: `.with_uvs(vec![[u, v], …])` (required for textures — see
|
|
[Materials & textures](materials.md)). `geometry.validate()` checks the arrays for
|
|
consistency (aligned lengths, indices in range) before upload.
|
|
|
|
> **Indices**: `Vec<u16>` — a custom mesh must therefore stay under 65,536 vertices. The
|
|
> engine's primitives respect this limit.
|
|
|
|
## 3. Registering in the scene
|
|
|
|
```rust
|
|
// The mesh is built (GPU buffers) and bound to its material in one call.
|
|
// `material = None`: the scene will use its default material (`standard`) at render time.
|
|
app.scene.create_mesh("cube_mesh", geometry, Some("cube_material"))?;
|
|
|
|
// The entity references the mesh by its id (String IDs).
|
|
app.scene.add_entity("cube", "cube_mesh")?;
|
|
// …or with an explicit placement:
|
|
app.scene.add_entity_with_transform("cube", "cube_mesh", transform)?;
|
|
```
|
|
|
|
All these methods return `Result<_, String>` (unifying the typed errors is on the
|
|
horizon — see [ROADMAP](../ROADMAP.md)).
|
|
|
|
## 4. Moving / animating: the `Transform`
|
|
|
|
Placement lives on the **entity** (not on the mesh): `Transform { translation: Vec3,
|
|
rotation: Quat, scale: Vec3 }`, converted to a world matrix by the engine every frame.
|
|
|
|
The snippet below is the animation from the [`cube`](../../lib/examples/cube.rs) example:
|
|
|
|
```rust
|
|
fn update(&mut self, app: &mut wsg_lib::App) {
|
|
self.angle += 0.02;
|
|
let mut tf = *app.scene.entity_transform("cube").expect("entity present");
|
|
tf.rotation = Quat::from_rotation_y(self.angle) * Quat::from_rotation_x(self.angle * 0.3);
|
|
app.scene.set_entity_transform("cube", tf);
|
|
}
|
|
```
|
|
|
|
Other entity operations: `entity_transform(label)` (read), `remove_entity(label)` (hides
|
|
without freeing resources), `entity_count()`.
|
|
|
|
> **Rotation order**: `Quat` does not commute — `rot_y * rot_x` is not `rot_x * rot_y`.
|
|
> The order above (Y then X) gives a readable "top spinning" motion.
|
|
|
|
## 5. Mesh sharing
|
|
|
|
Create **one** mesh per geometry and as many entities as occurrences:
|
|
|
|
```rust
|
|
app.scene.create_mesh("rock_mesh", icosphere(0.3, 1), Some("rock_mat")).unwrap();
|
|
for i in 0..10 {
|
|
let label = format!("rock_{i}");
|
|
let mut tf = Transform::identity();
|
|
tf.translation = Vec3::new(i as f32 * 0.8, 0.15, 0.0);
|
|
app.scene.add_entity_with_transform(&label, "rock_mesh", tf).unwrap();
|
|
}
|
|
```
|
|
|
|
The GPU buffers are uploaded only once; only the world matrices differ.
|
|
|
|
## Links
|
|
|
|
- [User README](README.md) · [Quickstart](quickstart.md) · [Materials & textures](materials.md) · [Lights](lights.md)
|
|
- [Root README](../../README.md) · [ARCHI_APP](../tech/ARCHI_APP.md)
|