Skip to main content

trx_rs/
vertex.rs

1use bytemuck::{Pod, Zeroable};
2
3/// A 3D position stored as `[T; 3]`, `#[repr(C)]` for zero-copy GPU upload.
4///
5/// For `T = f32`, this is 12 bytes with no padding and maps directly to
6/// `wgpu::VertexFormat::Float32x3`.
7#[derive(Debug, Clone, Copy, PartialEq)]
8#[repr(C)]
9pub struct Position3<T: Copy> {
10    pub coords: [T; 3],
11}
12
13// SAFETY: [f32; 3] is Pod+Zeroable, and Position3 is #[repr(C)] with no padding.
14unsafe impl Zeroable for Position3<f32> {}
15unsafe impl Pod for Position3<f32> {}
16
17unsafe impl Zeroable for Position3<f64> {}
18unsafe impl Pod for Position3<f64> {}
19
20unsafe impl Zeroable for Position3<half::f16> {}
21unsafe impl Pod for Position3<half::f16> {}
22
23impl<T: Copy> Position3<T> {
24    pub fn new(x: T, y: T, z: T) -> Self {
25        Self { coords: [x, y, z] }
26    }
27}
28
29impl<T: Copy> From<[T; 3]> for Position3<T> {
30    fn from(coords: [T; 3]) -> Self {
31        Self { coords }
32    }
33}
34
35impl<T: Copy> From<Position3<T>> for [T; 3] {
36    fn from(p: Position3<T>) -> Self {
37        p.coords
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn position3_size_and_alignment() {
47        assert_eq!(std::mem::size_of::<Position3<f32>>(), 12);
48        assert_eq!(std::mem::align_of::<Position3<f32>>(), 4);
49
50        assert_eq!(std::mem::size_of::<Position3<f64>>(), 24);
51
52        assert_eq!(std::mem::size_of::<Position3<half::f16>>(), 6);
53    }
54
55    #[test]
56    fn position3_bytemuck_cast() {
57        let positions = vec![
58            Position3::new(1.0f32, 2.0, 3.0),
59            Position3::new(4.0f32, 5.0, 6.0),
60        ];
61        let bytes: &[u8] = bytemuck::cast_slice(&positions);
62        assert_eq!(bytes.len(), 24);
63
64        let back: &[Position3<f32>] = bytemuck::cast_slice(bytes);
65        assert_eq!(back[0].coords, [1.0, 2.0, 3.0]);
66        assert_eq!(back[1].coords, [4.0, 5.0, 6.0]);
67    }
68}