Assignment 3 starting point

This commit is contained in:
ScorpionX90
2025-09-29 09:46:11 +02:00
parent 7d074b0ff5
commit be0d515d9e
7 changed files with 372837 additions and 13 deletions

View File

@@ -88,16 +88,48 @@ otherwise we end up with a shear instead. Because we have to change these values
together, it is impossible to observe rotational transformations from only
changing one value at a time, starting from the identity matrix.
## Task 4
### b)
Keybinds for the camera controls follow the default listed specifications. They are listed below:
**Camera rotation:**
Tilt : `UP and DOWN`
Yaw : `LEFT and RIGHT`
**Translation:**
Local Forward / Backward: `W and S`
Local Left / Right: `A and D`
Global up / down: `SPACE and LShift`
## Bonus task a)
Implemented in code. Note that we have only made translation relative to the yaw of camera, and not the tilt. This is done as a preference from our side.
Implemented in code. Note that we have made our translation relative to only the yaw of camera. This is simply a choice of preference from our side.
We could have implemented translation as a function of both yaw and tilt, in which case our forward vector would be:
$$
forward = \begin{bmatrix}
-cos(tilt)*sin(yaw)\\
sin(tilt)\\
cos(tilt)*cos(yaw)
\end{bmatrix}
$$
The right vector would also have to be changed to be a rotation in 3D space, as opposed to the current XZ plane rotation.
Finally the ```space``` and ```LShift``` controls would have to be mapped to our local y-vector as opposed to a global one.
## Bonus task b)
Applying some perspective to our scene, we see that the smooth interolation qualifier yields a color interpolation which is corrected in terms of perspective. In the below image you can clearly see how the colors are shifted between the smooth and noperspective interpolation models:
![Smooth interpolation](./images/smooth.png)
Smooth interpolation
![noperspective interpolation](./images/noperspective.png)
noperspective interpolation
![Noperspective interpolation](./images/noperspective.png)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -208,15 +208,15 @@ fn main() {
];
let colors = vec![
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0, 0.4,
1.0, 0.0, 0.0, 0.4,
0.0, 0.0, 1.0, 0.4,
1.0, 0.0, 0.0, 0.4,
0.0, 1.0, 0.0, 0.4,
0.0, 0.0, 1.0, 0.4,
1.0, 0.0, 0.0, 0.4,
1.0, 0.0, 0.0, 0.4,
1.0, 0.0, 0.0, 0.4,
];
let vao_id = unsafe { create_vao(&vertices, &indices, &colors) };

124
gloom-rs/src/mesh.rs Normal file
View File

@@ -0,0 +1,124 @@
use tobj;
// internal helper
fn generate_color_vec(color: [f32; 4], num: usize) -> Vec<f32> {
color.iter().cloned().cycle().take(num*4).collect()
}
// Mesh
pub struct Mesh {
pub vertices : Vec<f32>,
pub normals : Vec<f32>,
pub colors : Vec<f32>,
pub indices : Vec<u32>,
pub index_count : i32,
}
impl Mesh {
pub fn from(mesh: tobj::Mesh, color: [f32; 4]) -> Self {
let num_verts = mesh.positions.len() / 3;
let index_count = mesh.indices.len() as i32;
Mesh {
vertices: mesh.positions,
normals: mesh.normals,
indices: mesh.indices,
colors: generate_color_vec(color, num_verts),
index_count,
}
}
}
// Lunar terrain
pub struct Terrain;
impl Terrain {
pub fn load(path: &str) -> Mesh {
println!("Loading terrain model...");
let before = std::time::Instant::now();
let (models, _materials)
= tobj::load_obj(path,
&tobj::LoadOptions{
triangulate: true,
single_index: true,
..Default::default()
}
).expect("Failed to load terrain model");
let after = std::time::Instant::now();
println!("Done in {:.3}ms.", after.duration_since(before).as_micros() as f32 / 1e3);
if models.len() > 1 || models.len() == 0 {
panic!("Please use a model with a single mesh!")
// You could try merging the vertices and indices
// of the separate meshes into a single mesh.
// I'll leave that as an optional exercise. ;)
}
let terrain = models[0].to_owned();
println!("Loaded {} with {} points and {} triangles.",
terrain.name,
terrain.mesh.positions.len() /3,
terrain.mesh.indices.len() / 3,
);
Mesh::from(terrain.mesh, [1.0, 1.0, 1.0, 1.0])
}
}
// Helicopter
pub struct Helicopter {
pub body : Mesh,
pub door : Mesh,
pub main_rotor : Mesh,
pub tail_rotor : Mesh,
}
// You can use square brackets to access the components of the helicopter, if you want to use loops!
use std::ops::Index;
impl Index<usize> for Helicopter {
type Output = Mesh;
fn index<'a>(&'a self, i: usize) -> &'a Mesh {
match i {
0 => &self.body,
1 => &self.main_rotor,
2 => &self.tail_rotor,
3 => &self.door,
_ => panic!("Invalid index, try [0,3]"),
}
}
}
impl Helicopter {
pub fn load(path: &str) -> Self {
println!("Loading helicopter model...");
let before = std::time::Instant::now();
let (models, _materials)
= tobj::load_obj(path,
&tobj::LoadOptions{
triangulate: true,
single_index: true,
..Default::default()
}
).expect("Failed to load helicopter model");
let after = std::time::Instant::now();
println!("Done in {:.3}ms!", after.duration_since(before).as_micros() as f32 / 1e3);
for model in &models {
println!("Loaded {} with {} points and {} triangles.", model.name, model.mesh.positions.len() / 3, model.mesh.indices.len() / 3);
}
let body_model = models.iter().find(|m| m.name == "Body_body").expect("Incorrect model file!").to_owned();
let door_model = models.iter().find(|m| m.name == "Door_door").expect("Incorrect model file!").to_owned();
let main_rotor_model = models.iter().find(|m| m.name == "Main_Rotor_main_rotor").expect("Incorrect model file!").to_owned();
let tail_rotor_model = models.iter().find(|m| m.name == "Tail_Rotor_tail_rotor").expect("Incorrect model file!").to_owned();
Helicopter {
body: Mesh::from(body_model.mesh, [0.3, 0.3, 0.3, 1.0]),
door: Mesh::from(door_model.mesh, [0.1, 0.1, 0.3, 1.0]),
main_rotor: Mesh::from(main_rotor_model.mesh, [0.3, 0.1, 0.1, 1.0]),
tail_rotor: Mesh::from(tail_rotor_model.mesh, [0.1, 0.3, 0.1, 1.0]),
}
}
}

118
gloom-rs/src/scene_graph.rs Normal file
View File

@@ -0,0 +1,118 @@
extern crate nalgebra_glm as glm;
use std::mem::ManuallyDrop;
use std::pin::Pin;
// Used to create an unholy abomination upon which you should not cast your gaze. This ended up
// being a necessity due to wanting to keep the code written by students as "straight forward" as
// possible. It is very very double plus ungood Rust, and intentionally leaks memory like a sieve.
// But it works, and you're more than welcome to pretend it doesn't exist! In case you're curious
// about how it works: It allocates memory on the heap (Box), promises to prevent it from being
// moved or deallocated until dropped (Pin) and finally prevents the compiler from dropping it
// automatically at all (ManuallyDrop).
// ...
// If that sounds like a janky solution, it's because it is!
// Prettier, Rustier and better solutions were tried numerous times, but were all found wanting of
// having what I arbitrarily decided to be the required level of "simplicity of use".
pub type Node = ManuallyDrop<Pin<Box<SceneNode>>>;
pub struct SceneNode {
pub position : glm::Vec3, // Where I should be in relation to my parent
pub rotation : glm::Vec3, // How I should be rotated, around the X, the Y and the Z axes
pub scale : glm::Vec3, // How I should be scaled
pub reference_point : glm::Vec3, // The point I shall rotate and scale about
pub vao_id : u32, // What I should draw
pub index_count : i32, // How much of it there is to draw
pub children: Vec<*mut SceneNode>, // Those I command
}
impl SceneNode {
pub fn new() -> Node {
ManuallyDrop::new(Pin::new(Box::new(SceneNode {
position : glm::zero(),
rotation : glm::zero(),
scale : glm::vec3(1.0, 1.0, 1.0),
reference_point : glm::zero(),
vao_id : 0,
index_count : -1,
children : vec![],
})))
}
pub fn from_vao(vao_id: u32, index_count: i32) -> Node {
ManuallyDrop::new(Pin::new(Box::new(SceneNode {
position : glm::zero(),
rotation : glm::zero(),
scale : glm::vec3(1.0, 1.0, 1.0),
reference_point : glm::zero(),
vao_id,
index_count,
children: vec![],
})))
}
pub fn add_child(&mut self, child: &SceneNode) {
self.children.push(child as *const SceneNode as *mut SceneNode)
}
#[allow(dead_code)]
pub fn get_child(& mut self, index: usize) -> & mut SceneNode {
unsafe {
&mut (*self.children[index])
}
}
#[allow(dead_code)]
pub fn n_children(&self) -> usize {
self.children.len()
}
#[allow(dead_code)]
pub fn print(&self) {
println!(
"SceneNode {{
VAO: {}
Indices: {}
Children: {}
Position: [{:.2}, {:.2}, {:.2}]
Rotation: [{:.2}, {:.2}, {:.2}]
Reference: [{:.2}, {:.2}, {:.2}]
}}",
self.vao_id,
self.index_count,
self.children.len(),
self.position.x,
self.position.y,
self.position.z,
self.rotation.x,
self.rotation.y,
self.rotation.z,
self.reference_point.x,
self.reference_point.y,
self.reference_point.z,
);
}
}
// You can also use square brackets to access the children of a SceneNode
use std::ops::{Index, IndexMut};
impl Index<usize> for SceneNode {
type Output = SceneNode;
fn index(&self, index: usize) -> &SceneNode {
unsafe {
& *(self.children[index] as *const SceneNode)
}
}
}
impl IndexMut<usize> for SceneNode {
fn index_mut(&mut self, index: usize) -> &mut SceneNode {
unsafe {
&mut (*self.children[index])
}
}
}

36
gloom-rs/src/toolbox.rs Normal file
View File

@@ -0,0 +1,36 @@
extern crate nalgebra_glm as glm;
use std::f64::consts::PI;
pub struct Heading {
pub x : f32,
pub z : f32,
pub roll : f32, // measured in radians
pub pitch : f32, // measured in radians
pub yaw : f32, // measured in radians
}
pub fn simple_heading_animation(time: f32) -> Heading {
let t = time as f64;
let step = 0.05f64;
let path_size = 15f64;
let circuit_speed = 0.8f64;
let xpos = path_size * (2.0 * (t+ 0.0) * circuit_speed).sin();
let xpos_next = path_size * (2.0 * (t+step) * circuit_speed).sin();
let zpos = 3.0 * path_size * ((t+ 0.0) * circuit_speed).cos();
let zpos_next = 3.0 * path_size * ((t+step) * circuit_speed).cos();
let delta_pos = glm::vec2(xpos_next - xpos, zpos_next - zpos);
let roll = (t * circuit_speed).cos() * 0.5;
let pitch = -0.175 * glm::length(&delta_pos);
let yaw = PI + delta_pos.x.atan2(delta_pos.y);
Heading {
x : xpos as f32,
z : zpos as f32,
roll : roll as f32,
pitch : pitch as f32,
yaw : yaw as f32,
}
}