concinnity_core/components/
validate.rs1use alloc::string::ToString;
9
10use crate::components::{
11 Decal, DirectionalLight, GlassPanel, GlassPanelGeometry, InstancedProp, MAX_WATER_WAVES,
12 Material, ParticleEmitter, PhysicsJoint, PhysicsJointKind, PointLight, Prop, RectAreaLight,
13 ReflectionProbe, RigidBody, SPOT_MAX_ANGLE_DEG, SdfVolume, SkyRotation, SpotLight,
14 SpotLightGeometry, VolumetricFog, VoxelChunk, WaterSurface, WaterWave,
15};
16use crate::math::sqrt;
17
18pub fn sdf_volume(mut v: SdfVolume) -> SdfVolume {
23 use crate::components::sdf_volume::{SDF_MAX_STEPS_CEILING, SDF_MAX_STEPS_FLOOR};
24 for axis in v.extent.iter_mut() {
27 if !axis.is_finite() || *axis <= 0.0 {
28 *axis = 1.0;
29 }
30 }
31 if !v.max_gradient.is_finite() || v.max_gradient <= 0.0 {
32 v.max_gradient = 1.0;
33 }
34 v.max_steps = v
35 .max_steps
36 .clamp(SDF_MAX_STEPS_FLOOR, SDF_MAX_STEPS_CEILING);
37 if !v.max_distance.is_finite() || v.max_distance < 0.1 {
38 v.max_distance = 0.1;
39 }
40 if v.volumetric {
41 v.cast_shadows = false;
42 }
43 v
44}
45
46pub fn point_light(mut args: PointLight) -> PointLight {
48 args.intensity = args.intensity.max(0.0);
49 args.range = args.range.max(0.0);
50 args
51}
52
53pub fn spot_light(mut args: SpotLight) -> SpotLight {
55 args.intensity = args.intensity.max(0.0);
56 args.range = args.range.max(0.0);
57 args.direction = args.unit_direction();
58 args.outer_angle = args.outer_angle.clamp(0.0, SPOT_MAX_ANGLE_DEG);
59 args.inner_angle = args.inner_angle.clamp(0.0, args.outer_angle);
60 args
61}
62
63pub fn rect_area_light(mut args: RectAreaLight) -> RectAreaLight {
65 args.intensity = args.intensity.max(0.0);
66 args.range = args.range.max(0.0);
67 let n = args.normal;
70 let len = sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
71 args.normal = if len < 1e-6 {
72 [0.0, 0.0, 1.0]
73 } else {
74 [n[0] / len, n[1] / len, n[2] / len]
75 };
76 args.half_size[0] = args.half_size[0].max(1e-3);
77 args.half_size[1] = args.half_size[1].max(1e-3);
78 args
79}
80
81pub fn directional_light(mut args: DirectionalLight) -> DirectionalLight {
83 args.intensity = args.intensity.max(0.0);
84 args
85}
86
87pub fn sky_rotation(mut args: SkyRotation) -> SkyRotation {
91 let [x, y, z] = args.axis;
92 if sqrt(x * x + y * y + z * z) < 1e-6 {
93 args.axis = SkyRotation::default().axis;
94 }
95 args
96}
97
98pub fn material(mut args: Material) -> Material {
102 args.roughness = args.roughness.clamp(0.0, 1.0);
103 args.metallic = args.metallic.clamp(0.0, 1.0);
104 args.alpha_cutoff = args.alpha_cutoff.clamp(0.0, 1.0);
105 args.opacity = args.opacity.clamp(0.0, 1.0);
106 if args.see_through {
109 args.transparent = true;
110 }
111 args
112}
113
114pub fn glass_panel(mut args: GlassPanel) -> GlassPanel {
116 args.normal = args.unit_normal();
117 args.half_size[0] = args.half_size[0].max(1e-3);
118 args.half_size[1] = args.half_size[1].max(1e-3);
119 args.opacity = args.opacity.clamp(0.0, 1.0);
120 args.refraction_strength = args.refraction_strength.max(0.0);
121 args.fresnel_power = args.fresnel_power.max(0.0);
122 args
123}
124
125pub fn water_surface(mut args: WaterSurface) -> WaterSurface {
127 args.subdivisions = args.subdivisions.clamp(8, 255);
128 if args.waves.len() > MAX_WATER_WAVES {
129 args.waves.truncate(MAX_WATER_WAVES);
130 }
131 if args.waves.is_empty() {
132 args.waves.push(WaterWave::default());
133 }
134 args
135}
136
137pub fn joint(mut args: PhysicsJoint) -> PhysicsJoint {
139 if let Some(k) = PhysicsJointKind::from_str_norm(&args.kind) {
141 args.kind = k.as_str().to_string();
142 }
143 args
144}
145
146pub fn decal(mut args: Decal) -> Decal {
148 args.tint[3] = args.tint[3].clamp(0.0, 1.0);
152 args
153}
154
155pub fn reflection_probe(mut args: ReflectionProbe) -> ReflectionProbe {
157 for e in &mut args.half_extents {
160 *e = e.max(0.0);
161 }
162 args
163}
164
165pub fn rigid_body(mut args: RigidBody) -> RigidBody {
167 args.is_grounded = true;
168 args
169}
170
171pub fn prop(mut args: Prop) -> Prop {
173 args.cull_distance = args.cull_distance.max(0.0);
174 args.is_held = false;
175 args
176}
177
178pub fn particle_emitter(mut args: ParticleEmitter) -> ParticleEmitter {
180 args.spread_deg = args.spread_deg.clamp(0.0, 180.0);
184 args.speed_min = args.speed_min.max(0.0);
185 if !args.speed_max.is_finite() || args.speed_max < args.speed_min {
186 args.speed_max = args.speed_min;
187 }
188 if !args.lifetime_min.is_finite() || args.lifetime_min <= 0.0 {
189 args.lifetime_min = 0.001;
190 }
191 if !args.lifetime_max.is_finite() || args.lifetime_max < args.lifetime_min {
192 args.lifetime_max = args.lifetime_min;
193 }
194 args.spawn_rate = args.spawn_rate.max(0.0);
195 args.max_particles = args.max_particles.clamp(1, 65_536);
196 args.size_start = args.size_start.max(0.0);
197 args.size_end = args.size_end.max(0.0);
198 for c in args.color_start.iter_mut().chain(args.color_end.iter_mut()) {
199 if !c.is_finite() {
200 *c = 0.0;
201 }
202 }
203 args
204}
205
206pub fn volumetric_fog(mut args: VolumetricFog) -> VolumetricFog {
208 args.density = args.density.max(0.0);
212 args.height_falloff = args.height_falloff.max(0.0);
213 args.ambient = args.ambient.max(0.0);
214 if args.max_distance <= 0.0 || !args.max_distance.is_finite() {
215 args.max_distance = 1.0;
216 }
217 args.phase_g = args.phase_g.clamp(-0.95, 0.95);
220 args
221}
222
223pub fn instanced_prop(mut args: InstancedProp) -> InstancedProp {
225 args.cull_distance = args.cull_distance.max(0.0);
226 args
227}
228
229pub fn voxel_chunk(mut args: VoxelChunk) -> VoxelChunk {
231 args.block_size = args.block_size.max(0.0);
232 if args.lod_levels == 0 {
233 args.lod_levels = 1;
234 }
235 args.lod_levels = args.lod_levels.min(8);
236 args
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn material_clamps_and_see_through_implies_transparent() {
245 let m = material(Material {
246 roughness: 2.0,
247 metallic: -1.0,
248 opacity: 1.5,
249 see_through: true,
250 ..Material::default()
251 });
252 assert_eq!(m.roughness, 1.0);
253 assert_eq!(m.metallic, 0.0);
254 assert_eq!(m.opacity, 1.0);
255 assert!(m.transparent);
256 }
257
258 #[test]
259 fn water_surface_clamps_subdivisions_and_guarantees_a_wave() {
260 let w = water_surface(WaterSurface {
261 subdivisions: 3,
262 waves: alloc::vec::Vec::new(),
263 ..WaterSurface::default()
264 });
265 assert_eq!(w.subdivisions, 8);
266 assert_eq!(w.waves.len(), 1);
267 }
268
269 #[test]
270 fn lights_floor_intensity_and_range_at_zero() {
271 let p = point_light(PointLight {
272 intensity: -2.0,
273 range: -1.0,
274 ..PointLight::default()
275 });
276 assert_eq!((p.intensity, p.range), (0.0, 0.0));
277 let d = directional_light(DirectionalLight {
278 intensity: -1.0,
279 ..DirectionalLight::default()
280 });
281 assert_eq!(d.intensity, 0.0);
282 }
283}