Skip to content

Commit

Permalink
Update View2 and View3 (#210)
Browse files Browse the repository at this point in the history
This PR makes `View2` and `View3` more useful as camera types, and
implements proper 3D rendering in the `fidget-viewer` demo app.
  • Loading branch information
mkeeter authored Dec 26, 2024
1 parent d4bf6a9 commit 00401ce
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 115 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- `ThreadCount` is moved to `fidget::mesh`, because that's the only place
it's now used
- The plan is to switch to Rayon for meshing as well, eventually
- Tweak `View2` and `View3` APIs to make them more useful as camera types

# 0.3.3
- `Function` and evaluator types now produce multiple outputs
Expand Down
154 changes: 90 additions & 64 deletions demos/viewer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use eframe::egui;
use env_logger::Env;
use log::{debug, error, info, warn};
use nalgebra::{Point2, Vector3};
use nalgebra::{Point2, Point3};
use notify::Watcher;

use fidget::render::{ImageRenderConfig, View2, View3, VoxelRenderConfig};
Expand Down Expand Up @@ -205,24 +205,21 @@ fn render<F: fidget::eval::Function + fidget::render::RenderHints>(
}
}
}
RenderMode::ThreeD(camera, mode) => {
RenderMode::ThreeD { view, mode, .. } => {
// XXX allow selection of depth?
let config = VoxelRenderConfig {
image_size: fidget::render::VoxelSize::new(
image_size.width(),
image_size.height(),
512,
image_size.width().max(image_size.height()),
),
tile_sizes: F::tile_sizes_3d(),
view: View3::from_center_and_scale(
Vector3::new(camera.offset.x, camera.offset.y, 0.0),
camera.scale,
),
view: *view,
..Default::default()
};
let (depth, color) = config.run(shape);
match mode {
ThreeDMode::Color => {
Mode3D::Color => {
for (p, (&d, &c)) in
pixels.iter_mut().zip(depth.iter().zip(&color))
{
Expand All @@ -232,7 +229,7 @@ fn render<F: fidget::eval::Function + fidget::render::RenderHints>(
}
}

ThreeDMode::Heightmap => {
Mode3D::Heightmap => {
let max_depth =
depth.iter().max().cloned().unwrap_or(1).max(1);
for (p, &d) in pixels.iter_mut().zip(&depth) {
Expand Down Expand Up @@ -338,45 +335,18 @@ enum Mode2D {
Debug,
}

////////////////////////////////////////////////////////////////////////////////

#[derive(Copy, Clone)]
struct ThreeDCamera {
// 2D camera parameters
scale: f32,
offset: nalgebra::Vector3<f32>,
#[allow(unused)]
drag_start: Option<egui::Vec2>,
}

impl ThreeDCamera {
#[allow(unused)]
fn mouse_to_uv(
&self,
rect: egui::Rect,
uv: egui::Rect,
p: egui::Pos2,
) -> egui::Vec2 {
panic!()
}
}

impl Default for ThreeDCamera {
fn default() -> Self {
ThreeDCamera {
drag_start: None,
scale: 1.0,
offset: nalgebra::Vector3::zeros(),
}
}
}

#[derive(Copy, Clone, Eq, PartialEq)]
enum ThreeDMode {
enum Mode3D {
Color,
Heightmap,
}

#[derive(Copy, Clone)]
enum Drag3D {
Pan(fidget::render::TranslateHandle),
Rotate(fidget::render::RotateHandle),
}

////////////////////////////////////////////////////////////////////////////////

#[derive(Copy, Clone)]
Expand All @@ -388,7 +358,14 @@ enum RenderMode {
drag_start: Option<Point2<f32>>,
mode: Mode2D,
},
ThreeD(ThreeDCamera, ThreeDMode),
ThreeD {
view: View3,

/// Drag start position (in model coordinates)
drag_start: Option<Drag3D>,

mode: Mode3D,
},
}

impl RenderMode {
Expand All @@ -399,7 +376,7 @@ impl RenderMode {
*mode = new_mode;
changed
}
RenderMode::ThreeD(..) => {
RenderMode::ThreeD { .. } => {
*self = RenderMode::TwoD {
// TODO get parameters from 3D camera here?
view: Default::default(),
Expand All @@ -410,15 +387,19 @@ impl RenderMode {
}
}
}
fn set_3d_mode(&mut self, mode: ThreeDMode) -> bool {
fn set_3d_mode(&mut self, new_mode: Mode3D) -> bool {
match self {
RenderMode::TwoD { .. } => {
*self = RenderMode::ThreeD(ThreeDCamera::default(), mode);
*self = RenderMode::ThreeD {
view: View3::default(),
drag_start: None,
mode: new_mode,
};
true
}
RenderMode::ThreeD(_camera, m) => {
let changed = *m != mode;
*m = mode;
RenderMode::ThreeD { mode, .. } => {
let changed = *mode != new_mode;
*mode = new_mode;
changed
}
}
Expand Down Expand Up @@ -474,16 +455,16 @@ impl ViewerApp {
ui.menu_button("Config", |ui| {
let mut mode_3d = match &self.mode {
RenderMode::TwoD { .. } => None,
RenderMode::ThreeD(_camera, mode) => Some(*mode),
RenderMode::ThreeD { mode, .. } => Some(*mode),
};
ui.radio_value(
&mut mode_3d,
Some(ThreeDMode::Heightmap),
Some(Mode3D::Heightmap),
"3D heightmap",
);
ui.radio_value(
&mut mode_3d,
Some(ThreeDMode::Color),
Some(Mode3D::Color),
"3D color",
);
if let Some(m) = mode_3d {
Expand All @@ -492,7 +473,7 @@ impl ViewerApp {
ui.separator();
let mut mode_2d = match &self.mode {
RenderMode::TwoD { mode, .. } => Some(*mode),
RenderMode::ThreeD(..) => None,
RenderMode::ThreeD { .. } => None,
};
ui.radio_value(
&mut mode_2d,
Expand Down Expand Up @@ -562,9 +543,6 @@ impl ViewerApp {
});
const PADDING: egui::Vec2 = egui::Vec2 { x: 10.0, y: 10.0 };

if !matches!(self.mode, RenderMode::TwoD { .. }) {
panic!("can't render in 3D");
}
let rect = ui.ctx().available_rect();
let uv = egui::Rect {
min: egui::Pos2::new(0.0, 0.0),
Expand Down Expand Up @@ -664,8 +642,9 @@ impl eframe::App for ViewerApp {
rect.height() as u32,
);

let mat = view.world_to_model() * image_size.screen_to_world();
if let Some(pos) = r.interact_pointer_pos() {
let mat =
view.world_to_model() * image_size.screen_to_world();
let pos = mat.transform_point(&Point2::new(pos.x, pos.y));
if let Some(prev) = *drag_start {
view.translate(prev - pos);
Expand All @@ -679,18 +658,65 @@ impl eframe::App for ViewerApp {

if r.hovered() {
let scroll = ctx.input(|i| i.smooth_scroll_delta.y);
let mouse_pos = ctx
.input(|i| i.pointer.hover_pos())
.map(|p| mat.transform_point(&Point2::new(p.x, p.y)));
let mouse_pos = r.hover_pos().map(|p| {
let p = p - rect.min;
image_size.transform_point(Point2::new(p.x, p.y))
});
render_changed |=
view.zoom((scroll / 100.0).exp2(), mouse_pos);
}
}
RenderMode::ThreeD {
view, drag_start, ..
} => {
let image_size = fidget::render::VoxelSize::new(
rect.width() as u32,
rect.height() as u32,
rect.width().max(rect.height()) as u32,
);

if let Some(pos) = r.interact_pointer_pos() {
let pos_world = image_size
.transform_point(Point3::new(pos.x, pos.y, 0.0));
match *drag_start {
Some(Drag3D::Pan(prev)) => {
render_changed |= view.translate(prev, pos_world);
}
Some(Drag3D::Rotate(prev)) => {
render_changed |= view.rotate(prev, pos_world);
}
None => {
if r.dragged_by(egui::PointerButton::Primary) {
*drag_start = Some(Drag3D::Pan(
view.begin_translate(pos_world),
));
} else if r
.dragged_by(egui::PointerButton::Secondary)
{
*drag_start = Some(Drag3D::Rotate(
view.begin_rotate(pos_world),
));
}
}
}
} else {
*drag_start = None;
}

if r.hovered() {
let scroll = ctx.input(|i| i.smooth_scroll_delta.y);
let mouse_pos =
ctx.input(|i| i.pointer.hover_pos()).map(|p| {
let p = p - rect.min;
image_size
.transform_point(Point3::new(p.x, p.y, 0.0))
});
if scroll != 0.0 {
view.zoom((scroll / 100.0).exp2(), mouse_pos);
render_changed = true;
}
}
}
RenderMode::ThreeD(..) => {
unimplemented!()
}
}

// Kick off a new render if we changed any settings
Expand Down
2 changes: 1 addition & 1 deletion fidget/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod view;
use config::Tile;
pub use config::{ImageRenderConfig, ThreadPool, VoxelRenderConfig};
pub use region::{ImageSize, RegionSize, VoxelSize};
pub use view::{View2, View3};
pub use view::{RotateHandle, TranslateHandle, View2, View3};

use render2d::render as render2d;
use render3d::render as render3d;
Expand Down
12 changes: 11 additions & 1 deletion fidget/src/render/region.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use nalgebra::{
allocator::Allocator, Const, DefaultAllocator, DimNameAdd, DimNameSub,
DimNameSum, OMatrix, OVector, Vector2, Vector3, U1,
DimNameSum, OMatrix, OVector, Point2, Point3, Vector2, Vector3, U1,
};

/// Image size in pixels, used to generate a screen-to-world matrix
Expand Down Expand Up @@ -133,6 +133,11 @@ impl ImageSize {
size: Vector2::new(width, height),
}
}

/// Transforms a point from screen to world coordinates
pub fn transform_point(&self, p: Point2<f32>) -> Point2<f32> {
self.screen_to_world().transform_point(&p)
}
}

/// Size for 3D rendering of an image
Expand All @@ -149,6 +154,11 @@ impl VoxelSize {
pub fn depth(&self) -> u32 {
self.size.z
}

/// Transforms a point from screen to world coordinates
pub fn transform_point(&self, p: Point3<f32>) -> Point3<f32> {
self.screen_to_world().transform_point(&p)
}
}

impl<const N: usize> std::ops::Index<usize> for RegionSize<N>
Expand Down
Loading

0 comments on commit 00401ce

Please sign in to comment.