1use std::{
2 collections::HashMap,
3 f32::consts::{FRAC_PI_2, PI},
4};
5
6use bevy::{
7 asset::{AssetPath, RenderAssetUsages, embedded_asset, embedded_path},
8 camera::primitives::Aabb,
9 light::NotShadowCaster,
10 mesh::{Indices, PrimitiveTopology},
11 pbr::{ExtendedMaterial, MaterialExtension},
12 prelude::*,
13 render::render_resource::AsBindGroup,
14 shader::ShaderRef,
15};
16
17pub struct ClipmapPlugin;
18
19#[derive(Component)]
20struct Handles {
21 square: Handle<Mesh>,
22 filler: Handle<Mesh>,
23 center: Handle<Mesh>,
24 trim: Handle<Mesh>,
25 stitch: Handle<Mesh>,
26}
27
28impl Plugin for ClipmapPlugin {
29 fn build(&self, app: &mut App) {
30 embedded_asset!(app, "terrain.wgsl");
31
32 app.add_plugins(MaterialPlugin::<
33 ExtendedMaterial<StandardMaterial, GridMaterial>,
34 >::default())
35 .add_systems(PreUpdate, (init_clipmaps, init_grids))
36 .add_systems(Update, update_grids);
37 }
38}
39
40struct MeshBuilder {
41 unique_vertices: HashMap<(i32, i32), u32>,
42 vertices: Vec<[f32; 3]>,
43 indices: Vec<u32>,
44}
45
46impl MeshBuilder {
47 fn new() -> Self {
48 Self {
49 unique_vertices: HashMap::new(),
50 vertices: vec![],
51 indices: vec![],
52 }
53 }
54
55 fn add_vertex(&mut self, x: i32, y: i32) -> u32 {
56 if let Some(index) = self.unique_vertices.get(&(x, y)) {
57 *index
58 } else {
59 let index = self.vertices.len() as u32;
60 self.vertices.push([x as f32, 0.0, y as f32]);
61 self.unique_vertices.insert((x, y), index);
62 index
63 }
64 }
65
66 fn add_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
67 let p1 = self.add_vertex(x1, y1);
68 let p2 = self.add_vertex(x2, y2);
69 let p3 = self.add_vertex(x3, y3);
70 self.indices.extend([p1, p2, p3]);
71 }
72
73 fn add_square(&mut self, x: i32, y: i32) {
74 let p1 = self.add_vertex(x, y);
75 let p2 = self.add_vertex(x, y + 1);
76 let p3 = self.add_vertex(x + 1, y + 1);
77 let p4 = self.add_vertex(x + 1, y);
78 self.indices.extend([p1, p2, p3]);
79 self.indices.extend([p1, p3, p4]);
80 }
81
82 fn build(&self) -> Mesh {
83 Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::all())
84 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, self.vertices.clone())
85 .with_inserted_indices(Indices::U32(self.indices.clone()))
86 }
87}
88
89#[derive(Component)]
92pub struct Clipmap {
93 pub half_width: u32,
96
97 pub levels: u32,
100
101 pub base_scale: f32,
103
104 pub texel_size: f32,
106
107 pub target: Entity,
109
110 pub color: Handle<Image>,
112
113 pub heightmap: Handle<Image>,
115
116 pub horizon: Handle<Image>,
118
119 pub horizon_coeffs: u32,
121
122 pub min: f32,
124 pub max: f32,
125
126 pub wireframe: bool,
128}
129
130#[derive(Component)]
131struct ClipmapGrid {
132 level: u32,
133 trim: Entity,
134}
135
136impl ClipmapGrid {
137 fn scale(&self, base_scale: f32) -> f32 {
138 base_scale * 2u32.pow(self.level) as f32
139 }
140}
141
142fn init_clipmaps(
143 mut commands: Commands,
144 mut meshes: ResMut<Assets<Mesh>>,
145 clipmaps: Query<(Entity, &Clipmap), Added<Clipmap>>,
146) {
147 for (entity, clipmap) in clipmaps {
148 let builder_width = clipmap.half_width as i32 * 2;
149 let filler_width = 2 - clipmap.half_width as i32 % 2;
150 let square_width = (clipmap.half_width as i32 - filler_width) / 2;
151
152 let mut square = MeshBuilder::new();
153 let mut filler = MeshBuilder::new();
154 let mut center = MeshBuilder::new();
155 let mut trim = MeshBuilder::new();
156 let mut stitch = MeshBuilder::new();
157
158 for xy in 0..builder_width.pow(2) {
159 let x = xy % builder_width;
160 let y = xy / builder_width;
161 if x < square_width && y < square_width {
162 square.add_square(x, y);
163 }
164 let range = square_width * 2..square_width * 2 + filler_width;
165 if (range.contains(&x) || range.contains(&y))
166 && x < builder_width - filler_width
167 && y < builder_width - filler_width
168 {
169 center.add_square(x, y);
170 let range = square_width..builder_width - square_width - filler_width;
171 if !range.contains(&x) || !range.contains(&y) {
172 filler.add_square(x, y);
173 }
174 }
175 if x >= builder_width - filler_width || y >= builder_width - filler_width {
176 trim.add_square(x, y);
177 }
178 }
179
180 for x in 0..builder_width / 2 {
181 let x = x * 2;
182 stitch.add_triangle(x, 0, x + 1, 0, x + 2, 0);
183 stitch.add_triangle(x + 2, builder_width, x + 1, builder_width, x, builder_width);
184 stitch.add_triangle(0, x + 2, 0, x + 1, 0, x);
185 stitch.add_triangle(builder_width, x, builder_width, x + 1, builder_width, x + 2);
186 }
187
188 commands.entity(entity).insert((
189 Transform::default(),
190 Visibility::default(),
191 Handles {
192 square: meshes.add(square.build()),
193 filler: meshes.add(filler.build()),
194 center: meshes.add(center.build()),
195 trim: meshes.add(trim.build()),
196 stitch: meshes.add(stitch.build()),
197 },
198 ));
199
200 for level in 0..clipmap.levels {
201 commands.entity(entity).with_child(ClipmapGrid {
202 level,
203 trim: Entity::PLACEHOLDER,
204 });
205 }
206 }
207}
208
209fn init_grids(
210 mut commands: Commands,
211 mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, GridMaterial>>>,
212 clipmaps: Query<(&Clipmap, &Handles)>,
213 mut grids: Query<(Entity, &mut ClipmapGrid, &ChildOf), Added<ClipmapGrid>>,
214) {
215 for (entity, mut grid, clipmap) in &mut grids {
216 let (clipmap, handles) = clipmaps.get(clipmap.parent()).unwrap();
217
218 let filler_width = 2 - clipmap.half_width as i32 % 2;
219 let square_width = (clipmap.half_width as i32 - filler_width) / 2;
220
221 commands.entity(entity).insert((
222 Transform::from_scale(Vec3::splat(grid.scale(clipmap.base_scale))),
223 Visibility::default(),
224 ));
225
226 let terrain_material = materials.add(ExtendedMaterial {
227 base: StandardMaterial::default(),
228 extension: GridMaterial {
229 color: clipmap.color.clone(),
230 heightmap: clipmap.heightmap.clone(),
231 horizon: clipmap.horizon.clone(),
232 horizon_coeffs: clipmap.horizon_coeffs,
233 lod: grid.level,
234 texel_size: clipmap.texel_size,
235 minmax: Vec2 {
236 x: clipmap.min,
237 y: clipmap.max,
238 },
239 translation: Vec2::ZERO,
240 wireframe: 0,
241 },
242 });
243
244 let terrain_material_w = materials.add(ExtendedMaterial {
245 base: StandardMaterial::default(),
246 extension: GridMaterial {
247 color: clipmap.color.clone(),
248 heightmap: clipmap.heightmap.clone(),
249 horizon: clipmap.horizon.clone(),
250 horizon_coeffs: clipmap.horizon_coeffs,
251 lod: grid.level,
252 texel_size: clipmap.texel_size,
253 minmax: Vec2 {
254 x: clipmap.min,
255 y: clipmap.max,
256 },
257 translation: Vec2::ZERO,
258 wireframe: 1,
259 },
260 });
261
262 for xy in 0..4 * 4 {
263 let x = xy % 4;
264 let y = xy / 4;
265
266 if grid.level != 0 && (x == 1 || x == 2) && (y == 1 || y == 2) {
267 continue;
268 }
269
270 let offset_x = if x >= 2 { filler_width as f32 } else { 0.0 };
271 let offset_y = if y >= 2 { filler_width as f32 } else { 0.0 };
272
273 commands.entity(entity).with_children(|c| {
274 let mut e = c.spawn((
275 Mesh3d(handles.square.clone()),
276 MeshMaterial3d(terrain_material.clone()),
277 NotShadowCaster,
278 Transform::from_xyz(
279 (x - 2) as f32 * square_width as f32 + offset_x,
280 0.0,
281 (y - 2) as f32 * square_width as f32 + offset_y,
282 ),
283 ));
284 if clipmap.wireframe {
285 e.with_child((
286 Mesh3d(handles.square.clone()),
287 MeshMaterial3d(terrain_material_w.clone()),
288 ));
289 }
290 });
291 }
292
293 if grid.level == 0 {
294 commands.entity(entity).with_children(|c| {
295 let mut e = c.spawn((
296 Mesh3d(handles.center.clone()),
297 MeshMaterial3d(terrain_material.clone()),
298 NotShadowCaster,
299 Transform::from_xyz(
300 -2.0 * square_width as f32,
301 0.0,
302 -2.0 * square_width as f32,
303 ),
304 ));
305 if clipmap.wireframe {
306 e.with_child((
307 Mesh3d(handles.center.clone()),
308 MeshMaterial3d(terrain_material_w.clone()),
309 ));
310 }
311 });
312 } else {
313 commands.entity(entity).with_children(|c| {
314 let mut e = c.spawn((
315 Mesh3d(handles.filler.clone()),
316 MeshMaterial3d(terrain_material.clone()),
317 NotShadowCaster,
318 Transform::from_xyz(
319 -2.0 * square_width as f32,
320 0.0,
321 -2.0 * square_width as f32,
322 ),
323 ));
324 if clipmap.wireframe {
325 e.with_child((
326 Mesh3d(handles.filler.clone()),
327 MeshMaterial3d(terrain_material_w.clone()),
328 ));
329 }
330 });
331 commands.entity(entity).with_children(|c| {
332 let mut e = c.spawn((
333 Mesh3d(handles.stitch.clone()),
334 MeshMaterial3d(terrain_material.clone()),
335 NotShadowCaster,
336 Transform::from_xyz(-square_width as f32, 0.0, -square_width as f32)
337 .with_scale(Vec3::splat(0.5)),
338 ));
339 if clipmap.wireframe {
340 e.with_child((
341 Mesh3d(handles.stitch.clone()),
342 MeshMaterial3d(terrain_material_w.clone()),
343 ));
344 }
345 });
346 }
347
348 let mut trim = commands.spawn((
349 Mesh3d(handles.trim.clone()),
350 MeshMaterial3d(terrain_material.clone()),
351 NotShadowCaster,
352 Transform::from_xyz(-2.0 * square_width as f32, 0.0, -2.0 * square_width as f32),
353 ));
354 if clipmap.wireframe {
355 trim.with_child((
356 Mesh3d(handles.trim.clone()),
357 MeshMaterial3d(terrain_material_w.clone()),
358 ));
359 }
360 grid.trim = trim.id();
361 commands.entity(entity).add_child(grid.trim);
362 }
363}
364
365fn update_grids(
366 mut transforms: Query<&mut Transform>,
367 mut aabbs: Query<&mut Aabb>,
368 mut terrain_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, GridMaterial>>>,
369 terrain_material_handles: Query<
370 &MeshMaterial3d<ExtendedMaterial<StandardMaterial, GridMaterial>>,
371 >,
372 clipmaps: Query<&Clipmap>,
373 children: Query<&Children>,
374 grids: Query<(Entity, &ClipmapGrid, &ChildOf), With<Transform>>,
375) {
376 for (entity, grid, clipmap) in grids {
377 let clipmap = clipmaps.get(clipmap.parent()).unwrap();
378 let filler_width = 2 - clipmap.half_width as i32 % 2;
379 let scale = grid.scale(clipmap.base_scale) * filler_width as f32;
380 let target_pos = transforms.get(clipmap.target).unwrap().translation;
381 let snap_factor = (target_pos / scale).floor().as_ivec3();
382 let snap_pos = snap_factor.as_vec3() * scale;
383 transforms.get_mut(entity).unwrap().translation = snap_pos;
384
385 let snap_mod2 = ((snap_factor.xz() % 2) + 2) % 2;
386 let mut trim_transform = transforms.get_mut(grid.trim).unwrap();
387 trim_transform.translation = {
388 let offset_0 = filler_width as f32 - clipmap.half_width as f32;
389 let offset_1 = clipmap.half_width as f32;
390 Vec3 {
391 x: if snap_mod2.x == 0 { offset_0 } else { offset_1 },
392 y: 0.0,
393 z: if snap_mod2.y == 0 { offset_0 } else { offset_1 },
394 }
395 };
396 trim_transform.rotation = Quat::from_rotation_y(match snap_mod2 {
397 IVec2 { x: 0, y: 0 } => 0.0,
398 IVec2 { x: 0, y: 1 } => FRAC_PI_2,
399 IVec2 { x: 1, y: 0 } => -FRAC_PI_2,
400 IVec2 { x: 1, y: 1 } => PI,
401 _ => unreachable!(),
402 });
403
404 let grid_pos = (snap_pos + trim_transform.translation * scale).xz();
405 for child in children.iter_descendants(entity) {
406 let Ok(material) = terrain_material_handles.get(child) else {
407 continue;
408 };
409 let Some(material) = terrain_materials.get_mut(material) else {
410 continue;
411 };
412 let Ok(mut aabb) = aabbs.get_mut(child) else {
413 continue;
414 };
415 material.extension.translation = grid_pos;
416 aabb.center.y = clipmap.min + (clipmap.max - clipmap.min) / 2.0;
417 aabb.half_extents.y = (clipmap.max - clipmap.min) / 2.0;
418 }
419 }
420}
421
422#[repr(C)]
423#[derive(Eq, PartialEq, Hash, Copy, Clone)]
424struct WireframeKey {
425 wireframe: bool,
426}
427
428impl From<&GridMaterial> for WireframeKey {
429 fn from(material: &GridMaterial) -> Self {
430 Self {
431 wireframe: material.wireframe != 0,
432 }
433 }
434}
435
436#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
437#[bind_group_data(WireframeKey)]
438struct GridMaterial {
439 #[texture(100)]
440 #[sampler(101)]
441 color: Handle<Image>,
442 #[texture(102)]
443 #[sampler(103)]
444 heightmap: Handle<Image>,
445 #[texture(104, dimension = "2d_array")]
446 #[sampler(105)]
447 horizon: Handle<Image>,
448 #[uniform(106)]
449 horizon_coeffs: u32,
450 #[uniform(107)]
451 lod: u32,
452 #[uniform(108)]
453 texel_size: f32,
454 #[uniform(109)]
455 minmax: Vec2,
456 #[uniform(110)]
457 translation: Vec2,
458 #[uniform(111)]
459 wireframe: u32,
460}
461
462impl MaterialExtension for GridMaterial {
463 fn vertex_shader() -> ShaderRef {
464 ShaderRef::Path(
465 AssetPath::from_path_buf(embedded_path!("terrain.wgsl")).with_source("embedded"),
466 )
467 }
468
469 fn deferred_vertex_shader() -> ShaderRef {
470 ShaderRef::Path(
471 AssetPath::from_path_buf(embedded_path!("terrain.wgsl")).with_source("embedded"),
472 )
473 }
474
475 fn fragment_shader() -> ShaderRef {
476 ShaderRef::Path(
477 AssetPath::from_path_buf(embedded_path!("terrain.wgsl")).with_source("embedded"),
478 )
479 }
480
481 fn deferred_fragment_shader() -> ShaderRef {
482 ShaderRef::Path(
483 AssetPath::from_path_buf(embedded_path!("terrain.wgsl")).with_source("embedded"),
484 )
485 }
486
487 fn specialize(
488 _: &bevy::pbr::MaterialExtensionPipeline,
489 descriptor: &mut bevy::render::render_resource::RenderPipelineDescriptor,
490 _: &bevy::mesh::MeshVertexBufferLayoutRef,
491 key: bevy::pbr::MaterialExtensionKey<Self>,
492 ) -> std::result::Result<(), bevy::render::render_resource::SpecializedMeshPipelineError> {
493 if key.bind_group_data.wireframe {
494 descriptor.primitive.polygon_mode = bevy::render::render_resource::PolygonMode::Line;
495 descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
496 }
497 Ok(())
498 }
499}