1use std::collections::HashMap;
13
14use ponko::*;
15
16const N: usize = 129;
17const TILE: f32 = 256.0;
18const LOAD: f32 = 1400.0;
20const KEEP: f32 = 1700.0;
21const STARTS: usize = 4;
23const UPLOADS: usize = 2;
24
25fn noise(x: f32, z: f32) -> f32 {
26 let hash = |i: i32, j: i32| {
27 let h = (i.wrapping_mul(374_761_393) ^ j.wrapping_mul(668_265_263)) as u32;
28 let h = (h ^ (h >> 13)).wrapping_mul(1_274_126_177);
29 (h >> 8) as f32 / (1u32 << 24) as f32
30 };
31 let (i, j) = (x.floor() as i32, z.floor() as i32);
32 let (fx, fz) = (x - x.floor(), z - z.floor());
33 let (sx, sz) = (fx * fx * (3.0 - 2.0 * fx), fz * fz * (3.0 - 2.0 * fz));
34 let top = hash(i, j) + (hash(i + 1, j) - hash(i, j)) * sx;
35 let bottom = hash(i, j + 1) + (hash(i + 1, j + 1) - hash(i, j + 1)) * sx;
36 top + (bottom - top) * sz
37}
38
39fn height(x: f32, z: f32) -> f32 {
41 let mut h = 0.0;
42 let (mut scale, mut amp) = (1.0 / 700.0, 130.0);
43 for _ in 0..6 {
44 h += (noise(x * scale, z * scale) - 0.5) * amp;
45 scale *= 2.1;
46 amp *= 0.45;
47 }
48 let ridge = 1.0 - (noise(x / 1800.0 + 7.3, z / 1800.0 - 2.1) - 0.5).abs() * 2.0;
49 h + ridge.powi(3) * 90.0
50}
51
52const GROUND: &str = "
54fn surface(s: Surface) -> Surface {
55 var out = s;
56 let steep = 1.0 - s.normal.y;
57 let grain = noise2(s.world.xz * 0.35) * 0.5 + noise2(s.world.xz * 0.05) * 0.5;
58 let grass = mix(vec3<f32>(0.20, 0.36, 0.13), vec3<f32>(0.33, 0.42, 0.18), grain);
59 let rock = mix(vec3<f32>(0.33, 0.31, 0.29), vec3<f32>(0.48, 0.46, 0.43), grain);
60 var c = mix(grass, rock, smoothstep(0.18, 0.32, steep));
61 let snow_line = 95.0 + grain * 20.0;
62 c = mix(c, vec3<f32>(0.92, 0.94, 0.97), smoothstep(snow_line, snow_line + 8.0, s.world.y) * (1.0 - smoothstep(0.35, 0.55, steep)));
63 out.albedo = c;
64 out.roughness = 0.9;
65 return out;
66}";
67
68enum Tile {
70 Building(Job<TerrainTileData>),
71 Live(Entity, TerrainTile),
72}
73
74fn tile_data(corner: Vec3) -> TerrainTileData {
76 let heights: Vec<f32> = (0..N * N)
77 .map(|i| height(corner.x + (i % N) as f32 * 2.0, corner.z + (i / N) as f32 * 2.0))
78 .collect();
79 TerrainTileData::new(&heights, TILE)
80}
81
82fn main() {
83 let bench = std::env::var("BENCH").is_ok();
84 let shot = std::env::var("SHOT").ok();
85 let mut cells = CellStreamer::new(TILE, LOAD, KEEP);
86 let mut tiles: HashMap<(i32, i32), Tile> = HashMap::new();
87 let mut ground: Option<Material> = None;
88 let (mut yaw, mut speed, mut altitude) = (0.4f32, if bench { 600.0f32 } else { 120.0 }, 90.0f32);
89 let (mut uploaded, mut freed, mut upload_time, mut worst_upload) = (0usize, 0usize, 0.0f64, 0.0f64);
91 Game::new("Crabby Engine: streaming", 1280, 720)
92 .vsync(!bench)
93 .system(move |ctx: &mut Ctx| {
94 if ctx.key_pressed(Key::Escape) {
95 ctx.quit();
96 }
97 let dt = ctx.dt();
98 if ctx.key_down(Key::Left) {
99 yaw += dt * 0.8;
100 }
101 if ctx.key_down(Key::Right) {
102 yaw -= dt * 0.8;
103 }
104 if ctx.key_down(Key::W) {
105 speed = (speed + 80.0 * dt).min(600.0);
106 }
107 if ctx.key_down(Key::S) {
108 speed = (speed - 80.0 * dt).max(0.0);
109 }
110 if ctx.key_down(Key::E) {
111 altitude += 40.0 * dt;
112 }
113 if ctx.key_down(Key::Q) {
114 altitude = (altitude - 40.0 * dt).max(5.0);
115 }
116 let rotation = Quat::from_rotation_y(yaw);
118 let mut at = ctx.camera3d.position + rotation * Vec3::NEG_Z * speed * dt;
119 let ahead = at + rotation * Vec3::NEG_Z * 120.0;
120 let floor = height(at.x, at.z).max(height(ahead.x, ahead.z));
121 at.y += (floor + altitude - at.y) * (dt * 1.5).min(1.0);
122 ctx.camera3d.position = at;
123 ctx.camera3d.rotation = rotation * Quat::from_rotation_x(-0.22);
124
125 cells.update(at, STARTS);
127 for &cell in cells.to_load() {
128 let corner = cells.corner(cell);
129 tiles.insert(cell, Tile::Building(Job::spawn(move || tile_data(corner))));
130 }
131 for cell in cells.to_drop() {
132 if let Some(Tile::Live(entity, tile)) = tiles.remove(cell) {
134 ctx.world.despawn(entity);
135 ctx.free_terrain_tile(tile);
136 freed += 1;
137 }
138 }
139 let ground = *ground.get_or_insert_with(|| {
141 let shader = ctx.create_shader("ground", GROUND);
142 ctx.create_material(MaterialDesc {
143 shader: Some(shader),
144 ..MaterialDesc::color(Color::WHITE)
145 })
146 });
147 let mut uploads = 0;
148 for (&cell, tile) in tiles.iter_mut() {
149 if uploads == UPLOADS {
150 break;
151 }
152 if let Tile::Building(job) = tile
153 && let Some(data) = job.take()
154 {
155 let start = Instant::now();
156 let made = ctx.upload_terrain_tile(&data, ground);
157 let entity = ctx.world.spawn((Transform3D::at(cells.corner(cell)), made));
158 let took = start.elapsed().as_secs_f64() * 1000.0;
159 upload_time += took;
160 worst_upload = worst_upload.max(took);
161 uploaded += 1;
162 uploads += 1;
163 *tile = Tile::Live(entity, made);
164 }
165 }
166
167 if ctx.frame() == 400
168 && let Some(path) = &shot
169 {
170 ctx.save_screenshot(path);
171 }
172 if ctx.frame() == 402 && shot.is_some() {
173 ctx.quit();
174 }
175 if bench && ctx.frame() == 3000 {
176 let live = tiles.values().filter(|t| matches!(t, Tile::Live(..))).count();
177 println!(
178 "{uploaded} tiles uploaded ({:.2} ms each, worst {worst_upload:.2} ms), {freed} freed, {live} live; {:?}",
179 upload_time / uploaded.max(1) as f64,
180 ctx.render_stats()
181 );
182 ctx.quit();
183 }
184 })
185 .run(move |ctx| {
186 ctx.post.bloom = 0.08;
187 ctx.lighting.sun_direction = Sky::sun_direction_at(15.5);
188 ctx.lighting.shadow_distance = 500.0;
189 ctx.camera3d.far = LOAD + TILE;
190 ctx.sky.fog_density = 0.0007;
192 ctx.sky.fog_height = 40.0;
193 ctx.sky.fog_falloff = 90.0;
194 ctx.camera3d.position = vec3(0.0, height(0.0, 0.0) + 60.0, 0.0);
195 });
196}