1use celestial_algo::quadtree::Chunk;
15
16use crate::descriptors::TerrainGpu;
17
18#[repr(C)]
25#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
26pub struct ScatterParamsGpu {
27 pub chunk_count: u32,
29 pub capacity: u32,
31 pub k_per_cell: u32,
33 pub lod_level: u32,
36 pub radius: f32,
38 pub density: f32,
40 pub max_instances: u32,
42 pub seed: u32,
44 pub vis_count: u32,
46 pub min_height: f32,
49 pub max_height: f32,
52 pub base_scale: f32,
54 pub surface_enabled: f32,
57 pub surface_height_scale: f32,
60 pub tile_res: u32,
62 pub _pad: u32,
63 pub terrain: TerrainGpu,
65 pub _pad2: f32,
67 pub _pad3: f32,
68}
69
70#[allow(clippy::too_many_arguments)]
72pub fn pack_scatter_params(
73 chunk_count: u32,
74 capacity: u32,
75 k_per_cell: u32,
76 lod_level: u32,
77 radius: f32,
78 density: f32,
79 max_instances: u32,
80 seed: u32,
81 vis_count: u32,
82 min_height: f32,
83 max_height: f32,
84 base_scale: f32,
85 surface_enabled: f32,
86 surface_height_scale: f32,
87 tile_res: u32,
88 terrain: &TerrainGpu,
89) -> Vec<u8> {
90 let p = ScatterParamsGpu {
91 chunk_count,
92 capacity,
93 k_per_cell,
94 lod_level,
95 radius,
96 density,
97 max_instances,
98 seed,
99 vis_count,
100 min_height,
101 max_height,
102 base_scale,
103 surface_enabled,
104 surface_height_scale,
105 tile_res,
106 _pad: 0,
107 terrain: *terrain,
108 _pad2: 0.0,
109 _pad3: 0.0,
110 };
111 bytemuck::bytes_of(&p).to_vec()
112}
113
114#[repr(C)]
118#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
119pub struct ScatterAuxGpu {
120 pub path_lo: u32,
121 pub path_hi: u32,
122 pub face: u32,
123 pub _pad: u32,
124}
125
126pub fn pack_scatter_aux(realize: &[(u32, Chunk)]) -> Vec<u8> {
128 let entries: Vec<ScatterAuxGpu> = realize
129 .iter()
130 .map(|(_, c)| ScatterAuxGpu {
131 path_lo: c.id.path as u32,
132 path_hi: (c.id.path >> 32) as u32,
133 face: c.id.face as u32,
134 _pad: 0,
135 })
136 .collect();
137 bytemuck::cast_slice(&entries).to_vec()
138}
139
140pub fn pack_scatter_vis(slots: &[u32]) -> Vec<u8> {
143 let mut buf = Vec::with_capacity(slots.len() * 4);
144 for s in slots {
145 buf.extend_from_slice(&s.to_le_bytes());
146 }
147 buf
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use celestial_algo::quadtree::{Bary, Chunk, ChunkId};
154 use godot::builtin::Vector3;
155
156 #[test]
157 fn scatter_params_layout() {
158 use std::mem::offset_of;
159 assert_eq!(std::mem::size_of::<ScatterParamsGpu>(), 128);
160 assert_eq!(std::mem::size_of::<ScatterParamsGpu>() % 16, 0);
161 assert_eq!(offset_of!(ScatterParamsGpu, chunk_count), 0);
162 assert_eq!(offset_of!(ScatterParamsGpu, capacity), 4);
163 assert_eq!(offset_of!(ScatterParamsGpu, k_per_cell), 8);
164 assert_eq!(offset_of!(ScatterParamsGpu, lod_level), 12);
165 assert_eq!(offset_of!(ScatterParamsGpu, radius), 16);
166 assert_eq!(offset_of!(ScatterParamsGpu, density), 20);
167 assert_eq!(offset_of!(ScatterParamsGpu, max_instances), 24);
168 assert_eq!(offset_of!(ScatterParamsGpu, seed), 28);
169 assert_eq!(offset_of!(ScatterParamsGpu, vis_count), 32);
170 assert_eq!(offset_of!(ScatterParamsGpu, min_height), 36);
171 assert_eq!(offset_of!(ScatterParamsGpu, max_height), 40);
172 assert_eq!(offset_of!(ScatterParamsGpu, base_scale), 44);
173 assert_eq!(offset_of!(ScatterParamsGpu, surface_enabled), 48);
174 assert_eq!(offset_of!(ScatterParamsGpu, surface_height_scale), 52);
175 assert_eq!(offset_of!(ScatterParamsGpu, tile_res), 56);
176 assert_eq!(offset_of!(ScatterParamsGpu, terrain), 64);
177 }
178
179 #[test]
180 fn pack_params_roundtrips() {
181 let t = crate::descriptors::assemble(
182 &crate::descriptors::HeightGpu::default(),
183 &crate::descriptors::TextureGpu::default(),
184 );
185 let bytes = pack_scatter_params(
186 3, 256, 4, 9, 1000.0, 0.5, 100_000, 7, 42, 0.45, 0.9, 2.0, 1.0, 0.18, 64, &t,
187 );
188 assert_eq!(bytes.len(), 128);
189 let p: &ScatterParamsGpu = bytemuck::from_bytes(&bytes);
190 assert_eq!(p.chunk_count, 3);
191 assert_eq!(p.capacity, 256);
192 assert_eq!(p.k_per_cell, 4);
193 assert_eq!(p.lod_level, 9);
194 assert_eq!(p.radius, 1000.0);
195 assert_eq!(p.density, 0.5);
196 assert_eq!(p.max_instances, 100_000);
197 assert_eq!(p.seed, 7);
198 assert_eq!(p.vis_count, 42);
199 assert_eq!(p.min_height, 0.45);
200 assert_eq!(p.max_height, 0.9);
201 assert_eq!(p.base_scale, 2.0);
202 assert_eq!(p.surface_enabled, 1.0);
203 assert_eq!(p.surface_height_scale, 0.18);
204 assert_eq!(p.tile_res, 64);
205 assert_eq!(p.terrain, t);
206 }
207
208 #[test]
209 fn pack_aux_layout() {
210 let path: u64 = 0xdead_beef_0000_0003;
211 let chunk = Chunk {
212 id: ChunkId { face: 5, depth: 2, path },
213 bary: [
214 Bary { wb: 0.0, wc: 0.0 },
215 Bary { wb: 1.0, wc: 0.0 },
216 Bary { wb: 0.0, wc: 1.0 },
217 ],
218 corners: [Vector3::ZERO; 3],
219 level: 2,
220 };
221 let bytes = pack_scatter_aux(&[(9, chunk), (1, chunk)]);
222 assert_eq!(bytes.len(), 32); let entries: &[ScatterAuxGpu] = bytemuck::cast_slice(&bytes);
224 assert_eq!(entries[0].path_lo, 0x0000_0003);
225 assert_eq!(entries[0].path_hi, 0xdead_beef);
226 assert_eq!(entries[0].face, 5);
227 assert_eq!(entries[1], entries[0]);
228 }
229
230 #[test]
231 fn pack_vis_slots() {
232 let bytes = pack_scatter_vis(&[7, 3]);
233 assert_eq!(bytes.len(), 8);
234 let words: &[u32] = bytemuck::cast_slice(&bytes);
235 assert_eq!(words, &[7, 3]);
236 }
237}