Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 73 additions & 49 deletions examples/simple_scene.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,88 @@
use std::time::Instant;

use env_logger::Builder;
use glam::{Mat4, Quat, Vec3};
use glam::{Quat, Vec3};
use log::LevelFilter;
use render_engine::{Color, RendererSystem, ShapeBuilder};
use std::time::Instant;
use render_engine::{renderer::ShapeFactory, Color, RendererSystem, ShapeBuilder};

fn main() -> Result<(), Box<dyn std::error::Error>> {
Builder::new().filter_level(LevelFilter::Debug).init();

let mut renderer_system = RendererSystem::new(800, 600, "Metal Renderer")?;
let mut renderer_system = RendererSystem::new(800, 600, "Scene Graph Demo")?;
let start_time = Instant::now();

renderer_system.set_render_callback(move |r| {
let mut pyramid = None;
let mut cube = None;

renderer_system.set_render_callback(move |renderer| {
let elapsed = start_time.elapsed().as_secs_f32();

// Define pyramid dimensions
let base_width = 1.0;
let height = 1.5;
let half_width = base_width / 2.0;
if pyramid.is_none() {
// Create a pyramid
let pyramid_vertices = vec![
// Apex
(Vec3::new(0.0, 1.5, 0.0), Color::new(1.0, 0.0, 0.0, 1.0)),
// Base vertices
(Vec3::new(-0.5, 0.0, -0.5), Color::new(0.0, 1.0, 0.0, 1.0)),
(Vec3::new(0.5, 0.0, -0.5), Color::new(0.0, 0.0, 1.0, 1.0)),
(Vec3::new(0.5, 0.0, 0.5), Color::new(1.0, 1.0, 0.0, 1.0)),
(Vec3::new(-0.5, 0.0, 0.5), Color::new(0.0, 1.0, 1.0, 1.0)),
];

let pyramid_indices = vec![
0, 1, 2, // Front face
0, 2, 3, // Right face
0, 3, 4, // Back face
0, 4, 1, // Left face
1, 3, 2, // Base (part 1)
1, 4, 3, // Base (part 2)
];

// Create the mesh builder and add to scene
let pyramid_mesh = renderer
.create_shape(pyramid_vertices)
.as_mesh()
.with_indices(pyramid_indices);

pyramid = Some(renderer.create_object(
pyramid_mesh,
Vec3::new(-2.0, 0.0, 0.0),
Quat::IDENTITY,
Vec3::ONE,
)?);

// Create a cube
cube = Some(ShapeFactory::create_cube(
renderer,
1.0,
Color::new(0.2, 0.5, 0.8, 1.0),
Vec3::new(0.0, 0.5, 0.0),
Quat::IDENTITY,
Vec3::ONE,
)?);
}

if let Some(ref pyramid_obj) = pyramid {
pyramid_obj.set_rotation(renderer, Quat::from_rotation_y(elapsed * 1.0))?;
}

if let Some(ref obj) = cube {
obj.set_rotation(
renderer,
Quat::from_rotation_y(elapsed * 0.7) * Quat::from_rotation_x(elapsed * 0.5),
)?;
}

// Define pyramid vertices
let pyramid_vertices = vec![
// Apex
(Vec3::new(0.0, height, 0.0), Color::new(1.0, 0.0, 0.0, 1.0)),
// Base vertices
(
Vec3::new(-half_width, 0.0, -half_width),
Color::new(0.0, 1.0, 0.0, 1.0),
),
(
Vec3::new(half_width, 0.0, -half_width),
Color::new(0.0, 0.0, 1.0, 1.0),
),
(
Vec3::new(half_width, 0.0, half_width),
Color::new(1.0, 1.0, 0.0, 1.0),
),
(
Vec3::new(-half_width, 0.0, half_width),
Color::new(0.0, 1.0, 1.0, 1.0),
),
];
// Define indices for the pyramid faces
let pyramid_indices = vec![
0, 1, 2, // Front face
0, 2, 3, // Right face
0, 3, 4, // Back face
0, 4, 1, // Left face
1, 3, 2, // Base (part 1)
1, 4, 3, // Base (part 2)
];
r.create_shape(pyramid_vertices)
.as_mesh()
.with_indices(pyramid_indices)
.with_transform(Mat4::from_rotation_translation(
Quat::from_rotation_y(elapsed),
Vec3::new(0.0, -0.5, 0.0),
))
.draw(r);
// r.create_shape(pyramid_vertices)
// .as_mesh()
// .with_indices(pyramid_indices)
// .with_transform(Mat4::from_rotation_translation(
// Quat::from_rotation_y(elapsed),
// Vec3::new(0.0, -0.5, 0.0),
// ))
// .draw(r);

r.render()
renderer.render()
});

renderer_system.run()?;
Expand Down
197 changes: 197 additions & 0 deletions src/renderer/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use glam::{Quat, Vec3};
use std::f32::consts::PI;

use crate::{
renderer::{render_core::Renderer, scene_objects::SceneObject, Color, RendererError},
ShapeBuilder,
};

/// Factory methods for creating common 3D shapes as scene objects
pub struct ShapeFactory;

impl ShapeFactory {
/// Create a cube with the given properties
pub fn create_cube(
renderer: &mut Renderer,
size: f32,
color: Color,
position: Vec3,
rotation: Quat,
scale: Vec3,
) -> Result<SceneObject, RendererError> {
let half_size = size / 2.0;

// Define the 8 vertices of the cube
let vertices = vec![
// Front face
(Vec3::new(-half_size, -half_size, half_size), color),
(Vec3::new(half_size, -half_size, half_size), color),
(Vec3::new(half_size, half_size, half_size), color),
(Vec3::new(-half_size, half_size, half_size), color),
// Back face
(Vec3::new(-half_size, -half_size, -half_size), color),
(Vec3::new(half_size, -half_size, -half_size), color),
(Vec3::new(half_size, half_size, -half_size), color),
(Vec3::new(-half_size, half_size, -half_size), color),
];

// Define indices for the triangles
let indices = vec![
// Front face
0, 1, 2, 0, 2, 3, // Back face
4, 6, 5, 4, 7, 6, // Left face
0, 3, 7, 0, 7, 4, // Right face
1, 5, 6, 1, 6, 2, // Top face
3, 2, 6, 3, 6, 7, // Bottom face
0, 4, 5, 0, 5, 1,
];

let mesh_builder = renderer
.create_shape(vertices)
.as_mesh()
.with_indices(indices);

renderer.create_object(mesh_builder, position, rotation, scale)
}

/// Create a sphere with the given properties
pub fn create_sphere(
renderer: &mut Renderer,
radius: f32,
segments: u32,
rings: u32,
color: Color,
position: Vec3,
rotation: Quat,
scale: Vec3,
) -> Result<SceneObject, RendererError> {
let mut vertices = Vec::new();
let mut indices = Vec::new();

// Create vertices
for ring in 0..=rings {
let phi = PI * (ring as f32) / (rings as f32);
let sin_phi = phi.sin();
let cos_phi = phi.cos();

for segment in 0..=segments {
let theta = 2.0 * PI * (segment as f32) / (segments as f32);
let sin_theta = theta.sin();
let cos_theta = theta.cos();

let x = radius * sin_phi * cos_theta;
let y = radius * cos_phi;
let z = radius * sin_phi * sin_theta;

vertices.push((Vec3::new(x, y, z), color));
}
}

// Create indices
for ring in 0..rings {
let ring_start = ring * (segments + 1);
let next_ring_start = (ring + 1) * (segments + 1);

for segment in 0..segments {
// Upper triangle
indices.push(ring_start + segment);
indices.push(next_ring_start + segment);
indices.push(next_ring_start + segment + 1);

// Lower triangle
indices.push(ring_start + segment);
indices.push(next_ring_start + segment + 1);
indices.push(ring_start + segment + 1);
}
}

let mesh_builder = renderer
.create_shape(vertices)
.as_mesh()
.with_indices(indices);

renderer.create_object(mesh_builder, position, rotation, scale)
}

/// Create a cone with the given properties
pub fn create_cone(
renderer: &mut Renderer,
radius: f32,
height: f32,
segments: u32,
color: Color,
position: Vec3,
rotation: Quat,
scale: Vec3,
) -> Result<SceneObject, RendererError> {
let mut vertices = Vec::new();
let mut indices = Vec::new();

// Apex vertex (at the top)
vertices.push((Vec3::new(0.0, height / 2.0, 0.0), color));

// Base vertices
for i in 0..segments {
let angle = 2.0 * PI * (i as f32) / (segments as f32);
let x = radius * angle.cos();
let z = radius * angle.sin();
vertices.push((Vec3::new(x, -height / 2.0, z), color));
}

// Side faces
for i in 0..segments {
let next = (i + 1) % segments;
indices.push(0); // Apex
indices.push(i + 1);
indices.push(next + 1);
}

// Base faces (triangulate the base)
let center_idx = vertices.len();
vertices.push((Vec3::new(0.0, -height / 2.0, 0.0), color));

for i in 0..segments {
let next = (i + 1) % segments;
indices.push(center_idx.try_into().unwrap());
indices.push(next + 1);
indices.push(i + 1);
}

let mesh_builder = renderer
.create_shape(vertices)
.as_mesh()
.with_indices(indices);

renderer.create_object(mesh_builder, position, rotation, scale)
}

/// Create a plane (flat rectangle) with the given properties
pub fn create_plane(
renderer: &mut Renderer,
width: f32,
depth: f32,
color: Color,
position: Vec3,
rotation: Quat,
scale: Vec3,
) -> Result<SceneObject, RendererError> {
let half_width = width / 2.0;
let half_depth = depth / 2.0;

let vertices = vec![
(Vec3::new(-half_width, 0.0, -half_depth), color),
(Vec3::new(half_width, 0.0, -half_depth), color),
(Vec3::new(half_width, 0.0, half_depth), color),
(Vec3::new(-half_width, 0.0, half_depth), color),
];

let indices = vec![0, 2, 1, 0, 3, 2];

let mesh_builder = renderer
.create_shape(vertices)
.as_mesh()
.with_indices(indices);

renderer.create_object(mesh_builder, position, rotation, scale)
}
}
9 changes: 9 additions & 0 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
//! - `backend`: Handles the low-level graphics API interactions (e.g., Metal, Vulkan).
//! - `camera`: Provides a camera system for 3D scene navigation and projection.
//! - `common`: Contains common data structures and types used throughout the renderer.
//! - `factory`: Provides factory methods for creating common 3D shapes.
//! - `render_core`: Implements the core rendering logic and system management.
//! - `render_queue`: Handles the queuing and processing of draw commands.
//! - `scene_graph`: Manages the hierarchical organization of objects in the scene.
//! - `scene_objects`: Provides high-level API for managing scene objects.
//! - `shape_builders`: Offers utilities for creating various 3D shapes programmatically.
//!
//! This module abstracts away much of the complexity of 3D rendering, providing a
Expand All @@ -20,12 +23,18 @@
mod backend;
mod camera;
mod common;
pub mod factory;
mod mesh;
mod render_core;
mod render_queue;
pub mod scene_graph;
pub mod scene_objects;
pub mod shape_builders;

pub use self::common::{Color, RendererError};
pub use camera::Camera;
pub use factory::ShapeFactory;
pub use render_core::RendererSystem;
pub use render_queue::{DrawCommandBuilder, InstanceData};
pub use scene_graph::NodeId;
pub use scene_objects::SceneObject;
Loading
Loading