Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/viewport #29

Merged
merged 9 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ impl Sandbox for Gui {
GuiMessage::UpdateSurfaceLatitude(latitude) => {
self.surface_view_state.surface_latitude = latitude;
}
GuiMessage::UpdateViewportOpeningAngle(angle) => {
if angle.as_degrees() < 10. {
self.surface_view_state.viewport_horizontal_opening_angle =
Angle::from_degrees(10.);
} else if angle.as_degrees() > 170. {
self.surface_view_state.viewport_horizontal_opening_angle =
Angle::from_degrees(170.);
} else {
self.surface_view_state.viewport_horizontal_opening_angle = angle;
}
}
GuiMessage::UpdateLengthScale(m_per_px) => {
self.topview_state.set_meter_per_pixel(m_per_px);
}
Expand Down Expand Up @@ -206,6 +217,7 @@ pub(super) enum GuiMessage {
UpdateTimeStep(Time),
UpdateSurfaceLongitude(Angle),
UpdateSurfaceLatitude(Angle),
UpdateViewportOpeningAngle(Angle),
UpdateLengthScale(Float),
UpdateViewLongitude(Angle),
UpdateViewLatitude(Angle),
Expand Down
93 changes: 57 additions & 36 deletions src/gui/surface_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ use crate::model::celestial_body::CelestialBody;
use super::{Gui, GuiMessage};
use astro_utils::{
coordinates::{
cartesian::{CartesianCoordinates, ORIGIN},
direction::{Direction, X, Z},
ecliptic::EclipticCoordinates,
equatorial::EquatorialCoordinates,
cartesian::CartesianCoordinates, direction::Direction, equatorial::EquatorialCoordinates,
spherical::SphericalCoordinates,
},
surface_normal::{apparent_celestial_position, surface_normal_at_time},
surface_normal::{direction_relative_to_surface_normal, surface_normal_at_time},
units::angle::Angle,
Float,
};
Expand All @@ -20,14 +17,17 @@ use iced::{
canvas::{self, Path},
Column,
},
Alignment, Color,
Alignment, Color, Point,
};

const HUMAN_EYE_OPENING_ANGLE: Angle = Angle::from_radians(120. / 360. * 2. * PI);

pub(super) struct SurfaceViewState {
pub(super) background_cache: canvas::Cache,
pub(super) bodies_cache: canvas::Cache,
pub(super) surface_longitude: Angle,
pub(super) surface_latitude: Angle,
pub(super) viewport_horizontal_opening_angle: Angle,
}

impl SurfaceViewState {
Expand All @@ -37,6 +37,7 @@ impl SurfaceViewState {
bodies_cache: canvas::Cache::default(),
surface_longitude: Angle::from_degrees(0.0),
surface_latitude: Angle::from_degrees(0.0),
viewport_horizontal_opening_angle: HUMAN_EYE_OPENING_ANGLE,
}
}

Expand All @@ -48,26 +49,34 @@ impl SurfaceViewState {

impl Gui {
pub(super) fn surface_view_control_field(&self) -> iced::Element<'_, GuiMessage> {
const SURFACE_ANGLE_STEP: Angle = Angle::from_radians(10. * 2. * PI / 360.);
const ANGLE_STEP: Angle = Angle::from_radians(10. * 2. * PI / 360.);
let longitude = self.surface_view_state.surface_longitude;
let surface_longitude_control_field = self.control_field(
"Surface Longitude:",
format!("{}", longitude),
GuiMessage::UpdateSurfaceLongitude(longitude - SURFACE_ANGLE_STEP),
GuiMessage::UpdateSurfaceLongitude(longitude + SURFACE_ANGLE_STEP),
GuiMessage::UpdateSurfaceLongitude(longitude - ANGLE_STEP),
GuiMessage::UpdateSurfaceLongitude(longitude + ANGLE_STEP),
);
let latitude = self.surface_view_state.surface_latitude;
let surface_latitude_control_field = self.control_field(
"Surface Latitude:",
format!("{}", latitude),
GuiMessage::UpdateSurfaceLatitude(latitude - SURFACE_ANGLE_STEP),
GuiMessage::UpdateSurfaceLatitude(latitude + SURFACE_ANGLE_STEP),
GuiMessage::UpdateSurfaceLatitude(latitude - ANGLE_STEP),
GuiMessage::UpdateSurfaceLatitude(latitude + ANGLE_STEP),
);
let viewport_angle = self.surface_view_state.viewport_horizontal_opening_angle;
let viewport_angle_control_field = self.control_field(
"Horizontal Viewport Opening Angle:",
format!("{}", viewport_angle),
GuiMessage::UpdateViewportOpeningAngle(viewport_angle - ANGLE_STEP),
GuiMessage::UpdateViewportOpeningAngle(viewport_angle + ANGLE_STEP),
);
let planet_picker = self.planet_picker();
Column::new()
.push(self.time_control_fields())
.push(surface_longitude_control_field)
.push(surface_latitude_control_field)
.push(viewport_angle_control_field)
.push(planet_picker)
.width(iced::Length::Fill)
.align_items(Alignment::Center)
Expand All @@ -77,7 +86,7 @@ impl Gui {
fn observer_data(&self) -> (CartesianCoordinates, Direction) {
let body = match self.selected_body.as_ref() {
Some(body) => body,
None => return (ORIGIN, Z),
None => return (CartesianCoordinates::ORIGIN, Direction::Z),
};

let observer_equatorial_position = EquatorialCoordinates::new(
Expand All @@ -102,57 +111,69 @@ impl Gui {
(observer_position, observer_normal)
}

fn pixel_per_viewport_width(&self, canvas_width: f32) -> Float {
let opening_angle = self.surface_view_state.viewport_horizontal_opening_angle;
let viewport_width = (opening_angle / 2.).sin() * 2.; //Viewport is at unit distance
canvas_width / viewport_width
}

fn surface_view_canvas_position(
&self,
body: &CelestialBody,
observer_position: &CartesianCoordinates,
observer_normal: &Direction,
canvas_radius: f32,
) -> iced::Vector {
const PI_HALF: Float = PI / 2.;
pixel_per_viewport_width: Float,
) -> Option<iced::Vector> {
let relative_position = body.get_position() - observer_position;
let ecliptic_position = EclipticCoordinates::from_cartesian(&relative_position);
let surface_position = apparent_celestial_position(&ecliptic_position, observer_normal);
let x = surface_position.get_longitude().as_radians() / PI_HALF * canvas_radius;
let y = -surface_position.get_latitude().as_radians() / PI_HALF * canvas_radius; // y axis is inverted
iced::Vector::new(x as f32, y as f32)
let direction = Direction::from_cartesian(&relative_position);
let direction = direction_relative_to_surface_normal(&direction, observer_normal);
if direction.z() > 0.0 {
let x = direction.x() * pixel_per_viewport_width;
let y = -direction.y() * pixel_per_viewport_width; // y axis is inverted
Some(iced::Vector::new(x as f32, y as f32))
} else {
None
}
}

pub(super) fn surface_view_canvas(
&self,
renderer: &iced::Renderer,
bounds: iced::Rectangle,
) -> Vec<canvas::Geometry> {
let canvas_radius = bounds.size().width.min(bounds.size().height) / 2.;
let background =
self.surface_view_state
.background_cache
.draw(renderer, bounds.size(), |frame| {
let background = Path::circle(frame.center(), canvas_radius);
let background = Path::rectangle(Point::ORIGIN, bounds.size());
frame.fill(&background, Color::BLACK);
});

let (observer_position, observer_normal) = self.observer_data();

let pixel_per_viewport_width = self.pixel_per_viewport_width(bounds.width);
let bodies = self
.surface_view_state
.bodies_cache
.draw(renderer, bounds.size(), |frame| {
let bodies_path = Path::new(|path_builder| {
for body in self.celestial_bodies.iter() {
let pos = frame.center()
+ Self::surface_view_canvas_position(
body,
&observer_position,
&observer_normal,
canvas_radius,
);
path_builder.circle(pos, 3.0);

let mut name_widget = canvas::Text::default();
name_widget.color = Color::WHITE;
name_widget.content = body.get_name().to_string();
name_widget.position = pos;
frame.fill_text(name_widget);
let pos = self.surface_view_canvas_position(
body,
&observer_position,
&observer_normal,
pixel_per_viewport_width,
);
if let Some(pos) = pos {
let pos = frame.center() + pos;
path_builder.circle(pos, 3.0);

let mut name_widget = canvas::Text::default();
name_widget.color = Color::WHITE;
name_widget.content = body.get_name().to_string();
name_widget.position = pos;
frame.fill_text(name_widget);
}
}
});
frame.fill(&bodies_path, Color::WHITE);
Expand Down
Loading