Skip to content

Commit eb673d6

Browse files
authored
Add camera controllers. (#100)
1 parent 4b910c8 commit eb673d6

File tree

14 files changed

+715
-10
lines changed

14 files changed

+715
-10
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type_complexity = "allow"
2222
too_many_arguments = "allow"
2323

2424
[workspace.dependencies]
25-
bevy = { git = "https://github.com/bevyengine/bevy", branch = "main", features = ["file_watcher", "shader_format_wesl"] }
25+
bevy = { git = "https://github.com/bevyengine/bevy", branch = "main", features = ["file_watcher", "shader_format_wesl", "free_camera", "pan_camera"] }
2626
bevy_naga_reflect = { git = "https://github.com/tychedelia/bevy_naga_reflect" }
2727
naga = { version = "28", features = ["wgsl-in"] }
2828
wesl = { version = "0.3", default-features = false }
@@ -148,6 +148,10 @@ path = "examples/shapes.rs"
148148
name = "blend_modes"
149149
path = "examples/blend_modes.rs"
150150

151+
[[example]]
152+
name = "camera_controllers"
153+
path = "examples/camera_controllers.rs"
154+
151155
[profile.wasm-release]
152156
inherits = "release"
153157
opt-level = "z"

crates/processing_glfw/src/lib.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use bevy::prelude::Entity;
44
use glfw::{Action, Glfw, GlfwReceiver, PWindow, WindowEvent, WindowMode};
55
use processing_core::error::Result;
66
use processing_input::{
7-
input_flush, input_set_char, input_set_cursor_enter, input_set_cursor_leave, input_set_focus,
8-
input_set_key, input_set_mouse_button, input_set_mouse_move, input_set_scroll,
7+
input_cursor_grab_mode, input_cursor_visible, input_flush, input_set_char,
8+
input_set_cursor_enter, input_set_cursor_leave, input_set_focus, input_set_key,
9+
input_set_mouse_button, input_set_mouse_move, input_set_scroll,
910
};
1011

1112
pub struct GlfwContext {
@@ -173,10 +174,30 @@ impl GlfwContext {
173174
return false;
174175
}
175176

176-
input_flush().unwrap();
177+
let Ok(_) = input_flush() else {
178+
return false;
179+
};
180+
self.sync_cursor(surface);
177181

178182
true
179183
}
184+
185+
fn sync_cursor(&mut self, surface: Entity) {
186+
use bevy::window::CursorGrabMode;
187+
188+
let grab = input_cursor_grab_mode(surface).unwrap_or(CursorGrabMode::None);
189+
let visible = input_cursor_visible(surface).unwrap_or(true);
190+
191+
let mode = match grab {
192+
CursorGrabMode::Locked | CursorGrabMode::Confined => glfw::CursorMode::Disabled,
193+
CursorGrabMode::None if !visible => glfw::CursorMode::Hidden,
194+
CursorGrabMode::None => glfw::CursorMode::Normal,
195+
};
196+
197+
if self.window.get_cursor_mode() != mode {
198+
self.window.set_cursor_mode(mode);
199+
}
200+
}
180201
}
181202

182203
fn glfw_button_to_bevy(button: glfw::MouseButton) -> Option<MouseButton> {

crates/processing_input/src/lib.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,38 @@ pub fn input_set_focus(surface: Entity, focused: bool) -> error::Result<()> {
152152
})
153153
}
154154

155+
pub fn input_cursor_grab_mode(surface: Entity) -> error::Result<bevy::window::CursorGrabMode> {
156+
app_mut(|app| {
157+
let cursor = app
158+
.world()
159+
.get::<bevy::window::CursorOptions>(surface)
160+
.map(|c| c.grab_mode)
161+
.unwrap_or(bevy::window::CursorGrabMode::None);
162+
Ok(cursor)
163+
})
164+
}
165+
166+
pub fn input_cursor_visible(surface: Entity) -> error::Result<bool> {
167+
app_mut(|app| {
168+
let visible = app
169+
.world()
170+
.get::<bevy::window::CursorOptions>(surface)
171+
.map(|c| c.visible)
172+
.unwrap_or(true);
173+
Ok(visible)
174+
})
175+
}
176+
177+
/// Flushes the input state by running the relevant schedules. This is required to ensure that
178+
/// Bevy's bookkeeping of input state is up to date after manually sending input events.
179+
/// It should be called after sending any input events and before querying input state
180+
/// to ensure that the state reflects the events that were sent.
155181
pub fn input_flush() -> error::Result<()> {
156182
app_mut(|app| {
157-
app.world_mut().run_schedule(PreUpdate);
183+
let world = app.world_mut();
184+
world.run_schedule(First);
185+
world.run_schedule(PreUpdate);
186+
world.run_schedule(RunFixedMainLoop);
158187
Ok(())
159188
})
160189
}

crates/processing_pyo3/assets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../assets

crates/processing_pyo3/examples/assets

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from mewnala import *
2+
3+
angle = 0.0
4+
mode = 0
5+
6+
def setup():
7+
size(800, 600)
8+
mode_3d()
9+
orbit_camera()
10+
11+
dir_light = create_directional_light((1.0, 0.98, 0.95), 1500.0)
12+
dir_light.position(300.0, 400.0, 300.0)
13+
dir_light.look_at(0.0, 0.0, 0.0)
14+
15+
def draw():
16+
global angle, mode
17+
18+
if key_just_pressed(KEY_1):
19+
mode_3d()
20+
orbit_camera()
21+
mode = 0
22+
if key_just_pressed(KEY_2):
23+
mode_3d()
24+
free_camera()
25+
mode = 1
26+
if key_just_pressed(KEY_3):
27+
mode_2d()
28+
pan_camera()
29+
mode = 2
30+
31+
background(13, 13, 18)
32+
if mode < 2:
33+
fill(255, 217, 145)
34+
roughness(0.3)
35+
metallic(0.8)
36+
push_matrix()
37+
rotate(angle)
38+
box(100.0, 100.0, 100.0)
39+
pop_matrix()
40+
else:
41+
fill(204, 77, 51)
42+
rect(300.0, 200.0, 200.0, 200.0)
43+
44+
angle += 0.02
45+
46+
run()

crates/processing_pyo3/src/graphics.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,52 @@ impl Graphics {
10381038
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
10391039
}
10401040

1041+
pub fn orbit_camera(&self) -> PyResult<()> {
1042+
graphics_orbit_camera(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1043+
}
1044+
1045+
pub fn free_camera(&self) -> PyResult<()> {
1046+
graphics_free_camera(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1047+
}
1048+
1049+
pub fn pan_camera(&self) -> PyResult<()> {
1050+
graphics_pan_camera(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1051+
}
1052+
1053+
pub fn disable_camera(&self) -> PyResult<()> {
1054+
graphics_disable_camera_controller(self.entity)
1055+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1056+
}
1057+
1058+
pub fn camera_distance(&self, distance: f32) -> PyResult<()> {
1059+
camera_set_distance(self.entity, distance)
1060+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1061+
}
1062+
1063+
#[pyo3(signature = (*args))]
1064+
pub fn camera_center(&self, args: &Bound<'_, PyTuple>) -> PyResult<()> {
1065+
let v = extract_vec3(args)?;
1066+
camera_set_center(self.entity, v).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1067+
}
1068+
1069+
pub fn camera_min_distance(&self, min: f32) -> PyResult<()> {
1070+
camera_set_min_distance(self.entity, min)
1071+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1072+
}
1073+
1074+
pub fn camera_max_distance(&self, max: f32) -> PyResult<()> {
1075+
camera_set_max_distance(self.entity, max)
1076+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1077+
}
1078+
1079+
pub fn camera_speed(&self, speed: f32) -> PyResult<()> {
1080+
camera_set_speed(self.entity, speed).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1081+
}
1082+
1083+
pub fn camera_reset(&self) -> PyResult<()> {
1084+
camera_reset(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
1085+
}
1086+
10411087
pub fn light_directional(
10421088
&self,
10431089
color: crate::color::ColorLike,

crates/processing_pyo3/src/lib.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,12 @@ mod mewnala {
739739
graphics!(module).mode_3d()
740740
}
741741

742+
#[pyfunction]
743+
#[pyo3(pass_module)]
744+
fn mode_2d(module: &Bound<'_, PyModule>) -> PyResult<()> {
745+
graphics!(module).mode_2d()
746+
}
747+
742748
#[pyfunction]
743749
#[pyo3(pass_module, signature = (*args))]
744750
fn camera_position(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> {
@@ -751,6 +757,66 @@ mod mewnala {
751757
graphics!(module).camera_look_at(args)
752758
}
753759

760+
#[pyfunction]
761+
#[pyo3(pass_module)]
762+
fn orbit_camera(module: &Bound<'_, PyModule>) -> PyResult<()> {
763+
graphics!(module).orbit_camera()
764+
}
765+
766+
#[pyfunction]
767+
#[pyo3(pass_module)]
768+
fn free_camera(module: &Bound<'_, PyModule>) -> PyResult<()> {
769+
graphics!(module).free_camera()
770+
}
771+
772+
#[pyfunction]
773+
#[pyo3(pass_module)]
774+
fn pan_camera(module: &Bound<'_, PyModule>) -> PyResult<()> {
775+
graphics!(module).pan_camera()
776+
}
777+
778+
#[pyfunction]
779+
#[pyo3(pass_module)]
780+
fn disable_camera(module: &Bound<'_, PyModule>) -> PyResult<()> {
781+
graphics!(module).disable_camera()
782+
}
783+
784+
#[pyfunction]
785+
#[pyo3(pass_module)]
786+
fn camera_distance(module: &Bound<'_, PyModule>, distance: f32) -> PyResult<()> {
787+
graphics!(module).camera_distance(distance)
788+
}
789+
790+
#[pyfunction]
791+
#[pyo3(pass_module, signature = (*args))]
792+
fn camera_center(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> {
793+
graphics!(module).camera_center(args)
794+
}
795+
796+
#[pyfunction]
797+
#[pyo3(pass_module)]
798+
fn camera_min_distance(module: &Bound<'_, PyModule>, min: f32) -> PyResult<()> {
799+
graphics!(module).camera_min_distance(min)
800+
}
801+
802+
#[pyfunction]
803+
#[pyo3(pass_module)]
804+
fn camera_max_distance(module: &Bound<'_, PyModule>, max: f32) -> PyResult<()> {
805+
graphics!(module).camera_max_distance(max)
806+
}
807+
808+
#[pyfunction]
809+
#[pyo3(pass_module)]
810+
fn camera_speed(module: &Bound<'_, PyModule>, speed: f32) -> PyResult<()> {
811+
graphics!(module).camera_speed(speed)
812+
}
813+
814+
#[pyfunction]
815+
#[pyo3(pass_module)]
816+
fn camera_reset(module: &Bound<'_, PyModule>) -> PyResult<()> {
817+
graphics!(module).camera_reset()
818+
}
819+
754820
#[pyfunction]
755821
#[pyo3(pass_module)]
756822
fn push_matrix(module: &Bound<'_, PyModule>) -> PyResult<()> {

0 commit comments

Comments
 (0)