Skip to content

Commit d8768f0

Browse files
committed
Test.NativeLaunch: add a somewhat clunky orbit cam
1 parent 50e668b commit d8768f0

File tree

2 files changed

+192
-54
lines changed

2 files changed

+192
-54
lines changed

Test.NativeLaunch/src/main.rs

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ use std::ptr::null_mut;
7878
use std::sync::Mutex;
7979
use std::{path::PathBuf, time::SystemTime};
8080

81-
use glam::Vec3;
81+
use glam::{Vec2, Vec3};
8282
// If these imports get too ugly or rust analyzer sticks them all on one line, use LLM to reorg them.
8383
use winapi::ctypes::c_void;
8484
use winapi::shared::dxgiformat::DXGI_FORMAT_D24_UNORM_S8_UINT;
@@ -128,7 +128,7 @@ use winapi::um::errhandlingapi::GetLastError;
128128
use winapi::Interface;
129129

130130
use crate::load_mmobj::test_load_mmobj;
131-
use crate::render::{get_empty_vertices, get_indices, prepare_shader_constants};
131+
use crate::render::{get_empty_vertices, get_indices, prepare_shader_constants, ModelViewParams};
132132

133133
#[macro_use]
134134
extern crate anyhow;
@@ -158,6 +158,7 @@ fn add_winevent(evt:WinEvent) {
158158
enum WinEvent {
159159
MouseWheel(i16),
160160
MousePan(i16, i16),
161+
MouseRot(i16, i16),
161162
}
162163

163164
fn get_x_lparam(lparam: LPARAM) -> i16 {
@@ -173,6 +174,7 @@ unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam
173174

174175
static mut LAST_MOUSE_POS: (i16, i16) = (0, 0);
175176
static mut MOUSE_PAN_ACTIVE: bool = false;
177+
static mut MOUSE_MOVE_WITHOUT_SHIFT_ACTIVE: bool = false;
176178

177179
match msg {
178180
WM_DESTROY => {
@@ -188,30 +190,39 @@ unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam
188190
0
189191
}
190192
winapi::um::winuser::WM_MOUSEMOVE => {
191-
if MOUSE_PAN_ACTIVE {
192193
let x_pos = get_x_lparam(lparam);
193194
let y_pos = get_y_lparam(lparam);
194195

196+
if MOUSE_PAN_ACTIVE || MOUSE_MOVE_WITHOUT_SHIFT_ACTIVE {
195197
let delta_x = x_pos.saturating_sub(LAST_MOUSE_POS.0 as i16);
196198
let delta_y = y_pos.saturating_sub(LAST_MOUSE_POS.1 as i16);
197199

198-
add_winevent(WinEvent::MousePan(delta_x as i16, delta_y as i16));
199-
LAST_MOUSE_POS = (x_pos as i16, y_pos as i16);
200+
if MOUSE_PAN_ACTIVE {
201+
add_winevent(WinEvent::MousePan(delta_x as i16, delta_y as i16));
202+
} else if MOUSE_MOVE_WITHOUT_SHIFT_ACTIVE {
203+
add_winevent(WinEvent::MouseRot(delta_x as i16, delta_y as i16));
204+
}
200205
}
206+
207+
LAST_MOUSE_POS = (x_pos as i16, y_pos as i16);
201208
0
202209
}
203210
winapi::um::winuser::WM_MBUTTONDOWN => {
204-
if winapi::um::winuser::GetKeyState(winapi::um::winuser::VK_SHIFT) < 0 {
205-
MOUSE_PAN_ACTIVE = true;
206211
LAST_MOUSE_POS = (
207212
get_x_lparam(lparam),
208213
get_y_lparam(lparam),
209214
);
215+
216+
if winapi::um::winuser::GetKeyState(winapi::um::winuser::VK_SHIFT) < 0 {
217+
MOUSE_PAN_ACTIVE = true;
218+
} else {
219+
MOUSE_MOVE_WITHOUT_SHIFT_ACTIVE = true;
210220
}
211221
0
212222
}
213223
winapi::um::winuser::WM_MBUTTONUP => {
214224
MOUSE_PAN_ACTIVE = false;
225+
MOUSE_MOVE_WITHOUT_SHIFT_ACTIVE = false;
215226
0
216227
}
217228
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
@@ -987,8 +998,12 @@ unsafe fn runapp() -> anyhow::Result<()> {
987998
let mut total_dip_calls = 0;
988999
let mut info_start = SystemTime::now();
9891000
let mut removed_once = false;
990-
let mut zoom: i16 = 0;
1001+
let mut zoom: f32 = 0.0;
9911002
let (mut pan_x, mut pan_y): (i16, i16) = (0,0);
1003+
let (mut rot_x, mut rot_y): (f32, f32) = (0.0,0.0);
1004+
1005+
let orbit_cam = true;
1006+
9921007
while !done {
9931008
if SystemTime::now().duration_since(info_start).expect("whatever").as_secs() >= 1 {
9941009
println!("dip calls: {}, prim/vert count: {:?}", dip_calls, (prim_count,vert_count));
@@ -1039,14 +1054,28 @@ unsafe fn runapp() -> anyhow::Result<()> {
10391054
for e in evts.iter() {
10401055
match e {
10411056
WinEvent::MouseWheel(delta) => {
1042-
zoom = zoom.saturating_add(*delta * 10);
1043-
//println!("zoom {}", zoom);
1057+
if !orbit_cam {
1058+
zoom = (zoom as i32 + (*delta as i32 * 1000)) as f32;
1059+
1060+
// clamp zoom to range (-render::ZOOM_MAX, render::ZOOM_MAX)
1061+
zoom = zoom.clamp(-render::ZOOM_MAX as f32, render::ZOOM_MAX as f32);
1062+
} else {
1063+
const ZOOM_SENS : f32 = 0.005;
1064+
zoom = zoom + (*delta as f32 * ZOOM_SENS);
1065+
}
1066+
1067+
println!("zoom {}", zoom);
10441068
},
10451069
WinEvent::MousePan(x, y) => {
10461070
pan_x = pan_x.saturating_add( (- *x * 50) as i16 );
10471071
pan_y = pan_y.saturating_add( (- *y * 50) as i16 );
10481072

10491073
//println!("pan: {pan_x},{pan_y}")
1074+
},
1075+
WinEvent::MouseRot(x, y) => {
1076+
const ROT_SENS: f32 = 0.005;
1077+
rot_x += *x as f32 * ROT_SENS;
1078+
rot_y += *y as f32 * ROT_SENS;
10501079
}
10511080
}
10521081
}
@@ -1102,23 +1131,45 @@ unsafe fn runapp() -> anyhow::Result<()> {
11021131
};
11031132

11041133
let aspect_ratio = 800 as f32 / 600 as f32;
1105-
let fov_y = std::f32::consts::FRAC_PI_4; // 45 degrees
1134+
let fov_y_radians = std::f32::consts::FRAC_PI_4; // 45 degrees
11061135
let z_near = 0.1;
11071136
let z_far = 100.0;
11081137
let light_dir = Vec3::new(0.0, -1.0, -1.0); // pointing diagonally down-forward
11091138

1139+
let frustum = render::FrustumParams {
1140+
aspect_ratio,
1141+
fov_y_radians,
1142+
z_near,
1143+
z_far
1144+
};
1145+
1146+
let mvp_p = if !orbit_cam {
1147+
ModelViewParams::FixedCam {
1148+
zoom: zoom as i32,
1149+
pan: (pan_x,pan_y),
1150+
origin: origin,
1151+
eye: eye,
1152+
rotation_radians: rotation,
1153+
frustum,
1154+
}
1155+
} else {
1156+
let mut radius = 5.0;
1157+
radius = (radius - (zoom * 0.5)).max(0.1);
1158+
1159+
ModelViewParams::OrbitCam {
1160+
orbit_angles: Vec2::new(rot_x,rot_y),
1161+
radius: radius,
1162+
pan: (pan_x,pan_y),
1163+
pivot: Vec3 { x: 0.0, y: 0.0, z: 0.0 },
1164+
model_rotation: rotation,
1165+
frustum
1166+
}
1167+
};
1168+
11101169
prepare_shader_constants(
11111170
context,
11121171
rend_data,
1113-
zoom,
1114-
(pan_x,pan_y),
1115-
origin,
1116-
eye,
1117-
rotation,
1118-
aspect_ratio,
1119-
fov_y,
1120-
z_near,
1121-
z_far,
1172+
&mvp_p,
11221173
light_dir,
11231174
has_tex0,
11241175
)?;

Test.NativeLaunch/src/render.rs

Lines changed: 121 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use glam::{Mat3, Vec3};
1+
use glam::{Mat3, Vec2, Vec3};
22

33
use glam::{Mat4, Quat};
44
use winapi::um::d3d11::{ID3D11Buffer, ID3D11Device, ID3D11DeviceContext, ID3D11SamplerState, D3D11_COMPARISON_NEVER, D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_FLOAT32_MAX, D3D11_SAMPLER_DESC, D3D11_TEXTURE_ADDRESS_WRAP};
@@ -48,6 +48,8 @@ pub unsafe fn create_data(device:*mut ID3D11Device) -> anyhow::Result<RendData>
4848
Ok(RendData { mvp_buf, light_buf, normal_mat_buf, glob_const_buf: global_constants_buf })
4949
}
5050

51+
pub const ZOOM_MAX:i32 = 10000000;
52+
5153
/// Generates a Model-View-Projection matrix suitable for uploading to a D3D11 constant buffer.
5254
/// Rotation is applied in ZYX order (yaw, pitch, roll).
5355
///
@@ -61,34 +63,60 @@ pub unsafe fn create_data(device:*mut ID3D11Device) -> anyhow::Result<RendData>
6163
/// # Returns
6264
/// - A 4x4 matrix (`Mat4`) representing the MVP transformation.
6365
66+
#[derive(Copy,Clone)]
67+
pub struct FrustumParams {
68+
pub aspect_ratio: f32,
69+
pub fov_y_radians: f32,
70+
pub z_near: f32,
71+
pub z_far: f32,
72+
}
73+
74+
pub enum ModelViewParams {
75+
FixedCam {
76+
zoom: i32,
77+
pan: (i16, i16),
78+
origin: Vec3,
79+
eye: Vec3,
80+
rotation_radians: Vec3,
81+
frustum: FrustumParams,
82+
},
83+
OrbitCam {
84+
orbit_angles: Vec2, // (yaw, pitch)
85+
radius: f32, // orbit “zoom”
86+
pan: (i16, i16), // shift-MMB drag
87+
pivot: Vec3, // what we’re orbiting around
88+
model_rotation: Vec3, // object’s own rotation
89+
frustum: FrustumParams,
90+
},
91+
}
92+
6493
pub fn generate_mvp_matrix(
65-
zoom: i16,
66-
pan: (i16,i16),
67-
origin: Vec3,
68-
eye: Vec3,
69-
rotation_radians: Vec3,
70-
aspect_ratio: f32,
71-
fov_y_radians: f32,
72-
z_near: f32,
73-
z_far: f32,
74-
) -> (Mat4,Mat3) {
94+
mvp_p:&ModelViewParams,
95+
) -> anyhow::Result<(Mat4,Mat3)> {
96+
// If mvp_p is not a FixedCam variant, return an error.
97+
let (zoom, pan, origin, eye, rotation_radians, FrustumParams { aspect_ratio, fov_y_radians, z_near, z_far }) = match mvp_p {
98+
ModelViewParams::FixedCam { zoom, pan, origin, eye, rotation_radians, frustum } => {
99+
(*zoom, *pan, *origin, *eye, *rotation_radians, *frustum)
100+
},
101+
_ => return Err(anyhow::anyhow!("ModelViewParams is not a FixedCam variant")),
102+
};
103+
75104
// Model transform: apply rotation (ZYX)
76105
let rotation = Quat::from_euler(
77106
glam::EulerRot::ZYX,
78107
rotation_radians.z,
79108
rotation_radians.y,
80109
rotation_radians.x,
81110
);
82-
83-
84-
let zoom_normalized = 1.0 + zoom as f32 / i16::MAX as f32;
85-
let scale_factor = zoom_normalized * 2.0;
86-
let scale_matrix = Mat4::from_scale(Vec3::splat(scale_factor));
87111

88-
let pan_x = pan.0 as f32 / i16::MAX as f32 * 2.0; // Range from -2.0 to 2.0
89-
let pan_y = pan.1 as f32 / i16::MAX as f32 * 2.0; // Range from -2.0 to 2.0
112+
// Calculate dolly effect by moving the camera along its look axis
113+
let dolly_distance = zoom as f32 / ZOOM_MAX as f32 * 10.0;
114+
let look_axis = (origin - eye).normalize();
115+
let eye_dolly = eye + look_axis * dolly_distance;
116+
let pan_x = pan.0 as f32 / i16::MAX as f32 * 2.0;
117+
let pan_y = pan.1 as f32 / i16::MAX as f32 * 2.0;
90118
let pan_translation = Vec3::new(pan_x, pan_y, 0.0);
91-
let model = Mat4::from_rotation_translation(rotation, pan_translation) * scale_matrix;
119+
let model = Mat4::from_rotation_translation(rotation, pan_translation);
92120

93121
let col0 = model.x_axis.truncate(); // Vec3 from first column
94122
let col1 = model.y_axis.truncate(); // Vec3 from second column
@@ -97,36 +125,95 @@ pub fn generate_mvp_matrix(
97125

98126
let normal_matrix = model3x3.inverse().transpose();
99127

100-
// let model = Mat4::from_translation(Vec3::new(2.0, 0.0, 0.0)) *
101-
// Mat4::from_rotation_y(rotation_radians.y);
102-
103128
let up = Vec3::Y;
104-
let view = Mat4::look_at_rh(eye, origin, up);
129+
let view = Mat4::look_at_rh(eye_dolly, origin, up); // Use the dolly-adjusted eye position
105130

106131
// Projection: perspective with correct handedness for D3D
107132
let proj = Mat4::perspective_rh(fov_y_radians, aspect_ratio, z_near, z_far);
108133

109134
// Final MVP
110-
(proj * view * model, normal_matrix)
135+
Ok((proj * view * model, normal_matrix))
136+
}
137+
138+
/// Builds the Model-View-Projection and normal matrix for a classic orbit camera.
139+
pub fn generate_mvp_matrix_orbit(
140+
mvp_p: &ModelViewParams,
141+
) -> anyhow::Result<(Mat4, Mat3)> {
142+
let (orbit_angles, radius, pan, pivot, model_rotation, FrustumParams { aspect_ratio, fov_y_radians, z_near, z_far }) = match mvp_p {
143+
ModelViewParams::OrbitCam { orbit_angles, radius, pan, pivot, model_rotation, frustum } => {
144+
(*orbit_angles, *radius, *pan, *pivot, *model_rotation, *frustum)
145+
},
146+
_ => return Err(anyhow::anyhow!("ModelViewParams is not an OrbitCam variant")),
147+
};
148+
149+
// ───────────────────────────────────────────
150+
// 1) ORBIT EYE POSITION
151+
// ───────────────────────────────────────────
152+
// Clamp pitch to prevent flipping over the poles
153+
let pitch = orbit_angles.y.clamp(
154+
-std::f32::consts::FRAC_PI_2 + 0.001,
155+
std::f32::consts::FRAC_PI_2 - 0.001,
156+
);
157+
let yaw = orbit_angles.x;
158+
let (sy, cy) = yaw.sin_cos();
159+
let (sp, cp) = pitch.sin_cos();
160+
161+
// Eye in world space (spherical → Cartesian)
162+
let mut eye = Vec3::new(
163+
radius * cp * sy, // X
164+
radius * sp, // Y
165+
radius * cp * cy, // Z
166+
) + pivot;
167+
168+
// ───────────────────────────────────────────
169+
// 2) OPTIONAL PAN (shift + middle drag)
170+
// ───────────────────────────────────────────
171+
let pan_x = pan.0 as f32 / i16::MAX as f32 * 2.0;
172+
let pan_y = - pan.1 as f32 / i16::MAX as f32 * 2.0;
173+
let pan_vec = Vec3::new(pan_x, pan_y, 0.0);
174+
175+
eye += pan_vec;
176+
let pivot_panned = pivot + pan_vec; // keep target under cursor while panning
177+
178+
// ───────────────────────────────────────────
179+
// 3) MODEL MATRIX (object space → world)
180+
// ───────────────────────────────────────────
181+
let rotation = Quat::from_euler(
182+
glam::EulerRot::ZYX,
183+
model_rotation.z,
184+
model_rotation.y,
185+
model_rotation.x,
186+
);
187+
let model = Mat4::from_rotation_translation(rotation, Vec3::ZERO);
188+
189+
// Normal matrix (3×3, for lighting)
190+
let normal_matrix = Mat3::from_mat4(model).inverse().transpose();
191+
192+
// ───────────────────────────────────────────
193+
// 4) VIEW & PROJECTION
194+
// ───────────────────────────────────────────
195+
let view = Mat4::look_at_rh(eye, pivot_panned, Vec3::Y);
196+
let proj = Mat4::perspective_rh(fov_y_radians, aspect_ratio, z_near, z_far);
197+
198+
// ───────────────────────────────────────────
199+
// 5) MVP
200+
// ───────────────────────────────────────────
201+
Ok((proj * view * model, normal_matrix))
111202
}
112203

204+
113205
pub unsafe fn prepare_shader_constants(
114206
context: *mut ID3D11DeviceContext,
115207
shape_data:&RendData,
116-
zoom: i16,
117-
pan: (i16,i16),
118-
origin: Vec3,
119-
eye: Vec3,
120-
rotation: Vec3,
121-
aspect_ratio: f32,
122-
fov_y_radians: f32,
123-
z_near: f32,
124-
z_far: f32,
208+
mvp_p:&ModelViewParams,
125209
light_dir_world: Vec3,
126210
has_tex0: bool,
127211
) -> anyhow::Result<()> {
128212
// 1. Compute the MVP matrix
129-
let (mvp, normal_mat) = generate_mvp_matrix(zoom, pan, origin, eye, rotation, aspect_ratio, fov_y_radians, z_near, z_far);
213+
let (mvp, normal_mat) = match *mvp_p {
214+
ModelViewParams::FixedCam { .. } => generate_mvp_matrix(mvp_p)?,
215+
ModelViewParams::OrbitCam { .. } => generate_mvp_matrix_orbit(mvp_p)?,
216+
};
130217

131218
// 2. No transpose needed unless your shader uses 'row_major'
132219
// If needed, use: let mvp = mvp.transpose();

0 commit comments

Comments
 (0)