concinnity_core/components/
procedural_mesh.rs1use crate::ecs::PayloadLocator;
6use crate::ecs::asset_id::AssetId;
7use alloc::string::String;
8use alloc::vec::Vec;
9
10#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
27#[serde(default)]
28pub struct ProceduralMesh {
29 #[serde(skip)]
31 pub asset_id: AssetId,
32 pub generator: String,
35
36 pub half_width: f32,
39 pub half_depth: f32,
41 pub ceiling_height: f32,
43
44 pub half_extents: Option<[f32; 3]>,
47
48 pub radius: Option<f32>,
51 pub height: Option<f32>,
53 pub segments: Option<u32>,
55
56 pub rings: Option<u32>,
59
60 pub subdivisions: Option<u32>,
64 pub amplitude: Option<f32>,
66
67 pub source: Option<String>,
70 pub elevation_min: Option<f32>,
72 pub elevation_max: Option<f32>,
74
75 pub size: Option<f32>,
79
80 pub profile: Option<Vec<[f32; 2]>>,
83 pub corner_radius: Option<f32>,
85 pub corner_segments: Option<u32>,
87
88 pub lod_levels: u32,
91 pub lod_distances: Vec<f32>,
94
95 #[serde(skip)]
97 pub locator: Option<PayloadLocator>,
98}
99
100impl Default for ProceduralMesh {
101 fn default() -> Self {
102 Self {
103 asset_id: AssetId::default(),
104 generator: String::new(),
105 half_width: 8.0,
106 half_depth: 10.0,
107 ceiling_height: 3.5,
108 half_extents: None,
109 radius: None,
110 height: None,
111 segments: None,
112 rings: None,
113 subdivisions: None,
114 amplitude: None,
115 source: None,
116 elevation_min: None,
117 elevation_max: None,
118 size: None,
119 profile: None,
120 corner_radius: None,
121 corner_segments: None,
122 lod_levels: 1,
123 lod_distances: Vec::new(),
124 locator: None,
125 }
126 }
127}
128
129pub fn heightfield_blob_indices(
134 ctx: &crate::ecs::PipelineContext,
135) -> alloc::collections::BTreeSet<u32> {
136 ctx.query::<ProceduralMesh>()
137 .filter(|m| m.generator == "heightfield")
138 .filter_map(|m| m.locator.as_ref().map(|l| l.blob_index))
139 .collect()
140}
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn every_generator_specific_field_starts_unset() {
147 let m = ProceduralMesh::default();
150 assert!(m.generator.is_empty());
151 assert_eq!(m.half_extents, None);
152 assert_eq!(m.radius, None);
153 assert_eq!(m.height, None);
154 assert_eq!(m.segments, None);
155 assert_eq!(m.rings, None);
156 assert_eq!(m.subdivisions, None);
157 assert_eq!(m.amplitude, None);
158 assert_eq!(m.source, None);
159 assert_eq!(m.elevation_min, None);
160 assert_eq!(m.elevation_max, None);
161 assert_eq!(m.size, None);
162 assert_eq!(m.profile, None);
163 assert_eq!(m.corner_radius, None);
164 assert_eq!(m.corner_segments, None);
165 assert_eq!(m.half_width, 8.0);
167 assert_eq!(m.half_depth, 10.0);
168 assert_eq!(m.ceiling_height, 3.5);
169 assert_eq!(m.lod_levels, 1);
170 assert!(m.lod_distances.is_empty());
171 assert!(m.locator.is_none());
172 }
173
174 #[test]
175 fn a_heightfield_reads_its_own_fields_and_leaves_the_rest_unset() {
176 let m: ProceduralMesh = serde_json::from_str(
177 r#"{"generator":"heightfield","source":"terrain.png","subdivisions":128,
178 "elevation_min":-4,"elevation_max":40,"lod_levels":3,"lod_distances":[20,80]}"#,
179 )
180 .unwrap();
181 assert_eq!(m.generator, "heightfield");
182 assert_eq!(m.source.as_deref(), Some("terrain.png"));
183 assert_eq!(m.subdivisions, Some(128));
184 assert_eq!(m.elevation_min, Some(-4.0));
185 assert_eq!(m.elevation_max, Some(40.0));
186 assert_eq!(m.radius, None);
187 assert_eq!(m.rings, None);
188 }
189
190 #[test]
191 fn an_extruded_profile_round_trips_through_postcard() {
192 let m: ProceduralMesh = serde_json::from_str(
193 r#"{"generator":"extrude","profile":[[0,0],[1,0],[1,1]],"height":2.5,
194 "corner_radius":0.1,"corner_segments":4,"half_extents":[1,2,3],
195 "radius":0.5,"segments":32,"rings":16,"amplitude":3,"size":100}"#,
196 )
197 .unwrap();
198 let bytes = postcard::to_allocvec(&m).unwrap();
199 let back: ProceduralMesh = postcard::from_bytes(&bytes).unwrap();
200 assert_eq!(back, m);
201 assert_eq!(
202 back.profile.as_deref(),
203 Some(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]][..])
204 );
205 assert_eq!(back.corner_segments, Some(4));
206 assert_eq!(back.half_extents, Some([1.0, 2.0, 3.0]));
207 assert_eq!(back.size, Some(100.0));
208 assert_eq!(back.asset_id, AssetId::default());
209 }
210}