phreedee wendewing

This commit is contained in:
sfja 2026-04-04 03:41:31 +02:00
parent ff878bf068
commit d191fda839
12 changed files with 433 additions and 56 deletions

31
simul/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,31 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug executable 'simul'",
"type": "lldb",
"request": "launch",
"cargo": {
"args": [
"run",
"--bin=simul"
]
},
"args": []
},
{
"name": "Debug unit tests in executable 'simul'",
"type": "lldb",
"request": "launch",
"cargo": {
"args": [
"test",
"--bin=simul"
]
}
}
]
}

91
simul/Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.0"
@ -20,6 +26,43 @@ version = "0.2.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "obj-rs"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8247fd8f66340ad9c969f96e63a80a2f74b04e5403d92584b75e046006f4c7e1"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "sdl3" name = "sdl3"
version = "0.17.3" version = "0.17.3"
@ -58,9 +101,57 @@ dependencies = [
"sdl3-sys", "sdl3-sys",
] ]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "simul" name = "simul"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"obj-rs",
"sdl3", "sdl3",
] ]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"

View File

@ -4,4 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
obj-rs = "0.7.4"
sdl3 = { version = "0.17.3", features = ["ttf", "image"] } sdl3 = { version = "0.17.3", features = ["ttf", "image"] }

12
simul/assets/cube.mtl Normal file
View File

@ -0,0 +1,12 @@
# Blender 5.1.0 MTL File: 'None'
# www.blender.org
newmtl Material
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2

46
simul/assets/cube.obj Normal file
View File

@ -0,0 +1,46 @@
# Blender 5.1.0
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.875000 0.500000
vt 0.625000 0.750000
vt 0.625000 0.500000
vt 0.375000 1.000000
vt 0.375000 0.750000
vt 0.625000 0.000000
vt 0.375000 0.250000
vt 0.375000 0.000000
vt 0.375000 0.500000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.625000 0.250000
vt 0.875000 0.750000
vt 0.625000 1.000000
s 0
usemtl Material
f 5/1/1 3/2/1 1/3/1
f 3/2/2 8/4/2 4/5/2
f 7/6/3 6/7/3 8/8/3
f 2/9/4 8/10/4 6/11/4
f 1/3/5 4/5/5 2/9/5
f 5/12/6 2/9/6 6/7/6
f 5/1/1 7/13/1 3/2/1
f 3/2/2 7/14/2 8/4/2
f 7/6/3 5/12/3 6/7/3
f 2/9/4 4/5/4 8/10/4
f 1/3/5 3/2/5 4/5/5
f 5/12/6 1/3/6 2/9/6

View File

@ -2,43 +2,69 @@ use std::ops::Mul;
use crate::v3::V3; use crate::v3::V3;
pub struct M3x3([f64; 9]); #[derive(Clone, Copy, Debug)]
pub struct M3x3([[f64; 3]; 3]);
impl M3x3 { impl M3x3 {
pub fn init(v: f64) -> Self {
Self([[v; 3]; 3])
}
pub fn from_v3_rot(rot: V3) -> Self {
M3x3::new_rotate_x(rot.0) * M3x3::new_rotate_y(rot.1) * M3x3::new_rotate_z(rot.2)
}
#[rustfmt::skip] #[rustfmt::skip]
pub fn new_rotate_x(angle: f64) -> Self { pub fn new_rotate_x(angle: f64) -> Self {
Self([ Self([
1.0, 0.0, 0.0, [1.0, 0.0, 0.0],
0.0, f64::cos(angle), -f64::sin(angle), [0.0, f64::cos(angle), -f64::sin(angle)],
0.0, f64::sin(angle), f64::cos(angle), [0.0, f64::sin(angle), f64::cos(angle)],
]) ])
} }
#[rustfmt::skip] #[rustfmt::skip]
pub fn new_rotate_y(angle: f64) -> Self { pub fn new_rotate_y(angle: f64) -> Self {
Self([ Self([
f64::cos(angle), 0.0, f64::sin(angle), [ f64::cos(angle), 0.0, f64::sin(angle)],
0.0, 1.0, 0.0, [ 0.0, 1.0, 0.0],
-f64::sin(angle), 0.0, f64::cos(angle), [-f64::sin(angle), 0.0, f64::cos(angle)],
]) ])
} }
#[rustfmt::skip] #[rustfmt::skip]
pub fn new_rotate_z(angle: f64) -> Self { pub fn new_rotate_z(angle: f64) -> Self {
Self([ Self([
f64::cos(angle), -f64::sin(angle), 0.0, [f64::cos(angle), -f64::sin(angle), 0.0],
f64::sin(angle), f64::cos(angle), 0.0, [f64::sin(angle), f64::cos(angle), 0.0],
0.0, 0.0, 1.0, [ 0.0, 0.0, 1.0],
]) ])
} }
} }
impl Mul for M3x3 {
type Output = M3x3;
fn mul(self, rhs: M3x3) -> Self::Output {
let mut output = M3x3([[0.0; 3]; 3]);
for i in 0..3 {
for j in 0..3 {
let v = &mut output.0[i][j];
for k in 0..3 {
*v += self.0[i][k] * rhs.0[k][j];
}
}
}
output
}
}
impl Mul<V3> for M3x3 { impl Mul<V3> for M3x3 {
type Output = V3; type Output = V3;
fn mul(self, rhs: V3) -> Self::Output { fn mul(self, rhs: V3) -> Self::Output {
let V3(x, y, z) = rhs; let V3(x, y, z) = rhs;
let [xx, xy, xz, yx, yy, yz, zx, zy, zz] = self.0; let [[xx, xy, xz], [yx, yy, yz], [zx, zy, zz]] = self.0;
V3( V3(
x * xx + y * xy + z * xz, x * xx + y * xy + z * xz,
x * yx + y * yy + z * yz, x * yx + y * yy + z * yz,

View File

@ -1,7 +1,12 @@
#![allow(unused)] #![allow(unused)]
use std::{collections::HashMap, f64::consts::PI, fs::File, io::BufReader};
use obj::{Obj, load_obj};
use crate::{ use crate::{
scene::Scene, m3x3::M3x3,
scene::{Model, Scene},
tri2::Tri2, tri2::Tri2,
tri3::Tri3, tri3::Tri3,
v2::V2, v2::V2,
@ -17,23 +22,89 @@ mod v2;
mod v3; mod v3;
mod window; mod window;
pub struct App {} struct AssetStore {
assets: HashMap<String, Obj>,
}
impl AssetStore {
fn new() -> Self {
Self {
assets: HashMap::new(),
}
}
fn load<S: Into<String>>(&mut self, path: S) -> Obj {
let path = path.into();
if self.assets.contains_key(&path) {
return self.assets.get(&path).unwrap().clone();
}
let asset: Obj = load_obj(BufReader::new(File::open(&path).unwrap())).unwrap();
self.assets.insert(path, asset.clone());
asset
}
fn get<S: Into<String>>(&self, path: S) -> Option<Obj> {
let path = path.into();
self.assets.get(&path).cloned()
}
}
pub struct App {
assets: AssetStore,
rot: V3,
}
impl App {
pub fn new() -> Self {
Self {
assets: AssetStore::new(),
rot: V3::init(0.0),
}
}
}
impl<R: Renderer> window::App<R> for App { impl<R: Renderer> window::App<R> for App {
fn update(&mut self, delta_time: std::time::Duration) {} fn update(&mut self, delta_time: std::time::Duration) {
self.rot.0 += PI * 2.0 * delta_time.as_secs_f64() * 0.1;
self.rot.1 += PI * 2.0 * delta_time.as_secs_f64() * 0.1;
}
fn render(&self, r: &mut R) { fn render(&self, r: &mut R) {
r.draw_line(V2(0.0, 0.0), V2(0.6, 0.2), Color::RED);
let mut scene = Scene::new(); let mut scene = Scene::new();
scene.draw_triangle( // let mut model1 = Model::new();
Tri3(V3(0.0, 0.0, 0.0), V3(0.3, 0.3, 0.0), V3(0.3, 0.0, 0.0)), // model1
Color::GREEN, // .add_tri(
Color::BLACK, // Tri3(V3(0.0, 0.0, 0.0), V3(0.3, 0.3, 0.0), V3(0.3, 0.0, 0.0)),
// Color::RGB(200, 200, 200),
// )
// .rotate_by_m3x3(M3x3::from_v3_rot(self.rot))
// .translate(V3(1.0, 0.0, 2.0))
// .scale(V3(1.0, 1.0, 1.0));
//
// scene.draw_model(model1);
let mut model2 = Model::new();
let cube_obj = self.assets.get("assets/cube.obj").unwrap();
model2
.add_obj(&cube_obj, Color::RGB(165, 125, 165))
.rotate_by_m3x3(M3x3::from_v3_rot(self.rot))
.scale(V3(0.2, 0.2, 0.2))
.translate(V3(0.0, 0.0, 1.0));
scene.draw_model(model2);
// scene.draw_obj(&cube_obj, V3(0.1, 0.1, 0.1), Color::RGB(125, 125, 125));
scene.render(
r,
V3(0.0, 0.0, 0.0),
M3x3::from_v3_rot(V3(0.0, 0.0, 0.0)),
V3(0.0, 0.0, 1.0),
); );
scene.render(r, V3(0.0, 0.0, -1.0)); // r.draw_line(V2(0.0, 0.0), V2(0.6, 0.2), Color::RED);
} }
fn event(&mut self, event: Event) {} fn event(&mut self, event: Event) {}
@ -41,7 +112,8 @@ impl<R: Renderer> window::App<R> for App {
fn main() { fn main() {
let mut window = Window::new(); let mut window = Window::new();
let mut app = App {}; let mut app = App::new();
app.assets.load("assets/cube.obj");
window.run(&mut app); window.run(&mut app);
} }

View File

@ -1,4 +1,7 @@
use obj::Obj;
use crate::{ use crate::{
m3x3::M3x3,
tri2::Tri2, tri2::Tri2,
tri3::Tri3, tri3::Tri3,
v3::V3, v3::V3,
@ -6,7 +9,7 @@ use crate::{
}; };
pub struct Scene { pub struct Scene {
tris: Vec<(Tri3, Color, Color)>, tris: Vec<(Tri3, V3, Color)>,
} }
impl Scene { impl Scene {
@ -14,7 +17,13 @@ impl Scene {
Self { tris: Vec::new() } Self { tris: Vec::new() }
} }
pub fn render(&self, r: &mut impl Renderer, camera_pos: V3) { pub fn render(
&self,
r: &mut impl Renderer,
camera_pos: V3,
camera_rot: M3x3,
screen_rel_pos: V3,
) {
let mut indices_with_scores = self let mut indices_with_scores = self
.tris .tris
.iter() .iter()
@ -31,29 +40,102 @@ impl Scene {
indices_with_scores.sort_by(|a, b| b.1.total_cmp(&a.1)); indices_with_scores.sort_by(|a, b| b.1.total_cmp(&a.1));
for (i, _) in indices_with_scores { for (i, _) in indices_with_scores {
let (tri3, outline_color, fill_color) = &self.tris[i]; let (tri3, normal, color) = &self.tris[i];
// if !(tri3.0.2 >= -1.0 && tri3.1.2 >= -1.0 && tri3.2.2 >= -1.0) { // if !(tri3.0.2 >= -1.0 && tri3.1.2 >= -1.0 && tri3.2.2 >= -1.0) {
// continue; // continue;
// } // }
// if tri3.normal().dot(tri3.0 - camera_pos) >= 0.0 { // if normal.dot(camera_pos - tri3.0) < 0.0 {
// continue; // continue;
// } // }
//
println!("{tri3:?}");
let tri2 = tri3.project_2d(camera_pos); let tri2 = tri3.project_2d(camera_pos, camera_rot, screen_rel_pos);
println!("{tri2:?}"); r.draw_triangles(&[tri2], *color);
r.draw_line(tri2.0, tri2.1, Color::BLACK);
r.draw_triangles(&[tri2], *fill_color); r.draw_line(tri2.1, tri2.2, Color::BLACK);
r.draw_line(tri2.0, tri2.1, *outline_color); r.draw_line(tri2.2, tri2.0, Color::BLACK);
r.draw_line(tri2.0, tri2.2, *outline_color);
} }
} }
pub fn draw_triangle(&mut self, tri: Tri3, outline_color: Color, fill_color: Color) { pub fn draw_model(&mut self, model: Model) {
self.tris.push((tri, outline_color, fill_color)); self.tris.extend(model.tris());
}
pub fn draw_triangle(&mut self, tri: Tri3, color: Color) {
self.tris.push((tri, tri.normal(), color));
}
}
pub struct Model {
tris: Vec<(Tri3, V3, Color)>,
}
impl Model {
pub fn new() -> Self {
Self { tris: Vec::new() }
}
pub fn tris(&self) -> impl Iterator<Item = (Tri3, V3, Color)> {
self.tris.iter().copied()
}
pub fn translate(&mut self, offset: V3) -> &mut Self {
for tri in &mut self.tris {
tri.0 = tri.0.translate(offset);
}
self
}
pub fn rotate_by_m3x3(&mut self, rot: M3x3) -> &mut Self {
for tri in &mut self.tris {
tri.0 = tri.0.rotate_by_m3x3(rot);
}
self
}
pub fn scale(&mut self, scale: V3) -> &mut Self {
for tri in &mut self.tris {
tri.0 = tri.0.scale(scale);
}
self
}
pub fn add_tri(&mut self, tri: Tri3, color: Color) -> &mut Self {
self.tris.push((tri, tri.normal(), color));
self
}
pub fn add_obj(&mut self, obj: &Obj, color: Color) -> &mut Self {
let count = obj.indices.len() / 3;
for i in 0..count {
let v0 = obj.vertices[obj.indices[i * 3] as usize];
let v1 = obj.vertices[obj.indices[i * 3 + 1] as usize];
let v2 = obj.vertices[obj.indices[i * 3 + 2] as usize];
self.tris.push((
Tri3(
V3(
v0.position[0] as _,
v0.position[1] as _,
v0.position[2] as _,
),
V3(
v1.position[0] as _,
v1.position[1] as _,
v1.position[2] as _,
),
V3(
v2.position[0] as _,
v2.position[1] as _,
v2.position[2] as _,
),
),
V3(v0.normal[0] as _, v0.normal[1] as _, v0.normal[2] as _),
color,
));
}
self
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{tri2::Tri2, v3::V3}; use crate::{m3x3::M3x3, tri2::Tri2, v3::V3};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Tri3(pub V3, pub V3, pub V3); pub struct Tri3(pub V3, pub V3, pub V3);
@ -20,20 +20,27 @@ impl Tri3 {
) )
} }
pub fn rotate(&self, rot: V3) -> Self { pub fn rotate_by_v3(&self, rot: V3) -> Self {
self.map(|v| v.rotate(rot)) self.map(|v| v.rotate_by_v3(rot))
}
pub fn rotate_by_m3x3(&self, rot: M3x3) -> Self {
self.map(|v| v.rotate_by_m3x3(rot))
} }
pub fn project_2d(&self, camera_pos: V3) -> Tri2 { pub fn project_2d(&self, camera_pos: V3, camera_rot: M3x3, screen_rel_pos: V3) -> Tri2 {
Tri2( Tri2(
self.0.project_2d(camera_pos), self.0.project_2d(camera_pos, camera_rot, screen_rel_pos),
self.1.project_2d(camera_pos), self.1.project_2d(camera_pos, camera_rot, screen_rel_pos),
self.2.project_2d(camera_pos), self.2.project_2d(camera_pos, camera_rot, screen_rel_pos),
) )
} }
pub fn translate(&self, offset: V3) -> Self { pub fn translate(&self, offset: V3) -> Self {
self.map(|v| v + offset) self.map(|v| v.translate(offset))
}
pub fn scale(&self, scale: V3) -> Self {
self.map(|v| v.scale(scale))
} }
pub fn points(&self) -> [V3; 3] { pub fn points(&self) -> [V3; 3] {

View File

@ -19,8 +19,7 @@ impl V2 {
pub fn reduce<F: Fn(f64, f64) -> f64>(&self, initial: f64, func: F) -> f64 { pub fn reduce<F: Fn(f64, f64) -> f64>(&self, initial: f64, func: F) -> f64 {
let acc = initial; let acc = initial;
let acc = func(acc, self.0); let acc = func(acc, self.0);
let acc = func(acc, self.1); func(acc, self.1)
acc
} }
pub fn normal(&self) -> V2 { pub fn normal(&self) -> V2 {

View File

@ -26,25 +26,31 @@ impl V3 {
let acc = initial; let acc = initial;
let acc = func(acc, self.0); let acc = func(acc, self.0);
let acc = func(acc, self.1); let acc = func(acc, self.1);
let acc = func(acc, self.0); func(acc, self.0)
acc
} }
pub fn cross(&self, rhs: Self) -> Self { pub fn cross(&self, rhs: V3) -> Self {
let V3(ax, ay, az) = self; let V3(ax, ay, az) = self;
let V3(bx, by, bz) = rhs; let V3(bx, by, bz) = rhs;
V3(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx) V3(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx)
} }
pub fn rotate(&self, rot: Self) -> Self { pub fn rotate_by_v3(&self, rot: V3) -> Self {
M3x3::new_rotate_z(rot.2) M3x3::new_rotate_z(rot.2)
* (M3x3::new_rotate_y(rot.1) * (M3x3::new_rotate_x(rot.0) * *self)) * (M3x3::new_rotate_y(rot.1) * (M3x3::new_rotate_x(rot.0) * *self))
} }
pub fn project_2d(&self, camera_pos: V3) -> V2 { pub fn rotate_by_m3x3(&self, rot: M3x3) -> Self {
rot * *self
}
/// See https://en.wikipedia.org/wiki/3D_projection#Mathematical_formula
/// for details on the implementation.
pub fn project_2d(&self, camera_pos: V3, camera_rot: M3x3, screen_rel_pos: V3) -> V2 {
let a = *self;
let c = camera_pos; let c = camera_pos;
let d = *self - c; let d = (a - c).rotate_by_m3x3(camera_rot);
let e = V3(0.0, 0.0, 0.0) - c; let e = screen_rel_pos - c;
V2(e.2 / d.2 * d.0 + e.0, e.2 / d.2 * d.1 + e.1) V2(e.2 / d.2 * d.0 + e.0, e.2 / d.2 * d.1 + e.1)
} }
@ -52,8 +58,12 @@ impl V3 {
self.zip(rhs, |a, b| a * b).reduce(0.0, |acc, v| acc + v) self.zip(rhs, |a, b| a * b).reduce(0.0, |acc, v| acc + v)
} }
pub fn translate(&self, trans: V3) -> V3 { pub fn translate(&self, offset: Self) -> Self {
*self + trans *self + offset
}
pub fn scale(&self, scale: Self) -> Self {
self.zip(scale, |a, b| a * b)
} }
pub fn len(&self) -> f64 { pub fn len(&self) -> f64 {

View File

@ -88,7 +88,7 @@ impl Window {
time_before = time_now; time_before = time_now;
app.update(delta_time); app.update(delta_time);
self.canvas.set_draw_color(Color::RGB(20, 20, 20)); self.canvas.set_draw_color(Color::RGB(30, 30, 30));
self.canvas.clear(); self.canvas.clear();
app.render(self); app.render(self);
self.canvas.present(); self.canvas.present();