use egui::{Color32, PointerButton, Pos2, Response, Sense, Ui};
use egui_wgpu::RenderState;
use crate::core::scene3d::camera::{Camera, CameraDirection, CameraFace};
use crate::core::scene3d::interaction::{OrbitDrag, PanDrag, window_to_ndc};
use crate::core::scene3d::mat4::Vec3;
use crate::core::scene3d::pick::{picking_segment, segment_triangles_intersection};
use crate::render::gpu_scene3d::{
Scene3dGeometry, Scene3dId, install_scene3d, paint_scene3d, set_scene3d, snapshot_scene3d,
};
const DEFAULT_BACKGROUND: Color32 = Color32::from_gray(30);
const DEFAULT_BOX_COLOR: Color32 = Color32::from_gray(200);
pub struct SceneWidget {
id: Scene3dId,
camera: Camera,
bounds: (Vec3, Vec3),
box_color: Color32,
background: Color32,
content: Scene3dGeometry,
orbit: Option<OrbitDrag>,
pan: Option<PanDrag>,
}
impl SceneWidget {
pub fn new(render_state: &RenderState, id: Scene3dId) -> Self {
install_scene3d(render_state);
let bounds = (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0));
let mut camera = Camera::new(
30.0,
0.1,
100.0,
(1.0, 1.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 0.0, -1.0),
Vec3::new(0.0, 1.0, 0.0),
);
camera.extrinsic.reset(CameraFace::Side);
camera.reset_camera(bounds);
camera.adjust_depth_extent(bounds);
let widget = SceneWidget {
id,
camera,
bounds,
box_color: DEFAULT_BOX_COLOR,
background: DEFAULT_BACKGROUND,
content: Scene3dGeometry::new(),
orbit: None,
pan: None,
};
widget.upload(render_state);
widget
}
pub fn set_background(&mut self, color: Color32) {
self.background = color;
}
pub fn center(&self) -> Vec3 {
(self.bounds.0 + self.bounds.1) * 0.5
}
pub fn camera(&self) -> &Camera {
&self.camera
}
pub fn camera_mut(&mut self) -> &mut Camera {
&mut self.camera
}
pub fn set_bounds(&mut self, render_state: &RenderState, bounds: (Vec3, Vec3)) {
self.bounds = bounds;
self.camera.reset_camera(bounds);
self.camera.adjust_depth_extent(bounds);
self.upload(render_state);
}
pub fn set_bounds_keep_view(&mut self, render_state: &RenderState, bounds: (Vec3, Vec3)) {
self.bounds = bounds;
self.camera.adjust_depth_extent(bounds);
self.upload(render_state);
}
pub fn set_geometry(&mut self, render_state: &RenderState, geometry: Scene3dGeometry) {
self.content = geometry;
self.upload(render_state);
}
pub fn reset_camera(&mut self) {
self.camera.reset_camera(self.bounds);
self.camera.adjust_depth_extent(self.bounds);
}
pub fn set_viewpoint(&mut self, face: CameraFace) {
self.camera.extrinsic.reset(face);
self.camera.reset_camera(self.bounds);
self.camera.adjust_depth_extent(self.bounds);
}
pub fn rotate_scene(&mut self, angle_degrees: f32) {
let center = self.center();
self.camera
.extrinsic
.orbit(CameraDirection::Left, center, angle_degrees);
self.camera.adjust_depth_extent(self.bounds);
}
fn upload(&self, render_state: &RenderState) {
let mut geometry = Scene3dGeometry::new();
geometry.add_bounding_box_with_axes(self.bounds, self.box_color);
geometry.extend_from(&self.content);
set_scene3d(render_state, self.id, &geometry);
}
pub fn show(&mut self, ui: &mut Ui) -> Response {
let (rect, response) = ui.allocate_exact_size(ui.available_size(), Sense::click_and_drag());
let ppp = ui.ctx().pixels_per_point();
let size_px = (
(rect.width() * ppp).max(1.0),
(rect.height() * ppp).max(1.0),
);
self.camera.set_size(size_px);
let center = self.center();
let to_local = |p: Pos2| ((p.x - rect.min.x) * ppp, (p.y - rect.min.y) * ppp);
let press_origin = ui.ctx().input(|i| i.pointer.press_origin());
if response.drag_started_by(PointerButton::Primary)
&& let Some(p) = press_origin
{
self.orbit = Some(OrbitDrag::begin(&self.camera, to_local(p), center));
}
if response.dragged_by(PointerButton::Primary)
&& let (Some(orbit), Some(p)) = (self.orbit, response.interact_pointer_pos())
{
orbit.update(&mut self.camera, to_local(p), size_px);
}
if response.drag_stopped_by(PointerButton::Primary) {
self.orbit = None;
}
if response.drag_started_by(PointerButton::Secondary)
&& let Some(p) = press_origin
{
self.pan = Some(PanDrag::begin(&self.camera, to_local(p), size_px, center));
}
if response.dragged_by(PointerButton::Secondary)
&& let (Some(mut pan), Some(p)) = (self.pan, response.interact_pointer_pos())
{
pan.update(&mut self.camera, to_local(p), size_px);
self.pan = Some(pan);
}
if response.drag_stopped_by(PointerButton::Secondary) {
self.pan = None;
}
let scroll = ui.input(|i| i.smooth_scroll_delta.y);
if scroll != 0.0
&& let Some(p) = response.hover_pos()
{
let (nx, ny) = window_to_ndc(to_local(p), size_px);
let ndc_z = self.camera.matrix().transform_point(center, true).z;
self.camera.zoom_at((nx, ny), ndc_z, scroll > 0.0);
}
self.camera.adjust_depth_extent(self.bounds);
paint_scene3d(ui, rect, self.id, &self.camera, self.background);
response
}
pub fn snapshot(&self, render_state: &RenderState, size_px: (u32, u32)) -> Option<Vec<u8>> {
snapshot_scene3d(
render_state,
self.id,
&self.camera,
self.background,
size_px,
)
}
pub fn pick(&self, ndc: (f32, f32)) -> Option<ScenePick> {
let segment = picking_segment(&self.camera, ndc)?;
let mvp = self.camera.matrix();
let mut best: Option<ScenePick> = None;
let mut consider = |cand: ScenePick| {
if best.is_none_or(|b| cand.ndc_depth < b.ndc_depth) {
best = Some(cand);
}
};
let triangles = self.content.pick_triangles();
if let Some(hit) = segment_triangles_intersection(segment, &triangles).first() {
let position = hit.position(segment.0, segment.1);
let ndc_depth = mvp.transform_point(position, true).z;
consider(ScenePick {
position,
ndc_depth,
kind: ScenePickKind::Surface,
});
}
let (vw, vh) = self.camera.size();
let radius_ndc_x = 2.0 * PICK_POINT_TOLERANCE_PX / vw.max(1.0);
let radius_ndc_y = 2.0 * PICK_POINT_TOLERANCE_PX / vh.max(1.0);
for (index, world) in self.content.pick_points().into_iter().enumerate() {
let p = mvp.transform_point(world, true);
if !(-1.0..=1.0).contains(&p.z) {
continue; }
let dx = (p.x - ndc.0) / radius_ndc_x;
let dy = (p.y - ndc.1) / radius_ndc_y;
if dx * dx + dy * dy <= 1.0 {
consider(ScenePick {
position: world,
ndc_depth: p.z,
kind: ScenePickKind::Point { index },
});
}
}
best
}
}
pub const PICK_POINT_TOLERANCE_PX: f32 = 7.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ScenePickKind {
Surface,
Point { index: usize },
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScenePick {
pub position: Vec3,
pub ndc_depth: f32,
pub kind: ScenePickKind,
}
const VIEWPOINT_PRESETS: [(CameraFace, &str, &str); 7] = [
(CameraFace::Front, "Front", "View along the -Z axis"),
(CameraFace::Back, "Back", "View along the +Z axis"),
(CameraFace::Top, "Top", "View along the -Y axis"),
(CameraFace::Bottom, "Bottom", "View along the +Y axis"),
(CameraFace::Right, "Right", "View along the -X axis"),
(CameraFace::Left, "Left", "View along the +X axis"),
(CameraFace::Side, "Side", "Side view"),
];
pub fn viewpoint_menu(ui: &mut Ui, scene: &mut SceneWidget) -> Option<CameraFace> {
let mut chosen = None;
ui.menu_button("View", |ui| {
for (face, label, tip) in VIEWPOINT_PRESETS {
if ui.button(label).on_hover_text(tip).clicked() {
scene.set_viewpoint(face);
chosen = Some(face);
ui.close();
}
}
})
.response
.on_hover_text("Reset the viewpoint to a defined position");
chosen
}