diff --git a/README.md b/README.md index ead18cb..c9ba557 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,6 @@ A Stellarium game made for The icam Game jam 2024, using the Bevy game engine +Star data from : [YaleBrightStarCatalog (Bretton Wade)](https://github.com/brettonw/YaleBrightStarCatalog/blob/master/bsc5-short.json) (MIT License, Copyright (c) 2016 Bretton Wade) + +Constellation data from :[Lizard Tail (Isana Kashiwai)](https://www.lizard-tail.com/isana/lab/starlitnight/) diff --git a/src/game_state.rs b/src/game_state.rs index 2ef5903..3d72932 100644 --- a/src/game_state.rs +++ b/src/game_state.rs @@ -1,4 +1,6 @@ use bevy::prelude::*; +use bevy::render::render_resource::PrimitiveTopology; +use bevy::render::render_asset::RenderAssetUsages; use std::f64::consts::PI; use rand::seq::SliceRandom; use rand::RngCore; @@ -17,6 +19,7 @@ use crate::spawn_cons_lines; use crate::NORMAL_BUTTON; use crate::RIGHT_BUTTON; use crate::WRONG_BUTTON; +use crate::SKY_RADIUS; #[derive(Component)] pub struct AnswerButton; @@ -30,7 +33,14 @@ pub struct ScoreLabel; #[derive(Component)] pub struct HintLabel; -pub fn setup(mut commands: Commands, _asset_server: Res) { +#[derive(Component)] +pub struct DebugLine; + +pub fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { let container_node = NodeBundle { style: Style { width: Val::Percent(100.0), @@ -160,6 +170,30 @@ pub fn setup(mut commands: Commands, _asset_server: Res) { commands.entity(centered_container).push_children(&[hint_label]); + + let line_material = materials.add(StandardMaterial { + emissive: LinearRgba::rgb(2.0, 0.5, 0.5), + ..default() + }); + + let vertices = vec![ + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(1.0, 0.0, 0.0) + ]; + + let mut mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::RENDER_WORLD); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + commands.spawn(( + PbrBundle { + mesh: meshes.add(mesh), + material: line_material.clone(), + transform: Transform::default(), + ..default() + }, + DebugLine, + MainGame + )); } @@ -229,6 +263,94 @@ pub fn player_interact( } } +pub fn player_mouse_move ( + buttons: Res>, + mut player_query: Query<(&mut Player, &mut Transform)>, + camera_query: Query<(&Camera, &GlobalTransform), With>, + window_query: Query<&Window, With>, + mut debug_line_query: Query<&mut Transform, (With, Without)>, +) { + let Ok((mut player, mut player_transform)) = player_query.get_single_mut() else { + return; + }; + + if !buttons.pressed(MouseButton::Left) { + player.dragging_pos = None; + return + } + + let window = window_query.single(); + + let Some(cursor_position) = window.cursor_position() else { + return; + }; + + let (camera, camera_transform) = camera_query.single(); + + let Some(ray) = camera.viewport_to_world(camera_transform, cursor_position) else { + return; + }; + + let new_global_cursor = ray.get_point(SKY_RADIUS); + + let Some(old_global_cursor) = player.dragging_pos else { + player.dragging_pos = Some(new_global_cursor); + return; + }; + + if let Ok(mut debug_transform) = debug_line_query.get_single_mut() { + let direction = old_global_cursor - new_global_cursor ; + let distance = direction.length(); + + // Scale the line to match the distance between the points + debug_transform.scale = Vec3::new(distance*5.0, 1.0, 1.0); + + // Position the line at old_pos + debug_transform.translation = new_global_cursor; + + // Rotate the line to point from old_pos to new_pos + if distance > f32::EPSILON { + let rotation = Quat::from_rotation_arc(Vec3::X, direction.normalize()); + debug_transform.rotation = rotation; + } + } else { + info!("no debug line"); + } + + + + if old_global_cursor != new_global_cursor { + let target_rotation = player_transform.rotation * rotate_to_align(old_global_cursor, new_global_cursor); + player_transform.rotation = player_transform.rotation.slerp(target_rotation, 0.8); + } + + player.dragging_pos = Some(new_global_cursor); +} + +fn rotate_to_align(old_pos: Vec3, new_pos: Vec3) -> Quat { + // Step 1: Normalize the input vectors (assuming they're not already normalized) + let old_pos_normalized = old_pos.normalize(); + let new_pos_normalized = new_pos.normalize(); + + // Step 2: Compute the axis of rotation (cross product between old and new positions) + let axis_of_rotation = old_pos_normalized.cross(new_pos_normalized).normalize(); + + if axis_of_rotation.length_squared() < f32::EPSILON { + return Quat::IDENTITY; + } + + // Step 3: Compute the angle of rotation (dot product and arccosine) + //let dot_product = old_pos_normalized.dot(new_pos_normalized); + let dot_product = new_pos_normalized.dot(old_pos_normalized); + let angle_of_rotation = dot_product.acos(); // This gives us the angle in radians + + if angle_of_rotation.is_nan() || angle_of_rotation.is_infinite() { + return Quat::IDENTITY; + } + + // Step 4: Create a quaternion representing the rotation + Quat::from_axis_angle(axis_of_rotation, angle_of_rotation) +} pub fn ui_labels( diff --git a/src/main.rs b/src/main.rs index 2083765..cf35315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,6 +90,20 @@ struct Player { score: usize, health: usize, state: PlayerState, + dragging_pos: Option, +} + +impl Default for Player { + fn default() -> Self { + Player { + target_rotation: None, + target_cons_name: None, + score: 0, + health: 3, + state: PlayerState::Playing, + dragging_pos: None, + } + } } #[derive(PartialEq)] @@ -120,6 +134,7 @@ fn main() { .add_systems(OnExit(GameState::Start), despawn_screen::) .add_systems(OnEnter(GameState::Game), game_state::setup) .add_systems(Update, game_state::player_interact.run_if(in_state(GameState::Game))) + .add_systems(Update, game_state::player_mouse_move.run_if(in_state(GameState::Game))) .add_systems(Update, game_state::ui_buttons.run_if(in_state(GameState::Game))) .add_systems(Update, game_state::ui_labels.run_if(in_state(GameState::Game))) .add_systems(OnExit(GameState::Game), despawn_screen::) diff --git a/src/start_state.rs b/src/start_state.rs index c0fd344..6e5ef12 100644 --- a/src/start_state.rs +++ b/src/start_state.rs @@ -5,7 +5,6 @@ use crate::Player; use crate::GameState; use crate::GameOver; use crate::StartMenu; -use crate::PlayerState; #[derive(Component)] struct AudioPlayer; @@ -67,13 +66,7 @@ pub fn setup(mut commands: Commands, _asset_server: Res) { transform: Transform::from_xyz(0.0, 0.0, 0.0), ..default() }, - Player { - target_rotation: None, - target_cons_name: None, - score: 0, - health: 3, - state: PlayerState::Playing, - }, + Player::default(), GameOver, )); }