pub struct Sky {Show 14 fields
pub atmosphere: bool,
pub haze: f32,
pub sun_size: f32,
pub stars: f32,
pub night_light: Color,
pub fog_density: f32,
pub fog_height: f32,
pub fog_falloff: f32,
pub fog_color: Color,
pub fog_start: f32,
pub fog_end: f32,
pub fog_sun_glow: f32,
pub fog_sun_focus: f32,
pub light_shafts: f32,
}Expand description
The sky behind the 3D world, and the fog in front of it: ctx.sky.
With atmosphere on (the default), the sky is worked out from the sun
in Lighting: blue by day, orange and red when the sun is low, dark
with stars at night. The sun itself turns orange near the horizon and
sets, and the sky’s light replaces Lighting::sky_color and
ground_color. One world unit is taken to be a meter.
// Late evening, a little hazy, with fog lying in the valleys.
ctx.lighting.sun_direction = Sky::sun_direction_at(18.5);
ctx.sky.haze = 3.0;
ctx.sky.fog_density = 0.01;
ctx.sky.fog_height = 5.0;Fields§
§atmosphere: boolDraw a physical sky lit by the sun, and light the scene from it.
Off: the background is the clear color and the light comes from
Lighting’s colors. Default on.
haze: f32Dust and water in the air, from 0 (none) up: more makes the sky paler, the glow around the sun bigger and sunsets redder. Default 1.
sun_size: f32The sun’s size in the sky, in degrees across. Default 1.5 (the real sun is about 0.5, which looks small on a screen).
stars: f32How bright the stars are at night, 0 for none. Default 1.
night_light: ColorLight at night, when the sun is down: moonlight and starlight. Default a dim blue.
fog_density: f32How thick the fog is near the ground: the share of light it takes away per world unit. 0 (the default) is no fog; 0.002 hazes the far distance; 0.05 is thick.
fog_height: f32The height the fog lies at: below it, it’s thickest. Default 0.
fog_falloff: f32How quickly the fog thins out above fog_height, in world units:
it halves about every 0.7 times this. Default 50.
fog_color: ColorThe fog’s color without an atmosphere; with one, the fog takes the sky’s color behind it. Default a pale grey-blue.
fog_start: f32Distance fog: none closer than fog_start world units, all fog
past fog_end, and a straight blend between. It hides the far end
of a big world whatever the height. Off while fog_end is not
past fog_start (default 0 and 0). The sky itself takes none.
fog_end: f32Where the distance fog hides everything. Default 0 (off).
fog_sun_glow: f32How much sunlight the fog scatters toward the camera, so it glows when looking toward the sun: 0 (the default) none, 0.1 a soft glow.
fog_sun_focus: f32How tight that glow is around the sun, 0 (all around) to just under 1 (a small bright halo). Default 0.7.
light_shafts: f32Light shafts: how much shadows cut the fog’s sun glow, from 0 (the
default, off) to 1. Where the fog is in a tree’s or a house’s
shadow it stops glowing, so beams of sunlight show between them.
It needs fog, a fog_sun_glow and the sun’s shadows, and reaches
as far as they do. It costs 16 shadow map taps per pixel, and it is
grainy without AntiAliasing::Taa in
ctx.post, which smooths it out.
Implementations§
Source§impl Sky
impl Sky
Sourcepub fn sun_direction_at(hour: f32) -> Vec3
pub fn sun_direction_at(hour: f32) -> Vec3
Which way sunlight travels at hour o’clock (0 to 24), for a day
and night cycle: the sun rises in the east (+X) at 6, is highest at
noon (60° up, to the south, -Z) and sets in the west at 18.
assert!(Sky::sun_direction_at(12.0).y < -0.8, "noon: from high up");
assert!(Sky::sun_direction_at(0.0).y > 0.0, "midnight: from below the ground");Examples found in repository?
39fn main() {
40 let scene = std::env::var("SCENE").unwrap_or_else(|_| {
41 eprintln!("say which scene: SCENE=town/Town.json (a file under assets/)");
42 std::process::exit(2);
43 });
44 if std::env::var("HEADLESS").is_ok() {
45 Game::new("scene viewer", 1, 1).run_headless(0, |ctx| {
46 let start = Instant::now();
47 ctx.load_scene(&scene).unwrap_or_else(|e| panic!("{e}"));
48 let models = ctx.world.query::<&Model>().iter().count();
49 let (middle, size) = extent(&mut ctx.world);
50 println!(
51 "{scene}: {models} models in {:.2} s, around {middle} across {size:.0} m",
52 start.elapsed().as_secs_f32()
53 );
54 });
55 return;
56 }
57 let shot = std::env::var("SHOT").ok();
58 let (mut yaw, mut pitch) = (0.0f32, -0.3f32);
59 Game::new("Crabby Engine: scene viewer", 1280, 720)
60 .system(move |ctx: &mut Ctx| {
61 if ctx.key_pressed(Key::Escape) {
62 ctx.quit();
63 }
64 let dt = ctx.dt();
65 if ctx.frame() == 1 {
66 let (y, x, _) = ctx.camera3d.rotation.to_euler(glam::EulerRot::YXZ);
67 (yaw, pitch) = (y, x);
68 }
69 let turn = 1.2 * dt;
70 if ctx.key_down(Key::Left) {
71 yaw += turn;
72 }
73 if ctx.key_down(Key::Right) {
74 yaw -= turn;
75 }
76 if ctx.key_down(Key::Up) {
77 pitch = (pitch + turn).min(1.5);
78 }
79 if ctx.key_down(Key::Down) {
80 pitch = (pitch - turn).max(-1.5);
81 }
82 let rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
83 let speed = if ctx.key_down(Key::LeftShift) { 120.0 } else { 20.0 } * dt;
84 let mut go = Vec3::ZERO;
85 for (key, way) in [
86 (Key::W, Vec3::NEG_Z),
87 (Key::S, Vec3::Z),
88 (Key::A, Vec3::NEG_X),
89 (Key::D, Vec3::X),
90 ] {
91 if ctx.key_down(key) {
92 go += rotation * way;
93 }
94 }
95 if ctx.key_down(Key::E) {
96 go += Vec3::Y;
97 }
98 if ctx.key_down(Key::Q) {
99 go -= Vec3::Y;
100 }
101 ctx.camera3d.position += go * speed;
102 ctx.camera3d.rotation = rotation;
103 if ctx.frame() == 300
104 && let Some(path) = &shot
105 {
106 ctx.save_screenshot(path);
107 }
108 if ctx.frame() == 302 && shot.is_some() {
109 ctx.quit();
110 }
111 })
112 .run(move |ctx| {
113 ctx.load_scene(&scene).unwrap_or_else(|e| panic!("{e}"));
114 ctx.lighting.sun_direction = Sky::sun_direction_at(15.0);
115 ctx.lighting.shadow_distance = 400.0;
116 ctx.camera3d.far = 3000.0;
117 ctx.post.bloom = 0.05;
118 let (middle, size) = extent(&mut ctx.world);
119 let (at, yaw, pitch) =
120 camera_from_env().unwrap_or((middle + vec3(0.0, size * 0.25, size * 0.4), 0.0, -0.5));
121 ctx.camera3d.position = at;
122 ctx.camera3d.rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
123 });
124}More examples
44fn main() {
45 let mut seed = 11u32;
46 let mut random = move || {
47 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
48 (seed >> 8) as f32 / (1 << 24) as f32
49 };
50 let mut next_drop = 0.0;
51 Game::new("Crabby Engine: compute", 1280, 720)
52 .system(move |ctx: &mut Ctx| {
53 if ctx.key_pressed(Key::Escape) {
54 ctx.quit();
55 }
56 let Some(pass) = ctx.compute_passes.first().copied() else {
57 return;
58 };
59 let Some(drops) = pass.material else { return };
60 // Rain every so often, or a stone where the mouse clicks.
61 let mut drop = Vec4::ZERO;
62 next_drop -= ctx.dt();
63 if next_drop <= 0.0 {
64 next_drop = 0.05 + random() * 0.2;
65 drop = vec4(random(), random(), 0.3 + random() * 0.4, 0.0);
66 }
67 if ctx.mouse_pressed(MouseButton::Left) {
68 let (from, dir) = ctx.camera3d.screen_ray(ctx.mouse_pos());
69 if dir.y < 0.0 {
70 let at = from + dir * (-from.y / dir.y);
71 drop = vec4(at.x / SIZE + 0.5, at.z / SIZE + 0.5, 3.0, 0.0);
72 }
73 }
74 ctx.update_material(
75 drops,
76 MaterialDesc {
77 custom: [drop, Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
78 ..Default::default()
79 },
80 );
81 if ctx.frame() == 120
82 && let Ok(path) = std::env::var("SHOT")
83 {
84 ctx.save_screenshot(path);
85 }
86 if ctx.frame() == 122 && std::env::var("SHOT").is_ok() {
87 ctx.quit();
88 }
89 })
90 .run(|ctx| {
91 ctx.camera3d.position = vec3(0.0, 7.0, 10.0);
92 ctx.camera3d.look_at(vec3(0.0, 0.0, -1.0));
93 ctx.lighting.sun_direction = Sky::sun_direction_at(15.0);
94 ctx.post.bloom = 0.15;
95
96 let heights = ctx.create_render_target(512, 512);
97 let ripples = ctx.create_compute("ripples", RIPPLES);
98 let drops = ctx.create_material(MaterialDesc::default());
99 ctx.compute_passes
100 .push(ComputePass::new(ripples, heights).with_material(drops));
101
102 let shader = ctx.create_shader("water", WATER);
103 let water = ctx.create_material(MaterialDesc {
104 shader: Some(shader),
105 custom_textures: [Some(heights), None],
106 ..MaterialDesc::color(Color::hex(0x2A5A75))
107 });
108 let surface = ctx.create_mesh(&MeshData::plane(SIZE), water);
109 ctx.world.spawn((Transform3D::default(), Model::new(surface)));
110
111 let stone = ctx.create_material(MaterialDesc {
112 roughness: 0.8,
113 ..MaterialDesc::color(Color::hex(0x8A8578))
114 });
115 let rock = ctx.create_mesh(&MeshData::sphere(0.6, 16), stone);
116 for i in 0..40 {
117 let a = i as f32 / 40.0 * std::f32::consts::TAU;
118 let edge = vec3(a.cos(), 0.0, a.sin()) * SIZE * 0.55;
119 let t = Transform3D {
120 scale: Vec3::splat(0.8 + (i % 3) as f32 * 0.3),
121 ..Transform3D::at(edge)
122 };
123 ctx.world.spawn((t, Model::new(rock)));
124 }
125 let grass = ctx.create_material(MaterialDesc {
126 roughness: 0.95,
127 ..MaterialDesc::color(Color::hex(0x4E6B33))
128 });
129 let ground = ctx.create_mesh(&MeshData::plane(200.0), grass);
130 ctx.world
131 .spawn((Transform3D::at(vec3(0.0, -0.3, 0.0)), Model::new(ground)));
132 });
133}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 // The streaming's cost on the game's thread, for BENCH.
90 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 // Fly on, keeping above the hills ahead.
117 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 // Which tiles to start and drop.
126 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 // A job still building is dropped with its result.
133 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 // Finished tiles go on the GPU, a few a frame.
140 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 // Fog thick enough that tiles appear behind it, not at an edge.
191 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}40fn main() {
41 let env = |name| std::env::var(name).ok().and_then(|v| v.parse::<f32>().ok());
42 let mut hour = env("HOUR").unwrap_or(15.5);
43 let mut fog = env("FOG").unwrap_or(0.0) > 0.0;
44 Game::new("Crabby Engine: shaders", 1280, 720)
45 .system(move |ctx: &mut Ctx| {
46 if ctx.key_pressed(Key::Escape) {
47 ctx.quit();
48 }
49 // Two hours a second.
50 if ctx.key_down(Key::Right) {
51 hour = (hour + ctx.dt() * 2.0) % 24.0;
52 }
53 if ctx.key_down(Key::Left) {
54 hour = (hour - ctx.dt() * 2.0).rem_euclid(24.0);
55 }
56 if ctx.key_pressed(Key::F) {
57 fog = !fog;
58 }
59 ctx.lighting.sun_direction = Sky::sun_direction_at(hour);
60 ctx.sky.fog_density = if fog { 0.02 } else { 0.0 };
61 ctx.sky.fog_falloff = 8.0;
62 if ctx.frame() == 2
63 && let Ok(path) = std::env::var("SHOT")
64 {
65 ctx.save_screenshot(path);
66 }
67 if ctx.frame() == 4 && std::env::var("SHOT").is_ok() {
68 ctx.quit();
69 }
70 })
71 .run(|ctx| {
72 ctx.clear(Color::hex(0x9CC4E4));
73 ctx.camera3d.position = vec3(0.0, 4.0, 11.0);
74 ctx.camera3d.look_at(vec3(0.0, 0.5, 0.0));
75 ctx.post.bloom = 0.1;
76
77 let ground_look = MaterialDesc {
78 roughness: 0.95,
79 ..MaterialDesc::color(Color::hex(0x5E7A3A))
80 };
81 let ground = ctx.create_material(ground_look);
82 let ground = ctx.create_mesh(&MeshData::plane(600.0), ground);
83 ctx.world.spawn((Transform3D::default(), Model::new(ground)));
84
85 let water = ctx.load_shader("shaders/water.wgsl");
86 let lake = ctx.create_material(MaterialDesc {
87 shader: Some(water),
88 blend: Blend::Transparent,
89 custom: [vec4(0.06, 1.5, 1.2, 1.2), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
90 ..MaterialDesc::color(Color::hex(0x1E4D6B))
91 });
92 let mut surface = MeshData::plane(12.0);
93 // More vertices, so the waves have something to move.
94 surface = subdivide(surface, 5);
95 let lake = ctx.create_mesh(&surface, lake);
96 ctx.world
97 .spawn((Transform3D::at(vec3(-3.0, 0.15, -4.0)), Model::new(lake)));
98
99 // The lake bed, under the water: sand sloping down, and stones.
100 let sand = ctx.create_material(MaterialDesc {
101 roughness: 0.9,
102 ..MaterialDesc::color(Color::hex(0xC2A878))
103 });
104 let bed = ctx.create_mesh(&MeshData::plane(12.0), sand);
105 let tilt = Quat::from_rotation_x(0.12);
106 ctx.world.spawn((
107 Transform3D {
108 rotation: tilt,
109 ..Transform3D::at(vec3(-3.0, -0.6, -4.0))
110 },
111 Model::new(bed),
112 ));
113 let stone = ctx.create_material(MaterialDesc {
114 roughness: 0.7,
115 ..MaterialDesc::color(Color::hex(0x8A8A80))
116 });
117 let pebble = ctx.create_mesh(&MeshData::sphere(0.35, 16), stone);
118 for i in 0..12 {
119 let at = vec3(
120 -7.0 + i as f32 * 0.8,
121 -0.3 - (i % 3) as f32 * 0.2,
122 -3.0 + (i % 4) as f32 * -1.5,
123 );
124 ctx.world.spawn((Transform3D::at(at), Model::new(pebble)));
125 }
126
127 let wind = ctx.load_shader("shaders/wind.wgsl");
128 let grass = ctx.create_material(MaterialDesc {
129 shader: Some(wind),
130 double_sided: true,
131 roughness: 0.8,
132 custom: [vec4(0.25, 2.0, 0.0, 0.0), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
133 ..MaterialDesc::color(Color::hex(0x7FA83C))
134 });
135 let blade = ctx.create_mesh(&blade(), grass);
136 let mut seed = 7u32;
137 let mut random = move || {
138 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
139 (seed >> 8) as f32 / (1 << 24) as f32
140 };
141 for _ in 0..6000 {
142 let at = vec3(random() * 14.0 - 2.0, 0.0, random() * 10.0 - 3.0);
143 if at.x < 4.0 && at.z < 2.5 {
144 continue; // the lake
145 }
146 let t = Transform3D {
147 rotation: Quat::from_rotation_y(random() * 6.3),
148 scale: Vec3::splat(0.6 + random() * 0.8),
149 ..Transform3D::at(at)
150 };
151 ctx.world.spawn((t, Model::new(blade)));
152 }
153
154 let tiles = ctx.create_shader("tiles", TILES);
155 let ball = ctx.create_material(MaterialDesc {
156 shader: Some(tiles),
157 ..Default::default()
158 });
159 let ball = ctx.create_mesh(&MeshData::sphere(1.0, 48), ball);
160 ctx.world
161 .spawn((Transform3D::at(vec3(3.5, 1.0, 1.0)), Model::new(ball)));
162 });
163}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 // The first spot nearest the middle where the ground fits.
152 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 // The steepest ground near the middle, seen from 30 m off its foot.
170 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 // Downhill, away from the face.
179 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 // The lake's mirror follows the camera.
233 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 // Half the window's size is plenty for a rippled mirror.
278 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}Trait Implementations§
impl Copy for Sky
impl StructuralPartialEq for Sky
Auto Trait Implementations§
impl Forget for Sky
impl Freeze for Sky
impl RefUnwindSafe for Sky
impl Send for Sky
impl Sync for Sky
impl Unpin for Sky
impl UnsafeUnpin for Sky
impl UnwindSafe for Sky
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> Component for T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
impl<T> MaybeSync for T
impl<T> MaybeSync for T
Source§impl<T> Pointable for T
impl<T> Pointable for T
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<R, P> ReadPrimitive<R> for P
impl<R, P> ReadPrimitive<R> for P
Source§fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
ReadEndian::read_from_little_endian().impl<T> Scalar for T
impl<T> SerializableAny for T
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.