1use ponko::*;
13
14const TILES: i32 = 5;
15const N: usize = 129;
16const TILE: f32 = 256.0;
17const WATER: f32 = 2.0;
18
19fn noise(x: f32, z: f32) -> f32 {
21 tiling_noise(x, z, i32::MAX)
22}
23
24fn tiling_noise(x: f32, z: f32, period: i32) -> f32 {
26 let hash = |i: i32, j: i32| {
27 let (i, j) = (i.rem_euclid(period), j.rem_euclid(period));
28 let h = (i.wrapping_mul(374_761_393) ^ j.wrapping_mul(668_265_263)) as u32;
29 let h = (h ^ (h >> 13)).wrapping_mul(1_274_126_177);
30 (h >> 8) as f32 / (1u32 << 24) as f32
31 };
32 let (i, j) = (x.floor() as i32, z.floor() as i32);
33 let (fx, fz) = (x - x.floor(), z - z.floor());
34 let (sx, sz) = (fx * fx * (3.0 - 2.0 * fx), fz * fz * (3.0 - 2.0 * fz));
35 let top = hash(i, j) + (hash(i + 1, j) - hash(i, j)) * sx;
36 let bottom = hash(i, j + 1) + (hash(i + 1, j + 1) - hash(i, j + 1)) * sx;
37 top + (bottom - top) * sz
38}
39
40fn height(x: f32, z: f32) -> f32 {
42 let mut h = 0.0;
43 let (mut scale, mut amp) = (1.0 / 300.0, 40.0);
44 for _ in 0..5 {
45 h += (noise(x * scale, z * scale) - 0.5) * amp;
46 scale *= 2.1;
47 amp *= 0.45;
48 }
49 h + 6.0
50}
51
52fn layer(base: [f32; 3], grain: f32) -> Vec<u8> {
54 let size = 256;
55 (0..size * size)
56 .flat_map(|i| {
57 let (x, y) = ((i % size) as f32, (i / size) as f32);
58 let n = tiling_noise(x / 16.0, y / 16.0, 16) * 0.6 + tiling_noise(x / 4.0, y / 4.0, 64) * 0.4;
60 let shade = 1.0 + (n - 0.5) * grain;
61 let c = base.map(|v| ((v * shade).clamp(0.0, 1.0) * 255.0) as u8);
62 [c[0], c[1], c[2], 255]
63 })
64 .collect()
65}
66
67fn ground(ctx: &mut Ctx) -> Material {
68 let layers = [
69 layer([0.24, 0.42, 0.16], 0.6),
70 layer([0.40, 0.31, 0.22], 0.7),
71 layer([0.46, 0.45, 0.42], 0.9),
72 layer([0.70, 0.62, 0.45], 0.4),
73 ];
74 let refs: Vec<&[u8]> = layers.iter().map(Vec::as_slice).collect();
75 let layers = ctx.create_texture_array(256, 256, &refs, TextureKind::Color);
76 let cells = (TILES as usize * (N - 1)) / 2;
78 let area = TILES as f32 * TILE;
79 let origin = -area / 2.0;
80 let weights: Vec<u8> = (0..cells * cells)
81 .flat_map(|i| {
82 let x = origin + (i % cells) as f32 * area / cells as f32;
83 let z = origin + (i / cells) as f32 * area / cells as f32;
84 let h = height(x, z);
85 let slope = (height(x + 2.0, z) - height(x - 2.0, z)).hypot(height(x, z + 2.0) - height(x, z - 2.0)) / 4.0;
86 let sand = (1.0 - (h - WATER).abs() / 1.5).clamp(0.0, 1.0);
87 let rock = ((slope - 0.45) * 4.0).clamp(0.0, 1.0);
88 let dirt = ((h - 18.0) / 8.0).clamp(0.0, 1.0) * (1.0 - rock);
89 let grass = (1.0 - sand - rock - dirt).max(0.0);
90 [grass, dirt, rock, sand].map(|w| (w * 255.0) as u8)
91 })
92 .collect();
93 let weights = ctx.create_texture_array(cells as u32, cells as u32, &[&weights], TextureKind::Data);
94 let desc = TerrainMaterialDesc {
95 layer_size: [5.0, 4.0, 7.0, 3.0, 4.0, 4.0, 4.0, 4.0],
96 ..TerrainMaterialDesc::new(layers, weights, vec2(origin, origin), vec2(area, area))
97 };
98 ctx.create_terrain_material(&desc)
99}
100
101const LAKE: &str = "
105fn vertex(v: Vertex) -> Vertex {
106 var out = v;
107 let p = v.position.xz / material.custom[0].y;
108 let t = frame.time.x * material.custom[0].z;
109 out.position.y += (sin(p.x + t) + sin(p.y * 1.3 + t * 0.8)) * material.custom[0].x;
110 return out;
111}
112
113fn surface(s: Surface) -> Surface {
114 var out = s;
115 let p = s.world.xz / material.custom[0].y;
116 let t = frame.time.x * material.custom[0].z;
117 let slope = vec2<f32>(cos(p.x + t), cos(p.y * 1.3 + t * 0.8) * 1.3) * material.custom[0].x / material.custom[0].y;
118 let fine = vec2<f32>(noise2(s.world.xz * 3.0 + t), noise2(s.world.zx * 3.0 - t)) - 0.5;
119 out.normal = normalize(vec3<f32>(-slope.x - fine.x * 0.15, 1.0, -slope.y - fine.y * 0.15));
120
121 let bent = s.screen + out.normal.xz * 25.0;
122 let depth = max(scene_distance(bent) - distance(s.world, frame.camera.xyz), 0.0);
123 let seen = exp(-depth * material.custom[0].w);
124 let fresnel = mix(0.02, 1.0, pow(1.0 - max(dot(out.normal, s.view), 0.0), 5.0));
125
126 // The mirror image, upside down, nudged by the ripples.
127 let uv = s.screen * frame.screen.zw;
128 let mirrored = vec2<f32>(uv.x, 1.0 - uv.y) + out.normal.xz * 0.008;
129 let reflected = textureSample(custom_texture0, material_sampler, mirrored).rgb;
130
131 out.emissive = scene_color(bent) * seen * (1.0 - fresnel) + reflected * fresnel;
132 out.albedo = s.albedo * (1.0 - seen) * (1.0 - fresnel);
133 out.alpha = 1.0;
134 out.roughness = 0.05;
135 return out;
136}";
137
138const UNDERWATER: &str = "
139fn effect(p: Pixel) -> vec3<f32> {
140 if frame.camera.y > material.custom[0].x {
141 return p.color;
142 }
143 let deep = vec3<f32>(0.02, 0.12, 0.18);
144 let fade = 1.0 - exp(-p.distance * 0.05);
145 return mix(p.color * vec3<f32>(0.6, 0.9, 1.0), deep, fade);
146}";
147
148fn main() {
149 let view = std::env::var("VIEW").unwrap_or_default();
150 let hour = std::env::var("HOUR").ok().and_then(|h| h.parse().ok()).unwrap_or(16.0);
151 let spot = |fits: &dyn Fn(f32) -> bool| {
153 (0..200 * 200)
154 .map(|i| vec2((i % 200) as f32 * 4.0 - 400.0, (i / 200) as f32 * 4.0 - 400.0))
155 .filter(|p| fits(height(p.x, p.y)))
156 .min_by(|a, b| a.length().total_cmp(&b.length()))
157 .unwrap_or(Vec2::ZERO)
158 };
159 let (start, (mut yaw, mut pitch)) = match view.as_str() {
160 "low" => {
161 let p = spot(&|h| h > WATER + 4.0);
162 (vec3(p.x, height(p.x, p.y) + 1.8, p.y), (0.6f32, -0.08f32))
163 }
164 "under" => {
165 let p = spot(&|h| h < WATER - 2.0 && h > WATER - 5.0);
166 (vec3(p.x, WATER - 1.0, p.y), (0.6, -0.6))
167 }
168 "cliff" => {
169 let slope = |p: Vec2| {
171 (height(p.x + 2.0, p.y) - height(p.x - 2.0, p.y)).hypot(height(p.x, p.y + 2.0) - height(p.x, p.y - 2.0))
172 };
173 let steep = (0..200 * 200)
174 .map(|i| vec2((i % 200) as f32 * 4.0 - 400.0, (i / 200) as f32 * 4.0 - 400.0))
175 .filter(|p| height(p.x, p.y) > WATER + 2.0)
176 .max_by(|a, b| slope(*a).total_cmp(&slope(*b)))
177 .unwrap_or(Vec2::ZERO);
178 let down = -vec2(
180 height(steep.x + 2.0, steep.y) - height(steep.x - 2.0, steep.y),
181 height(steep.x, steep.y + 2.0) - height(steep.x, steep.y - 2.0),
182 )
183 .normalize_or_zero();
184 let eye = steep + down * 30.0;
185 let at = vec3(eye.x, height(eye.x, eye.y).max(WATER) + 6.0, eye.y);
186 let to = vec3(steep.x, height(steep.x, steep.y), steep.y) - at;
187 (at, (f32::atan2(-to.x, -to.z), (to.y / to.length()).asin()))
188 }
189 _ => (vec3(-300.0, 90.0, 300.0), (-0.8, -0.25)),
190 };
191 let mut parked: Vec<ScreenEffect> = Vec::new();
192 let bench = std::env::var("BENCH").is_ok();
193 Game::new("Crabby Engine: terrain", 1280, 720)
194 .vsync(!bench)
195 .fullscreen(bench)
196 .system(move |ctx: &mut Ctx| {
197 if ctx.key_pressed(Key::Escape) {
198 ctx.quit();
199 }
200 let dt = ctx.dt();
201 if ctx.key_down(Key::Left) {
202 yaw += dt;
203 }
204 if ctx.key_down(Key::Right) {
205 yaw -= dt;
206 }
207 if ctx.key_down(Key::Up) {
208 pitch = (pitch + dt).min(1.4);
209 }
210 if ctx.key_down(Key::Down) {
211 pitch = (pitch - dt).max(-1.4);
212 }
213 let rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
214 let forward = rotation * Vec3::NEG_Z;
215 let right = rotation * Vec3::X;
216 let speed = 60.0 * dt;
217 let mut go = Vec3::ZERO;
218 for (key, dir) in [
219 (Key::W, forward),
220 (Key::S, -forward),
221 (Key::D, right),
222 (Key::A, -right),
223 (Key::E, Vec3::Y),
224 (Key::Q, Vec3::NEG_Y),
225 ] {
226 if ctx.key_down(key) {
227 go += dir;
228 }
229 }
230 ctx.camera3d.position += go * speed;
231 ctx.camera3d.rotation = rotation;
232 if let Some(mirror) = ctx.target_cameras.first().map(|c| c.target) {
234 ctx.target_cameras[0] = TargetCamera::reflection(ctx.camera3d, mirror, WATER);
235 }
236 if ctx.key_pressed(Key::U) {
237 std::mem::swap(&mut ctx.effects, &mut parked);
238 }
239 if ctx.frame() == 3
240 && let Ok(path) = std::env::var("SHOT")
241 {
242 ctx.save_screenshot(path);
243 }
244 if ctx.frame() == 5 && std::env::var("SHOT").is_ok() {
245 ctx.quit();
246 }
247 if ctx.frame() == 300 {
248 let stats = ctx.render_stats();
249 println!("{stats:?} models drawn: {}", ctx.models_drawn());
250 if bench {
251 ctx.quit();
252 }
253 }
254 })
255 .run(move |ctx| {
256 ctx.post.bloom = 0.1;
257 ctx.lighting.sun_direction = Sky::sun_direction_at(hour);
258 ctx.lighting.shadow_distance = 400.0;
259 ctx.camera3d.far = 3000.0;
260 ctx.sky.fog_density = 0.0015;
261 ctx.sky.fog_height = 0.0;
262 ctx.sky.fog_falloff = 60.0;
263
264 let ground = ground(ctx);
265 let half = TILES / 2;
266 for tz in -half..=half {
267 for tx in -half..=half {
268 let corner = vec3(tx as f32 * TILE - TILE / 2.0, 0.0, tz as f32 * TILE - TILE / 2.0);
269 let heights: Vec<f32> = (0..N * N)
270 .map(|i| height(corner.x + (i % N) as f32 * 2.0, corner.z + (i / N) as f32 * 2.0))
271 .collect();
272 let tile = ctx.create_terrain_tile(&heights, TILE, ground);
273 ctx.world.spawn((Transform3D::at(corner), tile));
274 }
275 }
276
277 let size = ctx.window_size();
279 let mirror = ctx.create_render_target((size.x as u32 / 2).max(1), (size.y as u32 / 2).max(1));
280 ctx.target_cameras
281 .push(TargetCamera::reflection(ctx.camera3d, mirror, WATER));
282 let water = ctx.create_shader("lake", LAKE);
283 let lake = ctx.create_material(MaterialDesc {
284 shader: Some(water),
285 blend: Blend::Transparent,
286 custom_textures: [Some(mirror), None],
287 custom: [vec4(0.06, 1.5, 1.2, 1.2), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
288 ..MaterialDesc::color(Color::hex(0x1E4D6B))
289 });
290 let surface = ctx.create_mesh(&MeshData::plane(TILES as f32 * TILE), lake);
291 ctx.world
292 .spawn((Transform3D::at(vec3(0.0, WATER, 0.0)), Model::new(surface)));
293
294 let underwater = ctx.create_effect("underwater", UNDERWATER);
295 let level = ctx.create_material(MaterialDesc {
296 custom: [vec4(WATER, 0.0, 0.0, 0.0), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
297 ..Default::default()
298 });
299 ctx.effects.push(ScreenEffect::new(underwater).with_material(level));
300
301 ctx.camera3d.position = start;
302 });
303}