From 90cfdf5ac8ba88f416175b2c3009f49a984ab839 Mon Sep 17 00:00:00 2001 From: Froggy618157725 Date: Fri, 12 Jan 2024 21:16:07 -0800 Subject: [PATCH 1/3] Add Beacon to Utilities Beacons act as a navigational aid, allowing you to easily mark a location. The inspector panel allows you to teleport to or look towards beacons. --- src/camera.rs | 27 ++++++++- src/ecs/component_panels.rs | 113 ++++++++++++++++++++++++++++++++++-- src/ecs/components.rs | 18 ++++++ src/ecs/mod.rs | 3 +- src/main.rs | 34 ++++++++++- src/overlays/menu.rs | 32 +++++++++- src/render/tween.rs | 29 ++++++++- 7 files changed, 239 insertions(+), 17 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index b9bfeced..cd91ba4d 100755 --- a/src/camera.rs +++ b/src/camera.rs @@ -9,7 +9,7 @@ use crate::{ #[derive(Clone)] pub struct FpsCamera { - orientation: Vec2, + pub orientation: Vec2, pub rotation: Quat, pub front: Vec3, @@ -145,7 +145,8 @@ impl FpsCamera { } if let Some(tween) = &mut self.tween { - self.position = tween.update(); + self.position = tween.update_pos(); + self.orientation = tween.update_angle(); } else { self.position += direction * speed; } @@ -299,6 +300,8 @@ impl FpsCamera { tween::ease_out_exponential, self.position, pos - self.front * distance, + self.orientation, + self.orientation, 0.70, )); } @@ -309,4 +312,24 @@ impl FpsCamera { self.focus(center, radius); } + + // Calculate angle to point camera at pos. + // The angle has a minimal diff to current camera angle. + pub fn get_look_angle(&self, pos: Vec3) -> Vec2 { + let dir = pos - self.position; + let inv_r = dir.length_recip(); + if inv_r.is_infinite() { + self.orientation + } else { + let theta = dir.x.atan2(dir.y).to_degrees(); + let mut diff = (theta - self.orientation.y).rem_euclid(360.0); + if diff > 180.0 { + diff -= 360.0; + } + Vec2::new( + (dir.z * inv_r).acos().to_degrees() - 90.0, + self.orientation.y + diff, + ) + } + } } diff --git a/src/ecs/component_panels.rs b/src/ecs/component_panels.rs index 690591d9..6c6c361d 100644 --- a/src/ecs/component_panels.rs +++ b/src/ecs/component_panels.rs @@ -7,11 +7,12 @@ use crate::{ ecs::transform::TransformFlags, hotkeys::{SHORTCUT_DELETE, SHORTCUT_HIDE}, icons::{ - ICON_ALERT, ICON_ALPHA_A_BOX, ICON_ALPHA_B_BOX, ICON_AXIS_ARROW, ICON_CAMERA_CONTROL, - ICON_CUBE_OUTLINE, ICON_DELETE, ICON_EYE, ICON_EYE_OFF, ICON_HELP, ICON_IDENTIFIER, - ICON_MAP_MARKER, ICON_RADIUS_OUTLINE, ICON_RESIZE, ICON_ROTATE_ORBIT, ICON_RULER_SQUARE, - ICON_SPHERE, ICON_TAG, + ICON_ALERT, ICON_ALPHA_A_BOX, ICON_ALPHA_B_BOX, ICON_AXIS_ARROW, ICON_CAMERA, + ICON_CAMERA_CONTROL, ICON_CUBE_OUTLINE, ICON_DELETE, ICON_EYE, ICON_EYE_OFF, ICON_HELP, + ICON_IDENTIFIER, ICON_MAP_MARKER, ICON_RADIUS_OUTLINE, ICON_RESIZE, ICON_ROTATE_ORBIT, + ICON_RULER_SQUARE, ICON_SIGN_POLE, ICON_SPHERE, ICON_TAG, }, + render::tween::Tween, resources::Resources, util::{ text::{prettify_distance, split_pascal_case}, @@ -21,7 +22,7 @@ use crate::{ use super::{ components::{ - EntityModel, EntityWorldId, Global, Label, Mutable, ResourcePoint, Ruler, Sphere, + Beacon, EntityModel, EntityWorldId, Global, Label, Mutable, ResourcePoint, Ruler, Sphere, StaticInstances, Visible, }, resolve_entity_icon, resolve_entity_name, @@ -170,7 +171,8 @@ fn show_inspector_components(ui: &mut egui::Ui, e: EntityRef<'_>, resources: &Re // HavokShape, EntityWorldId, Ruler, - Sphere + Sphere, + Beacon ); } @@ -589,3 +591,102 @@ impl ComponentPanel for Sphere { }); } } + +impl ComponentPanel for Beacon { + fn inspector_name() -> &'static str { + "Beacon" + } + + fn inspector_icon() -> char { + ICON_SIGN_POLE + } + + fn has_inspector_ui() -> bool { + true + } + + fn show_inspector_ui(&mut self, e: EntityRef<'_>, ui: &mut egui::Ui, resources: &Resources) { + if !e.has::() { + ui.label(format!( + "{} This entity has no transform component", + ICON_ALERT + )); + } + + ui.horizontal(|ui| { + ui.strong("Distance after travel: "); + ui.add( + egui::DragValue::new(&mut self.distance) + .speed(0.1) + .clamp_range(0f32..=f32::INFINITY) + .min_decimals(2) + .max_decimals(2) + .suffix(" m"), + ) + }); + + ui.horizontal(|ui| { + ui.strong("Duration of travel: "); + ui.add( + egui::DragValue::new(&mut self.travel_time) + .speed(0.1) + .clamp_range(0f32..=60.0) + .min_decimals(2) + .max_decimals(2) + .suffix(" s"), + ) + }); + + ui.horizontal(|ui| { + ui.strong("Blink Frequency"); + ui.add( + egui::DragValue::new(&mut self.freq) + .speed(0.1) + .clamp_range(0.0..=20.0), + ) + }); + + ui.horizontal(|ui| { + ui.color_edit_button_srgb(&mut self.color); + + ui.label("Color"); + }); + + ui.separator(); + + let mut camera = resources.get_mut::().unwrap(); + if let Some(transform) = e.get::<&Transform>() { + ui.label(format!( + "Distance to Beacon: {:.2} m", + (transform.translation - camera.position).length() + )); + + ui.horizontal(|ui| { + if ui.button(ICON_MAP_MARKER.to_string()).clicked() { + camera.tween = Some(Tween::new( + |x| x, + camera.position, + transform.translation - camera.front * self.distance, + camera.orientation, + camera.orientation, + self.travel_time, + )); + } + ui.label("Go to Beacon Location"); + }); + ui.horizontal(|ui| { + if ui.button(ICON_CAMERA.to_string()).clicked() { + camera.tween = Some(Tween::new( + |x| x, + camera.position, + camera.position, + camera.orientation, + camera.get_look_angle(transform.translation), + self.travel_time, + )); + } + ui.label("Look at Beacon Location"); + }); + } + } +} diff --git a/src/ecs/components.rs b/src/ecs/components.rs index dccce1a0..45d44179 100644 --- a/src/ecs/components.rs +++ b/src/ecs/components.rs @@ -146,6 +146,24 @@ impl Default for Sphere { } } } + +pub struct Beacon { + pub color: [u8; 3], + pub freq: f32, + pub distance: f32, + pub travel_time: f32, +} + +impl Default for Beacon { + fn default() -> Self { + Self { + color: [255, 255, 255], + freq: 1.0, + distance: 0.5, + travel_time: 0.25, + } + } +} /// Marker component to indicate that the entity is allowed to be modified in potentially destructive ways /// (e.g. deleting it, changing it's name, etc.) pub struct Mutable; diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index c2abf41e..1803b4d4 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -35,6 +35,7 @@ pub fn resolve_entity_icon(e: EntityRef<'_>) -> Option { icon_from_component_panels!( // TODO(cohae): Custom havok icon // HavokShape, + Beacon, Ruler, Sphere, EntityModel, @@ -66,7 +67,7 @@ pub fn resolve_entity_name(e: EntityRef<'_>, append_ent: bool) -> String { }; } - name_from_component_panels!(Ruler, Sphere, EntityModel, StaticInstances); + name_from_component_panels!(Beacon, Ruler, Sphere, EntityModel, StaticInstances); format!("ent {}", e.entity().id()) } diff --git a/src/main.rs b/src/main.rs index 6602069d..e439923c 100755 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,8 @@ use std::time::{Duration, Instant}; use crate::activity::SActivity; use crate::ecs::components::{ - ActivityGroup, EntityModel, ResourcePoint, Ruler, Sphere, StaticInstances, Terrain, Visible, - Water, + ActivityGroup, Beacon, EntityModel, ResourcePoint, Ruler, Sphere, StaticInstances, Terrain, + Visible, Water, }; use crate::ecs::resolve_aabb; use crate::ecs::resources::SelectedEntity; @@ -853,6 +853,16 @@ pub async fn main() -> anyhow::Result<()> { } draw_sphere(&mut debugshapes, transform, sphere, start_time); } + for (_, (transform, beacon, visible)) in map + .scene + .query::<(&Transform, &Beacon, Option<&Visible>)>() + .iter() + { + if !visible.map_or(true, |v| v.0) { + continue; + } + draw_beacon(&mut debugshapes, transform, beacon, start_time); + } } if let Some(map) = maps.current_map_mut() { @@ -1106,6 +1116,26 @@ fn draw_sphere( debugshapes.sphere(transform.translation, transform.radius(), color); } +fn draw_beacon( + debugshapes: &mut DebugShapes, + transform: &Transform, + beacon: &Beacon, + start_time: Instant, +) { + let color = [ + beacon.color[0], + beacon.color[1], + beacon.color[2], + (150.0 + (start_time.elapsed().as_secs_f32() * 2.0 * PI * beacon.freq).sin() * 50.0) as u8, + ]; + debugshapes.sphere(transform.translation, 0.1, color); + debugshapes.line( + transform.translation + Vec3::Z * 0.1, + transform.translation + Vec3::Z * 5000.0, + color, + ); +} + fn load_render_globals(renderer: &Renderer) { let tag = get_named_tag::<0x8080978C>("render_globals").expect("Could not find render globals!"); diff --git a/src/overlays/menu.rs b/src/overlays/menu.rs index c6b9bb94..77ae972c 100644 --- a/src/overlays/menu.rs +++ b/src/overlays/menu.rs @@ -3,13 +3,12 @@ use glam::Vec3; use crate::{ camera::FpsCamera, ecs::{ - components::{Mutable, Ruler, Sphere}, + components::{Beacon, Mutable, Ruler, Sphere}, resources::SelectedEntity, tags::{EntityTag, Tags}, transform::{Transform, TransformFlags}, }, - icons::ICON_RULER_SQUARE, - icons::ICON_SPHERE, + icons::{ICON_RULER_SQUARE, ICON_SIGN_POLE, ICON_SPHERE}, map::MapDataList, }; @@ -74,6 +73,33 @@ impl Overlay for MenuBar { se.0 = Some(e); } + ui.close_menu(); + } + } + if ui.button(format!("{} Beacon", ICON_SIGN_POLE)).clicked() { + let mut maps: std::cell::RefMut<'_, MapDataList> = + resources.get_mut::().unwrap(); + + if let Some(map) = maps.current_map_mut() { + let camera = resources.get::().unwrap(); + let e = map.scene.spawn(( + Transform { + translation: camera.position, + flags: TransformFlags::IGNORE_ROTATION + | TransformFlags::IGNORE_SCALE, + ..Default::default() + }, + Beacon { + ..Default::default() + }, + Tags::from_iter([EntityTag::Utility]), + Mutable, + )); + + if let Some(mut se) = resources.get_mut::() { + se.0 = Some(e); + } + ui.close_menu(); } } diff --git a/src/render/tween.rs b/src/render/tween.rs index cee937c3..0faa4132 100644 --- a/src/render/tween.rs +++ b/src/render/tween.rs @@ -1,4 +1,4 @@ -use glam::Vec3; +use glam::{Vec2, Vec3}; use std::time::Instant; #[derive(Clone)] @@ -7,23 +7,36 @@ pub struct Tween { pub start: Vec3, pub end: Vec3, pub last_pos: Vec3, + pub start_angle: Vec2, + pub end_angle: Vec2, + pub last_angle: Vec2, pub start_time: Instant, pub duration: f32, } impl Tween { - pub fn new(func: fn(f32) -> f32, start: Vec3, end: Vec3, duration: f32) -> Self { + pub fn new( + func: fn(f32) -> f32, + start: Vec3, + end: Vec3, + start_angle: Vec2, + end_angle: Vec2, + duration: f32, + ) -> Self { Self { func, start, end, last_pos: start, + start_angle, + end_angle, + last_angle: start_angle, start_time: Instant::now(), duration, } } - pub fn update(&mut self) -> Vec3 { + pub fn update_pos(&mut self) -> Vec3 { let time = self.start_time.elapsed().as_secs_f32(); let t = (time / self.duration).clamp(0., 1.); let s = (self.func)(t); @@ -33,6 +46,16 @@ impl Tween { new_pos } + pub fn update_angle(&mut self) -> Vec2 { + let time = self.start_time.elapsed().as_secs_f32(); + let t = (time / self.duration).clamp(0., 1.); + let s = (self.func)(t); + + let new_angle = self.start_angle.lerp(self.end_angle, s); + self.last_angle = new_angle; + new_angle + } + pub fn is_finished(&self) -> bool { self.start_time.elapsed().as_secs_f32() >= self.duration } From a56eca3a6cdd1017ac1d71f84d3aea8118e6b03f Mon Sep 17 00:00:00 2001 From: Froggy618157725 Date: Fri, 12 Jan 2024 21:50:50 -0800 Subject: [PATCH 2/3] Update changelog for beacons --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16045bab..a3233e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - Add Global Utility Objects by @Froggy618167725 in [#12](https://github.com/cohaereo/alkahest/pull/12) - Lazy entity updating by @cohaereo - Global entity tag by @cohaereo +- Add Beacon Utility tool by @Froggy618157725 in [#13](https://github.com/cohaereo/alkahest/pull/13) ### Changed From 850043f59069717f1f49a212965986f90ccdde21 Mon Sep 17 00:00:00 2001 From: Froggy618157725 Date: Sun, 14 Jan 2024 02:13:27 -0800 Subject: [PATCH 3/3] Fix up tweens Angles and movement are now optional. Changing your angle won't interupt a movement only tween. --- src/camera.rs | 18 ++++++++++++------ src/ecs/component_panels.rs | 18 ++++++++++-------- src/ecs/components.rs | 2 +- src/render/tween.rs | 34 ++++++++++++++-------------------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index cd91ba4d..cffbde70 100755 --- a/src/camera.rs +++ b/src/camera.rs @@ -70,6 +70,14 @@ impl FpsCamera { pub fn update_mouse(&mut self, mouse_delta: Vec2) { self.orientation += Vec2::new(mouse_delta.y * 0.8, mouse_delta.x) * 0.15; + // Cancel angle tween if the user rotates the camera + if self + .tween + .as_ref() + .map_or(false, |t| t.angle_movement.is_some()) + { + self.tween = None; + } self.update_vectors(); } @@ -145,8 +153,8 @@ impl FpsCamera { } if let Some(tween) = &mut self.tween { - self.position = tween.update_pos(); - self.orientation = tween.update_angle(); + self.position = tween.update_pos().unwrap_or(self.position); + self.orientation = tween.update_angle().unwrap_or(self.orientation); } else { self.position += direction * speed; } @@ -298,10 +306,8 @@ impl FpsCamera { pub fn focus(&mut self, pos: Vec3, distance: f32) { self.tween = Some(Tween::new( tween::ease_out_exponential, - self.position, - pos - self.front * distance, - self.orientation, - self.orientation, + Some((self.position, pos - self.front * distance)), + None, 0.70, )); } diff --git a/src/ecs/component_panels.rs b/src/ecs/component_panels.rs index 6c6c361d..9bd0c891 100644 --- a/src/ecs/component_panels.rs +++ b/src/ecs/component_panels.rs @@ -665,10 +665,11 @@ impl ComponentPanel for Beacon { if ui.button(ICON_MAP_MARKER.to_string()).clicked() { camera.tween = Some(Tween::new( |x| x, - camera.position, - transform.translation - camera.front * self.distance, - camera.orientation, - camera.orientation, + Some(( + camera.position, + transform.translation - camera.front * self.distance, + )), + None, self.travel_time, )); } @@ -678,10 +679,11 @@ impl ComponentPanel for Beacon { if ui.button(ICON_CAMERA.to_string()).clicked() { camera.tween = Some(Tween::new( |x| x, - camera.position, - camera.position, - camera.orientation, - camera.get_look_angle(transform.translation), + None, + Some(( + camera.orientation, + camera.get_look_angle(transform.translation), + )), self.travel_time, )); } diff --git a/src/ecs/components.rs b/src/ecs/components.rs index 45d44179..261b3a2d 100644 --- a/src/ecs/components.rs +++ b/src/ecs/components.rs @@ -160,7 +160,7 @@ impl Default for Beacon { color: [255, 255, 255], freq: 1.0, distance: 0.5, - travel_time: 0.25, + travel_time: 0.7, } } } diff --git a/src/render/tween.rs b/src/render/tween.rs index 0faa4132..dad37865 100644 --- a/src/render/tween.rs +++ b/src/render/tween.rs @@ -4,12 +4,10 @@ use std::time::Instant; #[derive(Clone)] pub struct Tween { func: fn(f32) -> f32, - pub start: Vec3, - pub end: Vec3, - pub last_pos: Vec3, - pub start_angle: Vec2, - pub end_angle: Vec2, - pub last_angle: Vec2, + pub pos_movement: Option<(Vec3, Vec3)>, + pub last_pos: Option, + pub angle_movement: Option<(Vec2, Vec2)>, + pub last_angle: Option, pub start_time: Instant, pub duration: f32, } @@ -17,41 +15,37 @@ pub struct Tween { impl Tween { pub fn new( func: fn(f32) -> f32, - start: Vec3, - end: Vec3, - start_angle: Vec2, - end_angle: Vec2, + pos_movement: Option<(Vec3, Vec3)>, + angle_movement: Option<(Vec2, Vec2)>, duration: f32, ) -> Self { Self { func, - start, - end, - last_pos: start, - start_angle, - end_angle, - last_angle: start_angle, + pos_movement, + last_pos: pos_movement.map(|pos| pos.0), + angle_movement, + last_angle: angle_movement.map(|angle| angle.0), start_time: Instant::now(), duration, } } - pub fn update_pos(&mut self) -> Vec3 { + pub fn update_pos(&mut self) -> Option { let time = self.start_time.elapsed().as_secs_f32(); let t = (time / self.duration).clamp(0., 1.); let s = (self.func)(t); - let new_pos = self.start.lerp(self.end, s); + let new_pos = self.pos_movement.map(|pos| pos.0.lerp(pos.1, s)); self.last_pos = new_pos; new_pos } - pub fn update_angle(&mut self) -> Vec2 { + pub fn update_angle(&mut self) -> Option { let time = self.start_time.elapsed().as_secs_f32(); let t = (time / self.duration).clamp(0., 1.); let s = (self.func)(t); - let new_angle = self.start_angle.lerp(self.end_angle, s); + let new_angle = self.angle_movement.map(|angle| angle.0.lerp(angle.1, s)); self.last_angle = new_angle; new_angle }