1- use glam:: { Mat3 , Vec3 } ;
1+ use glam:: { Mat3 , Vec2 , Vec3 } ;
22
33use glam:: { Mat4 , Quat } ;
44use 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+
6493pub 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+
113205pub 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