From 451b0741d9b9b5a887d47caa385df34882bdc241 Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Fri, 8 Dec 2023 13:47:25 +0100 Subject: [PATCH] better terrain --- assets/config.json | 6 - assets/shaders/terrain.frag.wgsl | 18 +- assets/shaders/terrain.vert.wgsl | 75 ++++- engine/src/drawables/terrain.rs | 294 +++++++++--------- engine/src/gfx.rs | 4 +- engine/src/texture.rs | 135 ++++---- engine/src/u8slice.rs | 6 + engine/src/uniform.rs | 41 ++- engine_demo/src/helmet.rs | 37 +++ engine_demo/src/main.rs | 147 ++++++--- engine_demo/src/spheres.rs | 62 ++++ engine_demo/src/terrain.rs | 92 ++++++ geom/src/lib.rs | 2 + geom/src/noise.rs | 81 +++++ .../src/rendering/map_rendering/terrain.rs | 18 +- simulation/src/map/procgen/heightmap.rs | 85 +---- simulation/src/utils/config.rs | 1 - 17 files changed, 722 insertions(+), 382 deletions(-) create mode 100644 engine_demo/src/helmet.rs create mode 100644 engine_demo/src/spheres.rs create mode 100644 engine_demo/src/terrain.rs create mode 100644 geom/src/noise.rs diff --git a/assets/config.json b/assets/config.json index 84f3a8b6..690081c3 100644 --- a/assets/config.json +++ b/assets/config.json @@ -24,12 +24,6 @@ "b": 0.47983873, "a": 1.0 }, - "border_col": { - "r": 0.27822578, - "g": 0.23231916, - "b": 0.14584415, - "a": 1.0 - }, "roof_col": { "r": 0.2627451, "g": 0.21314174, diff --git a/assets/shaders/terrain.frag.wgsl b/assets/shaders/terrain.frag.wgsl index cf3a6593..5e54ecb4 100644 --- a/assets/shaders/terrain.frag.wgsl +++ b/assets/shaders/terrain.frag.wgsl @@ -6,12 +6,18 @@ struct FragmentOutput { @location(0) out_color: vec4, } +struct LevelData { + lod: u32, + resolution: u32, +} + @group(1) @binding(0) var params: RenderParams; @group(2) @binding(0) var t_terraindata: texture_2d; @group(2) @binding(1) var s_terraindata: sampler; @group(2) @binding(2) var t_grass: texture_2d; @group(2) @binding(3) var s_grass: sampler; +@group(2) @binding(4) var ldata: LevelData; @group(3) @binding(0) var t_ssao: texture_2d; @group(3) @binding(1) var s_ssao: sampler; @@ -108,9 +114,13 @@ fn textureNoTile(tex: texture_2d, samp: sampler, uv: vec2) -> vec4, +fn frag(@builtin(position) position: vec4, + @location(0) in_normal: vec3, @location(1) in_wpos: vec3, - @builtin(position) position: vec4) -> FragmentOutput { +#ifdef DEBUG + @location(2) debug: f32, +#endif + ) -> FragmentOutput { var ssao = 1.0; #ifdef SSAO ssao = textureSample(t_ssao, s_ssao, position.xy / params.viewport).r; @@ -140,6 +150,10 @@ fn frag(@location(0) in_normal: vec3, let normal: vec3 = normalize(in_normal); let F_spec: vec3 = F0; // simplified with constant folding: fresnelSchlickRoughness(max(dot(normal, V), 0.0), F0, roughness); + #ifdef DEBUG + c = 0.05 * vec3(0.0, 0.0, debug); + #endif + let final_rgb: vec3 = render(params.sun, V, position.xy, diff --git a/assets/shaders/terrain.vert.wgsl b/assets/shaders/terrain.vert.wgsl index b795825d..3dd102ab 100644 --- a/assets/shaders/terrain.vert.wgsl +++ b/assets/shaders/terrain.vert.wgsl @@ -1,3 +1,5 @@ +#include "render_params.wgsl" + struct Uniforms { u_view_proj: mat4x4, } @@ -5,13 +7,27 @@ struct Uniforms { struct VertexOutput { @location(0) out_normal: vec3, @location(1) out_wpos: vec3, +#ifdef DEBUG + @location(2) debug: f32, +#endif @builtin(position) member: vec4, } +struct LevelData { + lod: u32, + resolution: u32, + distance_lod_cutoff: f32, // max distance at which to switch to the next lod to have smooth transitions + cell_size: f32, + inv_cell_size: f32, +} + @group(0) @binding(0) var global: Uniforms; -@group(2) @binding(0) var t_terraindata: texture_2d; +@group(1) @binding(0) var params: RenderParams; + +@group(2) @binding(0) var t_terraindata: texture_2d; @group(2) @binding(1) var s_terraindata: sampler; +@group(2) @binding(4) var ldata: LevelData; /* normal: vec3(self.cell_size * scale as f32, 0.0, hx - height) @@ -19,19 +35,56 @@ normal: vec3(self.cell_size * scale as f32, 0.0, hx - height) .normalize(), */ -const CELL_SIZE: f32 = 1024.0 / 32.0; // chunk size / chunk resolution +const MAX_HEIGHT: f32 = 1024.0; +const MAX_DIFF: f32 = 32.0; + +fn unpack_height(h: u32) -> f32 { + return ((f32(h) - 32768.0) / 32767.0 ) * MAX_HEIGHT; +} + +fn unpack_diffs(v: u32) -> vec2 { + let x = v & 0xFFu; + let y = (v & 0xFF00u) >> 8u; + return vec2((f32(x) - 128.0) / 127.0 * MAX_DIFF, + (f32(y) - 128.0) / 127.0 * MAX_DIFF); +} @vertex -fn vert(@location(0) in_position: vec2, @location(1) in_off: vec2) -> VertexOutput { - let tpos: vec2 = vec2((in_position + in_off) / CELL_SIZE); - let height: f32 = textureLoad(t_terraindata, tpos, 0).r; +fn vert(@builtin(vertex_index) vid: u32, @location(0) in_position: vec2, @location(1) in_off: vec2) -> VertexOutput { + let tpos: vec2 = vec2((in_position + in_off) * ldata.inv_cell_size); + + let texLoad: vec2 = textureLoad(t_terraindata, tpos, 0).rg; + + let height: f32 = unpack_height(texLoad.r); + let diffs: vec2 = unpack_diffs(texLoad.g); + + let step: i32 = i32(pow(2.0, f32(ldata.lod))); + + let zf_off: vec2 = vec2( step * (i32(vid % ldata.resolution) % 2), + step * (i32(vid / ldata.resolution) % 2)); + + let world_pos: vec3 = vec3(in_position + in_off, height); + + //let dist_to_cam: f32 = length(params.cam_pos.xyz - vec3(pos.xy, 0.0)); + //let transition_alpha: f32 = smoothstep(ldata.distance_lod_cutoff * 0.8, ldata.distance_lod_cutoff, dist_to_cam); + + var out_normal: vec3 = normalize(vec3(diffs.x, diffs.y, ldata.cell_size * 2.0)); // https://stackoverflow.com/questions/49640250/calculate-normals-from-heightmap + + let position: vec4 = global.u_view_proj * vec4(world_pos, 1.0); - let hx: f32 = textureLoad(t_terraindata, vec2(1, 0) + tpos, 0).r; - let hy: f32 = textureLoad(t_terraindata, vec2(0, 1) + tpos, 0).r; +#ifdef DEBUG + var debug = f32(ldata.lod); - let pos: vec3 = vec3(in_position + in_off, height); - let out_normal: vec3 = normalize(cross(vec3(CELL_SIZE, 0.0, hx - height), vec3(0.0, CELL_SIZE, hy - height))); - let position: vec4 = global.u_view_proj * vec4(pos, 1.0); + if(height >= MAX_HEIGHT) { + debug = diffs.x; + } +#endif - return VertexOutput(out_normal, pos, position); + return VertexOutput( + out_normal, + world_pos, + #ifdef DEBUG + debug, + #endif + position); } diff --git a/engine/src/drawables/terrain.rs b/engine/src/drawables/terrain.rs index a01964c0..20510a32 100644 --- a/engine/src/drawables/terrain.rs +++ b/engine/src/drawables/terrain.rs @@ -1,19 +1,19 @@ use crate::{ bg_layout_litmesh, pbuffer::PBuffer, CompiledModule, Drawable, FrameContext, GfxContext, - IndexType, Material, Mesh, MeshBuilder, MeshVertex, MetallicRoughness, PipelineBuilder, - RenderParams, TerrainVertex, Texture, Uniform, TL, + IndexType, PipelineBuilder, RenderParams, TerrainVertex, Texture, Uniform, TL, }; -use geom::{ - vec2, vec3, Camera, InfiniteFrustrum, Intersect3, LinearColor, Matrix4, Polygon, Vec2, AABB3, -}; -use std::ops::Sub; +use geom::{vec2, vec3, Camera, InfiniteFrustrum, Intersect3, Matrix4, Vec2, AABB3}; use std::sync::Arc; use wgpu::{ - BufferUsages, Extent3d, FilterMode, ImageCopyTexture, ImageDataLayout, IndexFormat, Origin3d, - RenderPass, RenderPipeline, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, + BindGroupDescriptor, BindGroupLayoutDescriptor, BufferUsages, Extent3d, FilterMode, + ImageCopyTexture, ImageDataLayout, IndexFormat, Origin3d, RenderPass, RenderPipeline, + TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, }; const LOD: usize = 4; +const LOD_MIN_DIST_LOG2: f32 = 11.0; // 2^10 = 1024, meaning until 2048m away, we use the highest lod +const MAX_HEIGHT: f32 = 1024.0; +const MAX_DIFF: f32 = 32.0; pub struct TerrainChunk { pub dirt_id: u32, @@ -23,37 +23,28 @@ pub struct TerrainRender { terrain_tex: Arc, #[allow(unused)] grass_tex: Arc, // kept alive - borders: Arc>, vertices: [PBuffer; LOD], indices: [(PBuffer, u32); LOD], instances: [(PBuffer, u32); LOD], - bg: Arc, - border_col: LinearColor, - cell_size: f32, + bgs: [Arc; LOD], w: u32, h: u32, } pub struct TerrainPrepared { - terrainbg: Arc, + terrainbgs: [Arc; LOD], vertices: [PBuffer; LOD], indices: [(PBuffer, u32); LOD], instances: [(PBuffer, u32); LOD], } impl TerrainRender { - pub fn new( - gfx: &mut GfxContext, - w: u32, - h: u32, - col: LinearColor, - grass: Arc, - ) -> Self { + pub fn new(gfx: &mut GfxContext, w: u32, h: u32, grass: Arc) -> Self { let (indices, vertices) = Self::generate_indices_mesh(gfx); let mut tex = Texture::create_fbo( &gfx.device, - (w * CRESOLUTION as u32 + 2, h * CRESOLUTION as u32 + 2), - TextureFormat::R32Float, + (w * CRESOLUTION as u32 + 1, h * CRESOLUTION as u32 + 1), + TextureFormat::Rg16Uint, TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, None, ); @@ -67,23 +58,48 @@ impl TerrainRender TerrainRender Option, + get_down: impl Fn(usize) -> Option, + get_right: impl Fn(usize) -> Option, + get_left: impl Fn(usize) -> Option, ) -> bool { - let mut contents = Vec::with_capacity(CRESOLUTION * CRESOLUTION); + fn pack(height: f32, diffx: f32, diffy: f32) -> [u8; 4] { + let a = ((height.clamp(-MAX_HEIGHT, MAX_HEIGHT) / MAX_HEIGHT * i16::MAX as f32 + + 32768.0) as u16) + .to_le_bytes(); + + if height >= MAX_HEIGHT || height <= -MAX_HEIGHT { + return [a[0], a[1], 128, 128]; // normal is zero if we hit max height + } + let b = (diffx.clamp(-MAX_DIFF, MAX_DIFF) / MAX_DIFF * i8::MAX as f32 + 128.0) as u8; + let c = (diffy.clamp(-MAX_DIFF, MAX_DIFF) / MAX_DIFF * i8::MAX as f32 + 128.0) as u8; + [a[0], a[1], b, c] + } + + // Need to add one more vertex on the edge of the map because when rendering a chunk + // we render "to the next chunk", which doesn't exist on the edge let extrax = cell.0 + 1 == self.w; let extray = cell.1 + 1 == self.h; + let mut contents = + Vec::with_capacity((CRESOLUTION + extrax as usize) * (CRESOLUTION + extray as usize)); + + let mut holder_y_edge: [f32; CRESOLUTION] = [0.0; CRESOLUTION]; + let mut j = 0; + let mut last_ys = &[(); CRESOLUTION].map(|_| { + let height_down = get_down(j).unwrap_or(chunk[0][j]); + j += 1; + height_down + }); + for i in 0..CRESOLUTION { + let ys = &chunk[i]; + let next_ys = chunk.get(i + 1).unwrap_or_else(|| { + for j in 0..CRESOLUTION { + holder_y_edge[j] = get_up(j).unwrap_or(ys[j]); + } + &holder_y_edge + }); - let w = CRESOLUTION as u32 + 2 * extrax as u32; - let h = CRESOLUTION as u32 + 2 * extray as u32; - - for y in chunk - .iter() - .chain(extray.then(|| &chunk[CRESOLUTION - 1]).into_iter()) - .chain(extray.then(|| &chunk[CRESOLUTION - 1]).into_iter()) - { - for x in y { - contents.extend(x.to_le_bytes()); + let mut last_height = get_left(i).unwrap_or(ys[0]); + for j in 0..CRESOLUTION { + let height = ys[j]; + let dh_x = last_height + - ys.get(j + 1) + .copied() + .unwrap_or_else(|| get_right(i).unwrap_or(height)); + let dh_y = last_ys[j] - next_ys[j]; + + contents.extend(pack(height, dh_x, dh_y)); + last_height = height; } if extrax { - contents.extend(y[y.len() - 1].to_le_bytes()); - contents.extend(y[y.len() - 1].to_le_bytes()); + contents.extend(pack(ys[ys.len() - 1], 0.0, 0.0)); } - if w * 4 < wgpu::COPY_BYTES_PER_ROW_ALIGNMENT { - contents.resize( - contents.len() + wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize - w as usize * 4, - 0, - ); + last_ys = ys; + } + if extray { + for i in 0..CRESOLUTION { + contents.extend(pack(chunk[CRESOLUTION - 1][i], 0.0, 0.0)); + } + if extrax { + contents.extend(pack(chunk[CRESOLUTION - 1][CRESOLUTION - 1], 0.0, 0.0)); } } + let w = CRESOLUTION as u32 + extrax as u32; + let h = CRESOLUTION as u32 + extray as u32; + gfx.queue.write_texture( ImageCopyTexture { texture: &self.terrain_tex.texture, @@ -136,7 +194,7 @@ impl TerrainRender TerrainRender, ) { profiling::scope!("terrain::draw_terrain"); - for b in self.borders.iter() { - fctx.objs.push(Box::new(b.clone())); - } - let eye = cam.eye(); let mut instances = vec![Vec::::new(); LOD]; for y in 0..self.h { for x in 0..self.w { - let p = vec2(x as f32, y as f32) * CSIZE as f32; + let chunk_corner = vec2(x as f32, y as f32) * CSIZE as f32; + let chunk_center = chunk_corner + Vec2::splat(CSIZE as f32 * 0.5); if !frustrum.intersects(&AABB3::centered( - (p + Vec2::splat(CSIZE as f32 * 0.5)).z(0.0), - vec3(CSIZE as f32, CSIZE as f32, 1000.0), + chunk_center.z0(), + vec3(CSIZE as f32, CSIZE as f32, 2000.0), )) { continue; } - let lod = eye.distance(p.z0()).log2().sub(10.0).max(0.0) as usize; + let lod = + (eye.distance(chunk_center.z0()).log2() - LOD_MIN_DIST_LOG2).max(0.0) as usize; let lod = lod.min(LOD - 1); - instances[lod].push(TerrainInstance { offset: p }) + instances[lod].push(TerrainInstance { + offset: chunk_corner, + }) } } @@ -189,96 +247,13 @@ impl TerrainRender Option, - ) { - let minx = min.0; - let maxx = min.1 + 1; - let miny = max.0; - let maxy = max.1 + 1; - let cell_size = self.cell_size; - let mut mk_bord = |start, end, c, is_x, rev| { - let c = c as f32 * CSIZE as f32; - let flip = move |v: Vec2| { - if is_x { - v - } else { - vec2(v.y, v.x) - } - }; - - let mut poly = Polygon(vec![]); - poly.0.push(vec2(start as f32 * CSIZE as f32, -3000.0)); - for along in start * CRESOLUTION as u32..=end * CRESOLUTION as u32 { - let along = along as f32 * cell_size; - let p = flip(vec2(along, c)); - let height = unwrap_cont!(height(p - (p - Vec2::splat(3.0)).sign() * 1.0)); - poly.0.push(vec2(along, height + 1.5)); - } - poly.0.push(vec2(end as f32 * CSIZE as f32, -3000.0)); - - poly.simplify(); - - let mut indices = vec![]; - crate::earcut::earcut(&poly.0, &[], |mut a, b, mut c| { - if rev { - std::mem::swap(&mut a, &mut c); - } - indices.push(a as IndexType); - indices.push(b as IndexType); - indices.push(c as IndexType); - }); - let mat = gfx.register_material(Material::new( - gfx, - gfx.palette(), - MetallicRoughness { - metallic: 0.0, - roughness: 1.0, - tex: None, - }, - None, - )); - let mut mb = MeshBuilder::::new(mat); - mb.indices = indices; - mb.vertices = poly - .0 - .into_iter() - .map(|p| MeshVertex { - position: if is_x { - vec3(p.x, c, p.y) - } else { - vec3(c, p.x, p.y) - } - .into(), - normal: if rev ^ !is_x { 1.0 } else { -1.0 } - * vec3(!is_x as i32 as f32, is_x as i32 as f32, 0.0), - uv: [0.0, 0.0], - color: self.border_col.into(), - tangent: [0.0; 4], - }) - .collect(); - mb.build(gfx) - }; - - let borders = Arc::get_mut(&mut self.borders).unwrap(); - borders.clear(); - borders.extend(mk_bord(minx, maxx, miny, true, false)); - borders.extend(mk_bord(minx, maxx, maxy, true, true)); - borders.extend(mk_bord(miny, maxy, minx, false, true)); - borders.extend(mk_bord(miny, maxy, maxx, false, false)); - } - fn generate_indices_mesh(gfx: &GfxContext) -> ([(PBuffer, u32); LOD], [PBuffer; LOD]) { let mut indlod = vec![]; let mut vertlod = vec![]; @@ -335,12 +310,22 @@ struct TerrainPipeline { #[derive(Copy, Clone)] #[repr(C)] -pub struct TerrainInstance { +pub(crate) struct TerrainInstance { pub offset: Vec2, } - u8slice_impl!(TerrainInstance); +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) struct LevelData { + lod: u32, // 0 = highest resolution, 1 = half resolution, etc. + resolution: u32, // width of the vertex grid + distance_lod_cutoff: f32, // max distance at which to switch to the next lod to have smooth transitions + cell_size: f32, + inv_cell_size: f32, +} +u8slice_impl!(LevelData); + const ATTRS: &[VertexAttribute] = &wgpu::vertex_attr_array![1 => Float32x2]; impl TerrainInstance { @@ -356,14 +341,15 @@ impl TerrainInstance { impl TerrainPrepared { fn set_buffers<'a>(&'a self, rp: &mut RenderPass<'a>) { for lod in 0..LOD { - let (ind, n_indices) = &self.indices[lod]; - let vertices = &self.vertices[lod]; let (instances, n_instances) = &self.instances[lod]; - if *n_instances == 0 { continue; } + let (ind, n_indices) = &self.indices[lod]; + let vertices = &self.vertices[lod]; + + rp.set_bind_group(2, &self.terrainbgs[lod], &[]); rp.set_vertex_buffer(0, vertices.slice().unwrap()); rp.set_vertex_buffer(1, instances.slice().unwrap()); rp.set_index_buffer(ind.slice().unwrap(), IndexFormat::Uint32); @@ -378,8 +364,16 @@ impl PipelineBuilder for TerrainPipeline { gfx: &GfxContext, mut mk_module: impl FnMut(&str) -> CompiledModule, ) -> RenderPipeline { - let terrainlayout = - Texture::bindgroup_layout(&gfx.device, [TL::NonfilterableFloat, TL::Float]); + let terrainlayout = gfx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &Texture::bindgroup_layout_entries(0, [TL::UInt, TL::Float].into_iter()) + .chain(std::iter::once( + Uniform::::bindgroup_layout_entry(4), + )) + .collect::>(), + label: Some("terrain bindgroup layout"), + }); let vert = &mk_module("terrain.vert"); if !self.depth { @@ -423,7 +417,6 @@ impl Drawable for TerrainPrepared { rp.set_pipeline(pipeline); rp.set_bind_group(1, &gfx.render_params.bindgroup, &[]); - rp.set_bind_group(2, &self.terrainbg, &[]); rp.set_bind_group(3, &gfx.simplelit_bg, &[]); self.set_buffers(rp); @@ -443,7 +436,6 @@ impl Drawable for TerrainPrepared { smap: false, })); rp.set_bind_group(1, &gfx.render_params.bindgroup, &[]); - rp.set_bind_group(2, &self.terrainbg, &[]); self.set_buffers(rp); } diff --git a/engine/src/gfx.rs b/engine/src/gfx.rs index d3f02190..82c78e7e 100644 --- a/engine/src/gfx.rs +++ b/engine/src/gfx.rs @@ -57,8 +57,8 @@ pub struct GfxContext { pub sun_shadowmap: Texture, pub pbr: PBR, pub lamplights: LampLights, - pub defines: FastMap, - pub defines_changed: bool, + pub(crate) defines: FastMap, + pub(crate) defines_changed: bool, pub simplelit_bg: wgpu::BindGroup, pub bnoise_bg: wgpu::BindGroup, diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 0e2815ea..7e8de24d 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -8,9 +8,10 @@ use std::io; use std::io::Read; use std::path::Path; use wgpu::{ - BindGroup, BindGroupLayout, BindGroupLayoutEntry, CommandEncoderDescriptor, Device, Extent3d, - ImageCopyTexture, ImageDataLayout, PipelineLayoutDescriptor, SamplerDescriptor, TextureFormat, - TextureSampleType, TextureUsages, TextureViewDescriptor, TextureViewDimension, + BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, CommandEncoderDescriptor, + Device, Extent3d, ImageCopyTexture, ImageDataLayout, PipelineLayoutDescriptor, + SamplerDescriptor, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, + TextureViewDimension, }; pub struct Texture { @@ -32,6 +33,7 @@ pub enum TL { NonfilterableFloatMultisampled, Cube, UInt, + SInt, } impl Texture { @@ -122,57 +124,59 @@ impl Texture { self.texture.mip_level_count() } - pub fn bindgroup_layout(device: &Device, it: impl IntoIterator) -> BindGroupLayout { - let entries: Vec = it - .into_iter() - .enumerate() - .flat_map(|(i, bgtype)| { - vec![ - BindGroupLayoutEntry { - binding: (i * 2) as u32, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: matches!( + pub fn bindgroup_layout_entries( + binding_offset: u32, + it: impl Iterator, + ) -> impl Iterator { + it.enumerate().flat_map(move |(i, bgtype)| { + std::iter::once(BindGroupLayoutEntry { + binding: binding_offset + (i * 2) as u32, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: matches!( + bgtype, + TL::NonfilterableFloatMultisampled | TL::DepthMultisampled + ), + view_dimension: match bgtype { + TL::Cube => TextureViewDimension::Cube, + TL::DepthArray => TextureViewDimension::D2Array, + _ => TextureViewDimension::D2, + }, + sample_type: match bgtype { + TL::Depth | TL::DepthMultisampled | TL::DepthArray => { + TextureSampleType::Depth + } + TL::UInt => TextureSampleType::Uint, + TL::SInt => TextureSampleType::Sint, + _ => TextureSampleType::Float { + filterable: !matches!( bgtype, - TL::NonfilterableFloatMultisampled | TL::DepthMultisampled + TL::NonfilterableFloat | TL::NonfilterableFloatMultisampled ), - view_dimension: match bgtype { - TL::Cube => TextureViewDimension::Cube, - TL::DepthArray => TextureViewDimension::D2Array, - _ => TextureViewDimension::D2, - }, - sample_type: match bgtype { - TL::Depth | TL::DepthMultisampled | TL::DepthArray => { - TextureSampleType::Depth - } - TL::UInt => TextureSampleType::Uint, - _ => TextureSampleType::Float { - filterable: !matches!( - bgtype, - TL::NonfilterableFloat | TL::NonfilterableFloatMultisampled - ), - }, - }, }, - count: None, - }, - BindGroupLayoutEntry { - binding: (i * 2 + 1) as u32, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Sampler( - if matches!(bgtype, TL::Depth | TL::DepthMultisampled | TL::DepthArray) - { - wgpu::SamplerBindingType::Comparison - } else { - wgpu::SamplerBindingType::Filtering - }, - ), - count: None, }, - ] - .into_iter() + }, + count: None, }) - .collect::>(); + .chain(std::iter::once(BindGroupLayoutEntry { + binding: binding_offset + (i * 2 + 1) as u32, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Sampler( + if matches!(bgtype, TL::Depth | TL::DepthMultisampled | TL::DepthArray) { + wgpu::SamplerBindingType::Comparison + } else { + wgpu::SamplerBindingType::Filtering + }, + ), + count: None, + })) + .into_iter() + }) + } + + pub fn bindgroup_layout(device: &Device, it: impl IntoIterator) -> BindGroupLayout { + let entries: Vec = + Self::bindgroup_layout_entries(0, it.into_iter()).collect(); device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &entries, label: Some("Texture bindgroup layout"), @@ -196,27 +200,28 @@ impl Texture { }) } + pub fn multi_bindgroup_entries<'a>( + binding_offset: u32, + texs: &'a [&Texture], + ) -> impl Iterator> { + texs.iter().enumerate().flat_map(move |(i, tex)| { + std::iter::once(BindGroupEntry { + binding: binding_offset + (i * 2) as u32, + resource: wgpu::BindingResource::TextureView(&tex.view), + }) + .chain(std::iter::once(BindGroupEntry { + binding: binding_offset + (i * 2 + 1) as u32, + resource: wgpu::BindingResource::Sampler(&tex.sampler), + })) + }) + } + pub fn multi_bindgroup( texs: &[&Texture], device: &Device, layout: &BindGroupLayout, ) -> BindGroup { - let entries = texs - .iter() - .enumerate() - .flat_map(|(i, tex)| { - vec![ - wgpu::BindGroupEntry { - binding: (i * 2) as u32, - resource: wgpu::BindingResource::TextureView(&tex.view), - }, - wgpu::BindGroupEntry { - binding: (i * 2 + 1) as u32, - resource: wgpu::BindingResource::Sampler(&tex.sampler), - }, - ] - }) - .collect::>(); + let entries = Self::multi_bindgroup_entries(0, texs).collect::>(); device.create_bind_group(&wgpu::BindGroupDescriptor { layout, entries: &entries, diff --git a/engine/src/u8slice.rs b/engine/src/u8slice.rs index cbf891e1..1dee8e4e 100644 --- a/engine/src/u8slice.rs +++ b/engine/src/u8slice.rs @@ -48,6 +48,12 @@ impl ToU8Slice for f32 { } } +impl ToU8Slice for u32 { + fn cast_slice(self_slice: &[u32]) -> &[u8] { + bytemuck::cast_slice(self_slice) + } +} + impl ToU8Slice for [f32; 4] { fn cast_slice(self_slice: &[[f32; 4]]) -> &[u8] { bytemuck::cast_slice(self_slice) diff --git a/engine/src/uniform.rs b/engine/src/uniform.rs index e3cf963f..63ad772b 100644 --- a/engine/src/uniform.rs +++ b/engine/src/uniform.rs @@ -1,7 +1,7 @@ use crate::ToU8Slice; use std::sync::atomic::{AtomicBool, Ordering}; use wgpu::util::{BufferInitDescriptor, DeviceExt}; -use wgpu::{BufferBinding, BufferBindingType, ShaderStages}; +use wgpu::{BindGroupEntry, BufferBinding, BufferBindingType, ShaderStages}; pub struct Uniform { pub buffer: wgpu::Buffer, @@ -26,7 +26,7 @@ where let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &layout, - entries: &[wgpu::BindGroupEntry { + entries: &[BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(BufferBinding { buffer: &buffer, @@ -52,6 +52,17 @@ where } } + pub(crate) fn bindgroup_entry(&self, binding: u32) -> BindGroupEntry { + BindGroupEntry { + binding, + resource: wgpu::BindingResource::Buffer(BufferBinding { + buffer: &self.buffer, + offset: 0, + size: None, + }), + } + } + pub fn value(&self) -> &T { &self.value } @@ -77,18 +88,22 @@ where } impl Uniform { - pub fn bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + pub(crate) fn bindgroup_layout_entry(binding: u32) -> wgpu::BindGroupLayoutEntry { + wgpu::BindGroupLayoutEntry { + binding, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, // The dynamic field indicates whether this buffer will change size or not. This is useful if we want to store an array of things in our uniforms. + min_binding_size: None, + }, + count: None, + } + } + + pub(crate) fn bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, // The dynamic field indicates whether this buffer will change size or not. This is useful if we want to store an array of things in our uniforms. - min_binding_size: None, - }, - count: None, - }], + entries: &[Self::bindgroup_layout_entry(0)], label: Some(format!("bglayout for {}", std::any::type_name::()).as_ref()), }) } diff --git a/engine_demo/src/helmet.rs b/engine_demo/src/helmet.rs new file mode 100644 index 00000000..2fe736c1 --- /dev/null +++ b/engine_demo/src/helmet.rs @@ -0,0 +1,37 @@ +use crate::DemoElement; +use engine::meshload::load_mesh; +use engine::{Context, FrameContext, InstancedMesh, InstancedMeshBuilder, MeshInstance}; +use geom::{vec3, Camera, InfiniteFrustrum, LinearColor, Vec3}; + +pub struct Helmet { + mesh: Option, +} + +impl DemoElement for Helmet { + fn name(&self) -> &'static str { + "Helmet" + } + + fn init(ctx: &mut Context) -> Self { + let gfx = &mut ctx.gfx; + + let Ok(mesh) = load_mesh(gfx, "DamagedHelmet.glb") else { + return Self { mesh: None }; + }; + let mut i = InstancedMeshBuilder::::new(mesh); + i.instances.push(MeshInstance { + pos: vec3(0.0, 10.0, 0.0), + dir: Vec3::X * 3.0, + tint: LinearColor::WHITE, + }); + let mesh = i.build(gfx).unwrap(); + + Self { mesh: Some(mesh) } + } + + fn update(&mut self, _ctx: &mut Context) {} + + fn render(&mut self, fc: &mut FrameContext, _cam: &Camera, _frustrum: &InfiniteFrustrum) { + fc.draw(self.mesh.clone()); + } +} diff --git a/engine_demo/src/main.rs b/engine_demo/src/main.rs index f7e150b5..6c47aff9 100644 --- a/engine_demo/src/main.rs +++ b/engine_demo/src/main.rs @@ -1,29 +1,54 @@ +use common::{AudioKind, History}; use engine::meshload::load_mesh; use engine::{ - Context, FrameContext, GfxContext, InstancedMesh, InstancedMeshBuilder, KeyCode, Material, - MeshInstance, MetallicRoughness, MouseButton, + Context, FrameContext, GfxContext, InstancedMeshBuilder, KeyCode, MeshInstance, MouseButton, }; use geom::{vec3, Camera, InfiniteFrustrum, LinearColor, Matrix4, Plane, Radians, Vec2, Vec3}; +use crate::helmet::Helmet; +use crate::spheres::Spheres; +use crate::terrain::Terrain; + +mod helmet; +mod spheres; +mod terrain; + +trait DemoElement { + fn name(&self) -> &'static str; + fn init(ctx: &mut Context) -> Self + where + Self: Sized; + fn update(&mut self, ctx: &mut Context); + fn render(&mut self, fc: &mut FrameContext, cam: &Camera, frustrum: &InfiniteFrustrum); +} + struct State { - meshes: Vec, + demo_elements: Vec<(Box, bool)>, is_captured: bool, camera: Camera, + camera_speed: f32, + frustrum: InfiniteFrustrum, + last_cam: Camera, + + freeze_cam: bool, + + delta: f32, + play_queue: Vec<&'static str>, + + ms_hist: History, } impl engine::framework::State for State { fn new(ctx: &mut Context) -> Self { let gfx = &mut ctx.gfx; + gfx.set_vsync(false); gfx.render_params.value_mut().shadow_mapping_resolution = 2048; gfx.sun_shadowmap = GfxContext::mk_shadowmap(&gfx.device, 2048); gfx.update_simplelit_bg(); - let mesh = load_mesh(gfx, "sphere.glb").unwrap(); - let alb = gfx.material(mesh.materials[0].0).albedo.clone(); - let mut meshes = vec![]; if let Ok(m) = load_mesh(gfx, "DamagedHelmet.glb") { @@ -36,46 +61,36 @@ impl engine::framework::State for State { meshes.push(i.build(gfx).unwrap()); } - const N_MET: i32 = 5; - const N_ROUGH: i32 = 10; - for x in 0..N_ROUGH { - for z in 0..N_MET { - let mut c = mesh.clone(); - - c.materials[0].0 = gfx.register_material(Material::new_raw( - &gfx.device, - alb.clone(), - MetallicRoughness { - metallic: z as f32 / (N_MET as f32 - 1.0), - roughness: x as f32 / (N_ROUGH as f32 - 1.0), - tex: None, - }, - None, - &gfx.palette(), - )); - let mut i = InstancedMeshBuilder::::new(c); - i.instances.push(MeshInstance { - pos: 2.3 * vec3(x as f32, 0.0, z as f32), - dir: Vec3::X, - tint: LinearColor::WHITE, - }); - meshes.push(i.build(gfx).unwrap()); - } - } - let mut camera = Camera::new(vec3(9.0, -30.0, 13.0), 1000.0, 1000.0); camera.dist = 0.0; camera.pitch = Radians(0.0); camera.yaw = Radians(-std::f32::consts::PI / 2.0); + ctx.audio.set_settings(100.0, 100.0, 100.0, 100.0); + Self { + demo_elements: vec![ + (Box::new(Spheres::init(ctx)), true), + (Box::new(Helmet::init(ctx)), true), + (Box::new(Terrain::init(ctx)), true), + ], camera, - meshes, is_captured: false, + delta: 0.0, + play_queue: vec![], + camera_speed: 100.0, + frustrum: InfiniteFrustrum::new([Plane::X; 5]), + last_cam: camera, + freeze_cam: false, + ms_hist: History::new(128), } } fn update(&mut self, ctx: &mut Context) { + self.delta = ctx.delta; + + self.ms_hist.add_value(ctx.delta); + if ctx.input.mouse.pressed.contains(&MouseButton::Left) { let _ = ctx.window.set_cursor_grab(engine::CursorGrabMode::Confined); ctx.window.set_cursor_visible(false); @@ -99,7 +114,8 @@ impl engine::framework::State for State { 3.0 } else { 30.0 - } * delta; + } * delta + * self.camera_speed; if ctx.input.keyboard.pressed_scancode.contains(&17) { self.camera.pos -= self @@ -165,10 +181,13 @@ impl engine::framework::State for State { let params = gfx.render_params.value_mut(); params.time_always = (params.time_always + delta) % 3600.0; - params.sun_col = sun.z.max(0.0).sqrt().sqrt() + params.sun_col = 4.0 + * sun.z.max(0.0).sqrt().sqrt() * LinearColor::new(1.0, 0.95 + sun.z * 0.05, 0.95 + sun.z * 0.05, 1.0); - params.cam_pos = self.camera.eye(); - params.cam_dir = self.camera.dir(); + if !self.freeze_cam { + params.cam_pos = self.camera.eye(); + params.cam_dir = self.camera.dir(); + } params.sun = sun; params.viewport = Vec2::new(gfx.size.0 as f32, gfx.size.1 as f32); self.camera.dist = 300.0; @@ -183,21 +202,61 @@ impl engine::framework::State for State { .unwrap(); self.camera.dist = 0.0; params.shadow_mapping_resolution = 2048; + + for (de, enabled) in &mut self.demo_elements { + if !*enabled { + continue; + } + de.update(ctx); + } + + for v in self.play_queue.drain(..) { + ctx.audio.play(&v, AudioKind::Ui); + } } fn render(&mut self, fc: &mut FrameContext) { - fc.draw(self.meshes.clone()); - } + if !self.freeze_cam { + self.frustrum = InfiniteFrustrum::from_reversez_invviewproj( + self.camera.eye(), + fc.gfx.render_params.value().inv_proj, + ); + self.last_cam = self.camera; + } - fn render_gui(&mut self, ui: &egui::Context) { - egui::Window::new("Hello world!").show(ui, |ui| { - ui.label("Hello world!"); - }); + for (de, enabled) in &mut self.demo_elements { + if !*enabled { + continue; + } + de.render(fc, &self.last_cam, &self.frustrum); + } } fn resized(&mut self, _: &mut Context, size: (u32, u32, f64)) { self.camera.set_viewport(size.0 as f32, size.1 as f32); } + + fn render_gui(&mut self, ui: &egui::Context) { + egui::Window::new("Hello world!").show(ui, |ui| { + let avg_ms = self.ms_hist.avg(); + ui.label(format!( + "Avg (128 frames): {:.1}ms {:.0}FPS", + 1000.0 * avg_ms, + 1.0 / avg_ms + )); + + ui.add(egui::Slider::new(&mut self.camera_speed, 1.0..=100.0).text("Camera speed")); + ui.checkbox(&mut self.freeze_cam, "Freeze camera"); + + for (de, enabled) in &mut self.demo_elements { + ui.checkbox(enabled, de.name()); + } + + if ui.button("play sound: road_lay").clicked() { + self.play_queue.push("road_lay"); + } + }); + } } fn main() { diff --git a/engine_demo/src/spheres.rs b/engine_demo/src/spheres.rs new file mode 100644 index 00000000..114e480e --- /dev/null +++ b/engine_demo/src/spheres.rs @@ -0,0 +1,62 @@ +use crate::DemoElement; +use engine::meshload::load_mesh; +use engine::{ + Context, FrameContext, InstancedMesh, InstancedMeshBuilder, Material, MeshInstance, + MetallicRoughness, +}; +use geom::{vec3, Camera, InfiniteFrustrum, LinearColor, Vec3}; + +pub struct Spheres { + meshes: Vec, +} + +impl DemoElement for Spheres { + fn name(&self) -> &'static str { + "Spheres" + } + + fn init(ctx: &mut Context) -> Self { + let gfx = &mut ctx.gfx; + + let mesh = load_mesh(gfx, "sphere.glb").unwrap(); + let alb = gfx.material(mesh.materials[0].0).albedo.clone(); + + let mut meshes = vec![]; + + const N_MET: i32 = 5; + const N_ROUGH: i32 = 10; + + for x in 0..N_ROUGH { + for z in 0..N_MET { + let mut c = mesh.clone(); + + c.materials[0].0 = gfx.register_material(Material::new_raw( + &gfx.device, + alb.clone(), + MetallicRoughness { + metallic: z as f32 / (N_MET as f32 - 1.0), + roughness: x as f32 / (N_ROUGH as f32 - 1.0), + tex: None, + }, + None, + &gfx.palette(), + )); + let mut i = InstancedMeshBuilder::::new(c); + i.instances.push(MeshInstance { + pos: 2.3 * vec3(x as f32, 0.0, z as f32), + dir: Vec3::X, + tint: LinearColor::WHITE, + }); + meshes.push(i.build(gfx).unwrap()); + } + } + + Self { meshes } + } + + fn update(&mut self, _ctx: &mut Context) {} + + fn render(&mut self, fc: &mut FrameContext, _cam: &Camera, _frustrum: &InfiniteFrustrum) { + fc.draw(self.meshes.clone()); + } +} diff --git a/engine_demo/src/terrain.rs b/engine_demo/src/terrain.rs new file mode 100644 index 00000000..2b8f7936 --- /dev/null +++ b/engine_demo/src/terrain.rs @@ -0,0 +1,92 @@ +use crate::DemoElement; +use engine::terrain::TerrainRender as EngineTerrainRender; +use engine::{Context, FrameContext}; +use geom::{vec2, Camera, InfiniteFrustrum, LinearColor}; + +const CSIZE: usize = 256; +const CRESO: usize = 32; +const MAP_SIZE: usize = 25; + +pub struct Terrain { + terrain: EngineTerrainRender, + heights: Box<[[[[f32; CRESO]; CRESO]; MAP_SIZE]; MAP_SIZE]>, +} + +impl DemoElement for Terrain { + fn name(&self) -> &'static str { + "Terrain" + } + + fn init(ctx: &mut Context) -> Self { + let gfx = &mut ctx.gfx; + + gfx.set_define_flag("DEBUG", true); + + let mut heights: Box<[[[[f32; CRESO]; CRESO]; MAP_SIZE]; MAP_SIZE]> = + vec![[[[0.0; CRESO]; CRESO]; MAP_SIZE]; MAP_SIZE] + .into_boxed_slice() + .try_into() + .unwrap(); + + for x in 0..MAP_SIZE { + for y in 0..MAP_SIZE { + for i in 0..CRESO { + for j in 0..CRESO { + heights[y][x][i][j] = 600.0 + * (0.5 + + geom::fnoise( + 0.01 * vec2((x * CRESO + j) as f32, (y * CRESO + i) as f32), + ) + .0); + } + } + } + } + + let grass = gfx.texture("assets/sprites/grass.jpg", "grass"); + + let mut terrain = EngineTerrainRender::new(gfx, MAP_SIZE as u32, MAP_SIZE as u32, grass); + + for x in 0..MAP_SIZE { + for y in 0..MAP_SIZE { + terrain.update_chunk( + gfx, + (x as u32, y as u32), + &heights[y][x], + |j: usize| { + if y + 1 == MAP_SIZE || j >= CRESO { + return None; + } + Some(heights[y + 1][x][0][j]) + }, + |j: usize| { + if y == 0 || j >= CRESO { + return None; + } + Some(heights[y - 1][x][CRESO - 1][j]) + }, + |i: usize| { + if x + 1 == MAP_SIZE || i >= CRESO { + return None; + } + Some(heights[y][x + 1][i][0]) + }, + |i: usize| { + if x == 0 || i >= CRESO { + return None; + } + Some(heights[y][x - 1][i][CRESO - 1]) + }, + ); + } + } + + Self { terrain, heights } + } + + fn update(&mut self, _ctx: &mut Context) {} + + fn render(&mut self, fc: &mut FrameContext, cam: &Camera, frustrum: &InfiniteFrustrum) { + self.terrain.draw_terrain(cam, frustrum, fc); + } +} diff --git a/geom/src/lib.rs b/geom/src/lib.rs index 384f4d62..64055594 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -33,6 +33,7 @@ mod infinite_frustrum; mod line; mod line3; mod matrix4; +mod noise; mod obb; mod perp_camera; mod plane; @@ -65,6 +66,7 @@ pub use infinite_frustrum::*; pub use line::*; pub use line3::*; pub use matrix4::*; +pub use noise::*; pub use obb::*; pub use perp_camera::*; pub use plane::*; diff --git a/geom/src/noise.rs b/geom/src/noise.rs new file mode 100644 index 00000000..d7d06882 --- /dev/null +++ b/geom/src/noise.rs @@ -0,0 +1,81 @@ +use crate::{vec2, vec3, Vec2, Vec3}; + +fn permute(x: f32) -> f32 { + ((x * 34.0 + 1.0) * x) % 289.0 +} + +const CX: Vec2 = Vec2::new(0.211_324_87, 0.211_324_87); +const CY: Vec2 = Vec2::new(0.366_025_42, 0.366_025_42); +const CZ: Vec2 = Vec2::new(-0.577_350_26, -0.577_350_26); +const K: f32 = 0.024_390_243; + +// Gradient mapping with an extra rotation. +fn grad2(p: Vec2) -> Vec2 { + // Map from a line to a diamond such that a shift maps to a rotation. + let u = permute(permute(p.x) + p.y) * K; + let u = 4.0 * u.fract() - 2.0; + vec2(u.abs() - 1.0, ((u + 1.0).abs() - 2.0).abs() - 1.0) +} + +/* return range is [-0.5; 0.5] */ +#[allow(clippy::many_single_char_names)] +#[inline(always)] +pub fn simplex_noise(pos: Vec2) -> (f32, Vec2) { + let mut i: Vec2 = Vec2::floor(pos + Vec2::splat(Vec2::dot(pos, CY))); + let x0: Vec2 = pos - i + Vec2::splat(Vec2::dot(i, CX)); + let i1 = if x0.x > x0.y { + vec2(1.0, 0.0) + } else { + vec2(0.0, 1.0) + }; + let v1 = x0 + CX - i1; + let v2 = x0 + CZ; + + i.x %= 289.0; + i.y %= 289.0; + + let t: Vec3 = (Vec3::splat(0.5) - vec3(x0.mag2(), v1.mag2(), v2.mag2())).max(Vec3::ZERO); + let t2: Vec3 = t * t; + let t4 = t2 * t2; + + let g0 = grad2(i); + let g1 = grad2(i + i1); + let g2 = grad2(i + Vec2::splat(1.0)); + + let gv = vec3(g0.dot(x0), g1.dot(v1), g2.dot(v2)); + + // Compute partial derivatives in x and y + let temp = t2 * t * gv; + let mut grad = -8.0 + * vec2( + temp.dot(vec3(x0.x, v1.x, v2.x)), + temp.dot(vec3(x0.y, v1.y, v2.y)), + ); + grad.x += t4.dot(vec3(g0.x, g1.x, g2.x)); + grad.y += t4.dot(vec3(g0.y, g1.y, g2.y)); + grad = 40.0 * grad; + + (40.0 * t4.dot(gv), grad) +} + +const FBM_MAG: f32 = 0.4; + +#[inline] +pub fn fnoise(in_wv: Vec2) -> (f32, Vec2) { + let mut dec = in_wv; + + let mut noise: f32 = 0.0; + let mut amplitude: f32 = 1.0; + let mut grad: Vec2 = Vec2::ZERO; + + for _ in 0..4 { + let (n, g) = simplex_noise(dec); + noise += amplitude * n; + grad += g; + + dec *= 1.0 / FBM_MAG; + amplitude *= FBM_MAG; + } + + (noise, grad) +} diff --git a/native_app/src/rendering/map_rendering/terrain.rs b/native_app/src/rendering/map_rendering/terrain.rs index 082a0567..f8cf69a4 100644 --- a/native_app/src/rendering/map_rendering/terrain.rs +++ b/native_app/src/rendering/map_rendering/terrain.rs @@ -19,8 +19,7 @@ impl TerrainRender { let grass = gfx.texture("assets/sprites/grass.jpg", "grass"); - let terrain = - EngineTerrainRender::new(gfx, w, h, simulation::config().border_col.into(), grass); + let terrain = EngineTerrainRender::new(gfx, w, h, grass); /* let ter = &sim.map().terrain; @@ -46,10 +45,17 @@ impl TerrainRender { while let Some(cell) = self.terrain_sub.take_one_updated_chunk() { let chunk = unwrap_retlog!(ter.chunks.get(&cell), "trying to update nonexistent chunk"); - if self - .terrain - .update_chunk(&mut ctx.gfx, cell, &chunk.heights) - { + if self.terrain.update_chunk( + &mut ctx.gfx, + cell, + &chunk.heights, + &|i: usize| None, + &|i: usize| None, + &|i: usize| None, + &|i: usize| { + None // TODO + }, + ) { update_count += 1; #[cfg(not(debug_assertions))] const UPD_PER_FRAME: usize = 20; diff --git a/simulation/src/map/procgen/heightmap.rs b/simulation/src/map/procgen/heightmap.rs index 73f6c2be..bf763d85 100644 --- a/simulation/src/map/procgen/heightmap.rs +++ b/simulation/src/map/procgen/heightmap.rs @@ -1,86 +1,9 @@ -use geom::{vec2, vec3, Vec2, Vec3}; - -fn permute(x: f32) -> f32 { - ((x * 34.0 + 1.0) * x) % 289.0 -} - -const CX: Vec2 = Vec2::new(0.211_324_87, 0.211_324_87); -const CY: Vec2 = Vec2::new(0.366_025_42, 0.366_025_42); -const CZ: Vec2 = Vec2::new(-0.577_350_26, -0.577_350_26); -const K: f32 = 0.024_390_243; - -// Gradient mapping with an extra rotation. -fn grad2(p: Vec2) -> Vec2 { - // Map from a line to a diamond such that a shift maps to a rotation. - let u = permute(permute(p.x) + p.y) * K; - let u = 4.0 * u.fract() - 2.0; - vec2(u.abs() - 1.0, ((u + 1.0).abs() - 2.0).abs() - 1.0) -} - -/* return range is [-0.5; 0.5] */ -#[allow(clippy::many_single_char_names)] -#[inline(always)] -pub(crate) fn simplex_noise(pos: Vec2) -> (f32, Vec2) { - let mut i: Vec2 = Vec2::floor(pos + Vec2::splat(Vec2::dot(pos, CY))); - let x0: Vec2 = pos - i + Vec2::splat(Vec2::dot(i, CX)); - let i1 = if x0.x > x0.y { - vec2(1.0, 0.0) - } else { - vec2(0.0, 1.0) - }; - let v1 = x0 + CX - i1; - let v2 = x0 + CZ; - - i.x %= 289.0; - i.y %= 289.0; - - let t: Vec3 = (Vec3::splat(0.5) - vec3(x0.mag2(), v1.mag2(), v2.mag2())).max(Vec3::ZERO); - let t2: Vec3 = t * t; - let t4 = t2 * t2; - - let g0 = grad2(i); - let g1 = grad2(i + i1); - let g2 = grad2(i + Vec2::splat(1.0)); - - let gv = vec3(g0.dot(x0), g1.dot(v1), g2.dot(v2)); - - // Compute partial derivatives in x and y - let temp = t2 * t * gv; - let mut grad = -8.0 - * vec2( - temp.dot(vec3(x0.x, v1.x, v2.x)), - temp.dot(vec3(x0.y, v1.y, v2.y)), - ); - grad.x += t4.dot(vec3(g0.x, g1.x, g2.x)); - grad.y += t4.dot(vec3(g0.y, g1.y, g2.y)); - grad = 40.0 * grad; - - (40.0 * t4.dot(gv), grad) -} - -const FBM_MAG: f32 = 0.4; - -fn fnoise(ampl: f32, in_wv: Vec2) -> (f32, Vec2) { - let mut dec = Vec2::splat(70.69) + in_wv * ampl; - - let mut noise: f32 = 0.0; - let mut amplitude: f32 = 1.0; - let mut grad: Vec2 = Vec2::ZERO; - - for _ in 0..4 { - let (n, g) = simplex_noise(dec); - noise += amplitude * n; - grad += g; - - dec *= 1.0 / FBM_MAG; - amplitude *= FBM_MAG; - } - - (noise, grad * ampl) -} +use geom::{fnoise, simplex_noise, vec2, Vec2}; pub(crate) fn height(p: Vec2) -> (f32, Vec2) { - let (noise, mut grad) = fnoise(0.00006, p); + let (noise, mut grad) = fnoise(Vec2::splat(70.69) + 0.00006 * p); + grad *= 0.00006; + let ratio = 0.00005; let mut noise = noise - 0.1 + (p.y * 2.0 - 25000.0).abs() * ratio; grad += vec2(0.0, (p.y * 2.0 - 25000.0).signum() * ratio); diff --git a/simulation/src/utils/config.rs b/simulation/src/utils/config.rs index b9e49af2..e272cb4e 100644 --- a/simulation/src/utils/config.rs +++ b/simulation/src/utils/config.rs @@ -17,7 +17,6 @@ pub struct Config { pub grass_col: Color, pub sand_col: Color, pub sea_col: Color, - pub border_col: Color, pub roof_col: Color, pub house_col: Color,