11//! Bevy GenPlugin — command processing, default scene, screenshot capture.
22
3+ use bevy:: input:: mouse:: { MouseMotion , MouseWheel } ;
34use bevy:: prelude:: * ;
45use bevy:: render:: mesh:: Indices ;
56use bevy:: render:: render_asset:: RenderAssetUsages ;
@@ -35,6 +36,26 @@ struct PendingScreenshot {
3536 path : Option < String > ,
3637}
3738
39+ /// Marker component for the interactive fly camera.
40+ #[ derive( Component ) ]
41+ struct FlyCam ;
42+
43+ /// Configuration for the fly camera controller.
44+ #[ derive( Resource ) ]
45+ struct FlyCamConfig {
46+ move_speed : f32 ,
47+ look_sensitivity : f32 ,
48+ }
49+
50+ impl Default for FlyCamConfig {
51+ fn default ( ) -> Self {
52+ Self {
53+ move_speed : 5.0 ,
54+ look_sensitivity : 0.003 ,
55+ }
56+ }
57+ }
58+
3859/// Plugin that sets up the Gen 3D environment.
3960pub struct GenPlugin {
4061 pub channels : GenChannels ,
@@ -55,8 +76,18 @@ pub fn setup_gen_app(app: &mut App, channels: GenChannels) {
5576 app. insert_resource ( GenChannelRes :: new ( channels) )
5677 . init_resource :: < NameRegistry > ( )
5778 . init_resource :: < PendingScreenshots > ( )
79+ . init_resource :: < FlyCamConfig > ( )
5880 . add_systems ( Startup , setup_default_scene)
59- . add_systems ( Update , ( process_gen_commands, process_pending_screenshots) ) ;
81+ . add_systems (
82+ Update ,
83+ (
84+ process_gen_commands,
85+ process_pending_screenshots,
86+ fly_cam_movement,
87+ fly_cam_look,
88+ fly_cam_scroll_speed,
89+ ) ,
90+ ) ;
6091}
6192
6293/// Default scene: ground plane, camera, directional light, ambient light.
@@ -91,6 +122,7 @@ fn setup_default_scene(
91122 Camera3d :: default ( ) ,
92123 Transform :: from_translation ( Vec3 :: new ( 5.0 , 5.0 , 5.0 ) ) . looking_at ( Vec3 :: ZERO , Vec3 :: Y ) ,
93124 Name :: new ( "main_camera" ) ,
125+ FlyCam ,
94126 GenEntity {
95127 entity_type : GenEntityType :: Camera ,
96128 } ,
@@ -809,3 +841,92 @@ fn handle_spawn_mesh(
809841 entity_id,
810842 }
811843}
844+
845+ // ---------------------------------------------------------------------------
846+ // Fly camera systems
847+ // ---------------------------------------------------------------------------
848+
849+ /// WASD + Space/Shift movement relative to camera orientation.
850+ fn fly_cam_movement (
851+ keys : Res < ButtonInput < KeyCode > > ,
852+ time : Res < Time > ,
853+ config : Res < FlyCamConfig > ,
854+ mut query : Query < & mut Transform , With < FlyCam > > ,
855+ ) {
856+ let Ok ( mut transform) = query. get_single_mut ( ) else {
857+ return ;
858+ } ;
859+
860+ let forward = transform. forward ( ) . as_vec3 ( ) ;
861+ let right = transform. right ( ) . as_vec3 ( ) ;
862+
863+ let mut velocity = Vec3 :: ZERO ;
864+ if keys. pressed ( KeyCode :: KeyW ) {
865+ velocity += forward;
866+ }
867+ if keys. pressed ( KeyCode :: KeyS ) {
868+ velocity -= forward;
869+ }
870+ if keys. pressed ( KeyCode :: KeyA ) {
871+ velocity -= right;
872+ }
873+ if keys. pressed ( KeyCode :: KeyD ) {
874+ velocity += right;
875+ }
876+ if keys. pressed ( KeyCode :: Space ) {
877+ velocity += Vec3 :: Y ;
878+ }
879+ if keys. pressed ( KeyCode :: ShiftLeft ) || keys. pressed ( KeyCode :: ShiftRight ) {
880+ velocity -= Vec3 :: Y ;
881+ }
882+
883+ if velocity != Vec3 :: ZERO {
884+ transform. translation += velocity. normalize ( ) * config. move_speed * time. delta_secs ( ) ;
885+ }
886+ }
887+
888+ /// Right-click + mouse drag to rotate the camera (yaw and pitch).
889+ fn fly_cam_look (
890+ mouse : Res < ButtonInput < MouseButton > > ,
891+ config : Res < FlyCamConfig > ,
892+ mut motion_reader : EventReader < MouseMotion > ,
893+ mut query : Query < & mut Transform , With < FlyCam > > ,
894+ ) {
895+ let delta: Vec2 = motion_reader. read ( ) . map ( |e| e. delta ) . sum ( ) ;
896+ if delta == Vec2 :: ZERO || !mouse. pressed ( MouseButton :: Right ) {
897+ return ;
898+ }
899+
900+ let Ok ( mut transform) = query. get_single_mut ( ) else {
901+ return ;
902+ } ;
903+
904+ let yaw = -delta. x * config. look_sensitivity ;
905+ let pitch = -delta. y * config. look_sensitivity ;
906+
907+ // Apply yaw (rotate around global Y axis)
908+ transform. rotate_y ( yaw) ;
909+
910+ // Apply pitch (rotate around local X axis) with clamping
911+ let right = transform. right ( ) . as_vec3 ( ) ;
912+ let new_rotation = Quat :: from_axis_angle ( right, pitch) * transform. rotation ;
913+
914+ // Clamp pitch: check the angle between the camera's forward and the horizontal plane
915+ let new_forward = new_rotation * Vec3 :: NEG_Z ;
916+ let pitch_angle = new_forward. y . asin ( ) ;
917+ let max_pitch = 89.0_f32 . to_radians ( ) ;
918+
919+ if pitch_angle. abs ( ) < max_pitch {
920+ transform. rotation = new_rotation;
921+ }
922+ }
923+
924+ /// Scroll wheel adjusts movement speed.
925+ fn fly_cam_scroll_speed (
926+ mut scroll_reader : EventReader < MouseWheel > ,
927+ mut config : ResMut < FlyCamConfig > ,
928+ ) {
929+ for event in scroll_reader. read ( ) {
930+ config. move_speed = ( config. move_speed * ( 1.0 + event. y * 0.1 ) ) . clamp ( 0.5 , 100.0 ) ;
931+ }
932+ }
0 commit comments