three_d/renderer/object/
terrain.rs

1use crate::core::*;
2use crate::renderer::*;
3use std::sync::Arc;
4
5/// Specifies the Level of Detail (LOD) for a geometry.
6pub enum Lod {
7    /// High number of triangles - looks good, but slow to render. Use this close to the camera.
8    High,
9    /// Medium number of triangles.
10    Medium,
11    /// Low number of triangles - looks bad, but fast to render. Use this far away from the camera.
12    Low,
13}
14
15const VERTICES_PER_SIDE: usize = 33;
16
17///
18/// A terrain geometry based on a height map and with an applied material.
19///
20pub struct Terrain<M: Material> {
21    context: Context,
22    center: (i32, i32),
23    patches: Vec<Gm<TerrainPatch, M>>,
24    index_buffer1: Arc<ElementBuffer<u32>>,
25    index_buffer4: Arc<ElementBuffer<u32>>,
26    index_buffer16: Arc<ElementBuffer<u32>>,
27    material: M,
28    lod: Arc<dyn Fn(f32) -> Lod + Send + Sync>,
29    height_map: Arc<dyn Fn(f32, f32) -> f32 + Send + Sync>,
30    side_length: f32,
31    vertex_distance: f32,
32}
33impl<M: Material + Clone> Terrain<M> {
34    ///
35    /// Creates a new [Terrain].
36    /// The height map is a function of the (x, z) coordinates which returns the height of the terrain y.
37    ///
38    pub fn new(
39        context: &Context,
40        material: M,
41        height_map: Arc<dyn Fn(f32, f32) -> f32 + Send + Sync>,
42        side_length: f32,
43        vertex_distance: f32,
44        center: Vec2,
45    ) -> Self {
46        let index_buffer1 = Self::indices(context, 1);
47        let mut patches = Vec::new();
48        let (x0, y0) = pos2patch(vertex_distance, center);
49        let half_patches_per_side = half_patches_per_side(vertex_distance, side_length);
50        for ix in x0 - half_patches_per_side..x0 + half_patches_per_side + 1 {
51            for iy in y0 - half_patches_per_side..y0 + half_patches_per_side + 1 {
52                let patch = TerrainPatch::new(
53                    context,
54                    &*height_map.clone(),
55                    (ix, iy),
56                    index_buffer1.clone(),
57                    vertex_distance,
58                );
59                patches.push(Gm::new(patch, material.clone()));
60            }
61        }
62        Self {
63            context: context.clone(),
64            center: (x0, y0),
65            patches,
66            index_buffer1,
67            index_buffer4: Self::indices(context, 4),
68            index_buffer16: Self::indices(context, 16),
69            lod: Arc::new(|_| Lod::High),
70            material,
71            height_map,
72            side_length,
73            vertex_distance,
74        }
75    }
76
77    ///
78    /// Returns the height at the given position.
79    ///
80    pub fn height_at(&self, position: Vec2) -> f32 {
81        (*self.height_map)(position.x, position.y)
82    }
83
84    ///
85    /// Set the function that specifies when a certain level of detail [Lod] is uses.
86    /// The input to the function is the distance from the current camera to the center of a part of the terrain.
87    ///
88    pub fn set_lod(&mut self, lod: Arc<dyn Fn(f32) -> Lod + Send + Sync>) {
89        self.lod = lod;
90    }
91
92    ///
93    /// Set the center of the terrain.
94    /// To be able to move the terrain with the camera, thereby simulating infinite terrain.
95    ///
96    pub fn set_center(&mut self, center: Vec2) {
97        let (x0, y0) = pos2patch(self.vertex_distance, center);
98        let half_patches_per_side = half_patches_per_side(self.vertex_distance, self.side_length);
99
100        while x0 > self.center.0 {
101            self.center.0 += 1;
102            for iy in
103                self.center.1 - half_patches_per_side..self.center.1 + half_patches_per_side + 1
104            {
105                self.patches.push(Gm::new(
106                    TerrainPatch::new(
107                        &self.context,
108                        &*self.height_map.clone(),
109                        (self.center.0 + half_patches_per_side, iy),
110                        self.index_buffer1.clone(),
111                        self.vertex_distance,
112                    ),
113                    self.material.clone(),
114                ));
115            }
116        }
117
118        while x0 < self.center.0 {
119            self.center.0 -= 1;
120            for iy in
121                self.center.1 - half_patches_per_side..self.center.1 + half_patches_per_side + 1
122            {
123                self.patches.push(Gm::new(
124                    TerrainPatch::new(
125                        &self.context,
126                        &*self.height_map.clone(),
127                        (self.center.0 - half_patches_per_side, iy),
128                        self.index_buffer1.clone(),
129                        self.vertex_distance,
130                    ),
131                    self.material.clone(),
132                ));
133            }
134        }
135        while y0 > self.center.1 {
136            self.center.1 += 1;
137            for ix in
138                self.center.0 - half_patches_per_side..self.center.0 + half_patches_per_side + 1
139            {
140                self.patches.push(Gm::new(
141                    TerrainPatch::new(
142                        &self.context,
143                        &*self.height_map.clone(),
144                        (ix, self.center.1 + half_patches_per_side),
145                        self.index_buffer1.clone(),
146                        self.vertex_distance,
147                    ),
148                    self.material.clone(),
149                ));
150            }
151        }
152
153        while y0 < self.center.1 {
154            self.center.1 -= 1;
155            for ix in
156                self.center.0 - half_patches_per_side..self.center.0 + half_patches_per_side + 1
157            {
158                self.patches.push(Gm::new(
159                    TerrainPatch::new(
160                        &self.context,
161                        &*self.height_map.clone(),
162                        (ix, self.center.1 - half_patches_per_side),
163                        self.index_buffer1.clone(),
164                        self.vertex_distance,
165                    ),
166                    self.material.clone(),
167                ));
168            }
169        }
170
171        self.patches.retain(|p| {
172            let (ix, iy) = p.index();
173            (x0 - ix).abs() <= half_patches_per_side && (y0 - iy).abs() <= half_patches_per_side
174        });
175
176        self.patches.iter_mut().for_each(|p| {
177            let distance = p.center().distance(center);
178            p.index_buffer = match (*self.lod)(distance) {
179                Lod::Low => self.index_buffer16.clone(),
180                Lod::Medium => self.index_buffer4.clone(),
181                Lod::High => self.index_buffer1.clone(),
182            };
183        })
184    }
185
186    fn indices(context: &Context, resolution: u32) -> Arc<ElementBuffer<u32>> {
187        let mut indices: Vec<u32> = Vec::new();
188        let stride = VERTICES_PER_SIDE as u32;
189        let max = (stride - 1) / resolution;
190        for r in 0..max {
191            for c in 0..max {
192                indices.push(r * resolution + c * resolution * stride);
193                indices.push(r * resolution + resolution + c * resolution * stride);
194                indices.push(r * resolution + (c * resolution + resolution) * stride);
195                indices.push(r * resolution + (c * resolution + resolution) * stride);
196                indices.push(r * resolution + resolution + c * resolution * stride);
197                indices.push(r * resolution + resolution + (c * resolution + resolution) * stride);
198            }
199        }
200        Arc::new(ElementBuffer::new_with_data(context, &indices))
201    }
202}
203
204impl<'a, M: Material> IntoIterator for &'a Terrain<M> {
205    type Item = &'a dyn Object;
206    type IntoIter = std::vec::IntoIter<&'a dyn Object>;
207
208    fn into_iter(self) -> Self::IntoIter {
209        self.patches
210            .iter()
211            .map(|m| m as &dyn Object)
212            .collect::<Vec<_>>()
213            .into_iter()
214    }
215}
216
217fn patch_size(vertex_distance: f32) -> f32 {
218    vertex_distance * (VERTICES_PER_SIDE - 1) as f32
219}
220
221fn half_patches_per_side(vertex_distance: f32, side_length: f32) -> i32 {
222    let patch_size = patch_size(vertex_distance);
223    let patches_per_side = (side_length / patch_size).ceil() as u32;
224    (patches_per_side as i32 - 1) / 2
225}
226
227fn pos2patch(vertex_distance: f32, position: Vec2) -> (i32, i32) {
228    let patch_size = vertex_distance * (VERTICES_PER_SIDE - 1) as f32;
229    (
230        (position.x / patch_size).floor() as i32,
231        (position.y / patch_size).floor() as i32,
232    )
233}
234
235struct TerrainPatch {
236    context: Context,
237    index: (i32, i32),
238    positions_buffer: VertexBuffer<Vec3>,
239    normals_buffer: VertexBuffer<Vec3>,
240    center: Vec2,
241    aabb: AxisAlignedBoundingBox,
242    pub index_buffer: Arc<ElementBuffer<u32>>,
243}
244
245impl TerrainPatch {
246    pub fn new(
247        context: &Context,
248        height_map: impl Fn(f32, f32) -> f32 + Clone,
249        index: (i32, i32),
250        index_buffer: Arc<ElementBuffer<u32>>,
251        vertex_distance: f32,
252    ) -> Self {
253        let patch_size = patch_size(vertex_distance);
254        let offset = vec2(index.0 as f32 * patch_size, index.1 as f32 * patch_size);
255        let positions = Self::positions(height_map.clone(), offset, vertex_distance);
256        let aabb = AxisAlignedBoundingBox::new_with_positions(&positions);
257        let normals = Self::normals(height_map, offset, &positions, vertex_distance);
258
259        let positions_buffer = VertexBuffer::new_with_data(context, &positions);
260        let normals_buffer = VertexBuffer::new_with_data(context, &normals);
261        Self {
262            context: context.clone(),
263            index,
264            index_buffer,
265            positions_buffer,
266            normals_buffer,
267            aabb,
268            center: offset + vec2(0.5 * patch_size, 0.5 * patch_size),
269        }
270    }
271
272    pub fn center(&self) -> Vec2 {
273        self.center
274    }
275
276    pub fn index(&self) -> (i32, i32) {
277        self.index
278    }
279
280    fn positions(
281        height_map: impl Fn(f32, f32) -> f32,
282        offset: Vec2,
283        vertex_distance: f32,
284    ) -> Vec<Vec3> {
285        let mut data = vec![vec3(0.0, 0.0, 0.0); VERTICES_PER_SIDE * VERTICES_PER_SIDE];
286        for r in 0..VERTICES_PER_SIDE {
287            for c in 0..VERTICES_PER_SIDE {
288                let vertex_id = r * VERTICES_PER_SIDE + c;
289                let x = offset.x + r as f32 * vertex_distance;
290                let z = offset.y + c as f32 * vertex_distance;
291                data[vertex_id] = vec3(x, height_map(x, z), z);
292            }
293        }
294        data
295    }
296
297    fn normals(
298        height_map: impl Fn(f32, f32) -> f32,
299        offset: Vec2,
300        positions: &[Vec3],
301        vertex_distance: f32,
302    ) -> Vec<Vec3> {
303        let mut data = vec![vec3(0.0, 0.0, 0.0); VERTICES_PER_SIDE * VERTICES_PER_SIDE];
304        let h = vertex_distance;
305        for r in 0..VERTICES_PER_SIDE {
306            for c in 0..VERTICES_PER_SIDE {
307                let vertex_id = r * VERTICES_PER_SIDE + c;
308                let x = offset.x + r as f32 * vertex_distance;
309                let z = offset.y + c as f32 * vertex_distance;
310                let xp = if r == VERTICES_PER_SIDE - 1 {
311                    height_map(x + h, z)
312                } else {
313                    positions[vertex_id + VERTICES_PER_SIDE][1]
314                };
315                let xm = if r == 0 {
316                    height_map(x - h, z)
317                } else {
318                    positions[vertex_id - VERTICES_PER_SIDE][1]
319                };
320                let zp = if c == VERTICES_PER_SIDE - 1 {
321                    height_map(x, z + h)
322                } else {
323                    positions[vertex_id + 1][1]
324                };
325                let zm = if c == 0 {
326                    height_map(x, z - h)
327                } else {
328                    positions[vertex_id - 1][1]
329                };
330                let dx = xp - xm;
331                let dz = zp - zm;
332                data[vertex_id] = vec3(-dx, 2.0 * h, -dz).normalize();
333            }
334        }
335        data
336    }
337}
338
339impl Geometry for TerrainPatch {
340    fn vertex_shader_source(&self) -> String {
341        include_str!("shaders/terrain.vert").to_owned()
342    }
343
344    fn draw(&self, viewer: &dyn Viewer, program: &Program, render_states: RenderStates) {
345        program.use_uniform("viewProjectionMatrix", viewer.projection() * viewer.view());
346        program.use_vertex_attribute("position", &self.positions_buffer);
347        if program.requires_attribute("normal") {
348            program.use_vertex_attribute("normal", &self.normals_buffer);
349        }
350        program.draw_elements(render_states, viewer.viewport(), &self.index_buffer);
351    }
352
353    fn id(&self) -> GeometryId {
354        GeometryId::TerrainPatch
355    }
356
357    fn render_with_material(
358        &self,
359        material: &dyn Material,
360        viewer: &dyn Viewer,
361        lights: &[&dyn Light],
362    ) {
363        render_with_material(&self.context, viewer, &self, material, lights);
364    }
365
366    fn render_with_effect(
367        &self,
368        material: &dyn Effect,
369        viewer: &dyn Viewer,
370        lights: &[&dyn Light],
371        color_texture: Option<ColorTexture>,
372        depth_texture: Option<DepthTexture>,
373    ) {
374        render_with_effect(
375            &self.context,
376            viewer,
377            self,
378            material,
379            lights,
380            color_texture,
381            depth_texture,
382        )
383    }
384
385    fn aabb(&self) -> AxisAlignedBoundingBox {
386        self.aabb
387    }
388}