Skip to main content

ursus_core/assets/
mesh.rs

1use crate::render::gfx::types::{Format, VertexAttribute, VertexFormat, VertexLayout};
2use glam::{Vec2, Vec3};
3
4#[repr(C)]
5#[derive(Debug, Clone, Copy)]
6pub struct Vertex {
7    pub position: [f32; 3],
8    pub normal: [f32; 3],
9    pub uv: [f32; 2],
10    pub tangent: [f32; 4],
11}
12unsafe impl bytemuck::Pod for Vertex {}
13unsafe impl bytemuck::Zeroable for Vertex {}
14
15// TODO(vertex-format): Vertex is opinionated (pipelines-specific) and belongs in ursus-pipelines, not core; make CpuMesh/GpuMesh generic and add a #[derive(VertexFormat)] macro.
16impl VertexFormat for Vertex {
17    fn layout() -> VertexLayout {
18        VertexLayout {
19            stride: size_of::<Self>() as u32,
20            attributes: vec![
21                VertexAttribute { location: 0, format: Format::Rgb32Float, offset: 0 }, // position
22                VertexAttribute { location: 1, format: Format::Rgb32Float, offset: 12 }, // normal
23                VertexAttribute { location: 2, format: Format::Rg32Float, offset: 24 }, // uv
24                VertexAttribute { location: 3, format: Format::Rgba32Float, offset: 32 }, // tangent
25            ],
26        }
27    }
28}
29
30#[derive(Debug, Clone, Copy)]
31pub struct Aabb {
32    pub min: Vec3,
33    pub max: Vec3,
34}
35
36impl Aabb {
37    pub fn from_vertices(vertices: &[Vertex]) -> Self {
38        let mut min = Vec3::splat(f32::MAX);
39        let mut max = Vec3::splat(f32::MIN);
40        for v in vertices {
41            let p = Vec3::from(v.position);
42            min = min.min(p);
43            max = max.max(p);
44        }
45        Self { min, max }
46    }
47
48    pub fn intersects_frustum(&self, planes: &[glam::Vec4; 6]) -> bool {
49        for plane in planes {
50            let normal = Vec3::new(plane.x, plane.y, plane.z);
51            let p = Vec3::new(
52                if normal.x >= 0.0 { self.max.x } else { self.min.x },
53                if normal.y >= 0.0 { self.max.y } else { self.min.y },
54                if normal.z >= 0.0 { self.max.z } else { self.min.z },
55            );
56            if normal.dot(p) + plane.w < 0.0 {
57                return false;
58            }
59        }
60        true
61    }
62}
63
64impl Vertex {
65    pub fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
66        Self { position: position.into(), normal: normal.into(), uv: uv.into(), tangent: [1.0, 0.0, 0.0, 1.0] }
67    }
68}
69
70#[derive(Debug, Clone)]
71pub struct CpuMesh {
72    pub vertices: Vec<Vertex>,
73    pub indices: Vec<u32>,
74    pub name: String,
75}
76
77impl CpuMesh {
78    pub fn new(name: impl Into<String>, vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
79        Self { vertices, indices, name: name.into() }
80    }
81
82    pub fn vertex_count(&self) -> u32 {
83        self.vertices.len() as u32
84    }
85    pub fn index_count(&self) -> u32 {
86        self.indices.len() as u32
87    }
88
89    pub fn triangle() -> Self {
90        let vertices = vec![
91            Vertex::new(Vec3::new(0.0, 0.5, 0.0), Vec3::Z, Vec2::new(0.5, 0.0)),
92            Vertex::new(Vec3::new(-0.5, -0.5, 0.0), Vec3::Z, Vec2::new(0.0, 1.0)),
93            Vertex::new(Vec3::new(0.5, -0.5, 0.0), Vec3::Z, Vec2::new(1.0, 1.0)),
94        ];
95        Self::new("triangle", vertices, vec![0, 1, 2])
96    }
97
98    pub fn cube() -> Self {
99        let faces: &[(Vec3, Vec3, Vec3)] = &[
100            (Vec3::Z, Vec3::X, Vec3::Y),         // front
101            (Vec3::NEG_Z, Vec3::NEG_X, Vec3::Y), // back
102            (Vec3::X, Vec3::NEG_Z, Vec3::Y),     // right
103            (Vec3::NEG_X, Vec3::Z, Vec3::Y),     // left
104            (Vec3::Y, Vec3::X, Vec3::NEG_Z),     // top
105            (Vec3::NEG_Y, Vec3::X, Vec3::Z),     // bottom
106        ];
107
108        let uvs = [
109            Vec2::new(0.0, 1.0),
110            Vec2::new(1.0, 1.0),
111            Vec2::new(1.0, 0.0),
112            Vec2::new(0.0, 0.0),
113        ];
114
115        let mut vertices = Vec::new();
116        let mut indices = Vec::new();
117
118        for (normal, right, up) in faces {
119            let base = vertices.len() as u32;
120            let center = *normal * 0.5;
121            let corners = [
122                center - *right * 0.5 - *up * 0.5,
123                center + *right * 0.5 - *up * 0.5,
124                center + *right * 0.5 + *up * 0.5,
125                center - *right * 0.5 + *up * 0.5,
126            ];
127            for (pos, uv) in corners.iter().zip(uvs.iter()) {
128                vertices.push(Vertex::new(*pos, *normal, *uv));
129            }
130            indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
131        }
132
133        Self::new("cube", vertices, indices)
134    }
135
136    pub fn plane(size: f32, subdivisions: u32) -> Self {
137        let n = subdivisions + 1;
138        let mut vertices = Vec::new();
139        let mut indices = Vec::new();
140
141        for z in 0..n {
142            for x in 0..n {
143                let fx = x as f32 / subdivisions as f32;
144                let fz = z as f32 / subdivisions as f32;
145                vertices.push(Vertex::new(
146                    Vec3::new((fx - 0.5) * size, 0.0, (fz - 0.5) * size),
147                    Vec3::Y,
148                    Vec2::new(fx, fz),
149                ));
150            }
151        }
152
153        for z in 0..subdivisions {
154            for x in 0..subdivisions {
155                let i = z * n + x;
156                indices.extend_from_slice(&[i, i + n, i + 1, i + 1, i + n, i + n + 1]);
157            }
158        }
159
160        Self::new(format!("plane_{size}"), vertices, indices)
161    }
162}