1use crate::{AssetId, PayloadLocator};
4use alloc::string::String;
5use alloc::vec::Vec;
6
7#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
24#[serde(default)]
25pub struct ProceduralMesh {
26 #[serde(skip)]
28 pub asset_id: AssetId,
29 pub generator: String,
32
33 pub half_width: f32,
36 pub half_depth: f32,
38 pub ceiling_height: f32,
40
41 pub half_extents: Option<[f32; 3]>,
44
45 pub radius: Option<f32>,
48 pub height: Option<f32>,
50 pub segments: Option<u32>,
52
53 pub rings: Option<u32>,
56
57 pub subdivisions: Option<u32>,
61 pub amplitude: Option<f32>,
63
64 pub source: Option<String>,
67 pub elevation_min: Option<f32>,
69 pub elevation_max: Option<f32>,
71
72 pub size: Option<f32>,
76
77 pub profile: Option<Vec<[f32; 2]>>,
80 pub corner_radius: Option<f32>,
82 pub corner_segments: Option<u32>,
84
85 pub lod_levels: u32,
88 pub lod_distances: Vec<f32>,
91
92 #[serde(skip)]
94 pub locator: Option<PayloadLocator>,
95}
96
97impl Default for ProceduralMesh {
98 fn default() -> Self {
99 Self {
100 asset_id: AssetId::default(),
101 generator: String::new(),
102 half_width: 8.0,
103 half_depth: 10.0,
104 ceiling_height: 3.5,
105 half_extents: None,
106 radius: None,
107 height: None,
108 segments: None,
109 rings: None,
110 subdivisions: None,
111 amplitude: None,
112 source: None,
113 elevation_min: None,
114 elevation_max: None,
115 size: None,
116 profile: None,
117 corner_radius: None,
118 corner_segments: None,
119 lod_levels: 1,
120 lod_distances: Vec::new(),
121 locator: None,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn every_generator_specific_field_starts_unset() {
132 let m = ProceduralMesh::default();
135 assert!(m.generator.is_empty());
136 assert_eq!(m.half_extents, None);
137 assert_eq!(m.radius, None);
138 assert_eq!(m.height, None);
139 assert_eq!(m.segments, None);
140 assert_eq!(m.rings, None);
141 assert_eq!(m.subdivisions, None);
142 assert_eq!(m.amplitude, None);
143 assert_eq!(m.source, None);
144 assert_eq!(m.elevation_min, None);
145 assert_eq!(m.elevation_max, None);
146 assert_eq!(m.size, None);
147 assert_eq!(m.profile, None);
148 assert_eq!(m.corner_radius, None);
149 assert_eq!(m.corner_segments, None);
150 assert_eq!(m.half_width, 8.0);
152 assert_eq!(m.half_depth, 10.0);
153 assert_eq!(m.ceiling_height, 3.5);
154 assert_eq!(m.lod_levels, 1);
155 assert!(m.lod_distances.is_empty());
156 assert!(m.locator.is_none());
157 }
158
159 #[test]
160 fn a_heightfield_reads_its_own_fields_and_leaves_the_rest_unset() {
161 let m: ProceduralMesh = serde_json::from_str(
162 r#"{"generator":"heightfield","source":"terrain.png","subdivisions":128,
163 "elevation_min":-4,"elevation_max":40,"lod_levels":3,"lod_distances":[20,80]}"#,
164 )
165 .unwrap();
166 assert_eq!(m.generator, "heightfield");
167 assert_eq!(m.source.as_deref(), Some("terrain.png"));
168 assert_eq!(m.subdivisions, Some(128));
169 assert_eq!(m.elevation_min, Some(-4.0));
170 assert_eq!(m.elevation_max, Some(40.0));
171 assert_eq!(m.radius, None);
172 assert_eq!(m.rings, None);
173 }
174
175 #[test]
176 fn an_extruded_profile_round_trips_through_postcard() {
177 let m: ProceduralMesh = serde_json::from_str(
178 r#"{"generator":"extrude","profile":[[0,0],[1,0],[1,1]],"height":2.5,
179 "corner_radius":0.1,"corner_segments":4,"half_extents":[1,2,3],
180 "radius":0.5,"segments":32,"rings":16,"amplitude":3,"size":100}"#,
181 )
182 .unwrap();
183 let bytes = postcard::to_allocvec(&m).unwrap();
184 let back: ProceduralMesh = postcard::from_bytes(&bytes).unwrap();
185 assert_eq!(back, m);
186 assert_eq!(
187 back.profile.as_deref(),
188 Some(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]][..])
189 );
190 assert_eq!(back.corner_segments, Some(4));
191 assert_eq!(back.half_extents, Some([1.0, 2.0, 3.0]));
192 assert_eq!(back.size, Some(100.0));
193 assert_eq!(back.asset_id, AssetId::default());
194 }
195}