1use ponko::*;
12
13const N: usize = 65;
14const SIZE: f32 = 128.0;
15
16fn height(x: f32, z: f32) -> f32 {
17 (x * 0.05).sin() * 3.0 + (z * 0.07).cos() * 2.0 + (x * 0.013 + z * 0.021).sin() * 6.0
18}
19
20struct Player {
22 id: Entity,
23 yaw: f32,
24 pitch: f32,
25}
26
27fn build_world(ctx: &mut Ctx) {
29 let corner = vec3(-SIZE / 2.0, 0.0, -SIZE / 2.0);
32 let step = SIZE / (N - 1) as f32;
33 let heights: Vec<f32> = (0..N * N)
34 .map(|i| height(corner.x + (i % N) as f32 * step, corner.z + (i / N) as f32 * step))
35 .collect();
36 let ground = ctx.physics3d.heightfield(&heights, SIZE);
37 let look = grass(ctx);
38 let tile = ctx.create_terrain_tile(&heights, SIZE, look);
39 ctx.world.spawn((
40 Transform3D::at(corner),
41 tile,
42 RigidBody3D::fixed(),
43 Collider3D::mesh(ground),
44 ));
45
46 let stone = ctx.create_material(MaterialDesc::color(Color::hex(0x9A948A)));
48 let block = ctx.create_mesh(&MeshData::cube(1.0), stone);
49 for i in 0..8 {
50 let size = vec3(2.0, 0.25 * (i + 1) as f32, 0.6);
51 let at = vec3(6.0, height(6.0, 3.0 + i as f32 * 0.6), 3.0 + i as f32 * 0.6);
52 ctx.world.spawn((
53 Transform3D {
54 scale: size,
55 ..Transform3D::at(at + Vec3::Y * size.y / 2.0)
56 },
57 Model::new(block),
58 RigidBody3D::fixed(),
59 Collider3D::cuboid(size),
60 ));
61 }
62
63 let wood = ctx.create_material(MaterialDesc::color(Color::hex(0xB07A43)));
65 let crate_mesh = ctx.create_mesh(&MeshData::cube(1.0), wood);
66 for i in 0..6 {
67 let (x, z) = (-4.0 + i as f32 * 1.5, -6.0);
68 ctx.world.spawn((
69 Transform3D::at(vec3(x, height(x, z) + 2.0 + i as f32, z)),
70 Model::new(crate_mesh),
71 RigidBody3D::dynamic(),
72 Collider3D::cuboid(Vec3::ONE).density(150.0).friction(0.7),
73 Velocity3D::default(),
74 ));
75 }
76
77 let mut walker = Character3D::new(1.8, 0.3);
79 walker.step_height = 0.3;
80 let id = ctx
81 .world
82 .spawn((Transform3D::at(vec3(0.0, height(0.0, 0.0) + 1.0, 0.0)), walker));
83 ctx.world.insert_resource(Player {
84 id,
85 yaw: 0.0,
86 pitch: 0.0,
87 });
88}
89fn grass(ctx: &mut Ctx) -> Material {
93 let green: Vec<u8> = (0..16 * 16)
94 .flat_map(|i| {
95 let shade = 200 + (i * 37 % 56) as u8;
96 [
97 (60 * shade as u32 / 255) as u8,
98 (110 * shade as u32 / 255) as u8,
99 40,
100 255,
101 ]
102 })
103 .collect();
104 let layers = ctx.create_texture_array(16, 16, &[&green], TextureKind::Color);
105 let weights = ctx.create_texture_array(1, 1, &[&[255, 0, 0, 0]], TextureKind::Data);
106 let corner = vec2(-SIZE / 2.0, -SIZE / 2.0);
107 ctx.create_terrain_material(&TerrainMaterialDesc::new(layers, weights, corner, vec2(SIZE, SIZE)))
108}
109
110fn walk(ctx: &mut Ctx) {
114 let keys = [Key::W, Key::A, Key::S, Key::D, Key::LeftShift].map(|k| ctx.key_down(k));
115 let jump = ctx.key_down(Key::Space);
116 let player = ctx.world.resource::<Player>();
117 let (id, yaw) = (player.id, player.yaw);
118 let Some(c) = ctx.world.get_mut::<Character3D>(id) else {
119 return;
120 };
121 let forward = vec3(-yaw.sin(), 0.0, -yaw.cos());
122 let right = vec3(yaw.cos(), 0.0, -yaw.sin());
123 let axis = |plus: bool, minus: bool| plus as i32 as f32 - minus as i32 as f32;
124 let wish = (forward * axis(keys[0], keys[2]) + right * axis(keys[3], keys[1])).normalize_or_zero();
125 let speed = if keys[4] { 8.0 } else { 4.0 };
126 c.velocity.x = wish.x * speed;
127 c.velocity.z = wish.z * speed;
128 if c.grounded && jump {
129 c.velocity.y = 5.0;
130 }
131}
132fn shove(ctx: &mut Ctx) {
137 let me = ctx.world.resource::<Player>().id;
138 let (eye, forward) = (ctx.camera3d.position, ctx.camera3d.rotation * Vec3::NEG_Z);
139 let filter = QueryFilter3D::default().exclude(me);
140 if let Some(hit) = ctx.physics3d.cast_ray(eye, forward, 30.0, filter)
141 && let Some(mass) = ctx.physics3d.mass(hit.entity)
142 {
143 ctx.physics3d
144 .apply_impulse_at(hit.entity, forward * mass * 6.0, hit.point);
145 }
146}
147fn eyes(ctx: &mut Ctx) {
154 if ctx.mouse_pressed(MouseButton::Left) {
155 if ctx.cursor_locked() {
156 shove(ctx);
157 } else {
158 ctx.set_cursor_locked(true);
159 }
160 } else if ctx.key_pressed(Key::Escape) {
161 if ctx.cursor_locked() {
162 ctx.set_cursor_locked(false);
163 } else {
164 ctx.quit();
165 }
166 }
167 let turn = if ctx.cursor_locked() {
168 ctx.mouse_motion() * 0.003
169 } else {
170 Vec2::ZERO
171 };
172 let p = ctx.world.resource_mut::<Player>();
173 p.yaw -= turn.x;
174 p.pitch = (p.pitch - turn.y).clamp(-1.5, 1.5);
175 let (id, yaw, pitch) = (p.id, p.yaw, p.pitch);
176 if let Some(t) = ctx.world.get::<Transform3D>(id) {
177 ctx.camera3d.position = t.position + Vec3::Y * 1.65;
178 ctx.camera3d.rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
179 }
180}
181fn main() {
184 Game::new("Crabby Engine: 3D physics", 1280, 720)
186 .physics3d(vec3(0.0, -9.81, 0.0)) .sim_system(walk)
188 .system(eyes)
189 .run(|ctx| {
191 ctx.clear(Color::hex(0x9CC4E4));
192 build_world(ctx);
193 });
194}