Skip to main content

appcore_render/
scene_components.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: scene_components.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/10/14 16:59:22 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/10/15 10:24:40 by dnettoRaw
8//      ###########      S: 0.1.0-alpha.1
9// =============================================================================
10
11use crate::{RenderError, ResourceId};
12/// Validated local GPU-space transform, aligned for contiguous SIMD-friendly columns.
13#[repr(C, align(16))]
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Transform {
16    position: [f32; 3],
17    rotation: [f32; 4],
18    scale: [f32; 3],
19}
20impl Default for Transform {
21    fn default() -> Self {
22        Self {
23            position: [0.0; 3],
24            rotation: [0.0, 0.0, 0.0, 1.0],
25            scale: [1.0; 3],
26        }
27    }
28}
29impl Transform {
30    /// Construct with a unit quaternion and positive finite scale, without implicit normalization.
31    pub fn new(
32        position: [f32; 3],
33        rotation: [f32; 4],
34        scale: [f32; 3],
35    ) -> Result<Self, RenderError> {
36        let norm = rotation.iter().map(|v| v * v).sum::<f32>();
37        if !position
38            .iter()
39            .all(|v| v.is_finite() && v.abs() <= 1_000_000.0)
40            || !rotation.iter().all(|v| v.is_finite())
41            || (norm - 1.0).abs() > 0.0001
42            || !scale
43                .iter()
44                .all(|v| v.is_finite() && *v >= 0.000001 && *v <= 10_000.0)
45        {
46            return Err(RenderError::InvalidLimits);
47        }
48        Ok(Self {
49            position,
50            rotation,
51            scale,
52        })
53    }
54    /// Local translation; global f64 coordinates belong to world/chunk positioning.
55    pub fn position(self) -> [f32; 3] {
56        self.position
57    }
58    /// Unit quaternion in x,y,z,w order.
59    pub fn rotation(self) -> [f32; 4] {
60        self.rotation
61    }
62    /// Positive nonuniform scale.
63    pub fn scale(self) -> [f32; 3] {
64        self.scale
65    }
66    /// Validated replacement; rejection leaves the existing value unchanged.
67    pub fn with_position(self, position: [f32; 3]) -> Result<Self, RenderError> {
68        Self::new(position, self.rotation, self.scale)
69    }
70}
71/// Validated bounded velocity component; no fluid/rigid-body solver is implied.
72#[repr(C, align(16))]
73#[derive(Debug, Clone, Copy, PartialEq)]
74pub struct Velocity([f32; 3]);
75impl Velocity {
76    /// Reject non-finite or excessively large local velocities before storing them.
77    pub fn new(value: [f32; 3]) -> Result<Self, RenderError> {
78        if !value.iter().all(|v| v.is_finite() && v.abs() <= 100_000.0) {
79            return Err(RenderError::InvalidLimits);
80        }
81        Ok(Self(value))
82    }
83    /// Local units per second.
84    pub fn value(self) -> [f32; 3] {
85        self.0
86    }
87}
88/// Application-selected generic asset identities and small instance appearance data.
89/// Asset lifetime/type validation belongs to the renderer's asset registry before upload.
90#[derive(Debug, Clone, Copy, PartialEq)]
91pub struct RenderBinding {
92    /// Mesh identity; never a pointer into a mutable resource arena.
93    pub mesh: ResourceId,
94    /// Material identity, resolved separately from mesh identity.
95    pub material: ResourceId,
96    /// Finite linear RGBA multipliers in 0..1.
97    pub tint: [f32; 4],
98    /// Animation selection; the asset owner validates supported clips.
99    pub animation: u32,
100    /// Morph selection; the asset owner validates supported targets.
101    pub morph: u32,
102}
103impl RenderBinding {
104    /// Validate numeric instance data without resolving or assuming asset availability.
105    pub fn validate(self) -> Result<Self, RenderError> {
106        if !self
107            .tint
108            .iter()
109            .all(|v| v.is_finite() && (0.0..=1.0).contains(v))
110        {
111            return Err(RenderError::InvalidLimits);
112        }
113        Ok(self)
114    }
115}