# Directory: ../src
// File: camera.rs
// Path: ..\src\camera.rs
/* Start of file camera.rs */
use crate::object::{Transform, TransformSerializer};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct CameraSerializer {
transform: TransformSerializer,
fov: f32,
width: f32,
height: f32,
near: f32,
far: f32,
view: [[f32; 4]; 4],
projection: [[f32; 4]; 4],
}
#[derive(Copy, Clone)]
pub struct Camera {
pub transform: Transform,
pub fov: f32,
pub width: f32,
pub height: f32,
pub near: f32,
pub far: f32,
pub view: [[f32; 4]; 4],
pub projection: [[f32; 4]; 4],
}
impl Camera {
pub fn new(
position: Option<[f32; 3]>,
rotation: Option<[f32; 3]>, // Expected in degrees, will be converted to radians
fov: Option<f32>, // Expected in degrees, will be converted to radians
aspect: Option<f32>,
near: Option<f32>,
far: Option<f32>
) -> Self {
let mut c = Self {
transform: {
let mut t = Transform::new();
t.set_position(position.unwrap_or_else(|| [0.0, 0.0, 0.0]));
t.set_rotation(rotation.unwrap_or_else(|| [0.0, 0.0, 0.0]));
t
},
fov: fov.unwrap_or_else(|| 90.0).to_radians(),
width: aspect.unwrap_or_else(|| 1920.0),
height: aspect.unwrap_or_else(|| 1080.0),
near: near.unwrap_or_else(|| 0.1),
far: far.unwrap_or_else(|| 1024.0),
view: [[0.0; 4]; 4],
projection: [[0.0; 4]; 4],
};
c.update_matrices();
c
}
pub fn from_serializer(serializer: CameraSerializer) -> Self {
Self {
transform: Transform::from_serializer(serializer.transform),
fov: serializer.fov,
width: serializer.width,
height: serializer.height,
near: serializer.near,
far: serializer.far,
view: serializer.view,
projection: serializer.projection,
}
}
pub fn to_serializer(&self) -> CameraSerializer {
CameraSerializer {
transform: self.transform.to_serializer(),
fov: self.fov,
width: self.width,
height: self.height,
near: self.near,
far: self.far,
view: self.view,
projection: self.projection,
}
}
pub fn update_matrices(&mut self) {
self.view = Camera::view_matrix(
&self.transform.get_position().into(),
&self.calculate_direction_vector(),
&[0.0, 1.0, 0.0],
);
self.projection = Camera::projection_matrix(
self.fov,
self.width / self.height,
self.near,
self.far,
);
}
pub fn calculate_direction_vector(&self) -> [f32; 3] {
let pitch = self.transform.rotation[0]; // Rotation around X-axis
let yaw = self.transform.rotation[1]; // Rotation around Y-axis
let x = yaw.sin() * pitch.cos();
let y = pitch.sin();
let z = yaw.cos() * pitch.cos();
[-x, -y, -z] // Pointing down negative Z-axis
}
fn projection_matrix(fov: f32, aspect: f32, near: f32, far: f32) -> [[f32; 4]; 4] {
let f = 1.0 / (fov / 2.0).tan();
[
[f / aspect, 0.0, 0.0, 0.0],
[0.0, f, 0.0, 0.0],
[0.0, 0.0, (far + near) / (near - far), -1.0],
[0.0, 0.0, (2.0 * far * near) / (near - far), 0.0],
]
}
fn view_matrix(position: &[f32; 3], direction: &[f32; 3], up: &[f32; 3]) -> [[f32; 4]; 4] {
let f = {
let len = (direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]).sqrt();
[-direction[0] / len, -direction[1] / len, -direction[2] / len] // Negate direction for a right-handed system
};
let s = [
up[1] * f[2] - up[2] * f[1],
up[2] * f[0] - up[0] * f[2],
up[0] * f[1] - up[1] * f[0],
];
let s_norm = {
let len = (s[0] * s[0] + s[1] * s[1] + s[2] * s[2]).sqrt();
[s[0] / len, s[1] / len, s[2] / len]
};
let u = [
f[1] * s_norm[2] - f[2] * s_norm[1],
f[2] * s_norm[0] - f[0] * s_norm[2],
f[0] * s_norm[1] - f[1] * s_norm[0],
];
let p = [
-position[0] * s_norm[0] - position[1] * s_norm[1] - position[2] * s_norm[2],
-position[0] * u[0] - position[1] * u[1] - position[2] * u[2],
-position[0] * f[0] - position[1] * f[1] - position[2] * f[2],
];
[
[s_norm[0], u[0], f[0], 0.0],
[s_norm[1], u[1], f[1], 0.0],
[s_norm[2], u[2], f[2], 0.0],
[p[0], p[1], p[2], 1.0],
]
}
pub fn get_view_matrix(&self) -> [[f32; 4]; 4] {
Camera::view_matrix(
&self.transform.get_position().into(),
&self.calculate_direction_vector(),
&[0.0, 1.0, 0.0],
)
}
pub fn get_projection_matrix(&self) -> [[f32; 4]; 4] {
Camera::projection_matrix(
self.fov,
self.width / self.height,
self.near,
self.far,
)
}
pub fn get_position(&self) -> [f32; 3] {
self.transform.get_position().into()
}
pub fn get_rotation(&self) -> [f32; 3] {
self.transform.get_rotation().into()
}
pub fn get_fov(&self) -> f32 {
self.fov.clone()
}
pub fn get_aspect(&self) -> (f32, f32) {
(self.width.clone(), self.height.clone())
}
pub fn get_near(&self) -> f32 {
self.near.clone()
}
pub fn get_far(&self) -> f32 {
self.far.clone()
}
pub fn get_view(&self) -> [[f32; 4]; 4] {
self.view.clone()
}
pub fn get_projection(&self) -> [[f32; 4]; 4] {
self.projection.clone()
}
pub fn set_position(&mut self, position: [f32; 3]) {
self.transform.set_position(position);
self.update_matrices();
}
pub fn set_rotation(&mut self, rotation: [f32; 3]) {
self.transform.set_rotation(rotation);
self.update_matrices();
}
pub fn set_fov(&mut self, fov: f32) {
self.fov = fov;
self.update_matrices();
}
pub fn set_aspect(&mut self, width: f32, heigth: f32) {
self.width = width;
self.height = heigth;
self.update_matrices();
}
pub fn set_near(&mut self, near: f32) {
self.near = near;
self.update_matrices();
}
pub fn set_far(&mut self, far: f32) {
self.far = far;
self.update_matrices();
}
}
/* End of file camera.rs */
// File: collision_world.rs
// Path: ..\src\collision_world.rs
/* Start of file collision_world.rs */
use std::collections::HashMap;
use std::fmt::Debug;
use crate::AppState;
use nalgebra::{Matrix4, Point3, Vector3, Vector4};
use uuid::Uuid;
use crate::camera::Camera;
use crate::geometry::BoundingBox;
pub struct MousePosition {
pub screen_space: (f64, f64),
pub world_space: Vector3<f32>,
}
pub struct RayCast {
origin: Vector3<f32>,
direction: Vector3<f32>,
length: f32,
intersection_objects: HashMap<Uuid, Vector3<f32>>
}
pub fn is_colliding(aabb1: &BoundingBox, aabb2: &BoundingBox) -> bool {
let aabb1_min = aabb1.min_point();
let aabb1_max = aabb1.max_point();
let aabb2_min = aabb2.min_point();
let aabb2_max = aabb2.max_point();
if aabb1_min.x > aabb2_max.x || aabb1_max.x < aabb2_min.x {
return false;
}
if aabb1_min.y > aabb2_max.y || aabb1_max.y < aabb2_min.y {
return false;
}
if aabb1_min.z > aabb2_max.z || aabb1_max.z < aabb2_min.z {
return false;
}
true
}
impl Debug for MousePosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MousePosition")
.field("screen_space", &self.screen_space)
.field("world_space", &self.world_space)
.finish()
}
}
impl MousePosition {
pub fn new() -> Self {
Self {
screen_space: (0.0, 0.0),
world_space: Vector3::new(0.0, 0.0, 0.0),
}
}
pub fn get_world_position(&self, camera: &Camera) -> (Vector3<f32>, Vector3<f32>) {
let clip_space_x = (self.screen_space.0 as f32 / camera.width) * 2.0 - 1.0;
let clip_space_y = 1.0-(self.screen_space.1 as f32 / camera.height) * 2.0;
let clip_space_z = -1.0;
let clip_space_coord: Vector4<f32> = Vector4::new(clip_space_x, clip_space_y, clip_space_z, 1.0);
let view_space_coord = Matrix4::from(camera.get_projection_matrix()).try_inverse().unwrap().transform_point(&Point3::from_homogeneous(clip_space_coord).unwrap());
let world_space_coord = Matrix4::from(camera.get_view_matrix()).try_inverse().unwrap().transform_point(&view_space_coord);
let world_space_point: Point3<f32> = world_space_coord.xyz().into();
let ray_direction: Vector3<f32> = (world_space_point - camera.transform.get_position()).coords.normalize();
(world_space_point.coords, ray_direction)
}
pub fn get_screen_position(&self) -> (f64, f64) {
self.screen_space
}
pub fn set_screen_position(&mut self, position: (f64, f64)) {
self.screen_space = position;
}
}
impl RayCast {
pub fn new(origin: Vector3<f32>, direction: Vector3<f32>, length: f32) -> Self {
Self {
origin,
direction,
length,
intersection_objects: HashMap::new(),
}
}
pub fn get_intersection_map(&self) -> &HashMap<Uuid, Vector3<f32>> {
&self.intersection_objects
}
pub fn get_intersection_uuids(&self) -> Vec<Uuid> {
let mut uuids = Vec::new();
for (uuid, _) in self.intersection_objects.iter() {
uuids.push(*uuid);
}
uuids
}
pub fn get_intersection_points(&self) -> Vec<Vector3<f32>> {
let mut points = Vec::new();
for (_, point) in self.intersection_objects.iter() {
points.push(*point);
}
points
}
pub fn cast(&mut self, app_state: &mut AppState) {
for object in app_state.objects.iter_mut() {
let aabb = object.get_bounding_box();
match self.intersects_bounding_box(&aabb) {
Some(intersection_point) => {
self.intersection_objects.insert(object.get_unique_id(), intersection_point);
},
None => {}
}
}
}
fn intersects_bounding_box(&self, bounding_box: &BoundingBox) -> Option<Vector3<f32>> {
let inv_direction = Vector3::new(1.0 / self.direction.x, 1.0 / self.direction.y, 1.0 / self.direction.z);
let sign = [
(inv_direction.x < 0.0) as usize,
(inv_direction.y < 0.0) as usize,
(inv_direction.z < 0.0) as usize,
];
let bbox = [bounding_box.min_point(), bounding_box.max_point()];
let mut tmin = (bbox[sign[0]].x - self.origin.x) * inv_direction.x;
let mut tmax = (bbox[1 - sign[0]].x - self.origin.x) * inv_direction.x;
let tymin = (bbox[sign[1]].y - self.origin.y) * inv_direction.y;
let tymax = (bbox[1 - sign[1]].y - self.origin.y) * inv_direction.y;
if (tmin > tymax) || (tymin > tmax) {
return None;
}
if tymin > tmin {
tmin = tymin;
}
if tymax < tmax {
tmax = tymax;
}
let tzmin = (bbox[sign[2]].z - self.origin.z) * inv_direction.z;
let tzmax = (bbox[1 - sign[2]].z - self.origin.z) * inv_direction.z;
if (tmin > tzmax) || (tzmin > tmax) {
return None;
}
if tzmin > tmin {
tmin = tzmin;
}
if tzmax < tmax {
tmax = tzmax;
}
if (tmin < self.length) && (tmax > 0.0) {
return Some(self.origin + tmin * self.direction);
}
None
}
}
/* End of file collision_world.rs */
// File: data.rs
// Path: ..\src\data.rs
/* Start of file data.rs */
use std::any::Any;
pub struct AppStateData {
name: String,
value: Box<dyn Any>,
}
impl AppStateData {
pub fn new(name: &str, value: Box<dyn Any>) -> Self {
AppStateData {
name: name.to_string(),
value,
}
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_value(&self) -> &dyn Any {
self.value.as_ref()
}
pub fn get_value_mut(&mut self) -> &mut dyn Any {
self.value.as_mut()
}
// Since the value is already a Box<dyn Any>, setting a new value should accept a Box<dyn Any>
pub fn set_value(&mut self, value: Box<dyn Any>) {
self.value = value;
}
}
/* End of file data.rs */
// File: debug_geo.rs
// Path: ..\src\debug_geo.rs
/* Start of file debug_geo.rs */
use crate::geometry;
pub const TRIANGLE: [geometry::Vertex; 3] = [
geometry::Vertex { position: [-0.5, 0.5, 0.0], color: [1.0, 0.0, 0.0], texcoord: [0.0, 0.0], normal: [1.0, 0.0, 1.0] },
geometry::Vertex { position: [0.0, 0.9, 0.0], color: [0.0, 1.0, 0.0], texcoord: [0.5, 1.0], normal: [1.0, 0.0, 1.0] },
geometry::Vertex { position: [0.5, 0.5, 0.0], color: [0.0, 0.0, 1.0], texcoord: [0.0, 0.0], normal: [1.0, 0.0, 1.0] },
];
pub const SQUARE: [geometry::Vertex; 6] = [
geometry::Vertex { position: [-0.5, -0.8, 0.0], color: [1.0, 0.0, 0.0], texcoord: [0.0, 0.0], normal: [0.5, 0.0, 1.0] },
geometry::Vertex { position: [0.5, -0.8, 0.0], color: [0.0, 1.0, 0.0], texcoord: [0.0, 1.0], normal: [0.5, 0.0, 1.0] },
geometry::Vertex { position: [0.5, 0.5, 0.0], color: [0.0, 0.0, 1.0], texcoord: [1.0, 0.0], normal: [0.5, 0.0, 1.0] },
geometry::Vertex { position: [0.5, 0.5, 0.0], color: [0.0, 1.0, 1.0], texcoord: [1.0, 0.0], normal: [0.5, 0.0, 1.0] },
geometry::Vertex { position: [-0.5, 0.5, 0.0], color: [1.0, 1.0, 0.0], texcoord: [0.0, 1.0], normal: [0.5, 0.0, 1.0] },
geometry::Vertex { position: [-0.5, -0.8, 0.0], color: [1.0, 0.0, 1.0], texcoord: [0.0, 0.0], normal: [0.5, 0.0, 1.0] },
];
pub fn get_debug_shapes() -> Vec<&'static [geometry::Vertex]> {
vec![&SQUARE, &TRIANGLE]
}
/* End of file debug_geo.rs */
// File: default_events.rs
// Path: ..\src\default_events.rs
/* Start of file default_events.rs */
use uuid::Uuid;
use crate::AppState;
use crate::collision_world::RayCast;
pub fn select_object(app_state: &mut AppState){
app_state.object_selection.clear();
select_object_single(app_state);
}
pub fn select_object_add(app_state: &mut AppState){
select_object_single(app_state);
}
fn select_object_single(app_state: &mut AppState) {
match app_state.camera {
Some(camera) => {
let world_space_mouse_position = app_state.get_mouse_position().get_world_position(&camera);
let mut raycast = RayCast::new(
world_space_mouse_position.0,
world_space_mouse_position.1,
100.0,
);
raycast.cast(app_state);
for (id, _) in raycast.get_intersection_map().iter() {
let mut ids: Vec<Uuid> = app_state.object_selection.clone();
for object in app_state.get_objects_mut() {
if object.get_unique_id() == *id {
if !ids.contains(&object.get_unique_id()) {
ids.push(object.get_unique_id());
}
}
}
app_state.object_selection = ids;
}
println!("Selected object(s): {:?}", app_state.object_selection);
},
None => {
println!("No camera found to cast from, could not select object");
}
}
}
/* End of file default_events.rs */
// File: event.rs
// Path: ..\src\event.rs
/* Start of file event.rs */
use std::sync::Arc;
use crate::AppState;
#[derive(Copy, Clone)]
pub enum EventCharacteristic {
KeyPress(winit::event::VirtualKeyCode),
MousePress(winit::event::MouseButton),
MouseScroll(winit::event::MouseScrollDelta),
//TODO: impl more events
}
#[derive(Copy, Clone)]
pub struct EventModifiers {
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
}
impl EventModifiers {
pub fn new(ctrl: bool, shift: bool, alt: bool) -> Self {
Self {
ctrl,
shift,
alt,
}
}
pub fn default() -> Self {
Self {
ctrl: false,
shift: false,
alt: false,
}
}
}
impl PartialEq for EventModifiers {
fn eq(&self, other: &Self) -> bool {
self.ctrl == other.ctrl && self.shift == other.shift && self.alt == other.alt
}
}
pub type EventFunction = Arc<dyn Fn(&mut AppState)>;
/* End of file event.rs */
// File: geometry.rs
// Path: ..\src\geometry.rs
/* Start of file geometry.rs */
use std::fmt::Debug;
use nalgebra::Vector3;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Serialize, Deserialize)]
pub struct Vertex {
pub position: [f32; 3],
pub texcoord: [f32; 2],
pub color: [f32; 3],
pub normal: [f32; 3],
}
#[derive(Serialize, Deserialize)]
pub struct BoundingBoxSerializer {
pub center: [f32; 3],
pub width: f32,
pub height: f32,
pub depth: f32,
}
#[derive(Copy, Clone)]
pub struct BoundingBox {
pub center: Vector3<f32>,
//relative to the objects position
pub width: f32,
pub height: f32,
pub depth: f32,
}
#[derive(Serialize, Deserialize)]
pub struct BoundingBoxMesh {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
}
glium::implement_vertex!(Vertex, position, texcoord, color, normal);
impl Debug for Vertex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Vertex")
.field("position", &self.position)
.field("texcoord", &self.texcoord)
.field("color", &self.color)
.field("normal", &self.normal)
.finish()
}
}
impl BoundingBoxMesh {
pub fn new(bounding_box: &BoundingBox) -> Self {
let half_width = bounding_box.width / 2.0;
let half_height = bounding_box.height / 2.0;
let half_depth = bounding_box.depth / 2.0;
let corners = [
bounding_box.center + Vector3::new(-half_width, -half_height, -half_depth),
bounding_box.center + Vector3::new(half_width, -half_height, -half_depth),
bounding_box.center + Vector3::new(half_width, half_height, -half_depth),
bounding_box.center + Vector3::new(-half_width, half_height, -half_depth),
bounding_box.center + Vector3::new(-half_width, -half_height, half_depth),
bounding_box.center + Vector3::new(half_width, -half_height, half_depth),
bounding_box.center + Vector3::new(half_width, half_height, half_depth),
bounding_box.center + Vector3::new(-half_width, half_height, half_depth),
];
let mut vertices = Vec::new();
for i in 0..corners.len() {
let corner = corners[i];
vertices.push(Vertex {
position: [corner.x, corner.y, corner.z],
texcoord: [0.0, 0.0],
color: [1.0, 1.0, 1.0],
normal: [0.0, 0.0, 0.0],
});
}
let indices = vec![
0, 1, 2, 2, 3, 0, // Front face
1, 5, 6, 6, 2, 1, // Right face
5, 4, 7, 7, 6, 5, // Back face
4, 0, 3, 3, 7, 4, // Left face
3, 2, 6, 6, 7, 3, // Top face
4, 5, 1, 1, 0, 4, // Bottom face
];
Self {
vertices,
indices,
}
}
}
impl BoundingBox {
// Returns the minimum point of the bounding box
pub fn min_point(&self) -> Vector3<f32> {
Vector3::new(
self.center.x - self.width / 2.0,
self.center.y - self.height / 2.0,
self.center.z - self.depth / 2.0,
)
}
// Returns the maximum point of the bounding box
pub fn max_point(&self) -> Vector3<f32> {
Vector3::new(
self.center.x + self.width / 2.0,
self.center.y + self.height / 2.0,
self.center.z + self.depth / 2.0,
)
}
pub fn to_serializer(&self) -> BoundingBoxSerializer {
BoundingBoxSerializer {
center: [self.center.x, self.center.y, self.center.z],
width: self.width,
height: self.height,
depth: self.depth,
}
}
pub fn from_serializer(serializer: BoundingBoxSerializer) -> Self {
Self {
center: Vector3::new(serializer.center[0], serializer.center[1], serializer.center[2]),
width: serializer.width,
height: serializer.height,
depth: serializer.depth,
}
}
}
/* End of file geometry.rs */
// File: lib.rs
// Path: ..\src\lib.rs
/* Start of file lib.rs */
use std::any::Any;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use egui_glium::EguiGlium;
use winit::window::Window;
use glium::glutin::surface::WindowSurface;
use glium::{Display, Surface, Texture2d, uniform};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow};
use crate::camera::{Camera, CameraSerializer};
use crate::collision_world::MousePosition;
use crate::data::AppStateData;
use crate::event::EventModifiers;
use crate::light::{Light, LightEmissionType};
use crate::object::Object;
use crate::postprocessing::PostProcessingEffect;
use crate::texture::Texture;
pub mod shader;
pub mod geometry;
pub mod debug_geo;
pub mod texture;
pub mod material;
pub mod object;
pub mod light;
pub mod camera;
pub mod event;
pub mod collision_world;
pub mod default_events;
pub mod postprocessing;
pub mod ui;
pub mod resources;
pub mod data;
pub fn init_default(app_state: &mut AppState) {
app_state.set_renderscale(1);
app_state.set_fps(60);
app_state.set_max_buffers(3);
app_state.inject_event(
event::EventCharacteristic::MousePress(winit::event::MouseButton::Left),
Arc::new(default_events::select_object),
None,
);
app_state.inject_event(
event::EventCharacteristic::MousePress(winit::event::MouseButton::Right),
Arc::new(default_events::select_object_add),
None,
);
}
#[derive(Serialize, Deserialize)]
pub struct AppStateSerializer {
pub camera: Option<CameraSerializer>,
pub light: Vec<light::LightSerializer>,
pub ambient_light: Option<light::LightSerializer>,
pub skybox: Option<object::ObjectSerializer>,
pub skybox_texture: Option<texture::TextureSerializer>,
pub objects: Vec<object::ObjectSerializer>,
pub object_selection: Vec<String>,
}
pub struct AppState {
pub fps: u64,
pub camera: Option<camera::Camera>,
pub light: Vec<light::Light>,
pub ambient_light: Option<light::Light>,
pub skybox: Option<object::Object>,
pub skybox_texture: Option<texture::Texture>,
pub objects: Vec<object::Object>,
pub object_selection: Vec<Uuid>,
pub event_injections: Vec<(event::EventCharacteristic, event::EventFunction, event::EventModifiers)>,
pub update_injections: Vec<event::EventFunction>,
pub gui_injections: Vec<ui::GUIDrawFunction>,
pub post_processes: Vec<Box<dyn PostProcessingEffect>>,
pub display: Option<glium::Display<WindowSurface>>,
pub time: f32,
pub render_scale: u32,
pub max_buffers: usize,
mouse_position: MousePosition,
pub state_data: Vec<AppStateData>,
}
pub struct EventLoop {
pub event_loop: winit::event_loop::EventLoop<()>,
pub window: Window,
pub display: Display<WindowSurface>,
pub modifiers: EventModifiers,
gui_renderer: Option<EguiGlium>,
}
impl AppState {
pub fn new() -> Self {
AppState {
fps: 60,
camera: None,
skybox: None,
skybox_texture: None,
objects: Vec::new(),
object_selection: Vec::new(),
light: Vec::new(),
ambient_light: None,
event_injections: Vec::new(),
update_injections: Vec::new(),
post_processes: Vec::new(),
display: None,
time: 0.0,
render_scale: 1,
max_buffers: 3,
mouse_position: MousePosition::new(),
gui_injections: Vec::new(),
state_data: Vec::new(),
}
}
pub fn to_serializer(&self) -> AppStateSerializer {
println!("WARNING: an AppState Serializer does not completely serialize the AppState but only scene objects like Objects, Camera, Lights. It does NOT serialize any injections like code in form of functions or GUI!");
let camera = match self.camera {
Some(camera) => Some(camera.to_serializer()),
None => None,
};
let light = self.light.iter().map(|l| l.to_serializer()).collect();
let ambient_light = match &self.ambient_light {
Some(light) => Some(light.to_serializer()),
None => None,
};
let skybox = match &self.skybox {
Some(skybox) => Some(skybox.to_serializer()),
None => None,
};
let skybox_texture = match &self.skybox_texture {
Some(texture) => Some(texture.to_serializer()),
None => None,
};
let objects = self.objects.iter().map(|o| o.to_serializer()).collect();
let object_selection = self.object_selection.iter().map(|o| o.to_string()).collect();
AppStateSerializer {
camera,
light,
ambient_light,
skybox,
skybox_texture,
objects,
object_selection,
}
}
pub fn inject_serializer(&mut self, serializer: AppStateSerializer, display: Display<WindowSurface>, additive: bool) {
self.camera = match serializer.camera {
Some(camera) => Some(Camera::from_serializer(camera)),
None => None,
};
match serializer.ambient_light {
Some(light) => {
self.add_light(Light::from_serializer(light), LightEmissionType::Ambient);
},
None => {},
};
self.skybox = match serializer.skybox {
Some(skybox) => Some(Object::from_serializer(skybox, display.clone())),
None => None,
};
self.skybox_texture = match serializer.skybox_texture {
Some(texture) => Some(Texture::from_serializer(texture, &display.clone())),
None => None,
};
if !additive {
self.light.clear();
self.objects.clear();
self.object_selection.clear();
}
for l in serializer.light {
self.add_light(Light::from_serializer(l), LightEmissionType::Source);
}
for o in serializer.objects {
self.add_object(Object::from_serializer(o, display.clone()));
}
for o in serializer.object_selection {
self.object_selection.push(Uuid::parse_str(&o).unwrap());
}
}
pub fn add_state_data(&mut self, name: &str, data: Box<dyn Any>) {
self.state_data.push(AppStateData::new(name, data));
}
pub fn get_state_data_value<T: 'static>(&self, name: &str) -> Option<&T> {
for data in self.state_data.iter() {
if data.get_name() == name {
// Attempt to downcast to the requested type T
if let Some(value) = data.get_value().downcast_ref::<T>() {
return Some(value);
}
}
}
None
}
pub fn get_state_data_value_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T> {
for data in self.state_data.iter_mut() {
if data.get_name() == name {
// Attempt to downcast to the requested type T
if let Some(value) = data.get_value_mut().downcast_mut::<T>() {
return Some(value);
}
}
}
None
}
pub fn set_state_data_value(&mut self, name: &str, value: Box<dyn Any>) {
for data in &mut self.state_data {
if data.get_name() == name {
data.set_value(value);
return;
}
}
// If no existing data is found with the name, add as new state data
self.add_state_data(name, value);
}
pub fn inject_gui(&mut self, function: ui::GUIDrawFunction) {
self.gui_injections.push(function);
}
pub fn add_post_process(&mut self, post_process: Box<dyn PostProcessingEffect>) {
self.post_processes.push(post_process);
}
pub fn get_post_processes(&self) -> &Vec<Box<dyn PostProcessingEffect>> {
&self.post_processes
}
pub fn get_post_processes_mut(&mut self) -> &mut Vec<Box<dyn PostProcessingEffect>> {
&mut self.post_processes
}
pub fn get_mouse_position(&self) -> &MousePosition {
&self.mouse_position
}
pub fn get_mouse_position_mut(&mut self) -> &mut MousePosition {
&mut self.mouse_position
}
pub fn convert_to_arc_mutex(self) -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(self))
}
pub fn add_object(&mut self, object: object::Object) {
self.objects.push(object);
}
pub fn get_objects(&self) -> &Vec<object::Object> {
&self.objects
}
pub fn get_object(&self, name: &str) -> Option<&object::Object> {
for object in self.objects.iter() {
if object.name == name {
return Some(object);
}
}
None
}
pub fn get_object_mut(&mut self, name: &str) -> Option<&mut object::Object> {
for object in self.objects.iter_mut() {
if object.name == name {
return Some(object);
}
}
None
}
pub fn get_object_by_uuid(&self, uuid: Uuid) -> Option<&object::Object> {
for object in self.objects.iter() {
if object.get_unique_id() == uuid {
return Some(object);
}
}
None
}
pub fn get_object_by_uuid_mut(&mut self, uuid: Uuid) -> Option<&mut object::Object> {
for object in self.objects.iter_mut() {
if object.get_unique_id() == uuid {
return Some(object);
}
}
None
}
pub fn get_selected_objects_mut(&mut self) -> Vec<&mut object::Object> {
let mut selected = Vec::new();
for object in self.objects.iter_mut() {
if self.object_selection.contains(&object.get_unique_id()) {
selected.push(object);
}
}
selected
}
pub fn add_light(&mut self, light: light::Light, light_type: LightEmissionType) {
match light_type {
LightEmissionType::Source => self.light.push(light),
LightEmissionType::Ambient => self.ambient_light = Some(light),
}
}
pub fn remove_light(&mut self, index: usize, light_type: LightEmissionType) {
match light_type {
LightEmissionType::Source => {
if index >= self.light.len() {
panic!("Index out of bounds");
}
self.light.remove(index);
}
LightEmissionType::Ambient => {
self.ambient_light = None;
}
};
}
pub fn get_lights(&self) -> &Vec<light::Light> {
&self.light
}
pub fn set_fps(&mut self, fps: u64) {
self.fps = fps;
}
pub fn get_fps(&self) -> u64 {
self.fps
}
pub fn get_objects_mut(&mut self) -> &mut Vec<object::Object> {
&mut self.objects
}
pub fn set_camera(&mut self, camera: camera::Camera) {
self.camera = Some(camera);
}
pub fn get_camera(&self) -> &Option<camera::Camera> {
&self.camera
}
pub fn set_renderscale(&mut self, scale: u32) {
self.render_scale = scale;
}
pub fn get_renderscale(&self) -> u32 {
self.render_scale
}
pub fn set_max_buffers(&mut self, max_buffers: usize) {
self.max_buffers = max_buffers;
}
pub fn get_max_buffers(&self) -> usize {
self.max_buffers
}
pub fn inject_event(&mut self, characteristic: event::EventCharacteristic, function: event::EventFunction, modifiers: Option<event::EventModifiers>) {
match modifiers {
Some(modifiers) => self.event_injections.push((characteristic, function, modifiers)),
None => self.event_injections.push((characteristic, function, event::EventModifiers::default())),
}
}
pub fn inject_update_function(&mut self, function: event::EventFunction) {
self.update_injections.push(function);
}
pub fn set_skybox(&mut self, skybox: object::Object) {
self.skybox = Some(skybox);
}
pub fn get_skybox(&self) -> &Option<object::Object> {
&self.skybox
}
pub fn get_skybox_mut(&mut self) -> &mut Option<object::Object> {
&mut self.skybox
}
}
impl EventLoop {
pub fn new(title: &str, width: u32, height: u32) -> Self {
let event_loop = winit::event_loop::EventLoopBuilder::new().build();
let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new()
.with_title(title)
.with_inner_size(width, height)
.build(&event_loop);
EventLoop {
event_loop,
window,
display,
modifiers: EventModifiers::default(),
gui_renderer: None,
}
}
pub fn get_display_clone(&self) -> Display<WindowSurface> {
self.display.clone()
}
pub fn get_display_reference(&self) -> &Display<WindowSurface> {
&self.display
}
pub fn spawn_skybox(&self) -> (crate::object::Object, texture::Texture) {
let mut material = crate::material::Material::unlit(self.display.clone(), false);
material.set_texture_from_resource(resources::SKYBOX_TEXTURE, crate::material::TextureType::Albedo);
// create a default object
let mut object = Object::load_from_gltf_resource(resources::SKYBOX);
// set the material
object.add_material(material);
object.get_shapes_mut()[0].set_material_from_object_list(0);
object.name = "Skybox".to_string();
object.transform.set_scale([1.0, 1.0, 1.0]);
// skybox texture
let skybox_texture = texture::Texture::from_resource(&self.display, resources::SKYBOX_TEXTURE);
(object, skybox_texture)
}
pub fn set_icon_from_path(&self, path: &str) {
let image = image::open(path).expect("failed to load icon").to_rgba8();
let image_dimensions = image.dimensions();
let data = image.into_raw();
let icon = winit::window::Icon::from_rgba(data, image_dimensions.0, image_dimensions.1).expect("failed to load icon");
self.window.set_window_icon(Some(icon));
}
pub fn set_icon_from_resource(&self, data: &[u8]) {
let image = image::load_from_memory(data).expect("failed to load icon").to_rgba8();
let image_dimensions = image.dimensions();
let data = image.into_raw();
let icon = winit::window::Icon::from_rgba(data, image_dimensions.0, image_dimensions.1).expect("failed to load icon");
self.window.set_window_icon(Some(icon));
}
// This is just the render loop . an actual event loop still needs to be set up
pub fn run(mut self, app_state: Arc<Mutex<AppState>>) {
let mut temp_app_state = app_state.lock().unwrap();
temp_app_state.display = Some(self.display.clone());
//spawning skybox
let (skybox, skybox_texture) = self.spawn_skybox();
temp_app_state.set_skybox(skybox);
// managing fps
let mut next_frame_time = Instant::now();
let nanos = 1_000_000_000 / temp_app_state.fps;
let frame_duration = Duration::from_nanos(nanos); // 60 FPS (1,000,000,000 ns / 60)
let mut texture = Texture2d::empty(&self.display, self.window.inner_size().width * temp_app_state.render_scale, self.window.inner_size().height * temp_app_state.render_scale).expect("Failed to create texture");
let mut depth_texture = glium::texture::DepthTexture2d::empty(&self.display, self.window.inner_size().width * temp_app_state.render_scale, self.window.inner_size().height * temp_app_state.render_scale).expect("Failed to create depth texture");
let mut buffer_textures: Vec<Texture2d> = Vec::new();
for _ in 0..temp_app_state.max_buffers {
buffer_textures.push(Texture2d::empty(&self.display, self.window.inner_size().width * temp_app_state.render_scale, self.window.inner_size().height * temp_app_state.render_scale).expect("Failed to create texture"));
}
//dropping modified appstate
drop(temp_app_state);
// prepare post processing
let screen_vert_rect = postprocessing::get_screen_vert_rect(&self.display);
let screen_indices_rect = postprocessing::get_screen_indices_rect(&self.display);
let screen_program = postprocessing::get_screen_program(&self.display);
//initializing GUI
match self.gui_renderer {
Some(_) => {}
None => {
let egui_glium = EguiGlium::new(&self.display, &self.window, &self.event_loop);
self.gui_renderer = Some(egui_glium);
}
}
// run loop
self.event_loop.run(move |event, _window_target, control_flow| {
// unpacking appstate
let mut app_state = app_state.lock().unwrap();
let light = app_state.light.clone();
let ambient_light = app_state.ambient_light.clone();
let camera = app_state.camera.clone();
let event_injections = app_state.event_injections.clone();
let update_injections = app_state.update_injections.clone();
let gui_injections = app_state.gui_injections.clone();
*control_flow = ControlFlow::WaitUntil(next_frame_time);
next_frame_time = Instant::now() + frame_duration;
// passing framebuffer
let texture = &mut texture;
let depth_texture = &mut depth_texture;
let buffer_textures = &mut buffer_textures;
let mut framebuffer = glium::framebuffer::SimpleFrameBuffer::with_depth_buffer(&self.display, &*texture, &*depth_texture).expect("Failed to create framebuffer");
// passing skybox
let skybox_texture = &skybox_texture;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; }
WindowEvent::Resized(new_size) => {
let response = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer").on_event(&event);
if !response.consumed {
app_state.camera.as_mut().expect("failed to retrieve camera").set_aspect(new_size.width as f32, new_size.height as f32);
self.display.resize(new_size.into());
if let Some(app_state_display) = app_state.display.as_mut() {
app_state_display.resize(new_size.into());
}
}
}
WindowEvent::ModifiersChanged(modifiers) => {
self.modifiers.ctrl = modifiers.ctrl();
self.modifiers.shift = modifiers.shift();
self.modifiers.alt = modifiers.alt();
}
WindowEvent::CursorMoved { position, .. } => {
let response = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer").on_event(&event);
if !response.consumed {
app_state.get_mouse_position_mut().set_screen_position((position.x, position.y));
}
}
WindowEvent::MouseInput { state, button, .. } => {
let response = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer").on_event(&event);
if !response.consumed {
for (characteristic, function, modifiers) in event_injections {
if let event::EventCharacteristic::MousePress(mouse_button) = characteristic {
if state == winit::event::ElementState::Pressed && button == mouse_button && modifiers == self.modifiers {
function(&mut app_state);
}
}
};
}
}
WindowEvent::KeyboardInput { input, .. } => {
let response = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer").on_event(&event);
if !response.consumed {
for (characteristic, function, modifiers) in event_injections {
if let event::EventCharacteristic::KeyPress(key_code) = characteristic {
if input.state == winit::event::ElementState::Pressed && input.virtual_keycode == Some(key_code) && modifiers == self.modifiers {
function(&mut app_state);
}
}
};
}
}
_ => {
_ = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer").on_event(&event);
}
}
Event::RedrawRequested(_) => {
app_state.time += 0.001;
let render_target = &mut framebuffer;
render_target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
// render objects opaque
let opaque_rendering_parameter = glium::DrawParameters {
depth: glium::Depth {
test: glium::draw_parameters::DepthTest::IfLess,
write: true,
..Default::default()
},
backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise,
..Default::default()
};
for object in app_state.objects.iter_mut() {
let model_matrix = object.transform.get_matrix();
let closest_lights = object.get_closest_lights(&light);
for (buffer, (material, indices)) in object.get_vertex_buffers().iter().zip(object.get_materials().iter().zip(object.get_index_buffers().iter())) {
if material.render_transparent {
continue;
}
let uniforms = &material.get_uniforms(closest_lights.clone(), ambient_light, camera, Some(model_matrix), skybox_texture);
render_target.draw(buffer, indices, &material.program, uniforms, &opaque_rendering_parameter).expect("Failed to draw object");
}
}
// render skybox
let skybox_rendering_parameter = glium::DrawParameters {
depth: glium::Depth {
test: glium::draw_parameters::DepthTest::IfLess,
write: false,
..Default::default()
},
backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise,
..Default::default()
};
match app_state.get_skybox_mut() {
Some(skybox) => {
let model_matrix = skybox.transform.get_matrix();
let closest_lights = skybox.get_closest_lights(&light);
for (buffer, (material, indices)) in skybox.get_vertex_buffers().iter().zip(skybox.get_materials().iter().zip(skybox.get_index_buffers().iter())) {
let uniforms = &material.get_uniforms(closest_lights.clone(), ambient_light, camera, Some(model_matrix), skybox_texture);
render_target.draw(buffer, indices, &material.program, uniforms, &skybox_rendering_parameter).expect("Failed to draw object");
}
}
None => {}
}
// render objects transparent
app_state.objects.sort_by(|a, b| {
let distance_a = (camera.expect("failed to retrieve camera").transform.get_position() - a.transform.get_position()).len();
let distance_b = (camera.expect("failed to retrieve camera").transform.get_position() - b.transform.get_position()).len();
distance_b.partial_cmp(&distance_a).unwrap()
});
let transparent_rendering_parameter = glium::DrawParameters {
blend: glium::Blend::alpha_blending(),
..opaque_rendering_parameter
};
for object in app_state.objects.iter_mut() {
let model_matrix = object.transform.get_matrix();
let closest_lights = object.get_closest_lights(&light);
for (buffer, (material, indices)) in object.get_vertex_buffers().iter().zip(object.get_materials().iter().zip(object.get_index_buffers().iter())) {
if !material.render_transparent {
continue;
}
let uniforms = &material.get_uniforms(closest_lights.clone(), ambient_light, camera, Some(model_matrix), skybox_texture);
render_target.draw(buffer, indices, &material.program, uniforms, &transparent_rendering_parameter).expect("Failed to draw object");
}
}
// execute post processing#
for process in app_state.get_post_processes() {
process.render(&app_state, &screen_vert_rect, &screen_indices_rect, &mut framebuffer, &texture, &depth_texture, &buffer_textures);
}
// drawing to screen
let mut screen_target = self.display.draw();
let screen_uniforms = uniform! {
scene: &*texture,
};
screen_target.draw(
&screen_vert_rect,
&screen_indices_rect,
&screen_program,
&screen_uniforms,
&Default::default(),
).expect("Failed to draw screen");
// drawing GUI
let gui_renderer = self.gui_renderer.as_mut().expect("Failed to retrieve gui renderer");
gui_renderer.run(&self.window, |egui_context| {
for function in gui_injections.iter() {
function(egui_context, &mut app_state);
}
});
gui_renderer.paint(&self.display, &mut screen_target);
screen_target.finish().expect("Failed to swap buffers");
}
Event::MainEventsCleared => {
// executing update functions
for function in update_injections {
function(&mut app_state);
}
self.window.request_redraw();
}
_ => (),
}
});
}
}
/* End of file lib.rs */
// File: light.rs
// Path: ..\src\light.rs
/* Start of file light.rs */
use glium::implement_uniform_block;
use serde::{Deserialize, Serialize};
pub enum LightEmissionType {
Source,
Ambient,
}
#[derive(Serialize, Deserialize)]
pub struct LightSerializer {
pub position: [f32; 3],
pub color: [f32; 3],
pub intensity: f32,
pub direction: [f32; 3],
pub cast_shadow: bool,
}
#[derive(Copy, Clone)]
pub struct Light {
pub position: [f32; 3],
pub color: [f32; 3],
pub intensity: f32,
pub direction: [f32; 3],
pub cast_shadow: bool
}
impl Light {
pub fn new(position: [f32; 3], color: [f32; 3], intensity: f32, direction: Option<[f32;3]>, cast_shadow: bool) -> Self {
Self {
position,
color,
intensity,
direction : direction.unwrap_or_else(|| [0.0, 0.0, 0.0]),
cast_shadow,
}
}
pub fn is_directional(&self) -> bool {
self.direction != [0.0, 0.0, 0.0]
}
pub fn from_serializer(serializer: LightSerializer) -> Self {
Self {
position: serializer.position,
color: serializer.color,
intensity: serializer.intensity,
direction: serializer.direction,
cast_shadow: serializer.cast_shadow,
}
}
pub fn to_serializer(&self) -> LightSerializer {
LightSerializer {
position: self.position,
color: self.color,
intensity: self.intensity,
direction: self.direction,
cast_shadow: self.cast_shadow,
}
}
}
pub struct LightBlock {
pub position: [[f32; 4]; 4],
pub directions: [[f32; 4]; 4],
pub color: [[f32; 4]; 4],
pub intensity: [f32; 4],
pub cast_shadow: [i32; 4],
pub amount: i32,
pub ambient_color: [f32; 3],
pub ambient_intensity: f32,
}
impl std::fmt::Debug for LightBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LightBlock")
.field("position", &self.position)
.field("color", &self.color)
.field("intensity", &self.intensity)
.field("amount", &self.amount)
.field("ambient_color", &self.ambient_color)
.finish()
}
}
glium::implement_uniform_block!(Light, position, color, intensity, direction, cast_shadow);
glium::implement_uniform_block!(LightBlock, position, directions, cast_shadow, color, intensity, amount, ambient_color, ambient_intensity);
/* End of file light.rs */
// File: main.rs
// Path: ..\src\main.rs
/* Start of file main.rs */
use std::sync::Arc;
use enigma::object::Object;
use enigma::camera::Camera;
use enigma::{AppState, event, resources};
use rand::Rng;
use enigma::event::EventModifiers;
fn rotate_left(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([0.0, -5.0, 0.0]);
}
}
fn rotate_right(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([0.0, 5.0, 0.0]);
}
}
fn rotate_up(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([-5.0, 0.0, 0.0]);
}
}
fn rotate_down(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([5.0, 0.0, 0.0]);
}
}
fn roll_left(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([0.0, 0.0, 5.0]);
}
}
fn roll_right(app_state: &mut AppState) {
for object in app_state.get_selected_objects_mut() {
object.transform.rotate([0.0, 0.0, -5.0]);
}
}
fn hopping_objects(app_state: &mut AppState) {
for object in app_state.objects.iter_mut() {
let rand_scale = rand::thread_rng().gen_range(0.0..0.015);
object.transform.move_dir([0.0, (app_state.time * 20.0).sin() * rand_scale, 0.0])
}
}
fn spawn_object(app_state: &mut AppState) {
match &app_state.display {
Some(d) => {
let rand_bool = rand::thread_rng().gen_bool(0.5);
let mut material = enigma::material::Material::lit_pbr(d.clone(), rand_bool);
material.set_transparency_strength(0.2);
material.set_texture_from_resource(resources::UV_CHECKER, enigma::material::TextureType::Albedo);
let mut object = Object::load_from_gltf_resource(resources::SUZANNE);
object.name = format!("Suzanne_{}", rand::thread_rng().gen_range(0..1000));
object.add_material(material);
let random_x = rand::thread_rng().gen_range(-4.0..4.0);
let random_z = rand::thread_rng().gen_range(-4.0..-1.0);
object.transform.set_position([random_x, 0.0, random_z]);
object.transform.set_scale([0.3, 0.3, 0.3]);
app_state.add_object(object);
}
None => {
println!("No display found, could not spawn object");
}
}
}
fn enigma_ui_function(ctx: &egui::Context, app_state: &mut AppState) {
egui::Window::new("Enigma")
.default_width(200.0)
.default_height(200.0)
.show(ctx, |ui| {
ui.label("Enigma 3D Renderer");
ui.label("Press A, D, W, S, E, Q to rotate the selected object");
ui.label("Press Space to spawn a new object");
ui.label("Press Ctrl + S to save the current state");
ui.label("Press Ctrl + O to load the saved state");
});
egui::Window::new("Scene")
.default_width(200.0)
.default_height(200.0)
.show(ctx, |ui| {
ui.label("Scene Objects");
for object in app_state.objects.iter() {
if ui.button(object.name.clone()).clicked() {
let uuid = object.get_unique_id();
if !app_state.object_selection.contains(&uuid) {
app_state.object_selection.push(uuid);
} else {
app_state.object_selection.remove(app_state.object_selection.iter().position(|x| *x == uuid).unwrap());
}
}
}
});
egui::Window::new("Transform Edit")
.default_width(200.0)
.default_height(200.0)
.show(ctx, |ui| {
if app_state.get_selected_objects_mut().len() > 0 {
ui.label("Transform Edit");
ui.label("Position");
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.position[0], -10.0..=10.0).text("X"));
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.position[1], -10.0..=10.0).text("Y"));
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.position[2], -10.0..=10.0).text("Z"));
ui.label("Rotation");
let mut rotation = app_state.get_selected_objects_mut()[0].transform.get_rotation();
ui.add(egui::Slider::new(&mut rotation.x, -180.0..=180.0).text("X"));
ui.add(egui::Slider::new(&mut rotation.y, -180.0..=180.0).text("Y"));
ui.add(egui::Slider::new(&mut rotation.z, -180.0..=180.0).text("Z"));
app_state.get_selected_objects_mut()[0].transform.set_rotation(rotation.into());
ui.label("Scale");
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.scale[0], 0.0..=10.0).text("X"));
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.scale[1], 0.0..=10.0).text("Y"));
ui.add(egui::Slider::new(&mut app_state.get_selected_objects_mut()[0].transform.scale[2], 0.0..=10.0).text("Z"));
} else {
ui.label("No object selected");
}
});
}
pub fn print_data(app_state: &mut AppState) {
if app_state.time % 2.0 < 0.01 {
let intdata = app_state.get_state_data_value::<i32>("intdata");
let stringdata = app_state.get_state_data_value::<String>("stringdata");
let booldata = app_state.get_state_data_value::<bool>("booldata");
println!("Data: ");
if let Some(intdata) = intdata {
println!("intdata: {}", intdata);
}
if let Some(stringdata) = stringdata {
println!("stringdata: {}", stringdata);
}
if let Some(booldata) = booldata {
println!("booldata: {}", booldata);
}
}
}
fn save_app_state(app_state: &mut AppState) {
let serialize_app_state = app_state.to_serializer();
let serialized = serde_json::to_string_pretty(&serialize_app_state).unwrap();
std::fs::write("app_state.json", serialized).unwrap();
}
fn load_app_state(app_state: &mut AppState) {
let serialized = std::fs::read_to_string("app_state.json").unwrap();
match serde_json::from_str(&serialized) {
Ok(deserialized) => {
let display = app_state.display.clone().unwrap();
app_state.inject_serializer(deserialized, display, false);
}
Err(e) => {
println!("Could not load app state: {}", e);
}
}
}
fn main() {
// create an enigma eventloop and appstate
let event_loop = enigma::EventLoop::new("Enigma 3D Renderer Window", 1080, 720);
let mut app_state = enigma::AppState::new();
// set the icon from the resources
event_loop.set_icon_from_resource(resources::ICON);
// some default event setups like e.g. selection
enigma::init_default(&mut app_state);
// create a material and assign the UV checker texture from resources
let mut material = enigma::material::Material::lit_pbr(event_loop.get_display_clone(), false);
material.set_texture_from_resource(resources::UV_CHECKER, enigma::material::TextureType::Albedo);
// create an object, and load the Suzanne model from resources
let mut object = Object::load_from_gltf_resource(resources::SUZANNE);
// set the material to the suzan object to the first shape (submesh) slot
object.add_material(material);
object.get_shapes_mut()[0].set_material_from_object_list(0);
// set the name and position of the object
object.name = "Suzanne".to_string();
object.transform.set_position([0.0, 0.0, -2.0]);
// adding the object to the app state
app_state.add_object(object);
// create a bunch of lights
let light1 = enigma::light::Light::new([1.0, 1.0, 5.0], [0.0, 1.0, 0.0], 100.0, Some([1.0,0.0,0.0]), false);
let light2 = enigma::light::Light::new([5.0, 1.0, 1.0], [1.0, 0.0, 0.0], 100.0, None, false);
let light3 = enigma::light::Light::new([-5.0, 1.0, 1.0], [0.0, 0.0, 1.0], 100.0, None, false);
let ambient_light = enigma::light::Light::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0.1, None, false);
// add the lights to the app state
app_state.add_light(light1, enigma::light::LightEmissionType::Source);
app_state.add_light(light2, enigma::light::LightEmissionType::Source);
app_state.add_light(light3, enigma::light::LightEmissionType::Source);
app_state.add_light(ambient_light, enigma::light::LightEmissionType::Ambient); // only one ambient light is supported atm
// create and add a camera to the app state
let camera = Camera::new(Some([0.0, 1.0, 1.0]), Some([20.0, 0.0, 0.0]), Some(90.0), Some(16. / 9.), Some(0.01), Some(1024.));
app_state.set_camera(camera);
// add events
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::A),
Arc::new(rotate_left),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::D),
Arc::new(rotate_right),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::W),
Arc::new(rotate_up),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::S),
Arc::new(rotate_down),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::E),
Arc::new(roll_right),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::Q),
Arc::new(roll_left),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::Space),
Arc::new(spawn_object),
None,
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::S),
Arc::new(save_app_state),
Some(EventModifiers::new(true, false, false)),
);
app_state.inject_event(
event::EventCharacteristic::KeyPress(winit::event::VirtualKeyCode::O),
Arc::new(load_app_state),
Some(EventModifiers::new(true, false, false)),
);
// add update functions
app_state.inject_update_function(Arc::new(hopping_objects));
app_state.inject_update_function(Arc::new(print_data));
// add post processing effects
//app_state.add_post_process(Box::new(enigma::postprocessing::grayscale::GrayScale::new(&event_loop.display.clone())));
app_state.add_post_process(Box::new(enigma::postprocessing::bloom::Bloom::new(&event_loop.display.clone(), 0.9, 15)));
app_state.add_post_process(Box::new(enigma::postprocessing::edge::Edge::new(&event_loop.display.clone(), 0.8, [1.0, 0.0, 0.0])));
//add one ui function to the app state. multiple ui functions can be added modularly
app_state.inject_gui(Arc::new(enigma_ui_function));
// add some arbitrary state data. This can be used to store any kind of data in the app state
// game globals, or other data that needs to be shared between different parts of the application
app_state.add_state_data( "intdata", Box::new(10i32));
app_state.add_state_data( "stringdata", Box::new("Hello World".to_string() as String));
app_state.add_state_data( "booldata", Box::new(true as bool));
// run the event loop
event_loop.run(app_state.convert_to_arc_mutex());
}
/* End of file main.rs */
// File: material.rs
// Path: ..\src\material.rs
/* Start of file material.rs */
use glium::Display;
use glium::glutin::surface::WindowSurface;
use glium::texture::RawImage2d;
use serde::{Deserialize, Serialize};
use crate::{resources, shader, texture};
use crate::camera::Camera;
use crate::light::{Light, LightBlock};
#[derive(Serialize, Deserialize, Clone)]
pub struct MaterialSerializer {
name : Option<String>,
color: [f32; 3],
albedo: Option<texture::TextureSerializer>,
transparency: f32,
normal: Option<texture::TextureSerializer>,
normal_strength: f32,
roughness: Option<texture::TextureSerializer>,
roughness_strength: f32,
metallic: Option<texture::TextureSerializer>,
metallic_strength: f32,
emissive: Option<texture::TextureSerializer>,
emissive_strength: f32,
shader: shader::ShaderSerializer,
matrix: [[f32;4]; 4],
render_transparent: bool,
}
pub struct Material {
pub name: Option<String>,
pub color: [f32; 3],
pub albedo: Option<texture::Texture>,
pub transparency: f32,
pub normal: Option<texture::Texture>,
pub normal_strength: f32,
pub roughness: Option<texture::Texture>,
pub roughness_strength: f32,
pub metallic: Option<texture::Texture>,
pub metallic_strength: f32,
pub emissive: Option<texture::Texture>,
pub emissive_strength: f32,
pub shader: shader::Shader,
_tex_white: glium::texture::SrgbTexture2d,
_tex_black: glium::texture::SrgbTexture2d,
_tex_gray: glium::texture::SrgbTexture2d,
_tex_normal: glium::texture::SrgbTexture2d,
//this should be a raw image
pub display: glium::Display<WindowSurface>,
pub program: glium::Program,
pub time: f32,
pub matrix: [[f32; 4]; 4],
pub render_transparent: bool,
}
pub enum TextureType {
Albedo,
Normal,
Roughness,
Metallic,
Emissive,
}
impl Material {
pub fn default(shader: shader::Shader, display: &glium::Display<WindowSurface>) -> Self {
Material::new(shader, display.clone(), None, None, None, None, None, None, None, None, None, None)
}
pub fn from_serializer(serializer: MaterialSerializer, display: glium::Display<WindowSurface>) -> Self {
let shader = shader::Shader::from_serializer(serializer.shader);
let albedo = match serializer.albedo {
Some(albedo) => Some(texture::Texture::from_serializer(albedo, &display)),
None => None,
};
let normal = match serializer.normal {
Some(normal) => Some(texture::Texture::from_serializer(normal, &display)),
None => None,
};
let roughness = match serializer.roughness {
Some(roughness) => Some(texture::Texture::from_serializer(roughness, &display)),
None => None,
};
let metallic = match serializer.metallic {
Some(metallic) => Some(texture::Texture::from_serializer(metallic, &display)),
None => None,
};
let emissive = match serializer.emissive {
Some(emissive) => Some(texture::Texture::from_serializer(emissive, &display)),
None => None,
};
let mut mat = Material::new(shader, display, Some(serializer.color), albedo, normal, Some(serializer.normal_strength), roughness, Some(serializer.roughness_strength), metallic, Some(serializer.metallic_strength), emissive, Some(serializer.emissive_strength));
mat.name = serializer.name;
mat.matrix = serializer.matrix;
mat.set_transparency_strength(serializer.transparency);
mat.set_transparency(serializer.render_transparent);
mat
}
pub fn to_serializer(&self) -> MaterialSerializer {
MaterialSerializer {
name: self.name.clone(),
color: self.color,
albedo: match &self.albedo {
Some(albedo) => Some(albedo.to_serializer()),
None => None,
},
transparency: self.transparency,
normal: match &self.normal {
Some(normal) => Some(normal.to_serializer()),
None => None,
},
normal_strength: self.normal_strength,
roughness: match &self.roughness {
Some(roughness) => Some(roughness.to_serializer()),
None => None,
},
roughness_strength: self.roughness_strength,
metallic: match &self.metallic {
Some(metallic) => Some(metallic.to_serializer()),
None => None,
},
metallic_strength: self.metallic_strength,
emissive: match &self.emissive {
Some(emissive) => Some(emissive.to_serializer()),
None => None,
},
emissive_strength: self.emissive_strength,
shader: self.shader.to_serializer(),
matrix: self.matrix,
render_transparent: self.render_transparent,
}
}
pub fn new(
shader: shader::Shader,
display: glium::Display<WindowSurface>,
color: Option<[f32; 3]>,
albedo: Option<texture::Texture>,
normal: Option<texture::Texture>,
normal_strength: Option<f32>,
roughness: Option<texture::Texture>,
roughness_strength: Option<f32>,
metallic: Option<texture::Texture>,
metallic_strength: Option<f32>,
emissive: Option<texture::Texture>,
emissive_strength: Option<f32>,
) -> Self {
let _program = glium::Program::from_source(&display, &shader.get_vertex_shader(), &shader.get_fragment_shader(), None).expect("Failed to compile shader program");
let _tex_white = {
let raw = Material::tex_raw_from_array([1.0, 1.0, 1.0, 1.0]);
glium::texture::SrgbTexture2d::new(&display, raw).unwrap()
};
let _tex_black = {
let raw = Material::tex_raw_from_array([0.0, 0.0, 0.0, 1.0]);
glium::texture::SrgbTexture2d::new(&display, raw).unwrap()
};
let _tex_gray = {
let raw = Material::tex_raw_from_array([0.5, 0.5, 0.5, 1.0]);
glium::texture::SrgbTexture2d::new(&display, raw).unwrap()
};
let _tex_normal = {
let raw = Material::tex_raw_from_array([0.5, 0.5, 1.0, 1.0]);
glium::texture::SrgbTexture2d::new(&display, raw).unwrap()
};
Self {
name: None,
shader,
display,
color: color.unwrap_or_else(|| [1.0, 1.0, 1.0]),
albedo: match albedo {
Some(albedo) => Some(albedo),
None => None,
},
transparency: 1.0,
normal: match normal {
Some(normal) => Some(normal),
None => None,
},
normal_strength: normal_strength.unwrap_or_else(|| 1.0),
roughness: match roughness {
Some(roughness) => Some(roughness),
None => None,
},
roughness_strength: roughness_strength.unwrap_or_else(|| 1.0),
metallic: match metallic {
Some(metallic) => Some(metallic),
None => None,
},
metallic_strength: metallic_strength.unwrap_or_else(|| 1.0),
emissive: match emissive {
Some(emissive) => Some(emissive),
None => None,
},
emissive_strength: emissive_strength.unwrap_or_else(|| 1.0),
_tex_white,
_tex_black,
_tex_gray,
_tex_normal,
program: _program,
time: 0.0,
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0f32],
],
render_transparent: false,
}
}
pub fn set_transparency(&mut self, transparent: bool) {
self.render_transparent = transparent;
}
pub fn set_emissive(&mut self, emissive: texture::Texture) {
self.emissive = Some(emissive);
}
pub fn set_emissive_strength(&mut self, emissive_strength: f32) {
self.emissive_strength = emissive_strength;
}
pub fn set_color(&mut self, color: [f32; 3]) {
self.color = color;
}
pub fn set_shader(&mut self, shader: shader::Shader) {
self.shader = shader.clone();
self.program = glium::Program::from_source(&self.display, &shader.get_vertex_shader(), &shader.get_fragment_shader(), None).expect("Failed to compile shader program");
}
pub fn set_albedo(&mut self, albedo: texture::Texture) {
self.albedo = Some(albedo);
}
pub fn set_normal(&mut self, normal: texture::Texture) {
self.normal = Some(normal);
}
pub fn set_transparency_strength(&mut self, transparency: f32) {
self.transparency = transparency;
}
pub fn set_normal_strength(&mut self, normal_strength: f32) {
self.normal_strength = normal_strength;
}
pub fn set_roughness(&mut self, roughness: texture::Texture) {
self.roughness = Some(roughness);
}
pub fn set_roughness_strength(&mut self, roughness_strength: f32) {
self.roughness_strength = roughness_strength;
}
pub fn set_metallic(&mut self, metallic: texture::Texture) {
self.metallic = Some(metallic);
}
pub fn set_metallic_strength(&mut self, metallic_strength: f32) {
self.metallic_strength = metallic_strength;
}
pub fn set_texture_from_file(&mut self, path: &str, texture_type: TextureType) {
match texture_type {
TextureType::Albedo => self.albedo = Some(texture::Texture::new(&self.display, path)),
TextureType::Normal => self.normal = Some(texture::Texture::new(&self.display, path)),
TextureType::Roughness => self.roughness = Some(texture::Texture::new(&self.display, path)),
TextureType::Metallic => self.metallic = Some(texture::Texture::new(&self.display, path)),
TextureType::Emissive => self.emissive = Some(texture::Texture::new(&self.display, path)),
}
}
pub fn set_texture_from_resource(&mut self, data: &[u8], texture_type: TextureType) {
match texture_type {
TextureType::Albedo => self.albedo = Some(texture::Texture::from_resource(&self.display, data)),
TextureType::Normal => self.normal = Some(texture::Texture::from_resource(&self.display, data)),
TextureType::Roughness => self.roughness = Some(texture::Texture::from_resource(&self.display, data)),
TextureType::Metallic => self.metallic = Some(texture::Texture::from_resource(&self.display, data)),
TextureType::Emissive => self.emissive = Some(texture::Texture::from_resource(&self.display, data)),
}
}
pub fn lit_pbr(display: Display<WindowSurface>, transparency: bool) -> Self {
let mut mat = Material::default(shader::Shader::from_strings(resources::VERTEX_SHADER, resources::FRAGMENT_SHADER), &display);
mat.set_transparency(transparency);
mat
}
pub fn unlit(display: Display<WindowSurface>, transparency: bool) -> Self {
let mut mat = Material::default(shader::Shader::from_strings(resources::VERTEX_SHADER, resources::FRAGMENT_UNLIT_SHADER), &display);
mat.set_transparency(transparency);
mat
}
fn light_block_from_vec(lights: Vec<Light>, ambient_light: Option<Light>) -> LightBlock {
let mut light_amount = lights.len() as i32;
if light_amount > 4 {
light_amount = 4;
}
let mut light_position: [[f32; 4];4] = [[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0]];
let mut light_color: [[f32; 4];4] = [[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0]];
let mut light_intensity: [f32;4] = [0.0, 0.0, 0.0, 0.0];
let mut light_direction: [[f32; 4];4] = [[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0]];
let mut cast_shadow: [i32;4] = [0, 0, 0, 0];
for i in 0..5 {
if i < light_amount as usize {
light_position[i] = [lights[i].position[0], lights[i].position[1], lights[i].position[2], 0.0];
light_color[i] = [lights[i].color[0], lights[i].color[1], lights[i].color[2], 0.0];
light_intensity[i] = lights[i].intensity;
light_direction[i] = {
let direction = lights[i].direction;
if direction == [0.0, 0.0, 0.0] {
[0.0,0.0,0.0,0.0]
} else {
[lights[i].direction[0], lights[i].direction[1], lights[i].direction[2], 1.0]
}
};
cast_shadow[i] = if lights[i].cast_shadow { 1 } else { 0 };
}
}
LightBlock {
position: light_position,
directions: light_direction,
cast_shadow,
color: light_color,
intensity: light_intensity,
amount: light_amount,
ambient_color: match ambient_light {
Some(ambient_light) => ambient_light.color,
None => [0.0, 0.0, 0.0],
},
ambient_intensity: match ambient_light {
Some(ambient_light) => ambient_light.intensity,
None => 0.0,
},
}
}
pub fn get_uniforms<'a>(&'a self, lights: Vec<Light>, ambient_light: Option<Light>, camera: Option<Camera>, model_matrix: Option<[[f32; 4]; 4]>, skybox: &'a texture::Texture) -> impl glium::uniforms::Uniforms + '_ {
let light_block = Material::light_block_from_vec(lights, ambient_light);
glium::uniform! {
time: self.time,
matrix: self.matrix,
projection_matrix: match camera {
Some(camera) => camera.get_projection_matrix(),
None => Camera::new(None, None, None, None, None, None).get_projection_matrix(),
},
view_matrix: match camera {
Some(camera) => camera.get_view_matrix(),
None => Camera::new(None, None, None, None, None, None).get_view_matrix(),
},
mat_color: self.color,
mat_albedo: match &self.albedo {
Some(albedo) => &albedo.texture,
None => &self._tex_white
},
mat_normal: match &self.normal {
Some(normal) => &normal.texture,
None => &self._tex_normal,
},
mat_normal_strength: self.normal_strength,
mat_roughness: match &self.roughness {
Some(roughness) => &roughness.texture,
None => &self._tex_gray
},
mat_roughness_strength: self.roughness_strength,
mat_metallic: match &self.metallic {
Some(metallic) => &metallic.texture,
None => &self._tex_black
},
mat_metallic_strength: self.metallic_strength,
mat_emissive: match &self.emissive {
Some(emissive) => &emissive.texture,
None => &self._tex_black
},
mat_emissive_strength: self.emissive_strength,
mat_transparency_strength: self.transparency,
light_position: light_block.position,
light_direction: light_block.directions,
light_color: light_block.color,
light_intensity: light_block.intensity,
light_amount: light_block.amount,
ambient_light_color: light_block.ambient_color,
ambient_light_intensity: light_block.ambient_intensity,
model_matrix: model_matrix.unwrap_or_else(|| self.matrix),
skybox: &skybox.texture,
}
}
fn tex_raw_from_array(color: [f32; 4]) -> RawImage2d<'static, u8> {
let byte_color: [u8; 4] = [
(color[0] * 255.0) as u8,
(color[1] * 255.0) as u8,
(color[2] * 255.0) as u8,
(color[3] * 255.0) as u8,
];
RawImage2d::from_raw_rgba_reversed(&byte_color, (1, 1))
}
pub fn update(&mut self) {
self.time += 0.001;
}
}
/* End of file material.rs */
// File: object.rs
// Path: ..\src\object.rs
/* Start of file object.rs */
use std::vec::Vec;
use glium::Display;
use glium::glutin::surface::WindowSurface;
use crate::geometry::{BoundingBox, Vertex};
use crate::material::{Material, MaterialSerializer};
use nalgebra::{Vector3, Matrix4, Translation3, UnitQuaternion, Point3};
use crate::{debug_geo, geometry};
use uuid::Uuid;
use std::fs::File;
use std::io::BufReader;
use nalgebra_glm::normalize;
use obj::{load_obj, Obj};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct ObjectSerializer {
pub name: String,
pub transform: TransformSerializer,
shapes: Vec<Shape>,
materials: Vec<MaterialSerializer>,
unique_id: String,
}
pub struct Object {
pub name: String,
pub transform: Transform,
shapes: Vec<Shape>,
materials: Vec<Material>,
bounding_box: Option<geometry::BoundingBox>,
unique_id: Uuid,
}
impl Clone for Object {
fn clone(&self) -> Self {
//creating new object
let mut new_object = Object::new(Some(self.name.clone()));
//setting transform for new object
new_object.transform.set_position(self.transform.get_position().into());
new_object.transform.set_rotation(self.transform.get_rotation().into());
new_object.transform.set_scale(self.transform.get_scale().into());
//cloning shapes
for shape in self.shapes.iter() {
let mut new_shape = Shape::new();
new_shape.vertices = shape.vertices.clone();
new_shape.indices = shape.indices.clone();
new_shape.material_index = shape.material_index;
new_object.add_shape(new_shape);
}
//cloning materials
for material in self.materials.iter() {
let mut new_material = Material::default(material.shader.clone(), &material.display);
//coloring
new_material.set_color(material.color);
if let Some(texture) = &material.albedo {
let new_texture = texture.get_texture_clone(&new_material.display);
new_material.set_albedo(new_texture);
}
new_material.set_transparency_strength(material.transparency);
if let Some(texture) = &material.normal {
let new_texture = texture.get_texture_clone(&new_material.display);
new_material.set_normal(new_texture);
}
new_material.set_normal_strength(material.normal_strength);
if let Some(texture) = &material.roughness {
let new_texture = texture.get_texture_clone(&new_material.display);
new_material.set_roughness(new_texture);
}
new_material.set_roughness_strength(material.roughness_strength);
if let Some(texture) = &material.metallic {
let new_texture = texture.get_texture_clone(&new_material.display);
new_material.set_metallic(new_texture);
}
new_material.set_metallic_strength(material.metallic_strength);
if let Some(texture) = &material.emissive {
let new_texture = texture.get_texture_clone(&new_material.display);
new_material.set_emissive(new_texture);
}
new_material.set_emissive_strength(material.emissive_strength);
new_material.set_transparency(material.render_transparent);
new_material.time = material.time;
new_object.add_material(new_material);
}
new_object.bounding_box = self.bounding_box.clone();
new_object.unique_id = Uuid::new_v4();
new_object
}
}
#[derive(Serialize, Deserialize)]
pub struct Shape {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub material_index: usize,
}
impl Clone for Shape {
fn clone(&self) -> Self {
Shape {
vertices: self.vertices.clone(),
indices: self.indices.clone(),
material_index: self.material_index,
}
}
}
impl Shape {
pub fn new() -> Self {
Shape {
vertices: Vec::new(),
indices: Vec::new(),
material_index: 0,
}
}
pub fn from_vertices_indices(vertices: Vec<Vertex>, indices: Vec<u32>) -> Self {
Shape {
vertices,
indices,
material_index: 0,
}
}
pub fn default() -> Self {
let triangle = debug_geo::TRIANGLE;
let mut shape = Shape::new();
shape.vertices = triangle.to_vec();
for i in 0..triangle.iter().len() {
shape.indices.push(i as u32);
}
shape
}
pub fn get_vertex_buffer(&self, display: Display<WindowSurface>) -> glium::VertexBuffer<Vertex> {
glium::VertexBuffer::new(&display, &self.vertices).unwrap()
}
pub fn get_index_buffer(&self, display: Display<WindowSurface>) -> glium::IndexBuffer<u32> {
glium::IndexBuffer::new(&display, glium::index::PrimitiveType::TrianglesList, &self.indices).unwrap()
}
pub fn set_material_from_object_list(&mut self, material_index: usize) {
self.material_index = material_index;
}
}
impl Object {
pub fn new(name: Option<String>) -> Self {
let mut object = Object {
name: name.unwrap_or_else(|| String::from("Object")),
transform: Transform::new(),
shapes: Vec::new(),
materials: Vec::new(),
bounding_box: None,
unique_id: Uuid::new_v4(), //generating unique id for object
};
object.calculate_bounding_box();
object
}
pub fn to_serializer(&self) -> ObjectSerializer {
let name = self.name.clone();
let transform = self.transform.to_serializer();
let shapes = self.shapes.clone();
let materials = self.materials.iter().map(|x| x.to_serializer()).collect();
let unique_id = self.unique_id.to_string();
ObjectSerializer {
name,
transform,
shapes,
materials,
unique_id,
}
}
pub fn from_serializer(serializer: ObjectSerializer, display: Display<WindowSurface>) -> Self {
let mut object = Object::new(Some(serializer.name));
object.transform = Transform::from_serializer(serializer.transform);
object.shapes = serializer.shapes;
for mat in serializer.materials {
object.add_material(Material::from_serializer(mat, display.clone()));
}
object.unique_id = uuid::Uuid::parse_str(serializer.unique_id.as_str()).unwrap();
object.calculate_bounding_box();
object
}
pub fn get_unique_id(&self) -> Uuid {
self.unique_id
}
fn calculate_bounding_box(&mut self) -> BoundingBox {
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
let mut min_z = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut max_y = f32::NEG_INFINITY;
let mut max_z = f32::NEG_INFINITY;
for shape in self.get_shapes().iter() {
for vertex in shape.vertices.iter() {
min_x = min_x.min(vertex.position[0]);
min_y = min_y.min(vertex.position[1]);
min_z = min_z.min(vertex.position[2]);
max_x = max_x.max(vertex.position[0]);
max_y = max_y.max(vertex.position[1]);
max_z = max_z.max(vertex.position[2]);
}
}
let min_point = Point3::new(min_x, min_y, min_z);
let max_point = Point3::new(max_x, max_y, max_z);
let center = Point3::new(
(min_point.x + max_point.x) / 2.0,
(min_point.y + max_point.y) / 2.0,
(min_point.z + max_point.z) / 2.0,
);
self.transform.update();
let transformed_center = self.transform.matrix.transform_point(¢er);
let transformed_width = (max_x - min_x) * self.transform.get_scale().x;
let transformed_height = (max_y - min_y) * self.transform.get_scale().y;
let transformed_depth = (max_z - min_z) * self.transform.get_scale().z;
let aabb = BoundingBox {
center: Vector3::from([transformed_center.x, transformed_center.y, transformed_center.z]),
width: transformed_width,
height: transformed_height,
depth: transformed_depth,
};
self.bounding_box = Some(aabb);
aabb
}
pub fn default() -> Self {
let mut object = Object::new(None);
object.add_shape(Shape::default());
object
}
pub fn update(&mut self) {
self.transform.update();
self.materials.iter_mut().for_each(|x| x.update());
}
pub fn get_closest_lights(&self, lights: &Vec<crate::light::Light>) -> Vec<crate::light::Light> {
let mut closest_lights = Vec::new();
//collect the four closest lights to the object
for light in lights.iter() {
let light_pos = light.position;
let object_pos = self.transform.get_position();
let distance = (Vector3::from(light_pos) - object_pos).magnitude();
if closest_lights.len() < 4 {
closest_lights.push((light.clone(), distance));
} else {
let mut max_distance = 0.0;
let mut max_index = 0;
for (index, (_, distance)) in closest_lights.iter().enumerate() {
if *distance > max_distance {
max_distance = *distance;
max_index = index;
}
}
if distance < max_distance {
closest_lights[max_index] = (light.clone(), distance.clone());
}
}
}
closest_lights.iter().map(|(light, _)| light.clone()).collect()
}
pub fn add_shape(&mut self, shape: Shape) {
self.shapes.push(shape);
}
pub fn get_vertex_buffers(&self) -> Vec<glium::VertexBuffer<Vertex>> {
let shapes = self.get_shapes();
let mut buffer = Vec::new();
for shape in shapes.iter() {
let material = &self.materials[shape.material_index];
let vertex = glium::VertexBuffer::new(&material.display, &shape.vertices).unwrap();
buffer.push(vertex);
}
buffer
}
pub fn get_index_buffers(&self) -> Vec<glium::IndexBuffer<u32>> {
let shapes = self.get_shapes();
let mut buffer = Vec::new();
for shape in shapes.iter() {
let material = &self.materials[shape.material_index];
let index = glium::IndexBuffer::new(&material.display, glium::index::PrimitiveType::TrianglesList, &shape.indices).unwrap();
buffer.push(index);
}
buffer
}
pub fn get_bounding_box(&mut self) -> BoundingBox {
self.calculate_bounding_box()
}
pub fn get_materials(&self) -> &Vec<Material> {
&self.materials
}
pub fn get_materials_mut(&mut self) -> &mut Vec<Material> {
&mut self.materials
}
pub fn add_material(&mut self, material: Material) {
self.materials.push(material);
}
pub fn get_shapes(&self) -> &Vec<Shape> {
&self.shapes
}
pub fn get_shapes_mut(&mut self) -> &mut Vec<Shape> {
&mut self.shapes
}
pub fn get_name(&self) -> &String {
&self.name
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn load_from_obj(path: &str) -> Self {
let input = BufReader::new(File::open(path).expect("Failed to open file"));
let obj: Obj = load_obj(input).unwrap();
let mut vertices = Vec::new();
let mut indices = Vec::new();
for vert in obj.vertices.iter() {
let vertex = geometry::Vertex { position: vert.position, color: [1.0, 1.0, 1.0], texcoord: [0.0, 0.0], normal: vert.normal };
vertices.push(vertex);
}
for index in obj.indices.iter() {
indices.push((*index).into());
}
let shape = Shape::from_vertices_indices(vertices, indices);
let mut object = Object::new(obj.name);
object.add_shape(shape);
object
}
pub fn load_from_gltf_resource(data: &[u8]) -> Self {
let (gltf, buffers, _) = gltf::import_slice(data).expect("Failed to import gltf file"); // gltf::import(path).expect("Failed to import gltf file");
let mut object = Object::new(Some(String::from("INTERNAL ENIGMA RESOURCE")));
for mesh in gltf.meshes() {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for primitive in mesh.primitives() {
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let positions = reader.read_positions().unwrap();
let normals = reader.read_normals().unwrap();
let tex_coords = reader.read_tex_coords(0).unwrap().into_f32();
let prim_indices = reader.read_indices().unwrap().into_u32();
let mut flipped_tex_coords: Vec<[f32; 2]> = Vec::new();
// flip tex_coords
for mut tex_coord in tex_coords.into_iter() {
tex_coord[1] = 1.0 - tex_coord[1];
flipped_tex_coords.push(tex_coord);
}
for ((position, normal), tex_coord) in positions.zip(normals).zip(flipped_tex_coords) {
let vertex = geometry::Vertex { position, color: [1.0, 1.0, 1.0], texcoord: tex_coord, normal };
vertices.push(vertex);
}
prim_indices.for_each(|index| indices.push(index));
}
let shape = Shape::from_vertices_indices(vertices, indices);
object.add_shape(shape);
}
object
}
pub fn load_from_gltf(path: &str) -> Self {
let (gltf, buffers, _) = gltf::import(path).expect("Failed to import gltf file");
let mut object = Object::new(Some(String::from(path)));
for mesh in gltf.meshes() {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for primitive in mesh.primitives() {
// Adjust the reader to use the buffers vector correctly
let reader = primitive.reader(|buffer| {
// Access the corresponding buffer data using the buffer index
buffers.get(buffer.index()).map(|data| &data[..])
});
let positions = reader.read_positions().unwrap();
let normals = reader.read_normals().unwrap();
let tex_coords = reader.read_tex_coords(0).unwrap().into_f32();
let prim_indices = reader.read_indices().unwrap().into_u32();
let mut flipped_tex_coords: Vec<[f32; 2]> = Vec::new();
// flip tex_coords
for mut tex_coord in tex_coords.into_iter() {
tex_coord[1] = 1.0 - tex_coord[1];
flipped_tex_coords.push(tex_coord);
}
for ((position, normal), tex_coord) in positions.zip(normals).zip(flipped_tex_coords) {
let vertex = geometry::Vertex { position, color: [1.0, 1.0, 1.0], texcoord: tex_coord, normal };
vertices.push(vertex);
}
prim_indices.for_each(|index| indices.push(index));
}
let shape = Shape::from_vertices_indices(vertices, indices);
object.add_shape(shape);
}
object
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct TransformSerializer {
position: [f32; 3],
rotation: [f32; 3],
scale: [f32; 3],
}
#[derive(Copy, Clone)]
pub struct Transform {
pub position: Vector3<f32>,
pub rotation: Vector3<f32>,
// radian angles
pub scale: Vector3<f32>,
pub matrix: Matrix4<f32>,
}
impl Transform {
pub fn new() -> Self {
Transform {
position: Vector3::new(0.0, 0.0, 0.0),
rotation: Vector3::new(0.0, 0.0, 0.0),
scale: Vector3::new(1.0, 1.0, 1.0),
matrix: Matrix4::identity(),
}
}
pub fn forward(&self) -> Vector3<f32> {
// return the forward vector of the transform with positive z being forward
let rotation = UnitQuaternion::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z);
let forward = rotation * Vector3::new(0.0, 0.0, 1.0);
normalize(&forward)
}
pub fn left(&self) -> Vector3<f32> {
// return the left vector of the transform with positive x being left
let rotation = UnitQuaternion::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z);
let left = rotation * Vector3::new(-1.0, 0.0, 0.0);
normalize(&left)
}
pub fn up(&self) -> Vector3<f32> {
// return the up vector of the transform with positive y being up
let rotation = UnitQuaternion::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z);
let up = rotation * Vector3::new(0.0, 1.0, 0.0);
normalize(&up)
}
pub fn from_serializer(serializer: TransformSerializer) -> Self {
let mut t = Transform::new();
t.set_position(serializer.position);
t.set_rotation(serializer.rotation);
t.set_scale(serializer.scale);
t
}
pub fn to_serializer(&self) -> TransformSerializer {
TransformSerializer {
position: self.get_position().into(),
rotation: self.get_rotation().into(),
scale: self.get_scale().into(),
}
}
pub fn update(&mut self) {
let scale_matrix = Matrix4::new_nonuniform_scaling(&self.scale);
let rotation_matrix = UnitQuaternion::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z).to_homogeneous();
let translation_matrix = Translation3::from(self.position).to_homogeneous();
// Scale, then rotate, then translate
self.matrix = translation_matrix * rotation_matrix * scale_matrix;
}
pub fn set_position(&mut self, position: [f32; 3]) {
self.position = Vector3::from(position);
}
pub fn get_position(&self) -> Vector3<f32> {
self.position.clone()
}
pub fn set_rotation(&mut self, rotation: [f32; 3]) {
let radians = rotation.iter().map(|x| x.to_radians()).collect::<Vec<f32>>();
self.rotation = Vector3::from([radians[0], radians[1], radians[2]]);
}
pub fn rotate(&mut self, rotation: [f32; 3]) {
let cur_r = self.get_rotation();
let additive_rotation = [cur_r.x + rotation[0], cur_r.y + rotation[1], cur_r.z + rotation[2]];
let radians = additive_rotation.iter().map(|x| x.to_radians()).collect::<Vec<f32>>();
self.rotation = Vector3::from([radians[0], radians[1], radians[2]]);
}
pub fn move_dir(&mut self, position: [f32; 3]) {
let cur_p = self.get_position();
let additive_position = [cur_p.x + position[0], cur_p.y + position[1], cur_p.z + position[2]];
self.position = Vector3::from(additive_position);
}
pub fn get_rotation(&self) -> Vector3<f32> {
let x = self.rotation.x.to_degrees();
let y = self.rotation.y.to_degrees();
let z = self.rotation.z.to_degrees();
Vector3::from([x, y, z])
}
pub fn set_scale(&mut self, scale: [f32; 3]) {
self.scale = Vector3::from(scale);
}
pub fn get_scale(&self) -> Vector3<f32> {
self.scale.clone()
}
pub fn get_matrix(&mut self) -> [[f32; 4]; 4] {
self.update();
self.matrix.into()
}
}
/* End of file object.rs */
// File: resources.rs
// Path: ..\src\resources.rs
/* Start of file resources.rs */
// models
pub const SUZANNE: &'static [u8] = include_bytes!("res/models/suzanne.glb");
pub const SKYBOX: &'static [u8] = include_bytes!("res/models/skybox.glb");
// shaders
//// post processing
pub const POST_PROCESSING_VERTEX: &str = include_str!("res/shader/post_processing/post_processing_vert.glsl");
pub const POST_PROCESSING_BLOOM_BLUR_FRAGMENT: &str = include_str!("res/shader/post_processing/bloom/enigma_bloom_blur.glsl");
pub const POST_PROCESSING_BLOOM_COMBINE_FRAGMENT: &str = include_str!("res/shader/post_processing/bloom/enigma_bloom_combine.glsl");
pub const POST_PROCESSING_BLOOM_EXTRACT_FRAGMENT: &str = include_str!("res/shader/post_processing/bloom/enigma_bloom_extract.glsl");
pub const POST_PROCESSING_EDGE_FRAGMENT: &str = include_str!("res/shader/post_processing/edge/enigma_edge_detection.glsl");
pub const POST_PROCESSING_GRAYSCALE_FRAGMENT: &str = include_str!("res/shader/post_processing/grayscale/enigma_grayscale.glsl");
//// other
pub const FRAGMENT_SHADER: &str = include_str!("res/shader/enigma_fragment_shader.glsl");
pub const VERTEX_SHADER: &str = include_str!("res/shader/enigma_vertex_shader.glsl");
pub const FRAGMENT_UNLIT_SHADER: &str = include_str!("res/shader/enigma_fragment_unlit.glsl");
// textures
pub const UV_CHECKER: &'static [u8] = include_bytes!("res/textures/uv_checker.png");
pub const SKYBOX_TEXTURE: &'static [u8] = include_bytes!("res/textures/skybox.png");
pub const SKYBOX_TEXTURE_HDR: &'static [u8] = include_bytes!("res/textures/skybox.hdr");
pub const ICON: &'static [u8] = include_bytes!("res/textures/icon.png");
/* End of file resources.rs */
// File: shader.rs
// Path: ..\src\shader.rs
/* Start of file shader.rs */
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct ShaderSerializer {
fragment_shader: String,
vertex_shader: String,
}
pub struct Shader {
pub fragment_shader: String,
pub vertex_shader: String,
}
impl Shader {
pub fn new() -> Self {
Self {
fragment_shader: String::from(""),
vertex_shader: String::from(""),
}
}
pub fn from_serializer(serializer: ShaderSerializer) -> Self {
Self {
fragment_shader: serializer.fragment_shader,
vertex_shader: serializer.vertex_shader,
}
}
pub fn to_serializer(&self) -> ShaderSerializer {
ShaderSerializer {
fragment_shader: self.fragment_shader.clone(),
vertex_shader: self.vertex_shader.clone(),
}
}
pub fn set_fragment_shader(&mut self, fragment_shader: String) {
self.fragment_shader = fragment_shader;
}
pub fn set_vertex_shader(&mut self, vertex_shader: String) {
self.vertex_shader = vertex_shader;
}
pub fn get_fragment_shader(&self) -> String {
self.fragment_shader.clone()
}
pub fn get_vertex_shader(&self) -> String {
self.vertex_shader.clone()
}
pub fn from_files(vertex_shader: &str, fragment_shader: &str) -> Self {
let vertex_shader = std::fs::read_to_string(vertex_shader).expect("Unable to read file");
let fragment_shader = std::fs::read_to_string(fragment_shader).expect("Unable to read file");
Self {
fragment_shader,
vertex_shader,
}
}
pub fn from_strings(vertex_shader: &str, fragment_shader: &str) -> Self {
Self {
fragment_shader: String::from(fragment_shader),
vertex_shader: String::from(vertex_shader),
}
}
pub fn log(&self) {
println!("Vertex Shader:\n{}", self.vertex_shader);
println!("Fragment Shader:\n{}", self.fragment_shader);
}
pub fn default() -> Self {
let vertex_shader = r#"
#version 140
uniform float time;
uniform mat4 matrix;
in vec3 position;
void main() {
gl_Position = matrix * vec4(position, 1.0);
}
"#;
let fragment_shader = r#"
#version 140
uniform float time;
out vec4 color;
void main() {
color = vec4(1.0, 0.0, 1.0, 1.0);
}
"#;
Self {
fragment_shader: String::from(fragment_shader),
vertex_shader: String::from(vertex_shader),
}
}
}
impl Default for Shader {
fn default() -> Self {
Self::default()
}
}
impl std::fmt::Display for Shader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.vertex_shader, self.fragment_shader)
}
}
impl std::fmt::Debug for Shader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.vertex_shader, self.fragment_shader)
}
}
impl Clone for Shader {
fn clone(&self) -> Self {
Self {
fragment_shader: self.fragment_shader.clone(),
vertex_shader: self.vertex_shader.clone(),
}
}
}
/* End of file shader.rs */
// File: texture.rs
// Path: ..\src\texture.rs
/* Start of file texture.rs */
use glium::glutin::surface::WindowSurface;
use std::path::Path;
use std::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct TextureSerializer {
path: String,
width: u32,
height: u32,
binary_data: Option<Vec<u8>>,
}
pub struct Texture {
pub path: String,
pub texture: glium::texture::SrgbTexture2d,
pub width: u32,
pub height: u32,
pub binary_data: Option<Vec<u8>>,
}
impl Texture {
pub fn new(display: &glium::Display<WindowSurface>, path: &str) -> Self {
let image = image::open(path).unwrap().to_rgba8();
let image_dimensions = image.dimensions();
let image = glium::texture::RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dimensions);
let texture = glium::texture::SrgbTexture2d::new(display, image).unwrap();
Self {
texture,
path: String::from(path),
width: image_dimensions.0,
height: image_dimensions.1,
binary_data: None,
}
}
pub fn from_serializer(serializer: TextureSerializer, display: &glium::Display<WindowSurface>) -> Self {
let path = Path::new(&serializer.path);
if !path.is_file() {
match &serializer.binary_data {
Some(data) => {
return Texture::from_resource(display, data.as_slice());
}
None => {}
}
} else {
return Texture::new(display, path.to_str().unwrap());
}
println!("could not create texture from serializer, returned texture is empty");
let empty_tex = glium::texture::SrgbTexture2d::empty(display, serializer.width, serializer.height);
return Self {
texture: empty_tex.expect("Could not create Empty Texture"),
path: String::from("RESOURCE"),
width: serializer.width,
height: serializer.height,
binary_data: None,
};
}
pub fn to_serializer(&self) -> TextureSerializer {
TextureSerializer {
path: self.path.clone(),
width: self.width,
height: self.height,
binary_data: self.binary_data.clone(),
}
}
pub fn from_resource(display: &glium::Display<WindowSurface>, data: &[u8]) -> Self {
let image = image::load_from_memory(data).expect("Failed to load image").to_rgba8();
let image_dimensions = image.dimensions();
let image = glium::texture::RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dimensions);
let texture = glium::texture::SrgbTexture2d::new(display, image).unwrap();
Self {
texture,
path: String::from("INTERNAL ENIGMA RESOURCE"),
width: image_dimensions.0,
height: image_dimensions.1,
binary_data: Some(data.to_vec()),
}
}
pub fn get_texture_clone(&self, display: &glium::Display<WindowSurface>) -> Self {
let path_str = self.path.clone();
let path = Path::new(&path_str);
if !path.is_file() {
match &self.binary_data {
Some(data) => {
return Texture::from_resource(display, data.as_slice());
}
None => {
println!("could not clone texture , returned texture is empty");
let empty_tex = glium::texture::SrgbTexture2d::empty(display, self.width, self.height);
return Self {
texture: empty_tex.expect("Could not create Empty Texture"),
path: String::from("RESOURCE"),
width: self.width,
height: self.height,
binary_data: None,
};
}
}
} else {
return Texture::new(display, path_str.as_str());
}
}
}
/* End of file texture.rs */
// File: ui.rs
// Path: ..\src\ui.rs
/* Start of file ui.rs */
use std::sync::Arc;
use crate::AppState;
pub type GUIDrawFunction = Arc<dyn Fn(&egui::Context, &mut AppState)>;
/* End of file ui.rs */
// File: postprocessing\bloom.rs
// Path: ..\src\postprocessing\bloom.rs
/* Start of file postprocessing\bloom.rs */
use glium::{IndexBuffer, Surface, Texture2d, uniform, VertexBuffer};
use glium::framebuffer::SimpleFrameBuffer;
use glium::glutin::surface::WindowSurface;
use glium::texture::DepthTexture2d;
use crate::geometry::Vertex;
use crate::postprocessing::PostProcessingEffect;
use crate::{AppState, postprocessing, resources, shader};
pub struct Bloom {
pub threshold: f32,
pub iterations: i32,
program_extract: glium::Program,
program_blur: glium::Program,
program_combine: glium::Program,
program_copy: glium::Program,
}
impl Bloom {
pub fn new(display: &glium::Display<WindowSurface>, threshold: f32, iterations: i32) -> Self {
let extract_shader = shader::Shader::from_strings(resources::POST_PROCESSING_VERTEX, resources::POST_PROCESSING_BLOOM_EXTRACT_FRAGMENT);
let blur_shader = shader::Shader::from_strings(resources::POST_PROCESSING_VERTEX, resources::POST_PROCESSING_BLOOM_BLUR_FRAGMENT);
let combine_shader = shader::Shader::from_strings(resources::POST_PROCESSING_VERTEX, resources::POST_PROCESSING_BLOOM_COMBINE_FRAGMENT);
let program_extract = glium::Program::from_source(display, &extract_shader.get_vertex_shader(), &extract_shader.get_fragment_shader(), None).expect("Failed to compile shader program");
let program_blur = glium::Program::from_source(display, &blur_shader.get_vertex_shader(), &blur_shader.get_fragment_shader(), None).expect("Failed to compile shader program");
let program_combine = glium::Program::from_source(display, &combine_shader.get_vertex_shader(), &combine_shader.get_fragment_shader(), None).expect("Failed to compile shader program");
let program_copy = postprocessing::get_screen_program(&display);
Self {
program_extract,
program_blur,
program_combine,
program_copy,
threshold,
iterations,
}
}
}
impl PostProcessingEffect for Bloom {
fn render(&self, app_state: &AppState, vertex_buffer: &VertexBuffer<Vertex>, index_buffer: &IndexBuffer<u32>, target: &mut SimpleFrameBuffer, source: &Texture2d, _depth_texture: &DepthTexture2d, buffer_textures: &Vec<Texture2d>) {
// first create a temporary framebuffer to render the scene to and then use it as a texture
let mut work_framebuffer_1 = SimpleFrameBuffer::new(app_state.display.as_ref().unwrap(), &buffer_textures[0]).unwrap();
let mut work_framebuffer_2 = SimpleFrameBuffer::new(app_state.display.as_ref().unwrap(), &buffer_textures[1]).unwrap();
work_framebuffer_1.clear_color(0.0, 0.0, 0.0, 0.0);
work_framebuffer_2.clear_color(0.0, 0.0, 0.0, 0.0);
// creating copies of the incomming scene
let uniforms = uniform! {
scene: source.sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
};
work_framebuffer_1.draw(vertex_buffer, index_buffer, &self.program_copy, &uniforms, &Default::default()).unwrap();
work_framebuffer_2.draw(vertex_buffer, index_buffer, &self.program_copy, &uniforms, &Default::default()).unwrap();
// extract the bright parts of the scene
let uniforms = uniform! {
scene: source.sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
threshold: self.threshold,
};
work_framebuffer_1.draw(vertex_buffer, index_buffer, &self.program_extract, &uniforms, &Default::default()).unwrap();
// blur the bright parts of the scene
let uniforms = uniform! {
scene: buffer_textures[0].sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
horizontal: true,
iterations: self.iterations,
};
work_framebuffer_2.draw(vertex_buffer, index_buffer, &self.program_blur, &uniforms, &Default::default()).unwrap();
work_framebuffer_1.clear_color(0.0, 0.0, 0.0, 0.0);
let uniforms = uniform! {
scene: buffer_textures[1].sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
horizontal: false,
iterations: self.iterations,
};
work_framebuffer_1.draw(vertex_buffer, index_buffer, &self.program_blur, &uniforms, &Default::default()).unwrap();
// render to original framebuffer
let uniforms = uniform! {
scene: source.sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
bloomBlur: buffer_textures[0].sampled()
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.minify_filter(glium::uniforms::MinifySamplerFilter::LinearMipmapLinear)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Linear),
};
target.draw(vertex_buffer, index_buffer, &self.program_combine, &uniforms, &Default::default()).unwrap();
}
}
/* End of file postprocessing\bloom.rs */
// File: postprocessing\edge.rs
// Path: ..\src\postprocessing\edge.rs
/* Start of file postprocessing\edge.rs */
use glium::glutin::surface::WindowSurface;
use glium::{IndexBuffer, Surface, Texture2d, uniform, VertexBuffer};
use glium::framebuffer::SimpleFrameBuffer;
use glium::texture::DepthTexture2d;
use crate::postprocessing::PostProcessingEffect;
use crate::{AppState, resources, shader};
use crate::geometry::Vertex;
pub struct Edge {
pub threshold: f32,
pub color: [f32; 3],
program: glium::Program,
}
impl Edge {
pub fn new(display: &glium::Display<WindowSurface>, threshold: f32, color: [f32; 3]) -> Self {
let edge_shader = shader::Shader::from_strings(resources::POST_PROCESSING_VERTEX, resources::POST_PROCESSING_EDGE_FRAGMENT);
let program = glium::Program::from_source(display, &edge_shader.get_vertex_shader(), &edge_shader.get_fragment_shader(), None).expect("Failed to compile shader program");
Self {
program,
threshold,
color,
}
}
}
impl PostProcessingEffect for Edge {
fn render(&self, app_state: &AppState, vertex_buffer: &VertexBuffer<Vertex>, index_buffer: &IndexBuffer<u32>, target: &mut SimpleFrameBuffer, source: &Texture2d, depth_source: &DepthTexture2d, _buffer_textures: &Vec<Texture2d>) {
let uniforms = uniform! {
scene: source,
depth: depth_source,
threshold: self.threshold,
screenSize: [source.width() as f32, source.height() as f32],
outlineColor: self.color,
near: app_state.camera.unwrap().near,
far: app_state.camera.unwrap().far,
};
let params = glium::DrawParameters {
blend: glium::Blend::alpha_blending(),
depth: glium::Depth {
test: glium::DepthTest::IfLess,
write: false,
.. Default::default()
},
.. Default::default()
};
target.draw(
&*vertex_buffer,
&*index_buffer,
&self.program,
&uniforms,
¶ms,
).unwrap();
}
}
/* End of file postprocessing\edge.rs */
// File: postprocessing\grayscale.rs
// Path: ..\src\postprocessing\grayscale.rs
/* Start of file postprocessing\grayscale.rs */
use glium::framebuffer::SimpleFrameBuffer;
use glium::glutin::surface::WindowSurface;
use glium::{IndexBuffer, Surface, Texture2d, uniform, VertexBuffer};
use glium::texture::DepthTexture2d;
use crate::geometry::Vertex;
use crate::{AppState, resources, shader};
use crate::postprocessing::PostProcessingEffect;
pub struct GrayScale {
program: glium::Program,
}
impl PostProcessingEffect for GrayScale {
fn render(&self, _app_state: &AppState, vertex_buffer: &VertexBuffer<Vertex>, index_buffer: &IndexBuffer<u32>, target: &mut SimpleFrameBuffer, source: &Texture2d, _depth_source: &DepthTexture2d, _buffer_textures: &Vec<Texture2d>) {
let uniforms = uniform! {
scene: source,
};
target.draw(
&*vertex_buffer,
&*index_buffer,
&self.program,
&uniforms,
&Default::default(),
).unwrap();
}
}
impl GrayScale {
pub fn new(display: &glium::Display<WindowSurface>) -> Self {
let shader = shader::Shader::from_strings(resources::POST_PROCESSING_VERTEX, resources::POST_PROCESSING_GRAYSCALE_FRAGMENT);
let program = glium::Program::from_source(display, &shader.get_vertex_shader(), &shader.get_fragment_shader(), None).expect("Failed to compile shader program");
Self {
program,
}
}
}
/* End of file postprocessing\grayscale.rs */
// File: postprocessing\mod.rs
// Path: ..\src\postprocessing\mod.rs
/* Start of file postprocessing\mod.rs */
use glium::{Display, IndexBuffer, Texture2d, VertexBuffer};
use glium::framebuffer::SimpleFrameBuffer;
use glium::glutin::surface::WindowSurface;
use glium::texture::DepthTexture2d;
use crate::AppState;
use crate::geometry::Vertex;
pub mod grayscale;
pub mod bloom;
pub mod edge;
pub trait PostProcessingEffect {
fn render(&self, _app_state: &AppState, _vertex_buffer: &VertexBuffer<Vertex>, _index_buffer: &IndexBuffer<u32>, _target: &mut SimpleFrameBuffer, _source: &Texture2d, _depth_source: &DepthTexture2d, _buffer_textures: &Vec<Texture2d>) {
println!("PostProcessingEffect::render() not implemented. Please implement this method in your postprocessing struct.");
}
}
pub fn get_screen_vert_rect(display: &Display<WindowSurface>) -> glium::VertexBuffer<Vertex> {
let vertices = vec![
Vertex { position: [-1.0, -1.0, 0.0], texcoord: [0.0, 0.0], color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
Vertex { position: [-1.0, 1.0, 0.0], texcoord: [0.0, 1.0], color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
Vertex { position: [1.0, 1.0, 0.0], texcoord: [1.0, 1.0], color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
Vertex { position: [1.0, -1.0, 0.0], texcoord: [1.0, 0.0], color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0] },
];
glium::VertexBuffer::new(display, &vertices).unwrap()
}
pub fn get_screen_indices_rect(display: &Display<WindowSurface>) -> glium::IndexBuffer<u32> {
let indices: Vec<u32> = vec![0, 1, 2, 0, 2, 3];
glium::IndexBuffer::new(display, glium::index::PrimitiveType::TrianglesList, &indices).unwrap()
}
pub fn get_screen_program(display: &Display<WindowSurface>) -> glium::Program {
let vertex_shader_src = r#"
#version 140
in vec3 position;
in vec2 texcoord;
in vec3 color;
in vec3 normal;
out vec2 TEXCOORD;
void main() {
TEXCOORD = texcoord;
gl_Position = vec4(position, 1.0);
}
"#;
let fragment_shader_src = r#"
#version 140
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
void main() {
color = texture(scene, TEXCOORD);
}
"#;
glium::Program::from_source(display, vertex_shader_src, fragment_shader_src, None).expect("Failed to compile shader program")
}
/* End of file postprocessing\mod.rs */
// File: res\shader\enigma_fragment_shader.glsl
// Path: ..\src\res\shader\enigma_fragment_shader.glsl
/* Start of file res\shader\enigma_fragment_shader.glsl */
#version 140
//uniforms
uniform float time;
uniform mat4 light_position;
uniform mat4 light_direction;
uniform mat4 light_color;
uniform vec4 light_intensity;
uniform int light_amount;
uniform vec3 ambient_light_color;
uniform float ambient_light_intensity;
//attributes
in vec3 world_position;
in vec3 view_direction;
in vec3 vertex_color;
in vec3 vertex_normal;
in vec2 vertex_texcoord;
//material properties
// material uniforms
uniform vec3 mat_color;
uniform sampler2D mat_albedo;
uniform sampler2D mat_normal;
uniform float mat_normal_strength;
uniform sampler2D mat_roughness;
uniform float mat_roughness_strength;
uniform sampler2D mat_metallic;
uniform float mat_metallic_strength;
uniform sampler2D mat_emissive;
uniform float mat_emissive_strength;
uniform float mat_transparency_strength;
uniform sampler2D skybox;
// fragment outputs
out vec4 color;
//constants
const float PI = 3.14159265359;
// Helper Functions for PBR
vec2 getSphereMapUV(vec3 dir) {
float u = atan(dir.z, dir.x) / (2.0 * 3.14159265) + 0.5;
float v = asin(dir.y) / 3.14159265 + 0.5;
return vec2(u, v);
}
float DistributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, 0.000001); // Prevent division by zero
}
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
return ggx1 * ggx2;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
// Main PBR calculation function
// PBR calculations including skybox lighting
vec4 calculatePBRColor(vec3 viewDir) {
// Fetch material properties
vec4 albedo_texel = texture(mat_albedo, vertex_texcoord);
float albedo_alpha = albedo_texel.a;
vec3 albedo = albedo_texel.rgb * mat_color;
vec3 normal = normalize(vertex_normal + (texture(mat_normal, vertex_texcoord).rgb - 0.5) * mat_normal_strength);
float roughness = texture(mat_roughness, vertex_texcoord).r * mat_roughness_strength;
float metallic = texture(mat_metallic, vertex_texcoord).r * mat_metallic_strength;
vec3 emissive = texture(mat_emissive, vertex_texcoord).rgb * mat_emissive_strength;
// Calculate reflectance at normal incidence
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 result = vec3(0.0);
for(int i = 0; i < light_amount; i++) {
// Light calculations for each active light
vec4 lightDirUniform = light_direction[i];
vec3 lightDir = normalize(light_position[i].xyz - world_position);
if(lightDirUniform.w == 1.0) {
lightDir = normalize(lightDirUniform.xyz);
}
vec3 halfDir = normalize(lightDir + viewDir);
float distance = length(light_position[i].xyz - world_position);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = light_color[i].xyz * light_intensity[i] * attenuation;
// Cook-Torrance BRDF
float NDF = DistributionGGX(normal, halfDir, roughness);
float G = GeometrySmith(normal, viewDir, lightDir, roughness);
vec3 F = fresnelSchlick(max(dot(halfDir, viewDir), 0.0), F0);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float NdotL = max(dot(normal, lightDir), 0.0);
// Combine terms
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(normal, viewDir), 0.0) * NdotL + 0.0001;
vec3 specular = numerator / denominator;
// Ambient and diffuse lighting
vec3 ambient = ambient_light_color * ambient_light_intensity * albedo;
vec3 diffuse = kD * albedo / PI;
vec3 reflection = (diffuse + specular) * radiance * NdotL;
// Accumulate result from this light
result += ambient + reflection;
}
// Calculate reflection vector for environmental lighting
vec3 reflectionVector = reflect(-viewDir, normal);
vec2 uv = getSphereMapUV(reflectionVector);
vec3 envReflection = texture(skybox, uv).rgb;
// Apply fresnel effect to the environmental reflection
vec3 fresnelEffect = fresnelSchlick(max(dot(viewDir, normal), 0.0), F0);
vec3 envReflectionWithFresnel = envReflection * fresnelEffect * (1.0 - metallic);
// Combine PBR lighting with environmental reflection
vec3 finalColor = result + emissive + envReflectionWithFresnel;
return vec4(finalColor, albedo_alpha * mat_transparency_strength);
}
void main() {
color = calculatePBRColor(normalize(view_direction));
}
/* End of file res\shader\enigma_fragment_shader.glsl */
// File: res\shader\enigma_fragment_unlit.glsl
// Path: ..\src\res\shader\enigma_fragment_unlit.glsl
/* Start of file res\shader\enigma_fragment_unlit.glsl */
#version 140
//uniforms
uniform float time;
uniform float mat_transparency_strength;
//attributes
in vec3 world_position;
in vec3 view_direction;
in vec3 vertex_color;
in vec3 vertex_normal;
in vec2 vertex_texcoord;
//material properties
// material uniforms
uniform vec3 mat_color;
uniform sampler2D mat_albedo;
// fragment outputs
out vec4 color;
void main() {
vec4 tex = texture(mat_albedo, vertex_texcoord);
color = vec4(tex.rgb * mat_color * vertex_color, tex.a * mat_transparency_strength);
}
/* End of file res\shader\enigma_fragment_unlit.glsl */
// File: res\shader\enigma_vertex_shader.glsl
// Path: ..\src\res\shader\enigma_vertex_shader.glsl
/* Start of file res\shader\enigma_vertex_shader.glsl */
#version 150
//uniforms
uniform float time;
uniform mat4 matrix;
uniform mat4 projection_matrix;
uniform mat4 view_matrix;
uniform mat4 model_matrix;
//attributes
in vec3 position;
in vec2 texcoord;
in vec3 normal;
in vec3 color;
in uint index;
out vec3 world_position;
out vec3 view_direction;
out vec3 vertex_color;
out vec3 vertex_normal;
out vec2 vertex_texcoord;
// material uniforms
uniform vec3 mat_color;
uniform sampler2D mat_albedo;
uniform sampler2D mat_normal;
uniform float mat_normal_strength;
uniform sampler2D mat_roughness;
uniform float mat_roughness_strength;
uniform sampler2D mat_metallic;
uniform float mat_metallic_strength;
void main() {
vec3 pos = position;
float movement = 0.2;
mat4 modelview = view_matrix * model_matrix;
gl_Position = projection_matrix * modelview * vec4(position, 1.0);
world_position = (modelview * vec4(position, 1.0)).xyz;
vertex_normal = transpose(inverse(mat3(modelview))) * normal;
view_direction = (view_matrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz - world_position;
vertex_color = color;
vertex_texcoord = texcoord;
}
/* End of file res\shader\enigma_vertex_shader.glsl */
// File: res\shader\post_processing\post_processing_vert.glsl
// Path: ..\src\res\shader\post_processing\post_processing_vert.glsl
/* Start of file res\shader\post_processing\post_processing_vert.glsl */
#version 140
in vec3 position;
in vec2 texcoord;
in vec3 color;
in vec3 normal;
out vec2 TEXCOORD;
void main() {
TEXCOORD = texcoord;
gl_Position = vec4(position, 1.0);
}
/* End of file res\shader\post_processing\post_processing_vert.glsl */
// File: res\shader\post_processing\bloom\enigma_bloom_blur.glsl
// Path: ..\src\res\shader\post_processing\bloom\enigma_bloom_blur.glsl
/* Start of file res\shader\post_processing\bloom\enigma_bloom_blur.glsl */
#version 450 core
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
uniform bool horizontal;
uniform int iterations;
void main() {
float weight = 1.0/iterations;
vec2 tex_offset = 1.0 / textureSize(scene, 0); // gets size of single texel
vec3 result = texture(scene, TEXCOORD).rgb * weight; // current fragment's contribution
if(horizontal) {
for(int i = 1; i < iterations; ++i) {
result += texture(scene, TEXCOORD + vec2(tex_offset.x * i, 0.0)).rgb * weight;
result += texture(scene, TEXCOORD - vec2(tex_offset.x * i, 0.0)).rgb * weight;
}
} else {
for(int i = 1; i < iterations; ++i) {
result += texture(scene, TEXCOORD + vec2(0.0, tex_offset.y * i)).rgb * weight;
result += texture(scene, TEXCOORD - vec2(0.0, tex_offset.y * i)).rgb * weight;
}
}
color = vec4(result, 1.0);
}
/* End of file res\shader\post_processing\bloom\enigma_bloom_blur.glsl */
// File: res\shader\post_processing\bloom\enigma_bloom_combine.glsl
// Path: ..\src\res\shader\post_processing\bloom\enigma_bloom_combine.glsl
/* Start of file res\shader\post_processing\bloom\enigma_bloom_combine.glsl */
#version 450 core
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
uniform sampler2D bloomBlur;
void main() {
vec4 sceneColor = texture(scene, TEXCOORD);
vec4 bloomColor = texture(bloomBlur, TEXCOORD);
// Combine the scene with the bloom effect
color = sceneColor + bloomColor;
}
/* End of file res\shader\post_processing\bloom\enigma_bloom_combine.glsl */
// File: res\shader\post_processing\bloom\enigma_bloom_extract.glsl
// Path: ..\src\res\shader\post_processing\bloom\enigma_bloom_extract.glsl
/* Start of file res\shader\post_processing\bloom\enigma_bloom_extract.glsl */
#version 450 core
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
uniform float threshold;
void main() {
vec4 tex = texture(scene, TEXCOORD);
float brightness = dot(tex.rgb, vec3(0.2126, 0.7152, 0.0722));
if(brightness > threshold) { // Threshold for brightness
color = tex;
} else {
color = vec4(0.0);
}
}
/* End of file res\shader\post_processing\bloom\enigma_bloom_extract.glsl */
// File: res\shader\post_processing\edge\enigma_edge_detection.glsl
// Path: ..\src\res\shader\post_processing\edge\enigma_edge_detection.glsl
/* Start of file res\shader\post_processing\edge\enigma_edge_detection.glsl */
#version 450 core
#define MIN_DEPTH 0.995
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
uniform sampler2D depth;
uniform float threshold;
uniform vec2 screenSize;
uniform vec3 outlineColor;
uniform float near; // Camera's near plane
uniform float far; // Camera's far plane
float linearizeDepth(float depth) {
float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main() {
float depthValue = texture(depth, TEXCOORD).r;
float depthLeft = texture(depth, TEXCOORD + vec2(-1.0 / screenSize.x, 0)).r;
float depthRight = texture(depth, TEXCOORD + vec2(1.0 / screenSize.x, 0)).r;
float depthUp = texture(depth, TEXCOORD + vec2(0, 1.0 / screenSize.y)).r;
float depthDown = texture(depth, TEXCOORD + vec2(0, -1.0 / screenSize.y)).r;
float linearDepth = linearizeDepth(depthValue);
float linearDepthLeft = linearizeDepth(depthLeft);
float linearDepthRight = linearizeDepth(depthRight);
float linearDepthUp = linearizeDepth(depthUp);
float linearDepthDown = linearizeDepth(depthDown);
// Scale the depth value for visualization
float scaledDepth = (linearDepth - near) / (far - near);
float scaledDepthLeft = (linearDepthLeft - near) / (far - near);
float scaledDepthRight = (linearDepthRight - near) / (far - near);
float scaledDepthUp = (linearDepthUp - near) / (far - near);
float scaledDepthDown = (linearDepthDown - near) / (far - near);
float edge = 0.0;
if (abs(scaledDepth - scaledDepthLeft) > threshold || abs(scaledDepth - scaledDepthRight) > threshold ||
abs(scaledDepth - scaledDepthUp) > threshold || abs(scaledDepth - scaledDepthDown) > threshold) {
edge = 1.0;
}
vec4 c = mix(texture(scene, TEXCOORD), vec4(outlineColor, 1.0), edge);
color = c;
}
/* End of file res\shader\post_processing\edge\enigma_edge_detection.glsl */
// File: res\shader\post_processing\grayscale\enigma_grayscale.glsl
// Path: ..\src\res\shader\post_processing\grayscale\enigma_grayscale.glsl
/* Start of file res\shader\post_processing\grayscale\enigma_grayscale.glsl */
#version 140
in vec2 TEXCOORD;
out vec4 color;
uniform sampler2D scene;
void main() {
vec4 c = texture(scene, TEXCOORD);
float grayscale = dot(c.rgb, vec3(0.299, 0.587, 0.114));
color = vec4(grayscale, grayscale, grayscale, c.a);
}
/* End of file res\shader\post_processing\grayscale\enigma_grayscale.glsl */