Skip to main content

flow_ngin/data_structures/
instance.rs

1//! Instance transformation data for GPU rendering.
2//!
3//! Per-instance data like position, rotation, and scale is stored as
4//! GPU buffers and passed to shaders for efficient multi-draw instancing.
5
6use std::ops::{Add, Mul};
7
8use cgmath::{One, SquareMatrix};
9
10use crate::data_structures::model;
11
12/// Per-instance transformation: position, rotation (as quaternion), and scale.
13///
14/// Used for GPU instancing: multiple copies of the same model can be rendered
15/// with different transforms in a single draw call. The instance data is packed
16/// into a GPU buffer and accessible to vertex shaders.
17#[derive(Clone, Debug)]
18pub struct Instance {
19    pub position: cgmath::Vector3<f32>,
20    pub rotation: cgmath::Quaternion<f32>,
21    pub scale: cgmath::Vector3<f32>,
22}
23
24impl Instance {
25    /// Create a new instance with identity transformation (no move, rotate, or scale).
26    pub fn new() -> Self {
27        Self {
28            position: cgmath::Vector3::new(0.0, 0.0, 0.0),
29            // `Quaternion::one()` is the identity quaternion (no rotation)
30            rotation: cgmath::Quaternion::one(),
31            scale: cgmath::Vector3::new(1.0, 1.0, 1.0),
32        }
33    }
34
35    pub fn to_matrix(&self) -> cgmath::Matrix4<f32> {
36        cgmath::Matrix4::from_translation(self.position)
37            * cgmath::Matrix4::from(self.rotation)
38            * cgmath::Matrix4::from_nonuniform_scale(self.scale.x, self.scale.y, self.scale.z)
39    }
40
41    pub fn to_raw(&self) -> InstanceRaw {
42        let world_matrix = self.to_matrix();
43        let det = world_matrix.determinant();
44        let handedness = det.signum();
45        InstanceRaw {
46            model: self.to_matrix().into(),
47            normal: cgmath::Matrix3::from(self.rotation).into(),
48            handedness: handedness,
49        }
50    }
51}
52
53impl Mul<Instance> for Instance {
54    type Output = Self;
55
56    fn mul(self, rhs: Instance) -> Self::Output {
57        let new_rotation = self.rotation * rhs.rotation;
58
59        let new_scale = cgmath::Vector3::new(
60            self.scale.x * rhs.scale.x,
61            self.scale.y * rhs.scale.y,
62            self.scale.z * rhs.scale.z,
63        );
64        let scaled_rhs_pos = cgmath::Vector3::new(
65            self.scale.x * rhs.position.x,
66            self.scale.y * rhs.position.y,
67            self.scale.z * rhs.position.z,
68        );
69        let new_position = self.position + (self.rotation * scaled_rhs_pos);
70
71        Instance {
72            position: new_position,
73            rotation: new_rotation,
74            scale: new_scale,
75        }
76    }
77}
78
79impl Add<Instance> for Instance {
80    type Output = Self;
81
82    fn add(self, rhs: Instance) -> Self::Output {
83        Instance {
84            position: self.position + rhs.position,
85            rotation: self.rotation + rhs.rotation,
86            scale: self.scale + rhs.scale,
87        }
88    }
89}
90
91impl<'a, 'b> Mul<&'b Instance> for &'a Instance {
92    type Output = Instance;
93
94    fn mul(self, rhs: &'b Instance) -> Self::Output {
95        let new_rotation = self.rotation * rhs.rotation;
96
97        let new_scale = cgmath::Vector3::new(
98            self.scale.x * rhs.scale.x,
99            self.scale.y * rhs.scale.y,
100            self.scale.z * rhs.scale.z,
101        );
102        let scaled_rhs_pos = cgmath::Vector3::new(
103            self.scale.x * rhs.position.x,
104            self.scale.y * rhs.position.y,
105            self.scale.z * rhs.position.z,
106        );
107        let new_position = self.position + (self.rotation * scaled_rhs_pos);
108
109        Instance {
110            position: new_position,
111            rotation: new_rotation,
112            scale: new_scale,
113        }
114    }
115}
116
117impl<'a, 'b> Add<&'b Instance> for &'a Instance {
118    type Output = Instance;
119
120    fn add(self, rhs: &'b Instance) -> Self::Output {
121        Instance {
122            position: self.position + rhs.position,
123            rotation: self.rotation + rhs.rotation,
124            scale: self.scale + rhs.scale,
125        }
126    }
127}
128
129impl From<cgmath::Vector3<f32>> for Instance {
130    fn from(position: cgmath::Vector3<f32>) -> Self {
131        Instance {
132            position,
133            ..Default::default()
134        }
135    }
136}
137
138impl Default for Instance {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/**
145 * The raw instance is the actual data stored on the GPU
146 */
147#[repr(C)]
148#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
149#[allow(dead_code)]
150pub struct InstanceRaw {
151    model: [[f32; 4]; 4],
152    normal: [[f32; 3]; 3],
153    handedness: f32,
154}
155
156/**
157 * As we store vertex data directly in the GPU memory we need to tell what the bytes refer to:
158 *
159 * offset: zero as we want to use the full space.
160 * stride: length of a vertex
161 *
162 * Stride layout here: position + rotation + scale as 4x4 matrix (hence the four 4d vectors)
163 */
164impl model::Vertex for InstanceRaw {
165    fn desc() -> wgpu::VertexBufferLayout<'static> {
166        use std::mem;
167        wgpu::VertexBufferLayout {
168            array_stride: mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
169            // We need to switch from using a step mode of Vertex to Instance
170            // This means that our shaders will only change to use the next
171            // instance when the shader starts processing a new instance
172            step_mode: wgpu::VertexStepMode::Instance,
173            attributes: &[
174                wgpu::VertexAttribute {
175                    offset: 0,
176                    shader_location: 5,
177                    format: wgpu::VertexFormat::Float32x4,
178                },
179                // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot
180                // for each vec4. We don't have to do this in code, though.
181                wgpu::VertexAttribute {
182                    offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
183                    // corresponds to the @location in the shader file.
184                    shader_location: 6,
185                    format: wgpu::VertexFormat::Float32x4,
186                },
187                wgpu::VertexAttribute {
188                    offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
189                    shader_location: 7,
190                    format: wgpu::VertexFormat::Float32x4,
191                },
192                wgpu::VertexAttribute {
193                    offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
194                    shader_location: 8,
195                    format: wgpu::VertexFormat::Float32x4,
196                },
197                // Tangent data will be stored as 3x3 matrix
198                wgpu::VertexAttribute {
199                    offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
200                    shader_location: 9,
201                    format: wgpu::VertexFormat::Float32x3,
202                },
203                wgpu::VertexAttribute {
204                    offset: mem::size_of::<[f32; 19]>() as wgpu::BufferAddress,
205                    shader_location: 10,
206                    format: wgpu::VertexFormat::Float32x3,
207                },
208                wgpu::VertexAttribute {
209                    offset: mem::size_of::<[f32; 22]>() as wgpu::BufferAddress,
210                    shader_location: 11,
211                    format: wgpu::VertexFormat::Float32x3,
212                },
213                wgpu::VertexAttribute {
214                    offset: mem::size_of::<[f32; 25]>() as wgpu::BufferAddress,
215                    shader_location: 12,
216                    format: wgpu::VertexFormat::Float32,
217                },
218            ],
219        }
220    }
221}