pub struct CharacterBody { /* private fields */ }Expand description
A walking body: feet-positioned collision box + velocity +
contact flags. Construct with CharacterBody::new, place with
teleport, then call
walk once per frame.
Implementations§
Source§impl CharacterBody
impl CharacterBody
Sourcepub fn new(def: CharacterDef) -> Self
pub fn new(def: CharacterDef) -> Self
A body at the world origin with zero velocity — call
teleport before the first walk.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}
130
131/// WT — the water + swimming section's snippets: a deep pool the body
132/// falls into, floats in, dives through and breaches out of.
133fn swim_demo() {
134 // ANCHOR: water
135 // Physics water is a WaterVolume on the grid — a grid-local voxel
136 // AABB whose TOP face (min z — +z is down) is the surface. It is
137 // independent of the voxels: fill the same region with a
138 // volumetric-material shell for the visuals, or don't.
139 let mut scene = Scene::new();
140 let id = scene.add_grid(GridTransform::identity());
141 let g = scene.grid_mut(id).expect("grid");
142 // Basin floor, then 20 voxels of water above it (surface z = 100).
143 g.set_rect(
144 IVec3::new(20, 20, 120),
145 IVec3::new(140, 140, 130),
146 Some(VoxColor::rgb(0x50, 0x90, 0x50)),
147 );
148 g.add_water_volume(IVec3::new(20, 20, 100), IVec3::new(140, 140, 119));
149
150 // A Walk-mode body dropped in swims AUTOMATICALLY once submerged
151 // past `swim_enter_frac` of its height: buoyancy floats it to a
152 // bob at the surface, `jump` strokes up, `sink` strokes down, and
153 // a jump with the head above water breaches.
154 let mut body = CharacterBody::new(CharacterDef::default());
155 body.teleport(DVec3::new(80.0, 80.0, 115.0)); // deep in the pool
156 for _ in 0..600 {
157 body.walk(&scene, 1.0 / 60.0, WalkInput::default());
158 }
159 assert!(body.is_swimming(), "floated to the surface bob");
160 assert!(!body.eye_in_water(&scene), "bobbing: the camera is dry");
161
162 // Dive: hold `sink`. The submerged EYE is the hook for the
163 // underwater feel — drive the WT.2 frame tint and the WT.3
164 // listener lowpass from this one flag.
165 for _ in 0..90 {
166 body.walk(
167 &scene,
168 1.0 / 60.0,
169 WalkInput {
170 sink: true,
171 ..WalkInput::default()
172 },
173 );
174 }
175 assert!(body.eye_in_water(&scene), "diving: tint + muffle now");
176 // ANCHOR_END: water
177}Sourcepub fn set_mode(&mut self, mode: MoveMode)
pub fn set_mode(&mut self, mode: MoveMode)
Switch movement mode. Velocity is kept — dropping out of
Fly mid-air falls with whatever speed you had.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn def(&self) -> &CharacterDef
pub fn def(&self) -> &CharacterDef
The construction parameters.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn def_mut(&mut self) -> &mut CharacterDef
pub fn def_mut(&mut self) -> &mut CharacterDef
Tune parameters at runtime — sprint (walk_speed /
fly_speed), variable jump height, etc. Growing radius /
height while standing in a tight spot can leave the body
overlapping solid; the stuck-escape rule makes that
recoverable rather than a jam.
Sourcepub fn pos(&self) -> DVec3
pub fn pos(&self) -> DVec3
Feet position, world space.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn eye_pos(&self) -> DVec3
pub fn eye_pos(&self) -> DVec3
Eye position — the camera anchor: eye_height above the
feet, i.e. toward −z.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn vel(&self) -> DVec3
pub fn vel(&self) -> DVec3
Current velocity, world units per second.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn set_vel(&mut self, vel: DVec3)
pub fn set_vel(&mut self, vel: DVec3)
Overwrite the velocity — knockback, launch pads, spawn state.
Sourcepub fn on_ground(&self) -> bool
pub fn on_ground(&self) -> bool
true while the feet rest on solid ground (skin probe below
the feet, updated by walk).
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}Sourcepub fn is_swimming(&self) -> bool
pub fn is_swimming(&self) -> bool
WT.1 — true while the body is in the swim state (submerged
past CharacterDef::swim_enter_frac, held until it falls
below CharacterDef::swim_exit_frac). Updated by
walk; always false in the fly modes.
Examples found in repository?
133fn swim_demo() {
134 // ANCHOR: water
135 // Physics water is a WaterVolume on the grid — a grid-local voxel
136 // AABB whose TOP face (min z — +z is down) is the surface. It is
137 // independent of the voxels: fill the same region with a
138 // volumetric-material shell for the visuals, or don't.
139 let mut scene = Scene::new();
140 let id = scene.add_grid(GridTransform::identity());
141 let g = scene.grid_mut(id).expect("grid");
142 // Basin floor, then 20 voxels of water above it (surface z = 100).
143 g.set_rect(
144 IVec3::new(20, 20, 120),
145 IVec3::new(140, 140, 130),
146 Some(VoxColor::rgb(0x50, 0x90, 0x50)),
147 );
148 g.add_water_volume(IVec3::new(20, 20, 100), IVec3::new(140, 140, 119));
149
150 // A Walk-mode body dropped in swims AUTOMATICALLY once submerged
151 // past `swim_enter_frac` of its height: buoyancy floats it to a
152 // bob at the surface, `jump` strokes up, `sink` strokes down, and
153 // a jump with the head above water breaches.
154 let mut body = CharacterBody::new(CharacterDef::default());
155 body.teleport(DVec3::new(80.0, 80.0, 115.0)); // deep in the pool
156 for _ in 0..600 {
157 body.walk(&scene, 1.0 / 60.0, WalkInput::default());
158 }
159 assert!(body.is_swimming(), "floated to the surface bob");
160 assert!(!body.eye_in_water(&scene), "bobbing: the camera is dry");
161
162 // Dive: hold `sink`. The submerged EYE is the hook for the
163 // underwater feel — drive the WT.2 frame tint and the WT.3
164 // listener lowpass from this one flag.
165 for _ in 0..90 {
166 body.walk(
167 &scene,
168 1.0 / 60.0,
169 WalkInput {
170 sink: true,
171 ..WalkInput::default()
172 },
173 );
174 }
175 assert!(body.eye_in_water(&scene), "diving: tint + muffle now");
176 // ANCHOR_END: water
177}Sourcepub fn submerged_fraction(&self, scene: &Scene) -> f64
pub fn submerged_fraction(&self, scene: &Scene) -> f64
WT.1 — fraction of the body height below a water surface,
0.0..=1.0: the Scene::water_depth_at point depth at the
FEET over CharacterDef::height (exact for the
world-horizontal surfaces water is authored with —
PORTING-WATER.md decision 1: there is deliberately no
box-overlap query). This is the BODY-STATE number (it drives
the swim state) — do NOT wire underwater tint/audio to it: at
the default bobbing equilibrium it reads 0.75 while the head
(and camera) are in the air. Use Self::eye_in_water for
those.
Continuity guard: feet just below an authored volume’s BOTTOM face (a pool volume that stops short of the floor) read as fully submerged while the head is still in water — without this, the fraction would snap 0.55+ → 0.0 mid-dive and strobe the swim state faster than hysteresis can absorb. (Author volumes down to the floor anyway.)
Sourcepub fn eye_in_water(&self, scene: &Scene) -> bool
pub fn eye_in_water(&self, scene: &Scene) -> bool
WT.1 — true while the EYE (the camera anchor,
Self::eye_pos) is under a water surface. THE hook for the
underwater feel — WT.2 tint, WT.3 listener lowpass: it flips
exactly when the player’s view goes under, unlike
Self::submerged_fraction (feet-depth, 0.75 at the surface
bob with a dry camera).
Examples found in repository?
133fn swim_demo() {
134 // ANCHOR: water
135 // Physics water is a WaterVolume on the grid — a grid-local voxel
136 // AABB whose TOP face (min z — +z is down) is the surface. It is
137 // independent of the voxels: fill the same region with a
138 // volumetric-material shell for the visuals, or don't.
139 let mut scene = Scene::new();
140 let id = scene.add_grid(GridTransform::identity());
141 let g = scene.grid_mut(id).expect("grid");
142 // Basin floor, then 20 voxels of water above it (surface z = 100).
143 g.set_rect(
144 IVec3::new(20, 20, 120),
145 IVec3::new(140, 140, 130),
146 Some(VoxColor::rgb(0x50, 0x90, 0x50)),
147 );
148 g.add_water_volume(IVec3::new(20, 20, 100), IVec3::new(140, 140, 119));
149
150 // A Walk-mode body dropped in swims AUTOMATICALLY once submerged
151 // past `swim_enter_frac` of its height: buoyancy floats it to a
152 // bob at the surface, `jump` strokes up, `sink` strokes down, and
153 // a jump with the head above water breaches.
154 let mut body = CharacterBody::new(CharacterDef::default());
155 body.teleport(DVec3::new(80.0, 80.0, 115.0)); // deep in the pool
156 for _ in 0..600 {
157 body.walk(&scene, 1.0 / 60.0, WalkInput::default());
158 }
159 assert!(body.is_swimming(), "floated to the surface bob");
160 assert!(!body.eye_in_water(&scene), "bobbing: the camera is dry");
161
162 // Dive: hold `sink`. The submerged EYE is the hook for the
163 // underwater feel — drive the WT.2 frame tint and the WT.3
164 // listener lowpass from this one flag.
165 for _ in 0..90 {
166 body.walk(
167 &scene,
168 1.0 / 60.0,
169 WalkInput {
170 sink: true,
171 ..WalkInput::default()
172 },
173 );
174 }
175 assert!(body.eye_in_water(&scene), "diving: tint + muffle now");
176 // ANCHOR_END: water
177}Sourcepub fn set_pos(&mut self, pos: DVec3)
pub fn set_pos(&mut self, pos: DVec3)
Reposition the feet, KEEPING velocity and contact state — for
world-bounds clamps and moving-platform style corrections.
For spawns and respawns use teleport.
Sourcepub fn teleport(&mut self, pos: DVec3)
pub fn teleport(&mut self, pos: DVec3)
Hard-place the feet at pos, zeroing velocity and contact
flags. No collision check — placing inside solid is allowed
(the stuck-escape rule below makes it recoverable).
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}
130
131/// WT — the water + swimming section's snippets: a deep pool the body
132/// falls into, floats in, dives through and breaches out of.
133fn swim_demo() {
134 // ANCHOR: water
135 // Physics water is a WaterVolume on the grid — a grid-local voxel
136 // AABB whose TOP face (min z — +z is down) is the surface. It is
137 // independent of the voxels: fill the same region with a
138 // volumetric-material shell for the visuals, or don't.
139 let mut scene = Scene::new();
140 let id = scene.add_grid(GridTransform::identity());
141 let g = scene.grid_mut(id).expect("grid");
142 // Basin floor, then 20 voxels of water above it (surface z = 100).
143 g.set_rect(
144 IVec3::new(20, 20, 120),
145 IVec3::new(140, 140, 130),
146 Some(VoxColor::rgb(0x50, 0x90, 0x50)),
147 );
148 g.add_water_volume(IVec3::new(20, 20, 100), IVec3::new(140, 140, 119));
149
150 // A Walk-mode body dropped in swims AUTOMATICALLY once submerged
151 // past `swim_enter_frac` of its height: buoyancy floats it to a
152 // bob at the surface, `jump` strokes up, `sink` strokes down, and
153 // a jump with the head above water breaches.
154 let mut body = CharacterBody::new(CharacterDef::default());
155 body.teleport(DVec3::new(80.0, 80.0, 115.0)); // deep in the pool
156 for _ in 0..600 {
157 body.walk(&scene, 1.0 / 60.0, WalkInput::default());
158 }
159 assert!(body.is_swimming(), "floated to the surface bob");
160 assert!(!body.eye_in_water(&scene), "bobbing: the camera is dry");
161
162 // Dive: hold `sink`. The submerged EYE is the hook for the
163 // underwater feel — drive the WT.2 frame tint and the WT.3
164 // listener lowpass from this one flag.
165 for _ in 0..90 {
166 body.walk(
167 &scene,
168 1.0 / 60.0,
169 WalkInput {
170 sink: true,
171 ..WalkInput::default()
172 },
173 );
174 }
175 assert!(body.eye_in_water(&scene), "diving: tint + muffle now");
176 // ANCHOR_END: water
177}Sourcepub fn walk(&mut self, scene: &Scene, dt: f64, input: WalkInput)
pub fn walk(&mut self, scene: &Scene, dt: f64, input: WalkInput)
Advance the body by dt seconds against scene. Behaviour
follows the current MoveMode; everything below describes
MoveMode::Walk.
Integration order: horizontal velocity approaches
wish · walk_speed at the ground/air accel rate; gravity;
jump if grounded; then the displacement is applied in
substeps of at most radius per axis (the no-tunnel
guarantee), each substep moving x, then y, then z, clamping
flush against the blocking cell plane and zeroing that
velocity component on contact — unless a grounded horizontal
block steps up a ledge (CharacterDef::step_up). +z contact
grounds the body; −z contact is a head bump.
Stuck escape (kept verbatim from the demos): if the body starts the frame overlapping solid — an edit carved under the player, a bake reclassified a column — the whole frame moves without collision so the player can escape rather than jam.
Examples found in repository?
61fn main() {
62 let scene = build_world();
63
64 // ANCHOR: setup
65 // A walking body: a feet-positioned collision box, f64 world.
66 // Distances are voxels, times seconds; +z is DOWN, so gravity is
67 // positive and a jump impulse negative.
68 let mut body = CharacterBody::new(CharacterDef {
69 radius: 0.4, // xy half-extent of the box
70 height: 1.8, // feet → head (toward -z)
71 eye_height: 1.62, // feet → camera anchor
72 step_up: 1.05, // auto-step ledges up to 1 voxel
73 solidity: Solidity {
74 bedrock_blocks: false, // match your renderer's policy
75 passable: Some(water_passes),
76 },
77 ..CharacterDef::default()
78 });
79 body.teleport(DVec3::new(50.0, 50.0, 95.0)); // FEET position
80 // ANCHOR_END: setup
81
82 // ANCHOR: frame
83 // Per frame: build a wish direction from input (unit-length or
84 // zero — walk() clamps), call walk() EVERY frame (a zero wish is
85 // what stops the body), then anchor the camera at the eye.
86 let dt = 1.0 / 60.0;
87 for frame in 0..480 {
88 let input = WalkInput {
89 wish: DVec3::new(1.0, 0.2, 0.0), // toward the ledge
90 jump: frame == 120, // one hop on the way
91 ..WalkInput::default() // sink (WT.1) + future fields
92 };
93 body.walk(&scene, dt, input);
94 }
95 let eye = body.eye_pos(); // = Camera::from_yaw_pitch(eye.into(), yaw, pitch)
96 // ANCHOR_END: frame
97
98 // The trajectory the walk above promises, pinned:
99 assert!(body.on_ground(), "settled after the hop");
100 // It fell to the floor plane, then stepped up the 1-voxel ledge
101 // (surface z = 99) while walking +x.
102 assert!(body.pos().x > 90.0, "reached the ledge region");
103 assert!(
104 (body.pos().z - 99.0).abs() < 0.01,
105 "standing on the ledge, feet at {}",
106 body.pos().z
107 );
108 assert!(eye.z < body.pos().z, "the eye is above the feet (-z is up)");
109
110 // Fly mode is the demos' free camera: no gravity, full-3D wish,
111 // instant start/stop, still sliding along walls.
112 body.set_mode(MoveMode::Fly);
113 body.walk(&scene, dt, WalkInput::default());
114 assert_eq!(body.vel(), DVec3::ZERO, "fly idles in place");
115
116 // The water curtain from `build_world` is passable: probe the
117 // veto directly (the same query walk() uses).
118 let wading = roxlap_scene::box_overlaps_solid(
119 &scene,
120 DVec3::new(40.2, 50.0, 90.0),
121 DVec3::new(40.8, 50.6, 91.8),
122 body.def().solidity,
123 );
124 assert!(!wading, "water is passable under the veto");
125
126 swim_demo();
127
128 println!("book_controller: all controller assertions hold");
129}
130
131/// WT — the water + swimming section's snippets: a deep pool the body
132/// falls into, floats in, dives through and breaches out of.
133fn swim_demo() {
134 // ANCHOR: water
135 // Physics water is a WaterVolume on the grid — a grid-local voxel
136 // AABB whose TOP face (min z — +z is down) is the surface. It is
137 // independent of the voxels: fill the same region with a
138 // volumetric-material shell for the visuals, or don't.
139 let mut scene = Scene::new();
140 let id = scene.add_grid(GridTransform::identity());
141 let g = scene.grid_mut(id).expect("grid");
142 // Basin floor, then 20 voxels of water above it (surface z = 100).
143 g.set_rect(
144 IVec3::new(20, 20, 120),
145 IVec3::new(140, 140, 130),
146 Some(VoxColor::rgb(0x50, 0x90, 0x50)),
147 );
148 g.add_water_volume(IVec3::new(20, 20, 100), IVec3::new(140, 140, 119));
149
150 // A Walk-mode body dropped in swims AUTOMATICALLY once submerged
151 // past `swim_enter_frac` of its height: buoyancy floats it to a
152 // bob at the surface, `jump` strokes up, `sink` strokes down, and
153 // a jump with the head above water breaches.
154 let mut body = CharacterBody::new(CharacterDef::default());
155 body.teleport(DVec3::new(80.0, 80.0, 115.0)); // deep in the pool
156 for _ in 0..600 {
157 body.walk(&scene, 1.0 / 60.0, WalkInput::default());
158 }
159 assert!(body.is_swimming(), "floated to the surface bob");
160 assert!(!body.eye_in_water(&scene), "bobbing: the camera is dry");
161
162 // Dive: hold `sink`. The submerged EYE is the hook for the
163 // underwater feel — drive the WT.2 frame tint and the WT.3
164 // listener lowpass from this one flag.
165 for _ in 0..90 {
166 body.walk(
167 &scene,
168 1.0 / 60.0,
169 WalkInput {
170 sink: true,
171 ..WalkInput::default()
172 },
173 );
174 }
175 assert!(body.eye_in_water(&scene), "diving: tint + muffle now");
176 // ANCHOR_END: water
177}Trait Implementations§
Source§impl Clone for CharacterBody
impl Clone for CharacterBody
Source§fn clone(&self) -> CharacterBody
fn clone(&self) -> CharacterBody
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for CharacterBody
Auto Trait Implementations§
impl Freeze for CharacterBody
impl RefUnwindSafe for CharacterBody
impl Send for CharacterBody
impl Sync for CharacterBody
impl Unpin for CharacterBody
impl UnsafeUnpin for CharacterBody
impl UnwindSafe for CharacterBody
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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 more