Skip to main content

goud_engine/sdk/
rendering_3d.rs

1//! # SDK 3D Rendering API
2//!
3//! Provides methods on [`GoudGame`](super::GoudGame) for 3D rendering operations
4//! including primitive creation, object manipulation, lighting, camera control,
5//! and scene configuration.
6//!
7//! # Availability
8//!
9//! This module requires the `native` feature (desktop platform with OpenGL).
10
11use super::GoudGame;
12use crate::libs::graphics::renderer3d::{
13    FogConfig, GridConfig, Light, LightType, PrimitiveCreateInfo, PrimitiveType, SkyboxConfig,
14};
15use cgmath::{Vector3, Vector4};
16
17/// Sentinel value for invalid object handles.
18const INVALID_OBJECT: u32 = u32::MAX;
19
20// =============================================================================
21// 3D Rendering (annotated for FFI generation)
22// =============================================================================
23
24// NOTE: FFI wrappers are hand-written in ffi/renderer.rs. The `#[goud_api]`
25// attribute is omitted here to avoid duplicate `#[no_mangle]` symbol conflicts.
26impl GoudGame {
27    /// Creates a 3D primitive and returns its object ID.
28    pub fn create_primitive(&mut self, info: PrimitiveCreateInfo) -> u32 {
29        match &mut self.renderer_3d {
30            Some(renderer) => renderer.create_primitive(info),
31            None => INVALID_OBJECT,
32        }
33    }
34
35    /// Creates a 3D cube and returns its object ID.
36    pub fn create_cube(&mut self, texture_id: u32, width: f32, height: f32, depth: f32) -> u32 {
37        self.create_primitive(PrimitiveCreateInfo {
38            primitive_type: PrimitiveType::Cube,
39            width,
40            height,
41            depth,
42            segments: 1,
43            texture_id,
44        })
45    }
46
47    /// Creates a 3D plane and returns its object ID.
48    pub fn create_plane(&mut self, texture_id: u32, width: f32, depth: f32) -> u32 {
49        self.create_primitive(PrimitiveCreateInfo {
50            primitive_type: PrimitiveType::Plane,
51            width,
52            height: 0.0,
53            depth,
54            segments: 1,
55            texture_id,
56        })
57    }
58
59    /// Creates a 3D sphere and returns its object ID.
60    pub fn create_sphere(&mut self, texture_id: u32, diameter: f32, segments: u32) -> u32 {
61        self.create_primitive(PrimitiveCreateInfo {
62            primitive_type: PrimitiveType::Sphere,
63            width: diameter,
64            height: diameter,
65            depth: diameter,
66            segments,
67            texture_id,
68        })
69    }
70
71    /// Creates a 3D cylinder and returns its object ID.
72    pub fn create_cylinder(
73        &mut self,
74        texture_id: u32,
75        radius: f32,
76        height: f32,
77        segments: u32,
78    ) -> u32 {
79        self.create_primitive(PrimitiveCreateInfo {
80            primitive_type: PrimitiveType::Cylinder,
81            width: radius * 2.0,
82            height,
83            depth: radius * 2.0,
84            segments,
85            texture_id,
86        })
87    }
88
89    /// Sets the position of a 3D object.
90    pub fn set_object_position(&mut self, id: u32, x: f32, y: f32, z: f32) -> bool {
91        match &mut self.renderer_3d {
92            Some(renderer) => renderer.set_object_position(id, x, y, z),
93            None => false,
94        }
95    }
96
97    /// Sets the rotation of a 3D object (Euler angles in degrees).
98    pub fn set_object_rotation(&mut self, id: u32, x: f32, y: f32, z: f32) -> bool {
99        match &mut self.renderer_3d {
100            Some(renderer) => renderer.set_object_rotation(id, x, y, z),
101            None => false,
102        }
103    }
104
105    /// Sets the scale of a 3D object.
106    pub fn set_object_scale(&mut self, id: u32, x: f32, y: f32, z: f32) -> bool {
107        match &mut self.renderer_3d {
108            Some(renderer) => renderer.set_object_scale(id, x, y, z),
109            None => false,
110        }
111    }
112
113    /// Removes a 3D object from the scene.
114    pub fn destroy_object(&mut self, object_id: u32) -> bool {
115        match &mut self.renderer_3d {
116            Some(renderer) => renderer.remove_object(object_id),
117            None => false,
118        }
119    }
120
121    /// Adds a light to the 3D scene with flattened parameters.
122    #[allow(clippy::too_many_arguments)]
123    pub fn add_light(
124        &mut self,
125        light_type: i32,
126        pos_x: f32,
127        pos_y: f32,
128        pos_z: f32,
129        dir_x: f32,
130        dir_y: f32,
131        dir_z: f32,
132        r: f32,
133        g: f32,
134        b: f32,
135        intensity: f32,
136        range: f32,
137        spot_angle: f32,
138    ) -> u32 {
139        let lt = match light_type {
140            1 => LightType::Directional,
141            2 => LightType::Spot,
142            _ => LightType::Point,
143        };
144        match &mut self.renderer_3d {
145            Some(renderer) => renderer.add_light(Light {
146                light_type: lt,
147                position: Vector3::new(pos_x, pos_y, pos_z),
148                direction: Vector3::new(dir_x, dir_y, dir_z),
149                color: Vector3::new(r, g, b),
150                intensity,
151                range,
152                spot_angle,
153                enabled: true,
154            }),
155            None => u32::MAX,
156        }
157    }
158
159    /// Updates a light's properties.
160    #[allow(clippy::too_many_arguments)]
161    pub fn update_light(
162        &mut self,
163        light_id: u32,
164        light_type: i32,
165        pos_x: f32,
166        pos_y: f32,
167        pos_z: f32,
168        dir_x: f32,
169        dir_y: f32,
170        dir_z: f32,
171        r: f32,
172        g: f32,
173        b: f32,
174        intensity: f32,
175        range: f32,
176        spot_angle: f32,
177    ) -> bool {
178        let lt = match light_type {
179            1 => LightType::Directional,
180            2 => LightType::Spot,
181            _ => LightType::Point,
182        };
183        match &mut self.renderer_3d {
184            Some(renderer) => renderer.update_light(
185                light_id,
186                Light {
187                    light_type: lt,
188                    position: Vector3::new(pos_x, pos_y, pos_z),
189                    direction: Vector3::new(dir_x, dir_y, dir_z),
190                    color: Vector3::new(r, g, b),
191                    intensity,
192                    range,
193                    spot_angle,
194                    enabled: true,
195                },
196            ),
197            None => false,
198        }
199    }
200
201    /// Removes a light from the 3D scene.
202    pub fn remove_light(&mut self, light_id: u32) -> bool {
203        match &mut self.renderer_3d {
204            Some(renderer) => renderer.remove_light(light_id),
205            None => false,
206        }
207    }
208
209    /// Sets the 3D camera position.
210    pub fn set_camera_position(&mut self, x: f32, y: f32, z: f32) -> bool {
211        match &mut self.renderer_3d {
212            Some(renderer) => {
213                renderer.set_camera_position(x, y, z);
214                true
215            }
216            None => false,
217        }
218    }
219
220    /// Sets the 3D camera rotation (pitch, yaw, roll in degrees).
221    pub fn set_camera_rotation(&mut self, pitch: f32, yaw: f32, roll: f32) -> bool {
222        match &mut self.renderer_3d {
223            Some(renderer) => {
224                renderer.set_camera_rotation(pitch, yaw, roll);
225                true
226            }
227            None => false,
228        }
229    }
230
231    /// Configures the ground grid.
232    pub fn configure_grid(&mut self, enabled: bool, size: f32, divisions: u32) -> bool {
233        match &mut self.renderer_3d {
234            Some(renderer) => {
235                renderer.configure_grid(GridConfig {
236                    enabled,
237                    size,
238                    divisions,
239                    ..Default::default()
240                });
241                true
242            }
243            None => false,
244        }
245    }
246
247    /// Sets grid enabled state.
248    pub fn set_grid_enabled(&mut self, enabled: bool) -> bool {
249        match &mut self.renderer_3d {
250            Some(renderer) => {
251                renderer.set_grid_enabled(enabled);
252                true
253            }
254            None => false,
255        }
256    }
257
258    /// Configures the skybox/background color.
259    pub fn configure_skybox(&mut self, enabled: bool, r: f32, g: f32, b: f32, a: f32) -> bool {
260        match &mut self.renderer_3d {
261            Some(renderer) => {
262                renderer.configure_skybox(SkyboxConfig {
263                    enabled,
264                    color: Vector4::new(r, g, b, a),
265                });
266                true
267            }
268            None => false,
269        }
270    }
271
272    /// Configures fog settings.
273    pub fn configure_fog(&mut self, enabled: bool, r: f32, g: f32, b: f32, density: f32) -> bool {
274        match &mut self.renderer_3d {
275            Some(renderer) => {
276                renderer.configure_fog(FogConfig {
277                    enabled,
278                    color: Vector3::new(r, g, b),
279                    density,
280                });
281                true
282            }
283            None => false,
284        }
285    }
286
287    /// Sets fog enabled state.
288    pub fn set_fog_enabled(&mut self, enabled: bool) -> bool {
289        match &mut self.renderer_3d {
290            Some(renderer) => {
291                renderer.set_fog_enabled(enabled);
292                true
293            }
294            None => false,
295        }
296    }
297
298    /// Renders all 3D objects in the scene.
299    pub fn render(&mut self) -> bool {
300        match &mut self.renderer_3d {
301            Some(renderer) => {
302                renderer.render(None);
303                true
304            }
305            None => false,
306        }
307    }
308
309    /// Renders all 3D objects (alias for render).
310    pub fn render_all(&mut self) -> bool {
311        self.render()
312    }
313
314    /// Returns `true` if a 3D renderer is initialized.
315    #[inline]
316    pub fn has_3d_renderer(&self) -> bool {
317        self.renderer_3d.is_some()
318    }
319}
320
321// =============================================================================
322// Tests
323// =============================================================================
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use crate::sdk::GameConfig;
329
330    #[test]
331    fn test_create_cube_headless() {
332        let mut game = GoudGame::new(GameConfig::default()).unwrap();
333        assert_eq!(game.create_cube(0, 1.0, 1.0, 1.0), u32::MAX);
334    }
335
336    #[test]
337    fn test_set_object_position_headless() {
338        let mut game = GoudGame::new(GameConfig::default()).unwrap();
339        assert!(!game.set_object_position(0, 1.0, 2.0, 3.0));
340    }
341
342    #[test]
343    fn test_set_camera_position_headless() {
344        let mut game = GoudGame::new(GameConfig::default()).unwrap();
345        assert!(!game.set_camera_position(0.0, 5.0, -10.0));
346    }
347
348    #[test]
349    fn test_render_3d_headless() {
350        let mut game = GoudGame::new(GameConfig::default()).unwrap();
351        assert!(!game.render());
352    }
353
354    #[test]
355    fn test_has_3d_renderer_headless() {
356        let game = GoudGame::new(GameConfig::default()).unwrap();
357        assert!(!game.has_3d_renderer());
358    }
359
360    #[test]
361    fn test_add_light_headless() {
362        let mut game = GoudGame::new(GameConfig::default()).unwrap();
363        let id = game.add_light(
364            0, 0.0, 5.0, 0.0, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 10.0, 0.0,
365        );
366        assert_eq!(id, u32::MAX);
367    }
368
369    #[test]
370    fn test_render_all_headless() {
371        let mut game = GoudGame::new(GameConfig::default()).unwrap();
372        assert!(!game.render_all());
373    }
374
375    #[test]
376    fn test_configure_grid_headless() {
377        let mut game = GoudGame::new(GameConfig::default()).unwrap();
378        assert!(!game.configure_grid(true, 10.0, 10));
379    }
380
381    #[test]
382    fn test_configure_skybox_headless() {
383        let mut game = GoudGame::new(GameConfig::default()).unwrap();
384        assert!(!game.configure_skybox(true, 0.5, 0.5, 0.8, 1.0));
385    }
386
387    #[test]
388    fn test_configure_fog_headless() {
389        let mut game = GoudGame::new(GameConfig::default()).unwrap();
390        assert!(!game.configure_fog(true, 0.5, 0.5, 0.5, 0.01));
391    }
392
393    #[test]
394    fn test_set_fog_enabled_headless() {
395        let mut game = GoudGame::new(GameConfig::default()).unwrap();
396        assert!(!game.set_fog_enabled(true));
397    }
398
399    #[test]
400    fn test_destroy_object_headless() {
401        let mut game = GoudGame::new(GameConfig::default()).unwrap();
402        assert!(!game.destroy_object(0));
403    }
404}