├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── etc ├── 3d_rotation_axis_bivector.png ├── demo.gif ├── domino_track.gif ├── header.png ├── knock_over_tesseracts.gif ├── miegakure_4d_toys.png ├── quaternion_rotor_equivalence.png ├── rendering_architecture.png ├── rolling_600_cell.gif ├── sphere_render.gif ├── tetrahedron_plane_intersection.png └── update_formulae.png ├── rustfmt.toml └── src ├── alg ├── bivec4.rs ├── mod.rs ├── quadvec4.rs ├── rotor4.rs ├── trivec4.rs └── vec4.rs ├── context ├── graphics │ ├── context.rs │ ├── light.rs │ ├── mod.rs │ ├── shaders │ │ ├── shader.frag │ │ ├── shader.vert │ │ ├── shadow.frag │ │ ├── shadow.vert │ │ └── slice.comp │ ├── shadow_pipeline.rs │ ├── slice_pipeline.rs │ ├── slice_plane.rs │ ├── transform4.rs │ ├── triangle_list_pipeline.rs │ ├── vertex3.rs │ ├── vertex4.rs │ └── view_projection.rs └── mod.rs ├── main.rs ├── mesh ├── clip.rs ├── mod.rs ├── tetrahedra.rs └── todd_coxeter.rs ├── mesh4.rs ├── physics ├── body.rs ├── collider.rs ├── collision.rs ├── gjk.rs └── mod.rs ├── shapes.rs ├── util.rs └── world.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | /target 3 | **/*.rs.bk 4 | 5 | imgui.ini 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hypervis" 3 | version = "0.1.0" 4 | authors = ["Tyler Zhang "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | futures = "0.3" 9 | winit = "0.22" 10 | wgpu = "0.5" 11 | bytemuck = "1.2" 12 | anyhow = "1.0" 13 | glsl-to-spirv = "0.1" 14 | cgmath = "0.17" 15 | mint = "0.5" 16 | hsl = "0.1" 17 | rand = "0.7" 18 | lru = "0.4" 19 | slotmap = "0.4" 20 | smallvec = "1.2" 21 | imgui = "0.4" 22 | imgui-winit-support = "0.4" 23 | imgui-wgpu = "0.7" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hypervis - 4D Renderer & Physics Engine 2 | 3 | ![header image](etc/header.png) 4 | 5 | This is an earnest attempt at writing a renderer for 4D geometry and physics 6 | engine for 4D objects, written for learning both 7 | [wgpu-rs](https://github.com/gfx-rs/wgpu) and 4D geometry. 8 | 9 | I gave a talk about this project! You can watch the talk here: 10 | https://www.youtube.com/watch?v=_22oxXEX_xc&t=717s 11 | 12 | ![demo](etc/demo.gif) 13 | 14 | ## Demos 15 | 16 | Knocking over a stack of tesseracts with a hypersphere: 17 | ![a stack of tesseracts is knocked over with a 18 | hypersphere](etc/knock_over_tesseracts.gif) 19 | 20 | Rolling a 600-cell (a 4D Platonic "solid" composed of 600 tetrahedral cells, the 21 | equivalent of faces in 4D) around: 22 | ![a 600-cell is rolled around](etc/rolling_600_cell.gif) 23 | 24 | A domino track that appears to go "through" a wall, but in actuality goes around 25 | it in the 4th dimension: 26 | ![a domino track going around a wall in 4D is knocked over by a 27 | hypersphere](etc/domino_track.gif) 28 | 29 | ## Motivation 30 | 31 | This project was largely motivated by seeing Marc ten Bosch's games, 32 | [Miegakure](http://miegakure.com/) (still in development at the time of writing) 33 | and [4D Toys](http://4dtoys.com/). 34 | 35 | ![miegakure and 4d toys](etc/miegakure_4d_toys.png) 36 | 37 | While there have been many implementations of 4D visualisers, I've not come 38 | across an implementation or a good description of or even general pointers to 39 | how to implement a physics engine in 4D. 4D Toys is the only example of an 40 | actual 4D engine I could find, and it's closed-source. 41 | 42 | This gave me the idea of investigating and attempting to implement a 4D physics engine. How hard could it be, right? 43 | 44 | (Marc ten Bosch has published a paper [N-Dimensional Rigid Body 45 | Dynamics](https://marctenbosch.com/ndphysics/) about his 4D physics engine, but 46 | by this point most of my implementation was in place and it would've been 47 | helpful to be able to refer to this earlier on...) 48 | 49 | ## Rendering 50 | 51 | Hypervis implements rendering of 4D objects by only rendering a 3D slice of the 52 | 4D space to the user. The X, Y, and Z-axes have their conventional meanings, but 53 | in addition to these the user manually controls which W-axis slice is rendered 54 | via a slider. This means that we are showing incremental hyperplanar slices 55 | through the 4D space. 56 | 57 | ![rendering of a hypersphere](etc/sphere_render.gif) 58 | 59 | Just like how in 3D, the surface of an object is represented in a mesh of 60 | triangles, in 4D the surface of an object would be represented by a mesh of 61 | tetrahedra. 62 | 63 | To render this 4D mesh, we intersect it with the current W-slice that we are 64 | interested in. Computing an intersection between a hyperplane and a singular 65 | tetrahedron is not difficult - we simply apply the standard intersection test 66 | for a line and a plane (in its point-normal representation) for each of the 67 | edges of the tetrahedron, which gives us 4 possibilities: 68 | 69 | - The tetrahedron is parallel to the hyperplane. This is a degenerate 70 | case and can be handled by rendering the tetrahedron if it is contained 71 | within the hyperplane. 72 | - The tetrahedron is not parallel to the hyperplane, in which case we can 73 | consider the hyperplane of the tetrahedron, which is a 3D subspace whose 74 | intersection with the slice plane is a 2D plane. Thus, we can consider this 75 | as a problem of intersecting a tetrahedron and a 2D plane in 3D space, 76 | giving us 3 possibilities: 77 | - no edges intersect the plane - render nothing 78 | - 3 edges intersect the plane - render a triangle 79 | - 4 edges intersect the plane - render the resulting quadrilateral 80 | 81 | ![three cases for a tetrahedron intersecting a 82 | plane](etc/tetrahedron_plane_intersection.png) 83 | 84 | It is possible to encode "winding" data for tetrahedra so you can recover the 85 | correctly oriented triangles for backface culling, but for simplicity I just 86 | emitted two triangles, one in each orientation. 87 | 88 | All of this happens on a compute shader (with the help of wgpu-rs!) written to 89 | process a mesh of tetrahedra from a "Tetrahedron Buffer", which together with 90 | the slice hyperplane returns a regular list of triangles that are passed down 91 | the regular rendering pipeline. 92 | 93 | ![rendering architecture diagram](etc/rendering_architecture.png) 94 | 95 | ## Rotations in 4D 96 | 97 | For a rigid body physics engine the motion of any body can be described using 98 | two components, the linear velocity and the angular velocity. Linear velocity is 99 | easy - you simply store the 4D vector corresponding to the direction and speed 100 | of motion. Angular velocity is trickier though, because how do you represent 101 | rotations in 4D? 102 | 103 | When talking about rotations in 3D, we usually reach for a quaternion 104 | implementation, but quaternions are specific to rotations in 3D. To express 105 | rotations in 4D we need to reach for a field of mathematics called Geometric 106 | Algebra. 107 | 108 | A basic way to conceptualise it is this - in 3D, we tend to think of rotation as 109 | happening around an axis, but this is in fact a very 3D-centric view of looking 110 | at it. For example, if something is rotating in a 2D space, where is the axis of 111 | rotation? It can't exist! A more natural way to consider rotations is that they 112 | occur in a particular plane instead: 113 | 114 | ![a diagram showing a cube rotating abount an axis n or a plane 115 | n*](etc/3d_rotation_axis_bivector.png) 116 | 117 | **n** represents the axis of rotation, but equivalently the rotation could also 118 | be described by taking the _dual_ of **n** giving us the plane **n\***. What is 119 | **n\*** exactly though? It is an object which in geometric algebra is called a 120 | _bivector_. 121 | 122 | Bivectors, like their name suggests, act very similar to normal vectors, but 123 | instead of representing a direction and a magnitude we can think of bivectors 124 | representing a _particular plane_ and a magnitude. (A bivector also identifies 125 | the _orientation_ of the plane as well. If you negate a bivector, you get a 126 | bivector representing the same plane and magnitude, but the plane has flipped.) 127 | Just like how every vector in 3D is made out of linear combinations of the 3 128 | basis vectors, the unit X, Y, and Z vectors, every bivector in 3D is made out of 129 | linear combinations of the 3 basis bivectors, **xy** (spanning the x and y 130 | axes), **yz**, and **xz**. 131 | 132 | Similar to how we can build quaternions from a scalar plus an imaginary vector, 133 | we can build an object called a _rotor_ from a scalar plus a bivector. In fact, 134 | quaternions and 3D rotors are _isomorphic_ - they are exactly equivalent, and 135 | all operations on them behave exactly equivalently. 136 | 137 | ![a diagram showing equivalence between quaternions and rotors in 138 | 3D](etc/quaternion_rotor_equivalence.png) 139 | 140 | So what was the point of defining bivectors and rotors if we end up with the 141 | exact same thing as quaternions anyway? Well, quaternions don't generalise to 142 | 4D, but rotors do! A rotor in 4D looks like this: 143 | 144 | ```rust 145 | struct Bivec4 { 146 | xy: f32, 147 | xz: f32, 148 | xw: f32, 149 | yz: f32, 150 | yw: f32, 151 | zw: f32, 152 | } 153 | 154 | struct Quadvec4 { 155 | xyzw: f32, 156 | } 157 | 158 | struct Rotor4 { 159 | s: f32, 160 | b: Bivec4, 161 | q: QuadVec4, 162 | } 163 | ``` 164 | 165 | You may notice bivectors in 4D actually have 6 components - this is because 166 | there are 6 ways to combine the 4 axes resulting in 6 basis bivectors. In 167 | addition to the bivector component there is a _quadvector_ component - this is 168 | because N-dimensional rotors are actually elements of the even subalgebra of 169 | G(N, 0) and not just the addition of a scalar and a bivector component. 170 | 171 | This gives us the following update formulae for rigid body motion: 172 | 173 | ![formulae for rigid body motion](etc/update_formulae.png) 174 | -------------------------------------------------------------------------------- /etc/3d_rotation_axis_bivector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/3d_rotation_axis_bivector.png -------------------------------------------------------------------------------- /etc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/demo.gif -------------------------------------------------------------------------------- /etc/domino_track.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/domino_track.gif -------------------------------------------------------------------------------- /etc/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/header.png -------------------------------------------------------------------------------- /etc/knock_over_tesseracts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/knock_over_tesseracts.gif -------------------------------------------------------------------------------- /etc/miegakure_4d_toys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/miegakure_4d_toys.png -------------------------------------------------------------------------------- /etc/quaternion_rotor_equivalence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/quaternion_rotor_equivalence.png -------------------------------------------------------------------------------- /etc/rendering_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/rendering_architecture.png -------------------------------------------------------------------------------- /etc/rolling_600_cell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/rolling_600_cell.gif -------------------------------------------------------------------------------- /etc/sphere_render.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/sphere_render.gif -------------------------------------------------------------------------------- /etc/tetrahedron_plane_intersection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/tetrahedron_plane_intersection.png -------------------------------------------------------------------------------- /etc/update_formulae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t-veor/hypervis/de062130915443d7276c034ea323942f191da518/etc/update_formulae.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /src/alg/bivec4.rs: -------------------------------------------------------------------------------- 1 | use super::{Quadvec4, Rotor4, Trivec4, Vec4}; 2 | use std::ops::{Add, Mul}; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Bivec4 { 6 | pub xy: f32, 7 | pub xz: f32, 8 | pub xw: f32, 9 | pub yz: f32, 10 | pub yw: f32, 11 | pub zw: f32, 12 | } 13 | 14 | impl Bivec4 { 15 | pub fn new(xy: f32, xz: f32, xw: f32, yz: f32, yw: f32, zw: f32) -> Self { 16 | Self { 17 | xy, 18 | xz, 19 | xw, 20 | yz, 21 | yw, 22 | zw, 23 | } 24 | } 25 | 26 | pub fn zero() -> Self { 27 | Self::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) 28 | } 29 | 30 | pub fn reverse(&self) -> Self { 31 | Self::new(-self.xy, -self.xz, -self.xw, -self.yz, -self.yw, -self.zw) 32 | } 33 | 34 | pub fn dot_v(&self, v: &Vec4) -> Vec4 { 35 | let b = self; 36 | Vec4 { 37 | x: b.xw * v.w + b.xy * v.y + b.xz * v.z, 38 | y: -b.xy * v.x + b.yw * v.w + b.yz * v.z, 39 | z: -b.xz * v.x - b.yz * v.y + b.zw * v.w, 40 | w: -b.xw * v.x - b.yw * v.y - b.zw * v.z, 41 | } 42 | } 43 | 44 | pub fn wedge_v(&self, v: &Vec4) -> Trivec4 { 45 | let b = self; 46 | Trivec4 { 47 | xyz: b.xy * v.z - b.xz * v.y + b.yz * v.x, 48 | xyw: -b.xw * v.y + b.xy * v.w + b.yw * v.x, 49 | xzw: -b.xw * v.z + b.xz * v.w + b.zw * v.x, 50 | yzw: -b.yw * v.z + b.yz * v.w + b.zw * v.y, 51 | } 52 | } 53 | 54 | pub fn mul_v(&self, v: &Vec4) -> (Vec4, Trivec4) { 55 | (self.dot_v(v), self.wedge_v(v)) 56 | } 57 | 58 | #[rustfmt::skip] 59 | pub fn mul_bv(&self, c: &Bivec4) -> (f32, Bivec4, Quadvec4) { 60 | let b = self; 61 | 62 | let s = 63 | - b.xy * c.xy 64 | - b.xz * c.xz 65 | - b.xw * c.xw 66 | - b.yz * c.yz 67 | - b.yw * c.yw 68 | - b.zw * c.zw; 69 | 70 | let d = Bivec4 { 71 | xy: - b.xw * c.yw - b.xz * c.yz + b.yw * c.xw + b.yz * c.xz, 72 | xz: - b.xw * c.zw + b.xy * c.yz - b.yz * c.xy + b.zw * c.xw, 73 | xw: b.xy * c.yw + b.xz * c.zw - b.yw * c.xy - b.zw * c.xz, 74 | yz: - b.xy * c.xz + b.xz * c.xy - b.yw * c.zw + b.zw * c.yw, 75 | yw: b.xw * c.xy - b.xy * c.xw + b.yz * c.zw - b.zw * c.yz, 76 | zw: b.xw * c.xz - b.xz * c.xw + b.yw * c.yz - b.yz * c.yw, 77 | }; 78 | 79 | let q = Quadvec4 { 80 | xyzw: 81 | b.xw * c.yz 82 | + b.xy * c.zw 83 | - b.xz * c.yw 84 | - b.yw * c.xz 85 | + b.yz * c.xw 86 | + b.zw * c.xy, 87 | }; 88 | 89 | (s, d, q) 90 | } 91 | 92 | pub fn decompose(&self) -> (Bivec4, Bivec4) { 93 | let pos_half_xyzw = Quadvec4::new(0.5); 94 | let neg_half_xyzw = Quadvec4::new(-0.5); 95 | 96 | let b_plus = 0.5 * *self + pos_half_xyzw.mul_bv(self); 97 | let b_minus = 0.5 * *self + neg_half_xyzw.mul_bv(self); 98 | 99 | (b_plus, b_minus) 100 | } 101 | 102 | pub fn exp(&self) -> Rotor4 { 103 | let (b_plus, b_minus) = self.decompose(); 104 | 105 | let theta_plus = 2.0 106 | * (b_plus.xy.powi(2) + b_plus.xz.powi(2) + b_plus.xw.powi(2)) 107 | .sqrt(); 108 | let theta_minus = 2.0 109 | * (b_minus.xy.powi(2) + b_minus.xz.powi(2) + b_minus.xw.powi(2)) 110 | .sqrt(); 111 | 112 | let inv_theta_plus = if theta_plus > 0.0 { 113 | 1.0 / theta_plus 114 | } else { 115 | 0.0 116 | }; 117 | let inv_theta_minus = if theta_minus > 0.0 { 118 | 1.0 / theta_minus 119 | } else { 120 | 0.0 121 | }; 122 | 123 | let unit_b_plus = inv_theta_plus * b_plus; 124 | let unit_b_minus = inv_theta_minus * b_minus; 125 | 126 | Rotor4::new( 127 | 0.5 * theta_plus.cos() + 0.5 * theta_minus.cos(), 128 | theta_plus.sin() * unit_b_plus + theta_minus.sin() * unit_b_minus, 129 | Quadvec4::new(0.5 * theta_plus.cos() - 0.5 * theta_minus.cos()), 130 | ) 131 | } 132 | } 133 | 134 | impl Add for Bivec4 { 135 | type Output = Bivec4; 136 | fn add(self, c: Bivec4) -> Bivec4 { 137 | let b = self; 138 | Bivec4 { 139 | xy: b.xy + c.xy, 140 | xz: b.xz + c.xz, 141 | xw: b.xw + c.xw, 142 | yz: b.yz + c.yz, 143 | yw: b.yw + c.yw, 144 | zw: b.zw + c.zw, 145 | } 146 | } 147 | } 148 | 149 | impl Mul for f32 { 150 | type Output = Bivec4; 151 | fn mul(self, b: Bivec4) -> Bivec4 { 152 | Bivec4 { 153 | xy: self * b.xy, 154 | xz: self * b.xz, 155 | xw: self * b.xw, 156 | yz: self * b.yz, 157 | yw: self * b.yw, 158 | zw: self * b.zw, 159 | } 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod test { 165 | use super::*; 166 | 167 | #[test] 168 | fn decomp_test() { 169 | let b = Bivec4::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); 170 | let r = b.exp(); 171 | println!("{:?}", r); 172 | println!("{:?}", r.mag()); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/alg/mod.rs: -------------------------------------------------------------------------------- 1 | mod bivec4; 2 | mod quadvec4; 3 | mod rotor4; 4 | mod trivec4; 5 | mod vec4; 6 | 7 | pub use bivec4::Bivec4; 8 | use quadvec4::Quadvec4; 9 | pub use rotor4::Rotor4; 10 | pub use trivec4::Trivec4; 11 | pub use vec4::Vec4; 12 | 13 | use cgmath::Vector4; 14 | 15 | // Gets a vector that's perpendicular to all three vectors given. 16 | pub fn triple_cross_product( 17 | u: Vector4, 18 | v: Vector4, 19 | w: Vector4, 20 | ) -> Vector4 { 21 | let u: Vec4 = u.into(); 22 | u.wedge_v(&v.into()) 23 | .wedge_v(&w.into()) 24 | .mul_qv(&Quadvec4::one()) 25 | .into() 26 | } 27 | -------------------------------------------------------------------------------- /src/alg/quadvec4.rs: -------------------------------------------------------------------------------- 1 | use super::{Bivec4, Trivec4, Vec4}; 2 | use std::ops::{Add, Mul}; 3 | 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct Quadvec4 { 6 | pub xyzw: f32, 7 | } 8 | 9 | impl Quadvec4 { 10 | pub fn new(xyzw: f32) -> Self { 11 | Self { xyzw } 12 | } 13 | 14 | pub fn zero() -> Self { 15 | Self { xyzw: 0.0 } 16 | } 17 | 18 | pub fn one() -> Self { 19 | Self { xyzw: 1.0 } 20 | } 21 | 22 | pub fn mul_v(&self, v: &Vec4) -> Trivec4 { 23 | let q = self; 24 | Trivec4 { 25 | xyz: q.xyzw * v.w, 26 | xyw: -q.xyzw * v.z, 27 | xzw: q.xyzw * v.y, 28 | yzw: -q.xyzw * v.x, 29 | } 30 | } 31 | 32 | pub fn mul_bv(&self, b: &Bivec4) -> Bivec4 { 33 | let xyzw = self.xyzw; 34 | Bivec4 { 35 | xy: -b.zw * xyzw, 36 | xz: b.yw * xyzw, 37 | xw: -b.yz * xyzw, 38 | yz: -b.xw * xyzw, 39 | yw: b.xz * xyzw, 40 | zw: -b.xy * xyzw, 41 | } 42 | } 43 | } 44 | 45 | impl Add for Quadvec4 { 46 | type Output = Quadvec4; 47 | fn add(self, q: Quadvec4) -> Quadvec4 { 48 | Quadvec4 { 49 | xyzw: self.xyzw + q.xyzw, 50 | } 51 | } 52 | } 53 | 54 | impl Mul for f32 { 55 | type Output = Quadvec4; 56 | fn mul(self, q: Quadvec4) -> Quadvec4 { 57 | Quadvec4 { 58 | xyzw: self * q.xyzw, 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/alg/rotor4.rs: -------------------------------------------------------------------------------- 1 | use super::{Bivec4, Quadvec4, Vec4}; 2 | use cgmath::Matrix4; 3 | use std::ops::{Add, Mul}; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct Rotor4 { 7 | pub s: f32, 8 | pub b: Bivec4, 9 | pub q: Quadvec4, 10 | } 11 | 12 | impl Rotor4 { 13 | pub fn identity() -> Self { 14 | Rotor4 { 15 | s: 1., 16 | b: Bivec4::zero(), 17 | q: Quadvec4::zero(), 18 | } 19 | } 20 | 21 | pub fn new(s: f32, b: Bivec4, q: Quadvec4) -> Self { 22 | Self { s, b, q } 23 | } 24 | 25 | pub fn reverse(&self) -> Rotor4 { 26 | Rotor4::new(self.s, self.b.reverse(), self.q) 27 | } 28 | 29 | pub fn rotate(&self, v: &Vec4) -> Vec4 { 30 | // p = R v ~R. We do this in two steps: 31 | // Q = R v 32 | let (a_1, a_3) = self.b.mul_v(v); 33 | let b_3 = self.q.mul_v(v); 34 | let q_1 = self.s * *v + a_1; 35 | let q_3 = a_3 + b_3; 36 | 37 | // p = Q ~R 38 | let b_rev = self.b.reverse(); 39 | let p = self.s * q_1 40 | + q_1.left_contract_bv(&b_rev) 41 | + q_3.right_contract_bv(&b_rev) 42 | + q_3.mul_qv(&self.q); 43 | 44 | p 45 | } 46 | 47 | pub fn mul_bv(&self, c: &Bivec4) -> Rotor4 { 48 | let (a_0, a_2, a_4) = self.b.mul_bv(c); 49 | Self { 50 | s: a_0, 51 | b: self.s * *c + a_2 + self.q.mul_bv(c), 52 | q: a_4, 53 | } 54 | } 55 | 56 | pub fn update(&mut self, delta: &Bivec4) { 57 | *self = *self * (-0.5 * *delta).exp(); 58 | self.normalize(); 59 | } 60 | 61 | pub fn normalize(&mut self) { 62 | // we decompose into two isoclinic rotations, which are each equivalent 63 | // to a quaternion. Each quaternion component is normalised, and then we 64 | // recover the original rotor 65 | 66 | let (mut r_plus, mut r_minus) = self.decompose(); 67 | 68 | // get rid of the 1/2 (1 +- xyzw) components 69 | r_plus.s -= 0.5; 70 | r_minus.s -= 0.5; 71 | // we're going to overwrite the quadvector components since they should 72 | // be just +- the scalar components. 73 | 74 | let plus_mag = 2.0 75 | * (r_plus.s.powi(2) 76 | + r_plus.b.xy.powi(2) 77 | + r_plus.b.xz.powi(2) 78 | + r_plus.b.xw.powi(2)) 79 | .sqrt(); 80 | let minus_mag = 2.0 81 | * (r_minus.s.powi(2) 82 | + r_minus.b.xy.powi(2) 83 | + r_minus.b.xz.powi(2) 84 | + r_minus.b.xw.powi(2)) 85 | .sqrt(); 86 | 87 | if plus_mag > 0.0 { 88 | let inv_plus_mag = 1.0 / plus_mag; 89 | r_plus.s *= inv_plus_mag; 90 | r_plus.b = inv_plus_mag * r_plus.b; 91 | r_plus.q.xyzw = r_plus.s; 92 | 93 | // readd 1/2 (1 - xyzw) 94 | r_plus.s += 0.5; 95 | r_plus.q.xyzw -= 0.5; 96 | } else { 97 | // TODO: 98 | // unimplemented!("{:?} has zero magnitude!", r_plus); 99 | r_plus = Rotor4::identity(); 100 | } 101 | 102 | if minus_mag > 0.0 { 103 | let inv_minus_mag = 1.0 / minus_mag; 104 | r_minus.s *= inv_minus_mag; 105 | r_minus.b = inv_minus_mag * r_minus.b; 106 | r_minus.q.xyzw = -r_minus.s; 107 | 108 | // readd 1/2 (1 + xyzw) 109 | r_minus.s += 0.5; 110 | r_minus.q.xyzw += 0.5; 111 | } else { 112 | // TODO 113 | // unimplemented!("{:?} has zero magnitude!", r_minus); 114 | r_minus = Rotor4::identity(); 115 | } 116 | 117 | *self = r_plus * r_minus; 118 | } 119 | 120 | pub fn mag(&self) -> f32 { 121 | let mag_sq = self.s * self.s 122 | + self.b.xy * self.b.xy 123 | + self.b.xz * self.b.xz 124 | + self.b.xw * self.b.xw 125 | + self.b.yz * self.b.yz 126 | + self.b.yw * self.b.yw 127 | + self.b.zw * self.b.zw 128 | + self.q.xyzw * self.q.xyzw; 129 | mag_sq.sqrt() 130 | } 131 | 132 | pub fn weird_term(&self) -> f32 { 133 | -2.0 * self.b.xw * self.b.yz - 2.0 * self.b.xy * self.b.zw 134 | + 2.0 * self.b.xz * self.b.yw 135 | + 2.0 * self.q.xyzw * self.s 136 | } 137 | 138 | // Perform Cayley Factorisation to factorise the rotor into two pure 139 | // isoclinic rotations. 140 | pub fn decompose(&self) -> (Rotor4, Rotor4) { 141 | let pos_half_xyzw = Quadvec4::new(0.5); 142 | let neg_half_xyzw = Quadvec4::new(-0.5); 143 | 144 | let r_plus = Rotor4::new( 145 | 0.5 + 0.5 * self.s + 0.5 * self.q.xyzw, 146 | 0.5 * self.b + pos_half_xyzw.mul_bv(&self.b), 147 | 0.5 * self.q + self.s * pos_half_xyzw + neg_half_xyzw, 148 | ); 149 | 150 | let r_minus = Rotor4::new( 151 | 0.5 + 0.5 * self.s - 0.5 * self.q.xyzw, 152 | 0.5 * self.b + neg_half_xyzw.mul_bv(&self.b), 153 | 0.5 * self.q + self.s * neg_half_xyzw + pos_half_xyzw, 154 | ); 155 | 156 | (r_plus, r_minus) 157 | } 158 | 159 | pub fn to_matrix(&self) -> Matrix4 { 160 | let x = self.rotate(&Vec4 { 161 | x: 1.0, 162 | y: 0.0, 163 | z: 0.0, 164 | w: 0.0, 165 | }); 166 | let y = self.rotate(&Vec4 { 167 | x: 0.0, 168 | y: 1.0, 169 | z: 0.0, 170 | w: 0.0, 171 | }); 172 | let z = self.rotate(&Vec4 { 173 | x: 0.0, 174 | y: 0.0, 175 | z: 1.0, 176 | w: 0.0, 177 | }); 178 | let w = self.rotate(&Vec4 { 179 | x: 0.0, 180 | y: 0.0, 181 | z: 0.0, 182 | w: 1.0, 183 | }); 184 | 185 | // attributes are not allowed on expressions apparently 186 | #[rustfmt::skip] 187 | return Matrix4::new( 188 | x.x, x.y, x.z, x.w, 189 | y.x, y.y, y.z, y.w, 190 | z.x, z.y, z.z, z.w, 191 | w.x, w.y, w.z, w.w, 192 | ); 193 | } 194 | } 195 | 196 | impl Default for Rotor4 { 197 | fn default() -> Self { 198 | Self::identity() 199 | } 200 | } 201 | 202 | impl Add for Rotor4 { 203 | type Output = Rotor4; 204 | fn add(self, other: Rotor4) -> Rotor4 { 205 | Rotor4::new(self.s + other.s, self.b + other.b, self.q + other.q) 206 | } 207 | } 208 | 209 | impl Mul for Rotor4 { 210 | type Output = Rotor4; 211 | fn mul(self, r_1: Rotor4) -> Rotor4 { 212 | let r_0 = self; 213 | let (a_0, a_2, a_4) = r_0.b.mul_bv(&r_1.b); 214 | Rotor4::new( 215 | r_0.s * r_1.s + a_0 + r_0.q.xyzw * r_1.q.xyzw, 216 | r_0.s * r_1.b 217 | + r_1.s * r_0.b 218 | + a_2 219 | + r_0.q.mul_bv(&r_1.b) 220 | + r_1.q.mul_bv(&r_0.b), 221 | r_0.s * r_1.q + r_1.s * r_0.q + a_4, 222 | ) 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use super::*; 229 | use cgmath::SquareMatrix; 230 | 231 | #[test] 232 | fn long_term_rotation_error() { 233 | let mut r = Rotor4::identity(); 234 | for _ in 0..100000 { 235 | r.update(&Bivec4::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); 236 | } 237 | println!( 238 | "{:?}\n{} {} {}", 239 | r, 240 | r.mag(), 241 | r.weird_term(), 242 | r.to_matrix().determinant() 243 | ); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/alg/trivec4.rs: -------------------------------------------------------------------------------- 1 | use super::{Bivec4, Quadvec4, Vec4}; 2 | use std::ops::Add; 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct Trivec4 { 5 | pub xyz: f32, 6 | pub xyw: f32, 7 | pub xzw: f32, 8 | pub yzw: f32, 9 | } 10 | 11 | impl Trivec4 { 12 | pub fn zero() -> Self { 13 | Self { 14 | xyz: 0.0, 15 | xyw: 0.0, 16 | xzw: 0.0, 17 | yzw: 0.0, 18 | } 19 | } 20 | 21 | pub fn right_contract_bv(&self, b: &Bivec4) -> Vec4 { 22 | let t = self; 23 | Vec4 { 24 | x: -b.yw * t.xyw - b.yz * t.xyz - b.zw * t.xzw, 25 | y: b.xw * t.xyw + b.xz * t.xyz - b.zw * t.yzw, 26 | z: b.xw * t.xzw - b.xy * t.xyz + b.yw * t.yzw, 27 | w: -b.xy * t.xyw - b.xz * t.xzw - b.yz * t.yzw, 28 | } 29 | } 30 | 31 | pub fn mul_qv(&self, q: &Quadvec4) -> Vec4 { 32 | let t = self; 33 | let xyzw = q.xyzw; 34 | Vec4 { 35 | x: xyzw * t.yzw, 36 | y: -xyzw * t.xzw, 37 | z: xyzw * t.xyw, 38 | w: -xyzw * t.xyz, 39 | } 40 | } 41 | } 42 | 43 | impl Add for Trivec4 { 44 | type Output = Trivec4; 45 | fn add(self, t: Trivec4) -> Trivec4 { 46 | Trivec4 { 47 | xyz: self.xyz + t.xyz, 48 | xyw: self.xyw + t.xyw, 49 | xzw: self.xzw + t.xzw, 50 | yzw: self.yzw + t.yzw, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/alg/vec4.rs: -------------------------------------------------------------------------------- 1 | use super::{Bivec4, Trivec4}; 2 | use mint::Vector4; 3 | use std::ops::{Add, Mul}; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct Vec4 { 7 | pub x: f32, 8 | pub y: f32, 9 | pub z: f32, 10 | pub w: f32, 11 | } 12 | 13 | impl Vec4 { 14 | pub fn zero() -> Self { 15 | Self { 16 | x: 0.0, 17 | y: 0.0, 18 | z: 0.0, 19 | w: 0.0, 20 | } 21 | } 22 | 23 | pub fn left_contract_bv(&self, b: &Bivec4) -> Vec4 { 24 | let v = self; 25 | 26 | Vec4 { 27 | x: -v.y * b.xy - v.z * b.xz - v.w * b.xw, 28 | y: v.x * b.xy - v.z * b.yz - v.w * b.yw, 29 | z: v.x * b.xz + v.y * b.yz - v.w * b.zw, 30 | w: v.x * b.xw + v.y * b.yw + v.z * b.zw, 31 | } 32 | } 33 | 34 | pub fn wedge_bv(&self, b: &Bivec4) -> Trivec4 { 35 | let v = self; 36 | 37 | Trivec4 { 38 | xyz: v.x * b.yz - v.y * b.xz + v.z * b.xy, 39 | xyw: v.x * b.yw - v.y * b.xw + v.w * b.xy, 40 | xzw: v.x * b.zw - v.z * b.xw + v.w * b.xz, 41 | yzw: v.x * b.zw - v.z * b.yw + v.w * b.yz, 42 | } 43 | } 44 | 45 | pub fn wedge_v(&self, v: &Vec4) -> Bivec4 { 46 | let u = self; 47 | Bivec4 { 48 | xy: u.x * v.y - u.y * v.x, 49 | xz: u.x * v.z - u.z * v.x, 50 | xw: -u.w * v.x + u.x * v.w, 51 | yz: u.y * v.z - u.z * v.y, 52 | yw: -u.w * v.y + u.y * v.w, 53 | zw: -u.w * v.z + u.z * v.w, 54 | } 55 | } 56 | 57 | pub fn mul_bv(&self, b: &Bivec4) -> (Vec4, Trivec4) { 58 | (self.left_contract_bv(b), self.wedge_bv(b)) 59 | } 60 | } 61 | 62 | impl Mul for f32 { 63 | type Output = Vec4; 64 | fn mul(self, v: Vec4) -> Vec4 { 65 | Vec4 { 66 | x: self * v.x, 67 | y: self * v.y, 68 | z: self * v.z, 69 | w: self * v.w, 70 | } 71 | } 72 | } 73 | 74 | impl Add for Vec4 { 75 | type Output = Vec4; 76 | fn add(self, v: Vec4) -> Vec4 { 77 | let u = self; 78 | Vec4 { 79 | x: u.x + v.x, 80 | y: u.y + v.y, 81 | z: u.z + v.z, 82 | w: u.w + v.w, 83 | } 84 | } 85 | } 86 | 87 | impl Into> for Vec4 { 88 | fn into(self) -> Vector4 { 89 | Vector4 { 90 | x: self.x, 91 | y: self.y, 92 | z: self.z, 93 | w: self.w, 94 | } 95 | } 96 | } 97 | 98 | impl From> for Vec4 { 99 | fn from(v: Vector4) -> Self { 100 | Self { 101 | x: v.x, 102 | y: v.y, 103 | z: v.z, 104 | w: v.w, 105 | } 106 | } 107 | } 108 | 109 | impl Into> for Vec4 { 110 | fn into(self) -> cgmath::Vector4 { 111 | cgmath::Vector4::new(self.x, self.y, self.z, self.w) 112 | } 113 | } 114 | 115 | impl From> for Vec4 { 116 | fn from(v: cgmath::Vector4) -> Self { 117 | Self { 118 | x: v.x, 119 | y: v.y, 120 | z: v.z, 121 | w: v.w, 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/context/graphics/context.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use winit::window::Window; 3 | 4 | pub struct GraphicsContext { 5 | pub surface: wgpu::Surface, 6 | pub adapter: wgpu::Adapter, 7 | pub device: wgpu::Device, 8 | pub queue: wgpu::Queue, 9 | pub sc_desc: wgpu::SwapChainDescriptor, 10 | } 11 | 12 | impl GraphicsContext { 13 | pub async fn new(window: &Window) -> Result<(Self, wgpu::SwapChain)> { 14 | let size = window.inner_size(); 15 | let surface = wgpu::Surface::create(window); 16 | let adapter = wgpu::Adapter::request( 17 | &wgpu::RequestAdapterOptions { 18 | power_preference: wgpu::PowerPreference::HighPerformance, 19 | compatible_surface: None, 20 | }, 21 | wgpu::BackendBit::PRIMARY, 22 | ) 23 | .await 24 | .ok_or(anyhow!("Could not acquire adapter"))?; 25 | 26 | let (device, queue) = adapter 27 | .request_device(&wgpu::DeviceDescriptor { 28 | extensions: wgpu::Extensions { 29 | anisotropic_filtering: false, 30 | }, 31 | limits: Default::default(), 32 | }) 33 | .await; 34 | 35 | let sc_desc = wgpu::SwapChainDescriptor { 36 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 37 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 38 | width: size.width, 39 | height: size.height, 40 | present_mode: wgpu::PresentMode::Fifo, 41 | }; 42 | let swap_chain = device.create_swap_chain(&surface, &sc_desc); 43 | 44 | Ok(( 45 | Self { 46 | surface, 47 | adapter, 48 | device, 49 | queue, 50 | sc_desc, 51 | }, 52 | swap_chain, 53 | )) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/context/graphics/light.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{ 2 | Deg, EuclideanSpace, Matrix4, One, Point3, Vector3, Vector4, Zero, 3 | }; 4 | 5 | use super::SHADOW_SIZE; 6 | 7 | #[repr(C)] 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct Light { 10 | proj: Matrix4, 11 | position: Vector4, 12 | color: Vector4, 13 | } 14 | 15 | unsafe impl bytemuck::Pod for Light {} 16 | unsafe impl bytemuck::Zeroable for Light {} 17 | 18 | impl Light { 19 | pub fn new(position: Point3, fovy: f32, color: Vector4) -> Self { 20 | let aspect = SHADOW_SIZE.width as f32 / SHADOW_SIZE.height as f32; 21 | 22 | Self { 23 | proj: cgmath::perspective(Deg(fovy), aspect, 1.0, 20.0) 24 | * Matrix4::look_at( 25 | position, 26 | Point3::origin(), 27 | Vector3::unit_y(), 28 | ), 29 | position: Vector4::new(position.x, position.y, position.z, 1.0), 30 | color, 31 | } 32 | } 33 | } 34 | 35 | impl Default for Light { 36 | fn default() -> Self { 37 | Self { 38 | proj: Matrix4::one(), 39 | position: Vector4::zero(), 40 | color: Vector4::zero(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/context/graphics/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | pub mod light; 3 | pub mod shadow_pipeline; 4 | pub mod slice_pipeline; 5 | pub mod slice_plane; 6 | pub mod transform4; 7 | pub mod triangle_list_pipeline; 8 | pub mod vertex3; 9 | pub mod vertex4; 10 | pub mod view_projection; 11 | 12 | pub use context::*; 13 | pub use light::*; 14 | pub use shadow_pipeline::*; 15 | pub use slice_pipeline::*; 16 | pub use slice_plane::*; 17 | pub use transform4::*; 18 | pub use triangle_list_pipeline::*; 19 | pub use vertex3::*; 20 | pub use vertex4::*; 21 | pub use view_projection::*; 22 | 23 | pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; 24 | 25 | pub const SHADOW_FORMAT: wgpu::TextureFormat = 26 | wgpu::TextureFormat::Depth32Float; 27 | pub const SHADOW_SIZE: wgpu::Extent3d = wgpu::Extent3d { 28 | width: 2048, 29 | height: 2048, 30 | depth: 1, 31 | }; 32 | -------------------------------------------------------------------------------- /src/context/graphics/shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec4 v_position; 4 | layout(location=1) in vec4 v_color; 5 | layout(location=2) in vec3 v_normal; 6 | 7 | layout(location=0) out vec4 f_color; 8 | 9 | layout(set=0, binding=1) uniform Light { 10 | mat4 light_proj; 11 | vec4 light_pos; 12 | vec4 light_color; 13 | }; 14 | layout(set=0, binding=2) uniform texture2D t_shadow; 15 | layout(set=0, binding=3) uniform samplerShadow s_shadow; 16 | 17 | vec2 poisson_disk[4] = { 18 | { -0.94201624, -0.39906216}, 19 | { 0.94558609, -0.76890725}, 20 | {-0.094184101, -0.92938870}, 21 | { 0.34495938, 0.29387760}, 22 | }; 23 | 24 | float sigmoid(float x) { 25 | if (x >= 1.0) return 1.0; 26 | else if (x <= -1.0) return 0.0; 27 | else return 0.5 + x * (1.0 - abs(x) * 0.5); 28 | } 29 | 30 | float fetch_shadow(vec4 pos, float theta) { 31 | if (pos.w <= 0.0) { 32 | return 1.0; 33 | } 34 | float bias = 0.0005 * tan(theta); 35 | bias = clamp(bias, 0, 0.001); 36 | 37 | // compensate for the Y-flip difference between the NDC and texture coordinates 38 | const vec2 flip_correction = vec2(0.5, -0.5); 39 | vec3 light_local = vec3( 40 | pos.xy * flip_correction / pos.w + 0.5, 41 | pos.z / pos.w - bias 42 | ); 43 | 44 | float result = 0.0; 45 | for (int i = 0; i < 4; i++) { 46 | vec3 modified_local = vec3( 47 | light_local.xy + poisson_disk[i] / 700.0, 48 | light_local.z 49 | ); 50 | result += texture(sampler2DShadow(t_shadow, s_shadow), modified_local); 51 | } 52 | return sigmoid(result / 2.0 - 1.0); 53 | } 54 | 55 | void main() { 56 | vec3 ambient = vec3(0.2, 0.2, 0.2); 57 | 58 | vec3 light_dir = normalize(light_pos.xyz - v_position.xyz); 59 | float theta = acos(dot(v_normal, light_dir)); 60 | float diffuse = max(0.0, dot(v_normal, light_dir)); 61 | float shadow = fetch_shadow(light_proj * v_position, theta); 62 | vec3 color = ambient + shadow * diffuse * light_color.xyz * (1 - ambient); 63 | 64 | f_color = vec4(color, 1.0) * v_color; 65 | } 66 | -------------------------------------------------------------------------------- /src/context/graphics/shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec4 a_position; 4 | layout(location=1) in vec4 a_color; 5 | layout(location=2) in vec4 a_normal; 6 | 7 | layout(location=0) out vec4 v_position; 8 | layout(location=1) out vec4 v_color; 9 | layout(location=2) out vec3 v_normal; 10 | 11 | layout(set=0, binding=0) uniform Projection { 12 | mat4 view_proj; 13 | }; 14 | 15 | void main() { 16 | v_position = a_position; 17 | v_color = a_color; 18 | 19 | // correct the normal so that it's always pointing towards the camera, 20 | // i.e. the normal under transformation by the view_proj that ends up with 21 | // smaller z 22 | vec4 positive_normal = view_proj * a_normal; 23 | vec4 negative_normal = view_proj * vec4(-a_normal.xyz, a_normal.w); 24 | if (positive_normal.z < negative_normal.z) { 25 | v_normal = a_normal.xyz; 26 | } else { 27 | v_normal = -a_normal.xyz; 28 | } 29 | 30 | gl_Position = view_proj * a_position; 31 | } 32 | -------------------------------------------------------------------------------- /src/context/graphics/shaders/shadow.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | void main() { 4 | } 5 | -------------------------------------------------------------------------------- /src/context/graphics/shaders/shadow.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location=0) in vec4 a_position; 4 | 5 | layout(set=0, binding=0) uniform Light { 6 | mat4 light_proj; 7 | vec4 light_pos; 8 | vec4 light_color; 9 | }; 10 | 11 | void main() { 12 | gl_Position = light_proj * a_position; 13 | } 14 | -------------------------------------------------------------------------------- /src/context/graphics/shaders/slice.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #define EPSILON 0.000001 4 | #define APPROX_ZERO(x) (abs(x) < EPSILON) 5 | #define APPROX_EQ(a, b) (abs((a) - (b)) < EPSILON) 6 | #define APPROX_VEC_EQ(a, b) (dot((a) - (b), (a) - (b)) < EPSILON) 7 | 8 | layout (local_size_x = 256) in; 9 | 10 | struct Vertex4 { 11 | vec4 position; 12 | vec4 color; 13 | }; 14 | 15 | struct Vertex3 { 16 | vec4 position; 17 | vec4 color; 18 | vec4 normal; 19 | }; 20 | 21 | struct DrawIndirectCommand { 22 | uint vertex_count; 23 | uint instance_count; 24 | uint first_vertex; 25 | uint first_instance; 26 | }; 27 | 28 | layout(set = 0, binding = 0) uniform CutPlane { 29 | vec4 normal; 30 | vec4 base_point; 31 | mat4 proj_matrix; 32 | }; 33 | 34 | layout(set = 0, binding = 1) uniform Transform { 35 | vec4 displacement; 36 | mat4 transform_matrix; 37 | }; 38 | 39 | layout(set = 1, binding = 0) uniform SimplexCount { 40 | uint simplex_count; 41 | }; 42 | 43 | layout(set = 1, binding = 1) readonly buffer SrcVertices { 44 | Vertex4 src_vertices[]; 45 | }; 46 | 47 | layout(set = 1, binding = 2) readonly buffer SrcIndices { 48 | uint src_indices[]; 49 | }; 50 | 51 | layout(set = 2, binding = 0) buffer DrawCommand { 52 | DrawIndirectCommand command; 53 | }; 54 | 55 | layout(set = 2, binding = 1) writeonly buffer DstVertices { 56 | Vertex3 dst_vertices[]; 57 | }; 58 | 59 | const uvec2 edges[6] = { 60 | { 0, 1 }, 61 | { 0, 2 }, 62 | { 0, 3 }, 63 | { 1, 2 }, 64 | { 1, 3 }, 65 | { 2, 3 }, 66 | }; 67 | 68 | const uint tetrahedron_indices[12] = { 69 | 0, 1, 2, 70 | 0, 1, 3, 71 | 0, 2, 3, 72 | 1, 2, 3, 73 | }; 74 | 75 | vec4 project(vec4 x) { 76 | return vec4((proj_matrix * (x - base_point)).xyz, 1.0); 77 | } 78 | 79 | float saturate(float value) { 80 | return min(1.0, max(-1.0, value)); 81 | } 82 | 83 | void emit_triangle(Vertex4 a, Vertex4 b, Vertex4 c) { 84 | vec3 a3 = a.position.xyz / a.position.w; 85 | vec3 b3 = b.position.xyz / b.position.w; 86 | vec3 c3 = c.position.xyz / c.position.w; 87 | vec4 normal = vec4(normalize(cross(b3 - a3, c3 - a3)), 1.0); 88 | uint dst_index = atomicAdd(command.vertex_count, 3); 89 | 90 | dst_vertices[dst_index + 0].position = a.position; 91 | dst_vertices[dst_index + 0].color = a.color; 92 | dst_vertices[dst_index + 0].normal = normal; 93 | dst_vertices[dst_index + 1].position = b.position; 94 | dst_vertices[dst_index + 1].color = b.color; 95 | dst_vertices[dst_index + 1].normal = normal; 96 | dst_vertices[dst_index + 2].position = c.position; 97 | dst_vertices[dst_index + 2].color = c.color; 98 | dst_vertices[dst_index + 2].normal = normal; 99 | } 100 | 101 | void main() { 102 | uint index = gl_GlobalInvocationID.x; 103 | if (index > simplex_count) { 104 | return; 105 | } 106 | 107 | Vertex4 vertices[4]; 108 | for (uint i = 0; i < 4; i++) { 109 | Vertex4 source = src_vertices[src_indices[index * 4 + i]]; 110 | vertices[i].position = transform_matrix * source.position + displacement; 111 | vertices[i].color = source.color; 112 | } 113 | 114 | // check to see if the tetrahedron is exactly in the cut plane 115 | float cut_plane_offset = dot(base_point, normal); 116 | if (APPROX_EQ(dot(vertices[0].position, normal), cut_plane_offset) 117 | && APPROX_EQ(dot(vertices[1].position, normal), cut_plane_offset) 118 | && APPROX_EQ(dot(vertices[2].position, normal), cut_plane_offset) 119 | && APPROX_EQ(dot(vertices[3].position, normal), cut_plane_offset) 120 | ) { 121 | // emit a tetrahedron 122 | for (uint i = 0; i < 12; i += 3) { 123 | Vertex4 a = vertices[tetrahedron_indices[i + 0]]; 124 | Vertex4 b = vertices[tetrahedron_indices[i + 1]]; 125 | Vertex4 c = vertices[tetrahedron_indices[i + 2]]; 126 | a.position = project(a.position); 127 | b.position = project(b.position); 128 | c.position = project(c.position); 129 | 130 | emit_triangle(a, b, c); 131 | } 132 | } 133 | else { 134 | Vertex4 intersections[4]; 135 | uint count = 0; 136 | 137 | for (uint i = 0; i < 4; i++) { 138 | if (APPROX_ZERO(dot(normal, base_point - vertices[i].position))) { 139 | intersections[count++] = vertices[i]; 140 | } 141 | } 142 | 143 | for (uint i = 0; i < 6; i++) { 144 | uvec2 edge = edges[i]; 145 | Vertex4 start = vertices[edge.x]; 146 | Vertex4 end = vertices[edge.y]; 147 | vec4 a = start.position; 148 | vec4 b = end.position; 149 | 150 | float denom = dot(normal, b - a); 151 | if (!APPROX_ZERO(denom)) { 152 | float t = dot(normal, base_point - a) / denom; 153 | if (0.0 <= t && t <= 1.0) { 154 | vec4 intersection = a + t * (b - a); 155 | for (uint j = 0; j < 4; j++) { 156 | if (j == count) { 157 | intersections[count].position = intersection; 158 | intersections[count].color = mix(start.color, end.color, t); 159 | count++; 160 | break; 161 | } 162 | if (APPROX_VEC_EQ(intersections[j].position, intersection)) { 163 | break; 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | for (uint i = 0; i < count; i++) { 171 | intersections[i].position = project(intersections[i].position); 172 | } 173 | 174 | if (count < 3) { 175 | // do nothing 176 | return; 177 | } 178 | else if (count == 3) 179 | { 180 | // emit a triangle 181 | emit_triangle(intersections[0], intersections[1], intersections[2]); 182 | } 183 | else { 184 | // emit a quadrilateral 185 | vec2 angles[4] = { 186 | { 0.0, 0.0 }, 187 | { 1.0, 0.0 }, 188 | { 2.0, 0.0 }, 189 | { 3.0, 0.0 }, 190 | }; 191 | 192 | vec3 a = intersections[0].position.xyz; 193 | vec3 b = intersections[1].position.xyz; 194 | vec3 c = intersections[2].position.xyz; 195 | vec3 n = normalize(cross(b - a, c - a)); 196 | 197 | vec3 quad_centroid = vec3(0.0); 198 | for (uint i = 0; i < 4; i++) { 199 | quad_centroid += intersections[i].position.xyz; 200 | } 201 | quad_centroid /= 4.0; 202 | 203 | vec3 first = normalize(a - quad_centroid); 204 | for (uint i = 1; i < 4; i++) { 205 | vec3 edge = normalize( 206 | intersections[i].position.xyz - quad_centroid); 207 | float angle = acos(saturate(dot(first, edge))); 208 | if (dot(n, cross(first, edge)) < 0.0) { 209 | angle *= -1.0; 210 | } 211 | angles[i].y = angle; 212 | } 213 | 214 | for (uint i = 1; i < 4; i++) { 215 | for (uint j = i; j > 0 && angles[j - 1].y > angles[j].y; j--) { 216 | vec2 tmp = angles[j]; 217 | angles[j] = angles[j - 1]; 218 | angles[j - 1] = tmp; 219 | } 220 | } 221 | 222 | emit_triangle( 223 | intersections[uint(angles[0].x)], 224 | intersections[uint(angles[1].x)], 225 | intersections[uint(angles[2].x)] 226 | ); 227 | emit_triangle( 228 | intersections[uint(angles[0].x)], 229 | intersections[uint(angles[2].x)], 230 | intersections[uint(angles[3].x)] 231 | ); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/context/graphics/shadow_pipeline.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | GraphicsContext, Light, MeshBinding, Vertex3, SHADOW_FORMAT, SHADOW_SIZE, 3 | }; 4 | 5 | use anyhow::{anyhow, Context, Result}; 6 | pub struct ShadowPipeline { 7 | pipeline: wgpu::RenderPipeline, 8 | pub light_buffer: wgpu::Buffer, 9 | uniform_bind_group: wgpu::BindGroup, 10 | } 11 | 12 | impl ShadowPipeline { 13 | pub fn new(ctx: &GraphicsContext) -> Result { 14 | let vs_src = include_str!("shaders/shadow.vert"); 15 | let fs_src = include_str!("shaders/shadow.frag"); 16 | 17 | let vs_spirv = 18 | glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex) 19 | .map_err(|s| anyhow!(s)) 20 | .context( 21 | "Failed to compiler 'shaders/shadow.vert' to SPIR-V", 22 | )?; 23 | let fs_spirv = 24 | glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment) 25 | .map_err(|s| anyhow!(s)) 26 | .context( 27 | "Failed to compiler 'shaders/shadow.frag' to SPIR-V", 28 | )?; 29 | 30 | let vs_data = wgpu::read_spirv(vs_spirv)?; 31 | let fs_data = wgpu::read_spirv(fs_spirv)?; 32 | 33 | let vs_module = ctx.device.create_shader_module(&vs_data); 34 | let fs_module = ctx.device.create_shader_module(&fs_data); 35 | 36 | let light_buffer = ctx.device.create_buffer_with_data( 37 | bytemuck::cast_slice(&[Light::default()]), 38 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 39 | ); 40 | 41 | let uniform_bind_group_layout = ctx.device.create_bind_group_layout( 42 | &wgpu::BindGroupLayoutDescriptor { 43 | label: None, 44 | bindings: &[wgpu::BindGroupLayoutEntry { 45 | binding: 0, 46 | visibility: wgpu::ShaderStage::VERTEX, 47 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 48 | }], 49 | }, 50 | ); 51 | 52 | let uniform_bind_group = 53 | ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { 54 | label: Some("shadow_uniform_bind_group"), 55 | layout: &uniform_bind_group_layout, 56 | bindings: &[wgpu::Binding { 57 | binding: 0, 58 | resource: wgpu::BindingResource::Buffer { 59 | buffer: &light_buffer, 60 | range: 0..std::mem::size_of::() 61 | as wgpu::BufferAddress, 62 | }, 63 | }], 64 | }); 65 | 66 | let pipeline_layout = ctx.device.create_pipeline_layout( 67 | &wgpu::PipelineLayoutDescriptor { 68 | bind_group_layouts: &[&uniform_bind_group_layout], 69 | }, 70 | ); 71 | 72 | let pipeline = ctx.device.create_render_pipeline( 73 | &wgpu::RenderPipelineDescriptor { 74 | layout: &pipeline_layout, 75 | vertex_stage: wgpu::ProgrammableStageDescriptor { 76 | module: &vs_module, 77 | entry_point: "main", 78 | }, 79 | fragment_stage: Some(wgpu::ProgrammableStageDescriptor { 80 | module: &fs_module, 81 | entry_point: "main", 82 | }), 83 | rasterization_state: Some(wgpu::RasterizationStateDescriptor { 84 | front_face: wgpu::FrontFace::Ccw, 85 | cull_mode: wgpu::CullMode::None, 86 | depth_bias: 2, 87 | depth_bias_slope_scale: 2.0, 88 | depth_bias_clamp: 0.0, 89 | }), 90 | color_states: &[], 91 | primitive_topology: wgpu::PrimitiveTopology::TriangleList, 92 | depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { 93 | format: SHADOW_FORMAT, 94 | depth_write_enabled: true, 95 | depth_compare: wgpu::CompareFunction::LessEqual, 96 | stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE, 97 | stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE, 98 | stencil_read_mask: 0, 99 | stencil_write_mask: 0, 100 | }), 101 | vertex_state: wgpu::VertexStateDescriptor { 102 | index_format: wgpu::IndexFormat::Uint16, 103 | vertex_buffers: &[Vertex3::desc()], 104 | }, 105 | sample_count: 1, 106 | sample_mask: !0, 107 | alpha_to_coverage_enabled: false, 108 | }, 109 | ); 110 | 111 | Ok(Self { 112 | pipeline, 113 | light_buffer, 114 | uniform_bind_group, 115 | }) 116 | } 117 | 118 | pub fn update_light(&self, ctx: &mut GraphicsContext, light: &Light) { 119 | let mut encoder = ctx.device.create_command_encoder( 120 | &wgpu::CommandEncoderDescriptor { 121 | label: Some("light_update_encoder"), 122 | }, 123 | ); 124 | // update the projection 125 | { 126 | let staging_buffer = ctx.device.create_buffer_with_data( 127 | bytemuck::cast_slice(&[*light]), 128 | wgpu::BufferUsage::COPY_SRC, 129 | ); 130 | encoder.copy_buffer_to_buffer( 131 | &staging_buffer, 132 | 0, 133 | &self.light_buffer, 134 | 0, 135 | std::mem::size_of::() as wgpu::BufferAddress, 136 | ); 137 | } 138 | ctx.queue.submit(&[encoder.finish()]); 139 | } 140 | 141 | pub fn new_sampler(&self, ctx: &GraphicsContext) -> wgpu::Sampler { 142 | ctx.device.create_sampler(&wgpu::SamplerDescriptor { 143 | address_mode_u: wgpu::AddressMode::ClampToEdge, 144 | address_mode_v: wgpu::AddressMode::ClampToEdge, 145 | address_mode_w: wgpu::AddressMode::ClampToEdge, 146 | mag_filter: wgpu::FilterMode::Linear, 147 | min_filter: wgpu::FilterMode::Linear, 148 | mipmap_filter: wgpu::FilterMode::Nearest, 149 | lod_min_clamp: -100.0, 150 | lod_max_clamp: 100.0, 151 | compare: wgpu::CompareFunction::LessEqual, 152 | }) 153 | } 154 | 155 | pub fn new_texture(&self, ctx: &GraphicsContext) -> wgpu::TextureView { 156 | ctx.device 157 | .create_texture(&wgpu::TextureDescriptor { 158 | label: Some("shadow_texture"), 159 | size: SHADOW_SIZE, 160 | mip_level_count: 1, 161 | sample_count: 1, 162 | dimension: wgpu::TextureDimension::D2, 163 | format: SHADOW_FORMAT, 164 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT 165 | | wgpu::TextureUsage::SAMPLED, 166 | array_layer_count: 1, 167 | }) 168 | .create_default_view() 169 | } 170 | 171 | pub fn render<'a: 'c, 'b, 'c>( 172 | &'a self, 173 | render_pass: &'b mut wgpu::RenderPass<'c>, 174 | mesh: &'a MeshBinding, 175 | ) { 176 | render_pass.set_pipeline(&self.pipeline); 177 | render_pass.set_vertex_buffer(0, &mesh.dst_vertex_buffer, 0, 0); 178 | render_pass.set_bind_group(0, &self.uniform_bind_group, &[]); 179 | render_pass.draw_indirect(&mesh.indirect_command_buffer, 0); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/context/graphics/slice_pipeline.rs: -------------------------------------------------------------------------------- 1 | use super::{GraphicsContext, SlicePlane, Transform4, Vertex3, Vertex4}; 2 | 3 | use anyhow::{anyhow, Context, Result}; 4 | 5 | pub const WORK_GROUP_SIZE: u32 = 256; 6 | 7 | pub struct SlicePipeline { 8 | pipeline: wgpu::ComputePipeline, 9 | uniform_bind_group_layout: wgpu::BindGroupLayout, 10 | src_bind_group_layout: wgpu::BindGroupLayout, 11 | dst_bind_group_layout: wgpu::BindGroupLayout, 12 | } 13 | 14 | #[repr(C)] 15 | #[derive(Debug, Clone, Copy)] 16 | pub struct DrawIndirectCommand { 17 | vertex_count: u32, 18 | instance_count: u32, 19 | first_vertex: u32, 20 | first_instance: u32, 21 | } 22 | 23 | unsafe impl bytemuck::Pod for DrawIndirectCommand {} 24 | unsafe impl bytemuck::Zeroable for DrawIndirectCommand {} 25 | 26 | impl Default for DrawIndirectCommand { 27 | fn default() -> Self { 28 | Self { 29 | vertex_count: 0, 30 | instance_count: 1, 31 | first_vertex: 0, 32 | first_instance: 0, 33 | } 34 | } 35 | } 36 | 37 | pub struct MeshBinding { 38 | uniform_bind_group: wgpu::BindGroup, 39 | src_bind_group: wgpu::BindGroup, 40 | dst_bind_group: wgpu::BindGroup, 41 | simplex_count: u32, 42 | slice_plane_buffer: wgpu::Buffer, 43 | transform_buffer: wgpu::Buffer, 44 | pub(super) indirect_command_buffer: wgpu::Buffer, 45 | pub(super) dst_vertex_buffer: wgpu::Buffer, 46 | } 47 | 48 | fn ceil_div(x: u32, y: u32) -> u32 { 49 | x / y + if x % y != 0 { 1 } else { 0 } 50 | } 51 | 52 | impl SlicePipeline { 53 | pub fn new(ctx: &GraphicsContext) -> Result { 54 | let shader_src = include_str!("shaders/slice.comp"); 55 | let shader_spirv = glsl_to_spirv::compile( 56 | shader_src, 57 | glsl_to_spirv::ShaderType::Compute, 58 | ) 59 | .map_err(|s| anyhow!(s)) 60 | .context("Failed to compile 'shaders/slice.comp' into SPIR-V")?; 61 | let shader_data = wgpu::read_spirv(shader_spirv) 62 | .context("Failed to load 'shaders/slice.comp' into WGPU")?; 63 | let shader_module = ctx.device.create_shader_module(&shader_data); 64 | 65 | let uniform_bind_group_layout = ctx.device.create_bind_group_layout( 66 | &wgpu::BindGroupLayoutDescriptor { 67 | label: None, 68 | bindings: &[ 69 | wgpu::BindGroupLayoutEntry { 70 | binding: 0, 71 | visibility: wgpu::ShaderStage::COMPUTE, 72 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 73 | }, 74 | wgpu::BindGroupLayoutEntry { 75 | binding: 1, 76 | visibility: wgpu::ShaderStage::COMPUTE, 77 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 78 | }, 79 | ], 80 | }, 81 | ); 82 | 83 | let src_bind_group_layout = ctx.device.create_bind_group_layout( 84 | &wgpu::BindGroupLayoutDescriptor { 85 | label: None, 86 | bindings: &[ 87 | wgpu::BindGroupLayoutEntry { 88 | binding: 0, 89 | visibility: wgpu::ShaderStage::COMPUTE, 90 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 91 | }, 92 | wgpu::BindGroupLayoutEntry { 93 | binding: 1, 94 | visibility: wgpu::ShaderStage::COMPUTE, 95 | ty: wgpu::BindingType::StorageBuffer { 96 | dynamic: false, 97 | readonly: true, 98 | }, 99 | }, 100 | wgpu::BindGroupLayoutEntry { 101 | binding: 2, 102 | visibility: wgpu::ShaderStage::COMPUTE, 103 | ty: wgpu::BindingType::StorageBuffer { 104 | dynamic: false, 105 | readonly: true, 106 | }, 107 | }, 108 | ], 109 | }, 110 | ); 111 | 112 | let dst_bind_group_layout = ctx.device.create_bind_group_layout( 113 | &wgpu::BindGroupLayoutDescriptor { 114 | label: None, 115 | bindings: &[ 116 | wgpu::BindGroupLayoutEntry { 117 | binding: 0, 118 | visibility: wgpu::ShaderStage::COMPUTE, 119 | ty: wgpu::BindingType::StorageBuffer { 120 | dynamic: false, 121 | readonly: false, 122 | }, 123 | }, 124 | wgpu::BindGroupLayoutEntry { 125 | binding: 1, 126 | visibility: wgpu::ShaderStage::COMPUTE, 127 | ty: wgpu::BindingType::StorageBuffer { 128 | dynamic: false, 129 | readonly: false, 130 | }, 131 | }, 132 | ], 133 | }, 134 | ); 135 | 136 | let pipeline_layout = ctx.device.create_pipeline_layout( 137 | &wgpu::PipelineLayoutDescriptor { 138 | bind_group_layouts: &[ 139 | &uniform_bind_group_layout, 140 | &src_bind_group_layout, 141 | &dst_bind_group_layout, 142 | ], 143 | }, 144 | ); 145 | 146 | let pipeline = ctx.device.create_compute_pipeline( 147 | &wgpu::ComputePipelineDescriptor { 148 | layout: &pipeline_layout, 149 | compute_stage: wgpu::ProgrammableStageDescriptor { 150 | module: &shader_module, 151 | entry_point: "main", 152 | }, 153 | }, 154 | ); 155 | 156 | Ok(Self { 157 | pipeline, 158 | uniform_bind_group_layout, 159 | src_bind_group_layout, 160 | dst_bind_group_layout, 161 | }) 162 | } 163 | 164 | pub fn create_mesh_binding( 165 | &self, 166 | ctx: &GraphicsContext, 167 | vertices: &Vec, 168 | indices: &Vec, 169 | ) -> MeshBinding { 170 | let simplex_count = (indices.len() / 4) as u32; 171 | let vertex_buffer_size = (vertices.len() 172 | * std::mem::size_of::()) 173 | as wgpu::BufferAddress; 174 | let index_buffer_size = 175 | (indices.len() * std::mem::size_of::()) as wgpu::BufferAddress; 176 | 177 | // overestimate of how many triangles can be generated 178 | let dst_vertex_buffer_size = 179 | (simplex_count * 12 * std::mem::size_of::() as u32) 180 | as wgpu::BufferAddress; 181 | 182 | let slice_plane_buffer = ctx.device.create_buffer_with_data( 183 | bytemuck::cast_slice(&[SlicePlane::default()]), 184 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 185 | ); 186 | 187 | let transform_buffer = ctx.device.create_buffer_with_data( 188 | bytemuck::cast_slice(&[Transform4::default()]), 189 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 190 | ); 191 | 192 | let indirect_command_buffer = ctx.device.create_buffer_with_data( 193 | bytemuck::cast_slice(&[DrawIndirectCommand::default()]), 194 | wgpu::BufferUsage::INDIRECT 195 | | wgpu::BufferUsage::STORAGE 196 | | wgpu::BufferUsage::COPY_DST, 197 | ); 198 | 199 | let dst_vertex_buffer = 200 | ctx.device.create_buffer(&wgpu::BufferDescriptor { 201 | label: Some("dst_vertex_buffer"), 202 | size: dst_vertex_buffer_size, 203 | usage: wgpu::BufferUsage::STORAGE | wgpu::BufferUsage::VERTEX, 204 | }); 205 | 206 | let simplex_count_buffer = ctx.device.create_buffer_with_data( 207 | bytemuck::cast_slice(&[simplex_count]), 208 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 209 | ); 210 | 211 | let vertex_buffer = ctx.device.create_buffer_with_data( 212 | bytemuck::cast_slice(&vertices), 213 | wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::STORAGE_READ, 214 | ); 215 | 216 | let index_buffer = ctx.device.create_buffer_with_data( 217 | bytemuck::cast_slice(&indices), 218 | wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::STORAGE_READ, 219 | ); 220 | 221 | let src_bind_group = 222 | ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { 223 | label: Some("slice_src_bind_group"), 224 | layout: &self.src_bind_group_layout, 225 | bindings: &[ 226 | wgpu::Binding { 227 | binding: 0, 228 | resource: wgpu::BindingResource::Buffer { 229 | buffer: &simplex_count_buffer, 230 | range: 0..std::mem::size_of::() 231 | as wgpu::BufferAddress, 232 | }, 233 | }, 234 | wgpu::Binding { 235 | binding: 1, 236 | resource: wgpu::BindingResource::Buffer { 237 | buffer: &vertex_buffer, 238 | range: 0..vertex_buffer_size, 239 | }, 240 | }, 241 | wgpu::Binding { 242 | binding: 2, 243 | resource: wgpu::BindingResource::Buffer { 244 | buffer: &index_buffer, 245 | range: 0..index_buffer_size, 246 | }, 247 | }, 248 | ], 249 | }); 250 | 251 | let uniform_bind_group = 252 | ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { 253 | label: Some("slice_uniform_bind_group"), 254 | layout: &self.uniform_bind_group_layout, 255 | bindings: &[ 256 | wgpu::Binding { 257 | binding: 0, 258 | resource: wgpu::BindingResource::Buffer { 259 | buffer: &slice_plane_buffer, 260 | range: 0..std::mem::size_of::() 261 | as wgpu::BufferAddress, 262 | }, 263 | }, 264 | wgpu::Binding { 265 | binding: 1, 266 | resource: wgpu::BindingResource::Buffer { 267 | buffer: &transform_buffer, 268 | range: 0..std::mem::size_of::() 269 | as wgpu::BufferAddress, 270 | }, 271 | }, 272 | ], 273 | }); 274 | 275 | let dst_bind_group = 276 | ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { 277 | label: Some("slice_dst_bind_group"), 278 | layout: &self.dst_bind_group_layout, 279 | bindings: &[ 280 | wgpu::Binding { 281 | binding: 0, 282 | resource: wgpu::BindingResource::Buffer { 283 | buffer: &indirect_command_buffer, 284 | range: 0..std::mem::size_of::() 285 | as wgpu::BufferAddress, 286 | }, 287 | }, 288 | wgpu::Binding { 289 | binding: 1, 290 | resource: wgpu::BindingResource::Buffer { 291 | buffer: &dst_vertex_buffer, 292 | range: 0..dst_vertex_buffer_size, 293 | }, 294 | }, 295 | ], 296 | }); 297 | 298 | MeshBinding { 299 | uniform_bind_group, 300 | src_bind_group, 301 | dst_bind_group, 302 | simplex_count, 303 | slice_plane_buffer, 304 | transform_buffer, 305 | indirect_command_buffer, 306 | dst_vertex_buffer, 307 | } 308 | } 309 | 310 | pub fn render_mesh( 311 | &self, 312 | ctx: &GraphicsContext, 313 | encoder: &mut wgpu::CommandEncoder, 314 | slice: &SlicePlane, 315 | transform: &Transform4, 316 | mesh: &MeshBinding, 317 | ) { 318 | // update slice 319 | let slice_staging_buffer = ctx.device.create_buffer_with_data( 320 | bytemuck::cast_slice(&[*slice]), 321 | wgpu::BufferUsage::COPY_SRC, 322 | ); 323 | encoder.copy_buffer_to_buffer( 324 | &slice_staging_buffer, 325 | 0, 326 | &mesh.slice_plane_buffer, 327 | 0, 328 | std::mem::size_of::() as wgpu::BufferAddress, 329 | ); 330 | 331 | // update transform 332 | let transform_staging_buffer = ctx.device.create_buffer_with_data( 333 | bytemuck::cast_slice(&[*transform]), 334 | wgpu::BufferUsage::COPY_SRC, 335 | ); 336 | encoder.copy_buffer_to_buffer( 337 | &transform_staging_buffer, 338 | 0, 339 | &mesh.transform_buffer, 340 | 0, 341 | std::mem::size_of::() as wgpu::BufferAddress, 342 | ); 343 | 344 | // reset indirect command buffer 345 | let command_staging_buffer = ctx.device.create_buffer_with_data( 346 | bytemuck::cast_slice(&[DrawIndirectCommand::default()]), 347 | wgpu::BufferUsage::COPY_SRC, 348 | ); 349 | encoder.copy_buffer_to_buffer( 350 | &command_staging_buffer, 351 | 0, 352 | &mesh.indirect_command_buffer, 353 | 0, 354 | std::mem::size_of::() as wgpu::BufferAddress, 355 | ); 356 | 357 | // Compute into the destination bind group 358 | let mut compute_pass = encoder.begin_compute_pass(); 359 | compute_pass.set_pipeline(&self.pipeline); 360 | compute_pass.set_bind_group(0, &mesh.uniform_bind_group, &[]); 361 | compute_pass.set_bind_group(1, &mesh.src_bind_group, &[]); 362 | compute_pass.set_bind_group(2, &mesh.dst_bind_group, &[]); 363 | compute_pass.dispatch( 364 | ceil_div(mesh.simplex_count, WORK_GROUP_SIZE), 365 | 1, 366 | 1, 367 | ); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/context/graphics/slice_plane.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{ 2 | prelude::{One, Zero}, 3 | Matrix4, Vector4, 4 | }; 5 | 6 | #[repr(C)] 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct SlicePlane { 9 | pub normal: Vector4, 10 | pub base_point: Vector4, 11 | pub proj_matrix: Matrix4, 12 | } 13 | 14 | unsafe impl bytemuck::Pod for SlicePlane {} 15 | unsafe impl bytemuck::Zeroable for SlicePlane {} 16 | 17 | impl Default for SlicePlane { 18 | fn default() -> Self { 19 | Self { 20 | normal: Vector4::unit_w(), 21 | base_point: Vector4::zero(), 22 | proj_matrix: Matrix4::one(), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/context/graphics/transform4.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{ 2 | prelude::{One, Zero}, 3 | Matrix4, Vector4, 4 | }; 5 | 6 | // Vector5s and Matrix5s are kinda annoying. We're just going to store 7 | // transforms as a displacement and a matrix. 8 | // Who needs homogeneous coordinates anyway? 9 | #[repr(C)] 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct Transform4 { 12 | pub displacement: Vector4, 13 | pub transform: Matrix4, 14 | } 15 | 16 | unsafe impl bytemuck::Pod for Transform4 {} 17 | unsafe impl bytemuck::Zeroable for Transform4 {} 18 | 19 | impl Default for Transform4 { 20 | fn default() -> Self { 21 | Self { 22 | displacement: Vector4::zero(), 23 | transform: Matrix4::one(), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/context/graphics/triangle_list_pipeline.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | GraphicsContext, Light, MeshBinding, Vertex3, ViewProjection, DEPTH_FORMAT, 3 | }; 4 | 5 | use anyhow::{anyhow, Context, Result}; 6 | 7 | const SAMPLE_COUNT: u32 = 4; 8 | 9 | pub struct TriangleListPipeline { 10 | pipeline: wgpu::RenderPipeline, 11 | pub view_proj_buffer: wgpu::Buffer, 12 | uniform_bind_group: wgpu::BindGroup, 13 | } 14 | 15 | impl TriangleListPipeline { 16 | pub fn new( 17 | ctx: &GraphicsContext, 18 | light_buffer: &wgpu::Buffer, 19 | shadow_texture: &wgpu::TextureView, 20 | shadow_sampler: &wgpu::Sampler, 21 | ) -> Result { 22 | let vs_src = include_str!("shaders/shader.vert"); 23 | let fs_src = include_str!("shaders/shader.frag"); 24 | 25 | let vs_spirv = 26 | glsl_to_spirv::compile(vs_src, glsl_to_spirv::ShaderType::Vertex) 27 | .map_err(|s| anyhow!(s)) 28 | .context("Failed to compile 'shaders/shader.vert' to SPIR-V")?; 29 | let fs_spirv = 30 | glsl_to_spirv::compile(fs_src, glsl_to_spirv::ShaderType::Fragment) 31 | .map_err(|s| anyhow!(s)) 32 | .context("Failed to compile 'shaders/shader.frag' to SPIR-V")?; 33 | 34 | let vs_data = wgpu::read_spirv(vs_spirv)?; 35 | let fs_data = wgpu::read_spirv(fs_spirv)?; 36 | 37 | let vs_module = ctx.device.create_shader_module(&vs_data); 38 | let fs_module = ctx.device.create_shader_module(&fs_data); 39 | 40 | let view_proj_buffer = ctx.device.create_buffer_with_data( 41 | bytemuck::cast_slice(&[ViewProjection::default()]), 42 | wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, 43 | ); 44 | 45 | let uniform_bind_group_layout = ctx.device.create_bind_group_layout( 46 | &wgpu::BindGroupLayoutDescriptor { 47 | label: None, 48 | bindings: &[ 49 | wgpu::BindGroupLayoutEntry { 50 | binding: 0, 51 | visibility: wgpu::ShaderStage::VERTEX, 52 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 53 | }, 54 | wgpu::BindGroupLayoutEntry { 55 | binding: 1, 56 | visibility: wgpu::ShaderStage::FRAGMENT, 57 | ty: wgpu::BindingType::UniformBuffer { dynamic: false }, 58 | }, 59 | wgpu::BindGroupLayoutEntry { 60 | binding: 2, 61 | visibility: wgpu::ShaderStage::FRAGMENT, 62 | ty: wgpu::BindingType::SampledTexture { 63 | multisampled: false, 64 | dimension: wgpu::TextureViewDimension::D2, 65 | component_type: wgpu::TextureComponentType::Uint, 66 | }, 67 | }, 68 | wgpu::BindGroupLayoutEntry { 69 | binding: 3, 70 | visibility: wgpu::ShaderStage::FRAGMENT, 71 | ty: wgpu::BindingType::Sampler { comparison: true }, 72 | }, 73 | ], 74 | }, 75 | ); 76 | 77 | let uniform_bind_group = 78 | ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { 79 | label: None, 80 | layout: &uniform_bind_group_layout, 81 | bindings: &[ 82 | wgpu::Binding { 83 | binding: 0, 84 | resource: wgpu::BindingResource::Buffer { 85 | buffer: &view_proj_buffer, 86 | range: 0..std::mem::size_of::() 87 | as wgpu::BufferAddress, 88 | }, 89 | }, 90 | wgpu::Binding { 91 | binding: 1, 92 | resource: wgpu::BindingResource::Buffer { 93 | buffer: light_buffer, 94 | range: 0..std::mem::size_of::() 95 | as wgpu::BufferAddress, 96 | }, 97 | }, 98 | wgpu::Binding { 99 | binding: 2, 100 | resource: wgpu::BindingResource::TextureView( 101 | shadow_texture, 102 | ), 103 | }, 104 | wgpu::Binding { 105 | binding: 3, 106 | resource: wgpu::BindingResource::Sampler( 107 | shadow_sampler, 108 | ), 109 | }, 110 | ], 111 | }); 112 | 113 | let pipeline_layout = ctx.device.create_pipeline_layout( 114 | &wgpu::PipelineLayoutDescriptor { 115 | bind_group_layouts: &[&uniform_bind_group_layout], 116 | }, 117 | ); 118 | 119 | let pipeline = ctx.device.create_render_pipeline( 120 | &wgpu::RenderPipelineDescriptor { 121 | layout: &pipeline_layout, 122 | vertex_stage: wgpu::ProgrammableStageDescriptor { 123 | module: &vs_module, 124 | entry_point: "main", 125 | }, 126 | fragment_stage: Some(wgpu::ProgrammableStageDescriptor { 127 | module: &fs_module, 128 | entry_point: "main", 129 | }), 130 | rasterization_state: Some(wgpu::RasterizationStateDescriptor { 131 | front_face: wgpu::FrontFace::Ccw, 132 | cull_mode: wgpu::CullMode::None, 133 | depth_bias: 0, 134 | depth_bias_slope_scale: 0.0, 135 | depth_bias_clamp: 0.0, 136 | }), 137 | color_states: &[wgpu::ColorStateDescriptor { 138 | format: ctx.sc_desc.format, 139 | color_blend: wgpu::BlendDescriptor::REPLACE, 140 | alpha_blend: wgpu::BlendDescriptor::REPLACE, 141 | write_mask: wgpu::ColorWrite::ALL, 142 | }], 143 | primitive_topology: wgpu::PrimitiveTopology::TriangleList, 144 | depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { 145 | format: DEPTH_FORMAT, 146 | depth_write_enabled: true, 147 | depth_compare: wgpu::CompareFunction::Less, 148 | stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE, 149 | stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE, 150 | stencil_read_mask: 0, 151 | stencil_write_mask: 0, 152 | }), 153 | vertex_state: wgpu::VertexStateDescriptor { 154 | index_format: wgpu::IndexFormat::Uint16, 155 | vertex_buffers: &[Vertex3::desc()], 156 | }, 157 | sample_count: SAMPLE_COUNT, 158 | sample_mask: !0, 159 | alpha_to_coverage_enabled: false, 160 | }, 161 | ); 162 | 163 | Ok(Self { 164 | pipeline, 165 | view_proj_buffer, 166 | uniform_bind_group, 167 | }) 168 | } 169 | 170 | pub fn create_ms_framebuffer( 171 | &self, 172 | ctx: &GraphicsContext, 173 | ) -> wgpu::TextureView { 174 | let multisampled_texture_extent = wgpu::Extent3d { 175 | width: ctx.sc_desc.width, 176 | height: ctx.sc_desc.height, 177 | depth: 1, 178 | }; 179 | let multisampled_frame_descriptor = &wgpu::TextureDescriptor { 180 | label: None, 181 | size: multisampled_texture_extent, 182 | mip_level_count: 1, 183 | array_layer_count: 1, 184 | sample_count: SAMPLE_COUNT, 185 | dimension: wgpu::TextureDimension::D2, 186 | format: ctx.sc_desc.format, 187 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 188 | }; 189 | 190 | ctx.device 191 | .create_texture(multisampled_frame_descriptor) 192 | .create_default_view() 193 | } 194 | 195 | pub fn create_ms_depth_texture( 196 | &self, 197 | ctx: &GraphicsContext, 198 | ) -> wgpu::TextureView { 199 | let multisampled_texture_extent = wgpu::Extent3d { 200 | width: ctx.sc_desc.width, 201 | height: ctx.sc_desc.height, 202 | depth: 1, 203 | }; 204 | let multisampled_frame_descriptor = &wgpu::TextureDescriptor { 205 | label: None, 206 | size: multisampled_texture_extent, 207 | mip_level_count: 1, 208 | array_layer_count: 1, 209 | sample_count: SAMPLE_COUNT, 210 | dimension: wgpu::TextureDimension::D2, 211 | format: DEPTH_FORMAT, 212 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 213 | }; 214 | 215 | ctx.device 216 | .create_texture(multisampled_frame_descriptor) 217 | .create_default_view() 218 | } 219 | 220 | pub fn update_view_proj( 221 | &self, 222 | ctx: &mut GraphicsContext, 223 | view_proj: &ViewProjection, 224 | ) { 225 | let mut encoder = ctx.device.create_command_encoder( 226 | &wgpu::CommandEncoderDescriptor { 227 | label: Some("view_proj_update_encoder"), 228 | }, 229 | ); 230 | // update the projection 231 | { 232 | let staging_buffer = ctx.device.create_buffer_with_data( 233 | bytemuck::cast_slice(&[*view_proj]), 234 | wgpu::BufferUsage::COPY_SRC, 235 | ); 236 | encoder.copy_buffer_to_buffer( 237 | &staging_buffer, 238 | 0, 239 | &self.view_proj_buffer, 240 | 0, 241 | std::mem::size_of::() as wgpu::BufferAddress, 242 | ); 243 | } 244 | ctx.queue.submit(&[encoder.finish()]); 245 | } 246 | 247 | pub fn render<'a: 'c, 'b, 'c>( 248 | &'a self, 249 | render_pass: &'b mut wgpu::RenderPass<'c>, 250 | mesh: &'a MeshBinding, 251 | ) { 252 | render_pass.set_pipeline(&self.pipeline); 253 | render_pass.set_vertex_buffer(0, &mesh.dst_vertex_buffer, 0, 0); 254 | render_pass.set_bind_group(0, &self.uniform_bind_group, &[]); 255 | render_pass.draw_indirect(&mesh.indirect_command_buffer, 0); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/context/graphics/vertex3.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector4; 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vertex3 { 6 | pub position: Vector4, 7 | pub color: Vector4, 8 | pub normal: Vector4, 9 | } 10 | 11 | impl Vertex3 { 12 | pub fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { 13 | use std::mem; 14 | wgpu::VertexBufferDescriptor { 15 | stride: mem::size_of::() as wgpu::BufferAddress, 16 | step_mode: wgpu::InputStepMode::Vertex, 17 | attributes: &[ 18 | wgpu::VertexAttributeDescriptor { 19 | offset: 0, 20 | shader_location: 0, 21 | format: wgpu::VertexFormat::Float4, 22 | }, 23 | wgpu::VertexAttributeDescriptor { 24 | offset: mem::size_of::>() 25 | as wgpu::BufferAddress, 26 | shader_location: 1, 27 | format: wgpu::VertexFormat::Float4, 28 | }, 29 | wgpu::VertexAttributeDescriptor { 30 | offset: mem::size_of::>() 31 | as wgpu::BufferAddress 32 | * 2, 33 | shader_location: 2, 34 | format: wgpu::VertexFormat::Float4, 35 | }, 36 | ], 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/context/graphics/vertex4.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector4; 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Vertex4 { 6 | pub position: Vector4, 7 | pub color: Vector4, 8 | } 9 | 10 | unsafe impl bytemuck::Pod for Vertex4 {} 11 | unsafe impl bytemuck::Zeroable for Vertex4 {} 12 | 13 | impl Vertex4 { 14 | pub fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> { 15 | use std::mem; 16 | wgpu::VertexBufferDescriptor { 17 | stride: mem::size_of::() as wgpu::BufferAddress, 18 | step_mode: wgpu::InputStepMode::Vertex, 19 | attributes: &[ 20 | wgpu::VertexAttributeDescriptor { 21 | offset: 0, 22 | shader_location: 0, 23 | format: wgpu::VertexFormat::Float4, 24 | }, 25 | wgpu::VertexAttributeDescriptor { 26 | offset: mem::size_of::>() 27 | as wgpu::BufferAddress, 28 | shader_location: 1, 29 | format: wgpu::VertexFormat::Float4, 30 | }, 31 | ], 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/context/graphics/view_projection.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Ctx; 2 | use cgmath::{Deg, Matrix4, One, Point3, SquareMatrix, Vector3, Vector4}; 3 | 4 | #[repr(C)] 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct ViewProjection { 7 | view_proj: Matrix4, 8 | } 9 | 10 | unsafe impl bytemuck::Pod for ViewProjection {} 11 | unsafe impl bytemuck::Zeroable for ViewProjection {} 12 | 13 | impl ViewProjection { 14 | pub fn new( 15 | ctx: &Ctx, 16 | fovy: f32, 17 | look_from: Point3, 18 | look_at: Point3, 19 | ) -> Self { 20 | let aspect = ctx.graphics_ctx.sc_desc.width as f32 21 | / ctx.graphics_ctx.sc_desc.height as f32; 22 | 23 | Self { 24 | view_proj: cgmath::perspective(Deg(fovy), aspect, 1.0, 100.0) 25 | * Matrix4::look_at(look_from, look_at, Vector3::unit_y()), 26 | } 27 | } 28 | 29 | pub fn world_to_screen(&self, world: Vector4) -> Vector4 { 30 | self.view_proj * world 31 | } 32 | 33 | pub fn screen_to_world(&self, screen: Vector4) -> Vector4 { 34 | self.view_proj.invert().unwrap() * screen 35 | } 36 | } 37 | 38 | impl Default for ViewProjection { 39 | fn default() -> Self { 40 | Self { 41 | view_proj: Matrix4::one(), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/context/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod graphics; 2 | 3 | use anyhow::Result; 4 | use winit::{ 5 | dpi::PhysicalSize, 6 | event::{Event, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | window::{Window, WindowBuilder}, 9 | }; 10 | 11 | pub use graphics::GraphicsContext; 12 | 13 | pub trait Application: 'static + Sized { 14 | fn init(ctx: &mut Ctx) -> Self; 15 | fn resize(&mut self, ctx: &mut Ctx); 16 | fn on_event(&mut self, ctx: &mut Ctx, event: WindowEvent); 17 | fn update(&mut self, ctx: &mut Ctx); 18 | fn render<'ui>( 19 | &mut self, 20 | ctx: &mut GraphicsContext, 21 | frame: &wgpu::SwapChainOutput, 22 | ui: &imgui::Ui<'ui>, 23 | ); 24 | } 25 | 26 | pub struct Ctx { 27 | pub window: Window, 28 | pub swap_chain: wgpu::SwapChain, 29 | 30 | pub graphics_ctx: GraphicsContext, 31 | 32 | pub imgui: imgui::Context, 33 | pub imgui_platform: imgui_winit_support::WinitPlatform, 34 | pub imgui_renderer: imgui_wgpu::Renderer, 35 | } 36 | 37 | impl Ctx { 38 | async fn new( 39 | title: &str, 40 | size: (u32, u32), 41 | event_loop: &EventLoop<()>, 42 | ) -> Result { 43 | let window = WindowBuilder::new() 44 | .with_inner_size(PhysicalSize::new(size.0, size.1)) 45 | .with_title(title) 46 | .build(event_loop)?; 47 | 48 | let (mut graphics_ctx, swap_chain) = 49 | GraphicsContext::new(&window).await?; 50 | 51 | let mut imgui = imgui::Context::create(); 52 | let mut imgui_platform = 53 | imgui_winit_support::WinitPlatform::init(&mut imgui); 54 | imgui_platform.attach_window( 55 | imgui.io_mut(), 56 | &window, 57 | imgui_winit_support::HiDpiMode::Default, 58 | ); 59 | let imgui_renderer = imgui_wgpu::Renderer::new( 60 | &mut imgui, 61 | &graphics_ctx.device, 62 | &mut graphics_ctx.queue, 63 | graphics_ctx.sc_desc.format, 64 | None, 65 | ); 66 | 67 | Ok(Self { 68 | window, 69 | swap_chain, 70 | graphics_ctx, 71 | imgui, 72 | imgui_platform, 73 | imgui_renderer, 74 | }) 75 | } 76 | } 77 | 78 | pub async fn run( 79 | title: &str, 80 | size: (u32, u32), 81 | ) -> Result<()> { 82 | let event_loop = EventLoop::new(); 83 | let mut ctx = Ctx::new(title, size, &event_loop).await?; 84 | 85 | let mut app = App::init(&mut ctx); 86 | 87 | event_loop.run(move |event, _, control_flow| { 88 | /* 89 | if let Event::WindowEvent { 90 | event: 91 | WindowEvent::CursorMoved { 92 | ref mut position, .. 93 | }, 94 | .. 95 | } = event 96 | { 97 | *position = position 98 | .to_logical::(1.0) 99 | .to_physical(ctx.imgui_platform.hidpi_factor()); 100 | } 101 | */ 102 | 103 | ctx.imgui_platform.handle_event( 104 | ctx.imgui.io_mut(), 105 | &ctx.window, 106 | &event, 107 | ); 108 | 109 | match event { 110 | Event::WindowEvent { event, .. } => { 111 | match event { 112 | WindowEvent::CloseRequested => { 113 | *control_flow = ControlFlow::Exit 114 | } 115 | WindowEvent::Resized(size) => { 116 | ctx.graphics_ctx.sc_desc.width = size.width; 117 | ctx.graphics_ctx.sc_desc.height = size.height; 118 | ctx.swap_chain = 119 | ctx.graphics_ctx.device.create_swap_chain( 120 | &ctx.graphics_ctx.surface, 121 | &ctx.graphics_ctx.sc_desc, 122 | ); 123 | app.resize(&mut ctx); 124 | } 125 | _ => (), 126 | }; 127 | app.on_event(&mut ctx, event); 128 | } 129 | Event::MainEventsCleared => { 130 | app.update(&mut ctx); 131 | ctx.window.request_redraw(); 132 | } 133 | Event::RedrawRequested(_) => { 134 | let frame = ctx.swap_chain.get_next_texture().unwrap(); 135 | ctx.imgui_platform 136 | .prepare_frame(ctx.imgui.io_mut(), &ctx.window) 137 | .expect("Failed to prepare frame."); 138 | 139 | let ui = ctx.imgui.frame(); 140 | 141 | app.render(&mut ctx.graphics_ctx, &frame, &ui); 142 | 143 | let mut encoder = ctx 144 | .graphics_ctx 145 | .device 146 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { 147 | label: Some("imgui_encoder"), 148 | }); 149 | 150 | ctx.imgui_platform.prepare_render(&ui, &ctx.window); 151 | ctx.imgui_renderer 152 | .render( 153 | ui.render(), 154 | &mut ctx.graphics_ctx.device, 155 | &mut encoder, 156 | &frame.view, 157 | ) 158 | .expect("imgui rendering failed."); 159 | 160 | ctx.graphics_ctx.queue.submit(&[encoder.finish()]); 161 | } 162 | _ => (), 163 | } 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod alg; 2 | mod context; 3 | mod mesh; 4 | mod mesh4; 5 | mod physics; 6 | mod shapes; 7 | mod util; 8 | mod world; 9 | 10 | use anyhow::Result; 11 | use cgmath::{InnerSpace, Matrix4, Point3, SquareMatrix, Vector4, Zero}; 12 | use winit::event::{WindowEvent, VirtualKeyCode}; 13 | 14 | use context::graphics::{ 15 | Light, ShadowPipeline, SlicePipeline, SlicePlane, TriangleListPipeline, 16 | ViewProjection, 17 | }; 18 | use context::{Application, Ctx, GraphicsContext}; 19 | use physics::Material; 20 | use shapes::RegularSolid; 21 | use world::{ObjectKey, World}; 22 | 23 | #[derive(Debug)] 24 | struct DragSelection { 25 | key: ObjectKey, 26 | plane_normal: Vector4, 27 | plane_distance: f32, 28 | anchor_offset: Vector4, 29 | } 30 | 31 | struct KeyStates { 32 | up: bool, 33 | down: bool, 34 | ana: bool, 35 | kata: bool, 36 | } 37 | 38 | struct TestApp { 39 | render_pipeline: TriangleListPipeline, 40 | slice_pipeline: SlicePipeline, 41 | shadow_pipeline: ShadowPipeline, 42 | slice_plane: SlicePlane, 43 | shadow_texture: wgpu::TextureView, 44 | depth_texture: wgpu::TextureView, 45 | ms_framebuffer: wgpu::TextureView, 46 | view_proj: ViewProjection, 47 | world: World, 48 | frames: usize, 49 | steps: usize, 50 | cursor_ray: (Vector4, Vector4), 51 | selection: Option, 52 | drag_selection: Option, 53 | key_states: KeyStates, 54 | } 55 | 56 | const ARENA_SIZE: f32 = 4.0; 57 | 58 | impl Application for TestApp { 59 | fn init(ctx: &mut Ctx) -> Self { 60 | let orthogonal = SlicePlane { 61 | normal: Vector4::new(0.0, 0.0, 0.0, 1.0), 62 | base_point: Vector4::new(0.0, 0.0, 0.0, 0.0), 63 | #[rustfmt::skip] 64 | proj_matrix: Matrix4::new( 65 | 1.0, 0.0, 0.0, 0.0, 66 | 0.0, 1.0, 0.0, 0.0, 67 | 0.0, 0.0, 1.0, 0.0, 68 | 0.0, 0.0, 0.0, 1.0, 69 | ), 70 | }; 71 | 72 | let slice_plane = orthogonal; 73 | 74 | let light = Light::new( 75 | Point3::new(-4.0, 10.0, -6.0), 76 | 60.0, 77 | Vector4::new(1.0, 1.0, 1.0, 1.0), 78 | ); 79 | 80 | let shadow_pipeline = ShadowPipeline::new(&ctx.graphics_ctx).unwrap(); 81 | shadow_pipeline.update_light(&mut ctx.graphics_ctx, &light); 82 | 83 | let shadow_texture = shadow_pipeline.new_texture(&ctx.graphics_ctx); 84 | let shadow_sampler = shadow_pipeline.new_sampler(&ctx.graphics_ctx); 85 | 86 | let render_pipeline = TriangleListPipeline::new( 87 | &ctx.graphics_ctx, 88 | &shadow_pipeline.light_buffer, 89 | &shadow_texture, 90 | &shadow_sampler, 91 | ) 92 | .unwrap(); 93 | let slice_pipeline = SlicePipeline::new(&ctx.graphics_ctx).unwrap(); 94 | 95 | let mut world = World::new(); 96 | 97 | world.objects.insert(shapes::create_floor( 98 | &ctx.graphics_ctx, 99 | &slice_pipeline, 100 | 2.0 * ARENA_SIZE, 101 | Material { restitution: 0.4 }, 102 | )); 103 | 104 | // side walls 105 | world.objects.insert(shapes::create_wall( 106 | -ARENA_SIZE * Vector4::unit_x(), 107 | Vector4::unit_x(), 108 | Material { restitution: 0.4 }, 109 | )); 110 | world.objects.insert(shapes::create_wall( 111 | ARENA_SIZE * Vector4::unit_x(), 112 | -Vector4::unit_x(), 113 | Material { restitution: 0.4 }, 114 | )); 115 | world.objects.insert(shapes::create_wall( 116 | -ARENA_SIZE * Vector4::unit_z(), 117 | Vector4::unit_z(), 118 | Material { restitution: 0.4 }, 119 | )); 120 | world.objects.insert(shapes::create_wall( 121 | ARENA_SIZE * Vector4::unit_z(), 122 | -Vector4::unit_z(), 123 | Material { restitution: 0.4 }, 124 | )); 125 | world.objects.insert(shapes::create_wall( 126 | -ARENA_SIZE * Vector4::unit_w(), 127 | Vector4::unit_w(), 128 | Material { restitution: 0.4 }, 129 | )); 130 | world.objects.insert(shapes::create_wall( 131 | ARENA_SIZE * Vector4::unit_w(), 132 | -Vector4::unit_w(), 133 | Material { restitution: 0.4 }, 134 | )); 135 | 136 | let view_proj = ViewProjection::new( 137 | ctx, 138 | 90.0, 139 | Point3::new(1.0, 5.0, -5.0), 140 | Point3::new(0.0, 0.0, 0.0), 141 | ); 142 | 143 | let depth_texture = 144 | render_pipeline.create_ms_depth_texture(&ctx.graphics_ctx); 145 | let ms_framebuffer = 146 | render_pipeline.create_ms_framebuffer(&ctx.graphics_ctx); 147 | 148 | TestApp { 149 | render_pipeline, 150 | slice_pipeline, 151 | shadow_pipeline, 152 | slice_plane, 153 | shadow_texture, 154 | ms_framebuffer, 155 | depth_texture, 156 | view_proj, 157 | world, 158 | frames: 0, 159 | steps: 0, 160 | cursor_ray: (Vector4::zero(), Vector4::unit_z()), 161 | selection: None, 162 | drag_selection: None, 163 | key_states: KeyStates { 164 | up: false, 165 | down: false, 166 | ana: false, 167 | kata: false, 168 | }, 169 | } 170 | } 171 | 172 | fn resize(&mut self, ctx: &mut Ctx) { 173 | // update the projection 174 | self.view_proj = ViewProjection::new( 175 | ctx, 176 | 90.0, 177 | Point3::new(1.0, 5.0, -5.0), 178 | Point3::new(0.0, 0.0, 0.0), 179 | ); 180 | 181 | self.depth_texture = self 182 | .render_pipeline 183 | .create_ms_depth_texture(&ctx.graphics_ctx); 184 | self.ms_framebuffer = self 185 | .render_pipeline 186 | .create_ms_framebuffer(&ctx.graphics_ctx); 187 | } 188 | 189 | fn on_event(&mut self, ctx: &mut Ctx, event: WindowEvent) { 190 | match event { 191 | WindowEvent::CursorMoved { position, .. } => { 192 | let size = ctx.window.inner_size(); 193 | let (x, y) = ( 194 | position.x as f32 / size.width as f32 * 2.0 - 1.0, 195 | position.y as f32 / size.height as f32 * -2.0 + 1.0, 196 | ); 197 | 198 | let mut v0 = self 199 | .view_proj 200 | .screen_to_world(Vector4::new(x, y, -1.0, 1.0)); 201 | v0 /= v0.w; 202 | v0.w = 0.0; 203 | v0 = self.slice_plane.proj_matrix.invert().unwrap() * v0 204 | + self.slice_plane.base_point; 205 | 206 | let mut v1 = self 207 | .view_proj 208 | .screen_to_world(Vector4::new(x, y, 1.0, 1.0)); 209 | v1 /= v1.w; 210 | v1.w = 0.0; 211 | v1 = self.slice_plane.proj_matrix.invert().unwrap() * v1 212 | + self.slice_plane.base_point; 213 | 214 | self.cursor_ray = (v0, (v1 - v0).normalize()); 215 | } 216 | WindowEvent::MouseInput { 217 | state: winit::event::ElementState::Pressed, 218 | button: winit::event::MouseButton::Left, 219 | .. 220 | } => { 221 | let mut min_lambda = std::f32::INFINITY; 222 | let mut selection = None; 223 | for (key, object) in self.world.objects.iter() { 224 | match object 225 | .body 226 | .ray_intersect(self.cursor_ray.0, self.cursor_ray.1) 227 | { 228 | Some(lambda) => { 229 | if lambda < min_lambda { 230 | selection = Some(key); 231 | min_lambda = lambda; 232 | } 233 | } 234 | None => (), 235 | } 236 | } 237 | 238 | match selection { 239 | Some(key) => { 240 | let object = &self.world.objects[key]; 241 | let contact_point = 242 | self.cursor_ray.0 + self.cursor_ray.1 * min_lambda; 243 | let plane_normal = Vector4::unit_y(); 244 | let plane_distance = contact_point.dot(plane_normal); 245 | let anchor_offset = contact_point - object.body.pos; 246 | 247 | self.selection = Some(key); 248 | self.drag_selection = Some(DragSelection { 249 | key, 250 | plane_normal, 251 | plane_distance, 252 | anchor_offset, 253 | }); 254 | } 255 | _ => (), 256 | } 257 | } 258 | WindowEvent::MouseInput { 259 | state: winit::event::ElementState::Released, 260 | button: winit::event::MouseButton::Left, 261 | .. 262 | } => { 263 | self.drag_selection = None; 264 | } 265 | WindowEvent::MouseInput { 266 | state: winit::event::ElementState::Pressed, 267 | button: winit::event::MouseButton::Right, 268 | .. 269 | } => { 270 | self.selection = None; 271 | self.drag_selection = None; 272 | } 273 | WindowEvent::KeyboardInput { 274 | input, 275 | is_synthetic: false, 276 | .. 277 | } => { 278 | let pressed = 279 | input.state == winit::event::ElementState::Pressed; 280 | 281 | match input.virtual_keycode { 282 | Some(VirtualKeyCode::W) => self.key_states.up = pressed, 283 | Some(VirtualKeyCode::S) => self.key_states.down = pressed, 284 | Some(VirtualKeyCode::A) => self.key_states.ana = pressed, 285 | Some(VirtualKeyCode::D) => self.key_states.kata = pressed, 286 | _ => (), 287 | } 288 | } 289 | _ => (), 290 | } 291 | } 292 | 293 | fn update(&mut self, _ctx: &mut Ctx) { 294 | let dt = 1f32 / 60f32; 295 | 296 | if let Some(selection) = &mut self.drag_selection { 297 | if let Some(object) = self.world.objects.get_mut(selection.key) { 298 | // intersect the current screen ray with the plane 299 | let lambda = (selection.plane_distance 300 | - self.cursor_ray.0.dot(selection.plane_normal)) 301 | / self.cursor_ray.1.dot(selection.plane_normal); 302 | let contact_point = 303 | self.cursor_ray.0 + self.cursor_ray.1 * lambda; 304 | 305 | if self.key_states.up { 306 | selection.anchor_offset -= Vector4::unit_y() * 0.02; 307 | } 308 | if self.key_states.down { 309 | selection.anchor_offset += Vector4::unit_y() * 0.02; 310 | } 311 | if self.key_states.ana { 312 | selection.anchor_offset -= Vector4::unit_w() * 0.02; 313 | } 314 | if self.key_states.kata { 315 | selection.anchor_offset += Vector4::unit_w() * 0.02; 316 | } 317 | 318 | let displacement = 319 | contact_point - selection.anchor_offset - object.body.pos; 320 | let spring_constant = 0.5; 321 | 322 | object.body.vel.linear += displacement * spring_constant; 323 | 324 | // damping 325 | object.body.vel.linear *= 0.8; 326 | object.body.vel.angular = 0.8 * object.body.vel.angular; 327 | } 328 | } 329 | 330 | self.world.update(dt); 331 | self.steps += 1; 332 | } 333 | 334 | fn render<'ui>( 335 | &mut self, 336 | graphics_ctx: &mut GraphicsContext, 337 | frame: &wgpu::SwapChainOutput, 338 | ui: &imgui::Ui<'ui>, 339 | ) { 340 | use imgui::*; 341 | Window::new(im_str!("w-axis control")).build(ui, || { 342 | VerticalSlider::new( 343 | im_str!(""), 344 | [120.0, 480.0], 345 | -ARENA_SIZE..=ARENA_SIZE, 346 | ) 347 | .build(ui, &mut self.slice_plane.base_point.w); 348 | }); 349 | 350 | Window::new(im_str!("controls")).build(ui, || { 351 | if ui.button(im_str!("Spawn a tesseract"), [0.0, 0.0]) { 352 | self.world.objects.insert( 353 | shapes::ShapeBuilder::new() 354 | .regular_solid(RegularSolid::EightCell) 355 | .build(graphics_ctx, &self.slice_pipeline), 356 | ); 357 | } 358 | if ui.button(im_str!("Spawn a sphere"), [0.0, 0.0]) { 359 | self.world.objects.insert( 360 | shapes::ShapeBuilder::new() 361 | .sphere(0.5) 362 | .build(graphics_ctx, &self.slice_pipeline), 363 | ); 364 | } 365 | if ui.button(im_str!("Spawn a 5-cell"), [0.0, 0.0]) { 366 | self.world.objects.insert( 367 | shapes::ShapeBuilder::new() 368 | .regular_solid(RegularSolid::FiveCell) 369 | .build(graphics_ctx, &self.slice_pipeline), 370 | ); 371 | } 372 | if ui.button(im_str!("Spawn a 16-cell"), [0.0, 0.0]) { 373 | self.world.objects.insert( 374 | shapes::ShapeBuilder::new() 375 | .regular_solid(RegularSolid::SixteenCell) 376 | .build(graphics_ctx, &self.slice_pipeline), 377 | ); 378 | } 379 | if ui.button(im_str!("Spawn a 24-cell"), [0.0, 0.0]) { 380 | self.world.objects.insert( 381 | shapes::ShapeBuilder::new() 382 | .regular_solid(RegularSolid::TwentyFourCell) 383 | .build(graphics_ctx, &self.slice_pipeline), 384 | ); 385 | } 386 | 387 | ui.text("Left click to select and drag an object."); 388 | ui.text("Right click to deselect."); 389 | ui.text("While dragging:"); 390 | ui.text("W/S: raise/lower"); 391 | ui.text("A/D: move in 4th dimension"); 392 | 393 | if let Some(obj) = self 394 | .selection 395 | .and_then(|key| self.world.objects.get_mut(key)) 396 | { 397 | ui.text("Position:"); 398 | { 399 | let token = ui.push_id("position"); 400 | Slider::new(im_str!("x"), -ARENA_SIZE..=ARENA_SIZE) 401 | .build(ui, &mut obj.body.pos.x); 402 | Slider::new(im_str!("y"), -ARENA_SIZE..=ARENA_SIZE) 403 | .build(ui, &mut obj.body.pos.y); 404 | Slider::new(im_str!("z"), -ARENA_SIZE..=ARENA_SIZE) 405 | .build(ui, &mut obj.body.pos.z); 406 | Slider::new(im_str!("w"), -ARENA_SIZE..=ARENA_SIZE) 407 | .build(ui, &mut obj.body.pos.w); 408 | token.pop(ui); 409 | } 410 | 411 | ui.text("Velocity:"); 412 | { 413 | let token = ui.push_id("velocity"); 414 | Slider::new(im_str!("x"), -10.0..=10.0) 415 | .build(ui, &mut obj.body.vel.linear.x); 416 | Slider::new(im_str!("y"), -10.0..=10.0) 417 | .build(ui, &mut obj.body.vel.linear.y); 418 | Slider::new(im_str!("z"), -10.0..=10.0) 419 | .build(ui, &mut obj.body.vel.linear.z); 420 | Slider::new(im_str!("w"), -10.0..=10.0) 421 | .build(ui, &mut obj.body.vel.linear.w); 422 | token.pop(ui); 423 | } 424 | 425 | ui.text("Angular Velocity:"); 426 | { 427 | let token = ui.push_id("angular_velocity"); 428 | Slider::new(im_str!("xy"), -10.0..=10.0) 429 | .build(ui, &mut obj.body.vel.angular.xy); 430 | Slider::new(im_str!("xz"), -10.0..=10.0) 431 | .build(ui, &mut obj.body.vel.angular.xz); 432 | Slider::new(im_str!("xw"), -10.0..=10.0) 433 | .build(ui, &mut obj.body.vel.angular.xw); 434 | Slider::new(im_str!("yz"), -10.0..=10.0) 435 | .build(ui, &mut obj.body.vel.angular.yz); 436 | Slider::new(im_str!("yw"), -10.0..=10.0) 437 | .build(ui, &mut obj.body.vel.angular.yw); 438 | Slider::new(im_str!("zw"), -10.0..=10.0) 439 | .build(ui, &mut obj.body.vel.angular.zw); 440 | token.pop(ui); 441 | } 442 | } 443 | }); 444 | 445 | let mut encoder = graphics_ctx.device.create_command_encoder( 446 | &wgpu::CommandEncoderDescriptor { 447 | label: Some("compute_encoder"), 448 | }, 449 | ); 450 | 451 | self.world.compute( 452 | graphics_ctx, 453 | &self.slice_pipeline, 454 | &mut encoder, 455 | &self.slice_plane, 456 | ); 457 | 458 | // for some reason I need to do the compute and render passes in two 459 | // goes to have it work on vulkan without visual glitches 460 | graphics_ctx.queue.submit(&[encoder.finish()]); 461 | 462 | self.render_pipeline 463 | .update_view_proj(graphics_ctx, &self.view_proj); 464 | 465 | let mut encoder = graphics_ctx.device.create_command_encoder( 466 | &wgpu::CommandEncoderDescriptor { 467 | label: Some("render_encoder"), 468 | }, 469 | ); 470 | 471 | { 472 | let mut shadow_pass = 473 | encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 474 | color_attachments: &[], 475 | depth_stencil_attachment: Some( 476 | wgpu::RenderPassDepthStencilAttachmentDescriptor { 477 | attachment: &self.shadow_texture, 478 | depth_load_op: wgpu::LoadOp::Clear, 479 | depth_store_op: wgpu::StoreOp::Store, 480 | stencil_load_op: wgpu::LoadOp::Clear, 481 | stencil_store_op: wgpu::StoreOp::Store, 482 | clear_depth: 1.0, 483 | clear_stencil: 0, 484 | }, 485 | ), 486 | }); 487 | 488 | self.world 489 | .shadow_pass(&self.shadow_pipeline, &mut shadow_pass); 490 | } 491 | 492 | { 493 | let mut render_pass = 494 | encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 495 | color_attachments: &[ 496 | wgpu::RenderPassColorAttachmentDescriptor { 497 | attachment: &self.ms_framebuffer, 498 | resolve_target: Some(&frame.view), 499 | load_op: wgpu::LoadOp::Clear, 500 | store_op: wgpu::StoreOp::Store, 501 | clear_color: wgpu::Color { 502 | r: 0.0, 503 | g: 0.0, 504 | b: 0.0, 505 | a: 1.0, 506 | }, 507 | }, 508 | ], 509 | depth_stencil_attachment: Some( 510 | wgpu::RenderPassDepthStencilAttachmentDescriptor { 511 | attachment: &self.depth_texture, 512 | depth_load_op: wgpu::LoadOp::Clear, 513 | depth_store_op: wgpu::StoreOp::Store, 514 | clear_depth: 1.0, 515 | stencil_load_op: wgpu::LoadOp::Clear, 516 | stencil_store_op: wgpu::StoreOp::Store, 517 | clear_stencil: 0, 518 | }, 519 | ), 520 | }); 521 | 522 | self.world.render(&self.render_pipeline, &mut render_pass); 523 | } 524 | 525 | graphics_ctx.queue.submit(&[encoder.finish()]); 526 | 527 | self.frames += 1; 528 | } 529 | } 530 | 531 | fn main() -> Result<()> { 532 | let future = context::run::("Hello world!", (1280, 720)); 533 | futures::executor::block_on(future) 534 | } 535 | -------------------------------------------------------------------------------- /src/mesh/clip.rs: -------------------------------------------------------------------------------- 1 | // Adapted from https://www.geometrictools.com/Documentation/ClipMesh.pdf 2 | 3 | use super::{Cell, Mesh}; 4 | use cgmath::{InnerSpace, Vector4}; 5 | use smallvec::{smallvec, SmallVec}; 6 | use std::collections::HashMap; 7 | 8 | use crate::util::EPSILON; 9 | 10 | #[derive(Debug)] 11 | struct ClipVertex { 12 | point: Vector4, 13 | distance: f32, 14 | // temporary variable used for getting the open polyline. Can be initialised 15 | // to anything you want - it'll be reset before it's used 16 | occurs: i32, 17 | visible: bool, 18 | } 19 | 20 | #[derive(Debug)] 21 | struct ClipEdge { 22 | hd_vertex: usize, 23 | tl_vertex: usize, 24 | faces: SmallVec<[usize; 8]>, 25 | visible: bool, 26 | } 27 | 28 | #[derive(Debug)] 29 | struct ClipFace { 30 | edges: SmallVec<[usize; 8]>, 31 | visible: bool, 32 | } 33 | 34 | pub struct ClipMesh { 35 | vertices: Vec, 36 | edges: Vec, 37 | faces: Vec, 38 | } 39 | 40 | enum ProcessVertexResult { 41 | NoneClipped, 42 | AllClipped, 43 | PartiallyClipped, 44 | } 45 | 46 | impl ClipMesh { 47 | pub fn from_cell(mesh: &Mesh, cell_idx: usize) -> Self { 48 | ClipMeshBuilder::new(mesh, cell_idx).build() 49 | } 50 | 51 | pub fn clip_by(&mut self, clip_normal: Vector4, clip_distance: f32) { 52 | match self.process_vertices(clip_normal, clip_distance) { 53 | ProcessVertexResult::NoneClipped => return, 54 | ProcessVertexResult::AllClipped => { 55 | for edge in self.edges.iter_mut() { 56 | edge.visible = false; 57 | } 58 | 59 | for face in self.faces.iter_mut() { 60 | face.visible = false; 61 | } 62 | 63 | return; 64 | } 65 | ProcessVertexResult::PartiallyClipped => (), 66 | }; 67 | 68 | self.process_edges(); 69 | self.process_faces(); 70 | } 71 | 72 | pub fn to_vertices(self) -> Vec> { 73 | self.vertices 74 | .into_iter() 75 | .filter_map(|v| if v.visible { Some(v.point) } else { None }) 76 | .collect() 77 | } 78 | 79 | fn process_vertices( 80 | &mut self, 81 | clip_normal: Vector4, 82 | clip_distance: f32, 83 | ) -> ProcessVertexResult { 84 | let mut positive = 0; 85 | let mut negative = 0; 86 | 87 | for vertex in self.vertices.iter_mut() { 88 | if !vertex.visible { 89 | continue; 90 | } 91 | 92 | vertex.distance = clip_normal.dot(vertex.point) - clip_distance; 93 | 94 | if vertex.distance >= EPSILON { 95 | positive += 1; 96 | } else if vertex.distance < -EPSILON { 97 | negative += 1; 98 | vertex.visible = false; 99 | } else { 100 | vertex.distance = 0.0; 101 | } 102 | } 103 | 104 | if negative == 0 { 105 | return ProcessVertexResult::NoneClipped; 106 | } else if positive == 0 { 107 | return ProcessVertexResult::AllClipped; 108 | } else { 109 | return ProcessVertexResult::PartiallyClipped; 110 | } 111 | } 112 | 113 | fn process_edges(&mut self) { 114 | for (edge_idx, edge) in self.edges.iter_mut().enumerate() { 115 | if !edge.visible { 116 | continue; 117 | } 118 | 119 | let d0 = self.vertices[edge.hd_vertex].distance; 120 | let d1 = self.vertices[edge.tl_vertex].distance; 121 | 122 | if d0 <= 0.0 && d1 <= 0.0 { 123 | // edge is culled, remove edge from faces sharing it 124 | for face in self.faces.iter_mut() { 125 | for i in 0..face.edges.len() { 126 | if face.edges[i] == edge_idx { 127 | face.edges.remove(i); 128 | break; 129 | } 130 | } 131 | if face.edges.len() == 0 { 132 | face.visible = false; 133 | } 134 | } 135 | edge.visible = false; 136 | } else if d0 >= 0.0 && d1 >= 0.0 { 137 | // edge is on nonnegative side, faces retain the edge 138 | } else { 139 | // edge is split by plane 140 | // If the old edge is and I is the intersection 141 | // point, the new edge os when d0 > 0 or 142 | // when d1 > 0. 143 | 144 | // d0 and d1 are at least 2 EPSILONS apart here so this is 145 | // fine 146 | let t = d0 / (d0 - d1); 147 | let intersection = (1.0 - t) 148 | * self.vertices[edge.hd_vertex].point 149 | + t * self.vertices[edge.tl_vertex].point; 150 | 151 | let fresh_idx = self.vertices.len(); 152 | self.vertices.push(ClipVertex { 153 | point: intersection, 154 | distance: 0.0, 155 | occurs: 0, 156 | visible: true, 157 | }); 158 | 159 | if d0 > 0.0 { 160 | edge.tl_vertex = fresh_idx; 161 | } else { 162 | edge.hd_vertex = fresh_idx; 163 | } 164 | } 165 | } 166 | } 167 | 168 | fn process_faces(&mut self) { 169 | // If this is called, then the mesh straddles the plane. A new convex 170 | // polygonal face will be generated. Add it now and insert edges when 171 | // they are visited. 172 | let close_face = ClipFace { 173 | edges: SmallVec::new(), 174 | visible: true, 175 | }; 176 | let close_face_idx = self.faces.len(); 177 | self.faces.push(close_face); 178 | 179 | for face_idx in 0..self.faces.len() { 180 | if !self.faces[face_idx].visible { 181 | continue; 182 | } 183 | 184 | if let Some((start, end)) = self.get_open_polyline(face_idx) { 185 | let close_edge = ClipEdge { 186 | hd_vertex: start, 187 | tl_vertex: end, 188 | faces: smallvec![face_idx, close_face_idx], 189 | visible: true, 190 | }; 191 | let fresh_edge_idx = self.edges.len(); 192 | self.edges.push(close_edge); 193 | self.faces[face_idx].edges.push(fresh_edge_idx); 194 | 195 | self.faces[close_face_idx].edges.push(fresh_edge_idx); 196 | } 197 | } 198 | } 199 | 200 | fn get_open_polyline(&mut self, face_idx: usize) -> Option<(usize, usize)> { 201 | let face = &self.faces[face_idx]; 202 | 203 | for edge_idx in face.edges.iter() { 204 | let edge = &self.edges[*edge_idx]; 205 | self.vertices[edge.hd_vertex].occurs = 0; 206 | self.vertices[edge.tl_vertex].occurs = 0; 207 | } 208 | 209 | for edge_idx in face.edges.iter() { 210 | let edge = &self.edges[*edge_idx]; 211 | self.vertices[edge.hd_vertex].occurs += 1; 212 | self.vertices[edge.tl_vertex].occurs += 1; 213 | } 214 | 215 | // Now each occurs value on vertices on this face must be 1 or 2. If 216 | // it's 1, it's one end of the open polyline 217 | let mut start = None; 218 | let mut end = None; 219 | for edge_idx in face.edges.iter() { 220 | let edge = &self.edges[*edge_idx]; 221 | if self.vertices[edge.hd_vertex].occurs == 1 { 222 | if start == None { 223 | start = Some(edge.hd_vertex); 224 | } else if end == None { 225 | end = Some(edge.hd_vertex); 226 | } 227 | } 228 | if self.vertices[edge.tl_vertex].occurs == 1 { 229 | if start == None { 230 | start = Some(edge.tl_vertex); 231 | } else if end == None { 232 | end = Some(edge.tl_vertex); 233 | } 234 | } 235 | } 236 | 237 | match (start, end) { 238 | (Some(start), Some(end)) => Some((start, end)), 239 | _ => None, 240 | } 241 | } 242 | } 243 | 244 | struct ClipMeshBuilder<'a> { 245 | mesh: &'a Mesh, 246 | cell: &'a Cell, 247 | 248 | vertices: Vec, 249 | edges: Vec, 250 | faces: Vec, 251 | 252 | vertex_map: HashMap, 253 | edge_map: HashMap, 254 | face_map: HashMap, 255 | } 256 | 257 | impl<'a> ClipMeshBuilder<'a> { 258 | fn new(mesh: &'a Mesh, cell_idx: usize) -> Self { 259 | Self { 260 | mesh, 261 | cell: &mesh.cells[cell_idx], 262 | 263 | vertices: Vec::new(), 264 | edges: Vec::new(), 265 | faces: Vec::new(), 266 | 267 | vertex_map: HashMap::new(), 268 | edge_map: HashMap::new(), 269 | face_map: HashMap::new(), 270 | } 271 | } 272 | 273 | fn build(mut self) -> ClipMesh { 274 | for face_idx in self.cell.faces.iter() { 275 | self.push_face(*face_idx); 276 | } 277 | 278 | ClipMesh { 279 | vertices: self.vertices, 280 | edges: self.edges, 281 | faces: self.faces, 282 | } 283 | } 284 | 285 | fn push_vertex(&mut self, vertex_idx: usize) -> usize { 286 | match self.vertex_map.get(&vertex_idx) { 287 | Some(idx) => *idx, 288 | None => { 289 | let fresh_idx = self.vertices.len(); 290 | self.vertices.push(ClipVertex { 291 | point: self.mesh.vertices[vertex_idx], 292 | distance: 0.0, 293 | occurs: 0, 294 | visible: true, 295 | }); 296 | self.vertex_map.insert(vertex_idx, fresh_idx); 297 | fresh_idx 298 | } 299 | } 300 | } 301 | 302 | fn push_edge(&mut self, edge_idx: usize) -> usize { 303 | match self.edge_map.get(&edge_idx) { 304 | Some(idx) => *idx, 305 | None => { 306 | let edge = &self.mesh.edges[edge_idx]; 307 | let fresh_idx = self.edges.len(); 308 | let hd_vertex = self.push_vertex(edge.hd_vertex); 309 | let tl_vertex = self.push_vertex(edge.tl_vertex); 310 | self.edges.push(ClipEdge { 311 | hd_vertex, 312 | tl_vertex, 313 | faces: SmallVec::new(), 314 | visible: true, 315 | }); 316 | self.edge_map.insert(edge_idx, fresh_idx); 317 | fresh_idx 318 | } 319 | } 320 | } 321 | 322 | fn push_face(&mut self, face_idx: usize) -> usize { 323 | match self.face_map.get(&face_idx) { 324 | Some(idx) => *idx, 325 | None => { 326 | let face = &self.mesh.faces[face_idx]; 327 | let fresh_idx = self.faces.len(); 328 | let mut clip_face = ClipFace { 329 | edges: SmallVec::new(), 330 | visible: true, 331 | }; 332 | 333 | for edge_idx in face.edges.iter() { 334 | let clip_edge_idx = self.push_edge(*edge_idx); 335 | self.edges[clip_edge_idx].faces.push(fresh_idx); 336 | clip_face.edges.push(clip_edge_idx); 337 | } 338 | 339 | self.faces.push(clip_face); 340 | self.face_map.insert(face_idx, fresh_idx); 341 | 342 | fresh_idx 343 | } 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/mesh/mod.rs: -------------------------------------------------------------------------------- 1 | mod clip; 2 | mod tetrahedra; 3 | mod todd_coxeter; 4 | 5 | use crate::alg::triple_cross_product; 6 | use crate::util::NotNaN; 7 | use cgmath::{InnerSpace, Vector4, Zero}; 8 | use smallvec::SmallVec; 9 | 10 | pub use clip::*; 11 | pub use tetrahedra::*; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct VertexData { 15 | pub cells: SmallVec<[usize; 16]>, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub struct Edge { 20 | pub hd_vertex: usize, 21 | pub tl_vertex: usize, 22 | pub faces: SmallVec<[usize; 8]>, 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct Face { 27 | pub hd_cell: usize, 28 | pub tl_cell: usize, 29 | pub edges: SmallVec<[usize; 8]>, 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | pub struct Cell { 34 | pub normal: Vector4, 35 | pub faces: SmallVec<[usize; 16]>, 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct Mesh { 40 | pub radius: f32, 41 | pub vertices: Vec>, 42 | pub vertex_data: Vec, 43 | pub edges: Vec, 44 | pub faces: Vec, 45 | pub cells: Vec, 46 | } 47 | 48 | fn reflect(v: Vector4, mirror_normal: Vector4) -> Vector4 { 49 | v - 2.0 * mirror_normal.dot(v) * mirror_normal 50 | } 51 | 52 | fn get_mirror_normals(symbol: &[usize; 3]) -> [Vector4; 4] { 53 | use std::f32::consts::PI; 54 | 55 | let mut mirror_normals = [Vector4::zero(); 4]; 56 | 57 | mirror_normals[0] = Vector4::unit_x(); 58 | 59 | // dot(N_0, N_1) = cos(pi / symbol[0]) 60 | mirror_normals[1].x = (PI / symbol[0] as f32).cos(); 61 | mirror_normals[1].y = (1.0 - mirror_normals[1].x.powi(2)).sqrt(); 62 | 63 | // dot(N_0, N_2) = cos(pi / 2) = 0 64 | // dot(N_1, N_2) = cos(pi / symbol[1]) 65 | mirror_normals[2].y = (PI / symbol[1] as f32).cos() / mirror_normals[1].y; 66 | mirror_normals[2].z = (1.0 - mirror_normals[2].y.powi(2)).sqrt(); 67 | 68 | // dot(N_0, N_3) = cos(pi / 2) = 0 69 | // dot(N_1, N_3) = cos(pi / 2) = 0 70 | // dot(N_2, N_3) = cos(pi / symbol[2]) 71 | mirror_normals[3].z = (PI / symbol[2] as f32).cos() / mirror_normals[2].z; 72 | mirror_normals[3].w = (1.0 - mirror_normals[3].z.powi(2)).sqrt(); 73 | 74 | mirror_normals 75 | } 76 | 77 | impl Mesh { 78 | pub fn from_schlafli_symbol(symbol: &[usize; 3]) -> Self { 79 | // determine the mirror normals 80 | let mirror_normals = get_mirror_normals(symbol); 81 | 82 | // setup for todd-coxeter 83 | let num_gens = 4; 84 | let relations: &[&[usize]] = &[ 85 | &[0, 0], 86 | &[1, 1], 87 | &[2, 2], 88 | &[3, 3], 89 | &[0, 1].repeat(symbol[0]), 90 | &[1, 2].repeat(symbol[1]), 91 | &[2, 3].repeat(symbol[2]), 92 | &[0, 2].repeat(2), 93 | &[0, 3].repeat(2), 94 | &[1, 3].repeat(2), 95 | ]; 96 | 97 | let vertex_table = 98 | todd_coxeter::coset_table(num_gens, relations, &[1, 2, 3]); 99 | 100 | let edge_table = 101 | todd_coxeter::coset_table(num_gens, relations, &[0, 2, 3]); 102 | 103 | let face_table = 104 | todd_coxeter::coset_table(num_gens, relations, &[0, 1, 3]); 105 | 106 | let cell_table = 107 | todd_coxeter::coset_table(num_gens, relations, &[0, 1, 2]); 108 | 109 | // pick a v0 so that it's on planes 1, 2, and 3, but not on 0 110 | // set v_0.x = 1 arbitrarily 111 | let mut v0: Vector4 = Vector4::unit_x(); 112 | v0.y = -mirror_normals[1].x * v0.x / mirror_normals[1].y; 113 | v0.z = -mirror_normals[2].y * v0.y / mirror_normals[2].z; 114 | v0.w = -mirror_normals[3].z * v0.z / mirror_normals[3].w; 115 | v0 = v0.normalize(); 116 | let vertices = 117 | todd_coxeter::table_bfs_fold(&vertex_table, 0, v0, |v, mirror| { 118 | reflect(v, mirror_normals[mirror]) 119 | }); 120 | 121 | let e0 = { 122 | // The initial 123 | let mut faces: SmallVec<[usize; 8]> = SmallVec::new(); 124 | let mut curr_face = 0; 125 | loop { 126 | faces.push(curr_face); 127 | curr_face = face_table[face_table[curr_face][2]][3]; 128 | if curr_face == 0 { 129 | break; 130 | } 131 | } 132 | Edge { 133 | hd_vertex: 0, 134 | tl_vertex: 1, 135 | faces, 136 | } 137 | }; 138 | let edges = 139 | todd_coxeter::table_bfs_fold(&edge_table, 0, e0, |e, mirror| { 140 | Edge { 141 | hd_vertex: vertex_table[e.hd_vertex][mirror], 142 | tl_vertex: vertex_table[e.tl_vertex][mirror], 143 | faces: e 144 | .faces 145 | .into_iter() 146 | .map(|f| face_table[f][mirror]) 147 | .collect(), 148 | } 149 | }); 150 | 151 | // the initial face is all the edges invariant under the rotation (0, 1) 152 | let f0 = { 153 | let mut edges: SmallVec<[usize; 8]> = SmallVec::new(); 154 | let mut curr_edge = 0; 155 | loop { 156 | edges.push(curr_edge); 157 | curr_edge = edge_table[edge_table[curr_edge][0]][1]; 158 | if curr_edge == 0 { 159 | break; 160 | } 161 | } 162 | edges 163 | }; 164 | let face_tmp = 165 | todd_coxeter::table_bfs_fold(&face_table, 0, f0, |f, mirror| { 166 | f.into_iter().map(|e| edge_table[e][mirror]).collect() 167 | }); 168 | 169 | // The initial cell is invariant under mirrors 0, 1, and 2. 170 | // So, if we just apply mirrors 0, 1, and 2 to the inital face a 171 | // whole bunch of times, we should recover all the faces in the initial 172 | // cell 173 | let c0 = { 174 | // also pick a vector to be on planes 0, 1, and 2 - this will be the 175 | // normal vector of the cell 176 | // it turns out that this is the -unit-w vector because of the way we 177 | // chose the mirror normals 178 | let normal = -Vector4::unit_w(); 179 | 180 | let mut faces: SmallVec<[usize; 16]> = SmallVec::new(); 181 | faces.push(0); 182 | let mut i = 0; 183 | while i < faces.len() { 184 | let f = faces[i]; 185 | for j in 0..3 { 186 | let new_face = face_table[f][j]; 187 | if !faces.contains(&new_face) { 188 | faces.push(new_face); 189 | } 190 | } 191 | i += 1; 192 | } 193 | Cell { normal, faces } 194 | }; 195 | let cells = todd_coxeter::table_bfs_fold( 196 | &cell_table, 197 | 0, 198 | c0, 199 | |Cell { normal, faces }, mirror| Cell { 200 | normal: reflect(normal, mirror_normals[mirror]), 201 | faces: faces 202 | .into_iter() 203 | .map(|f| face_table[f][mirror]) 204 | .collect(), 205 | }, 206 | ); 207 | 208 | let mut faces: Vec<_> = face_tmp 209 | .into_iter() 210 | .map(|edges| Face { 211 | hd_cell: std::usize::MAX, 212 | tl_cell: std::usize::MAX, 213 | edges, 214 | }) 215 | .collect(); 216 | 217 | // populate cells for each face 218 | for (i, cell) in cells.iter().enumerate() { 219 | for j in cell.faces.iter() { 220 | if faces[*j].hd_cell == std::usize::MAX { 221 | faces[*j].hd_cell = i; 222 | } else { 223 | faces[*j].tl_cell = i; 224 | } 225 | } 226 | } 227 | 228 | // populate cells for each vertex 229 | let mut vertex_data = vec![ 230 | VertexData { 231 | cells: SmallVec::new() 232 | }; 233 | vertices.len() 234 | ]; 235 | for (cell_idx, cell) in cells.iter().enumerate() { 236 | for face_idx in cell.faces.iter() { 237 | for edge_idx in faces[*face_idx].edges.iter() { 238 | let edge = &edges[*edge_idx]; 239 | let v0 = &mut vertex_data[edge.hd_vertex]; 240 | if !v0.cells.contains(&cell_idx) { 241 | v0.cells.push(cell_idx); 242 | } 243 | let v1 = &mut vertex_data[edge.tl_vertex]; 244 | if !v1.cells.contains(&cell_idx) { 245 | v1.cells.push(cell_idx); 246 | } 247 | } 248 | } 249 | } 250 | 251 | Self { 252 | radius: 1.0, 253 | vertices, 254 | vertex_data, 255 | edges, 256 | faces, 257 | cells, 258 | } 259 | } 260 | 261 | pub fn closest_point_to(&self, point: Vector4) -> Vector4 { 262 | // first run half-space tests to determine if the point is inside the 263 | // mesh 264 | let mut inside = true; 265 | for cell in &self.cells { 266 | let v0 = self.cell_representative_vertex(cell); 267 | if v0.dot(cell.normal) < point.dot(cell.normal) { 268 | inside = false; 269 | break; 270 | } 271 | } 272 | 273 | if inside { 274 | return point; 275 | } 276 | 277 | // then, for each cell, find the closest point on the cell to the 278 | // vertex, and return the minimum 279 | self.cells 280 | .iter() 281 | .map(|c| self.closest_on_cell(c, point)) 282 | .min_by_key(|v| NotNaN::new((v - point).magnitude2()).unwrap()) 283 | .unwrap() 284 | } 285 | 286 | fn closest_on_cell( 287 | &self, 288 | cell: &Cell, 289 | point: Vector4, 290 | ) -> Vector4 { 291 | // project the point onto the cell hyperplane 292 | let v0 = self.cell_representative_vertex(cell); 293 | let k = (point.dot(cell.normal) - v0.dot(cell.normal)) 294 | / cell.normal.magnitude2(); 295 | let point = point - k * cell.normal; 296 | 297 | // This is the same algorithm but reduced down a dimension 298 | // Check to see if the point is within all the faces 299 | let mut inside = true; 300 | for face_idx in &cell.faces { 301 | let face = &self.faces[*face_idx]; 302 | let v0 = self.face_representative_vertex(face); 303 | let e0 = self.edge_vector(face.edges[0]); 304 | let e1 = self.edge_vector(face.edges[1]); 305 | let mut normal = 306 | triple_cross_product(e0, e1, cell.normal).normalize(); 307 | if v0.dot(normal) < 0.0 { 308 | normal = -normal; 309 | } 310 | 311 | if v0.dot(normal) < point.dot(normal) { 312 | inside = false; 313 | break; 314 | } 315 | } 316 | 317 | if inside { 318 | return point; 319 | } 320 | 321 | cell.faces 322 | .iter() 323 | .map(|face_idx| &self.faces[*face_idx]) 324 | .map(|face| self.closest_on_face(face, cell.normal, point)) 325 | .min_by_key(|v| NotNaN::new((v - point).magnitude2()).unwrap()) 326 | .unwrap() 327 | } 328 | 329 | fn closest_on_face( 330 | &self, 331 | face: &Face, 332 | cell_normal: Vector4, 333 | point: Vector4, 334 | ) -> Vector4 { 335 | // Project the point onto the face 336 | let v0 = self.face_representative_vertex(face); 337 | let e0 = self.edge_vector(face.edges[0]); 338 | let e1 = self.edge_vector(face.edges[1]); 339 | let mut normal = triple_cross_product(e0, e1, cell_normal); 340 | if v0.dot(normal) < 0.0 { 341 | normal = -normal; 342 | } 343 | 344 | let k = (point.dot(normal) - v0.dot(normal)) / normal.magnitude2(); 345 | let point = point - k * normal; 346 | 347 | // Check if the poinrt is inside all the edges 348 | let mut inside = true; 349 | for edge_idx in &face.edges { 350 | let edge = &self.edges[*edge_idx]; 351 | let v0 = self.edge_representative_vertex(edge); 352 | let e0 = self.edge_vector(*edge_idx); 353 | let mut edge_normal = triple_cross_product(e0, normal, cell_normal); 354 | if edge_normal.dot(v0) < 0.0 { 355 | edge_normal = -edge_normal; 356 | } 357 | 358 | if v0.dot(edge_normal) < point.dot(edge_normal) { 359 | inside = false; 360 | break; 361 | } 362 | } 363 | 364 | if inside { 365 | return point; 366 | } 367 | 368 | // then, for each edge, get the closest point on the edge to the point, 369 | // and return the minimum 370 | face.edges 371 | .iter() 372 | .map(|edge_idx| &self.edges[*edge_idx]) 373 | .map(|edge| self.closest_on_edge(edge, point)) 374 | .min_by_key(|v| NotNaN::new((v - point).magnitude2()).unwrap()) 375 | .unwrap() 376 | } 377 | 378 | fn closest_on_edge( 379 | &self, 380 | edge: &Edge, 381 | point: Vector4, 382 | ) -> Vector4 { 383 | let a = self.vertices[edge.hd_vertex]; 384 | let b = self.vertices[edge.tl_vertex]; 385 | let ab = b - a; 386 | 387 | let lambda = (a - point).dot(ab) / ab.magnitude2(); 388 | let lambda = lambda.min(1.0).max(0.0); 389 | 390 | a + lambda * ab 391 | } 392 | 393 | fn cell_representative_vertex(&self, cell: &Cell) -> Vector4 { 394 | self.face_representative_vertex(&self.faces[cell.faces[0]]) 395 | } 396 | 397 | fn face_representative_vertex(&self, face: &Face) -> Vector4 { 398 | self.edge_representative_vertex(&self.edges[face.edges[0]]) 399 | } 400 | 401 | fn edge_representative_vertex(&self, edge: &Edge) -> Vector4 { 402 | self.vertices[edge.hd_vertex] 403 | } 404 | 405 | fn edge_vector(&self, edge_idx: usize) -> Vector4 { 406 | let edge = &self.edges[edge_idx]; 407 | let v0 = self.vertices[edge.hd_vertex]; 408 | let v1 = self.vertices[edge.tl_vertex]; 409 | v1 - v0 410 | } 411 | } 412 | 413 | #[cfg(test)] 414 | mod test { 415 | use super::*; 416 | 417 | #[test] 418 | fn schlafli() { 419 | Mesh::from_schlafli_symbol(&[3, 3, 3]); 420 | Mesh::from_schlafli_symbol(&[4, 3, 3]); 421 | Mesh::from_schlafli_symbol(&[5, 3, 3]); 422 | Mesh::from_schlafli_symbol(&[3, 4, 3]); 423 | Mesh::from_schlafli_symbol(&[3, 3, 4]); 424 | Mesh::from_schlafli_symbol(&[3, 3, 5]); 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /src/mesh/tetrahedra.rs: -------------------------------------------------------------------------------- 1 | use super::Mesh; 2 | use crate::context::graphics::Vertex4; 3 | 4 | use cgmath::{InnerSpace, Vector4}; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Debug)] 8 | pub struct TetrahedronMesh { 9 | pub vertices: Vec, 10 | pub indices: Vec, 11 | } 12 | 13 | fn get_face_vertex_indices(mesh: &Mesh, face_idx: usize) -> Vec { 14 | let mut vertex_indices = Vec::new(); 15 | for edge_idx in mesh.faces[face_idx].edges.iter() { 16 | let edge = &mesh.edges[*edge_idx]; 17 | if vertex_indices.contains(&edge.hd_vertex) { 18 | vertex_indices.push(edge.tl_vertex); 19 | } else { 20 | vertex_indices.push(edge.hd_vertex); 21 | } 22 | } 23 | 24 | vertex_indices 25 | } 26 | 27 | impl TetrahedronMesh { 28 | fn tetrahedralize_cell( 29 | mesh: &Mesh, 30 | cell_idx: usize, 31 | color: Vector4, 32 | ) -> Self { 33 | let mut vertices = Vec::new(); 34 | let mut indices = Vec::new(); 35 | let mut vertex_map = HashMap::new(); 36 | 37 | let mut push_vertex = |vertex_idx: usize| { 38 | let colored_idx = 39 | *vertex_map.entry(vertex_idx).or_insert_with(|| { 40 | let new_colored_vertex = Vertex4 { 41 | position: mesh.vertices[vertex_idx], 42 | color, 43 | }; 44 | let new_colored_idx = vertices.len(); 45 | vertices.push(new_colored_vertex); 46 | new_colored_idx 47 | }); 48 | indices.push(colored_idx as u32); 49 | }; 50 | 51 | let cell = &mesh.cells[cell_idx]; 52 | 53 | // pick a point to be the apex from which all tetrahedra start from. 54 | let apex_idx = mesh.edges[mesh.faces[cell.faces[0]].edges[0]].hd_vertex; 55 | 56 | for face_idx in cell.faces.iter() { 57 | let vertex_indices = get_face_vertex_indices(mesh, *face_idx); 58 | if vertex_indices.contains(&apex_idx) { 59 | continue; 60 | } 61 | 62 | // because of the way faces were generated, we should be able to 63 | // assume all the vertices are already sorted in either clockwise or 64 | // anticlockwise order already 65 | for i in 1..vertex_indices.len() - 1 { 66 | push_vertex(apex_idx); 67 | push_vertex(vertex_indices[0]); 68 | push_vertex(vertex_indices[i]); 69 | push_vertex(vertex_indices[i + 1]); 70 | } 71 | } 72 | 73 | Self { vertices, indices } 74 | } 75 | 76 | fn append( 77 | &mut self, 78 | TetrahedronMesh { vertices, indices }: TetrahedronMesh, 79 | ) { 80 | let prev_len = self.vertices.len() as u32; 81 | self.vertices.extend(vertices); 82 | self.indices 83 | .extend(indices.into_iter().map(|i| i + prev_len)); 84 | } 85 | 86 | pub fn from_mesh(mesh: &Mesh, color_func: F) -> Self 87 | where 88 | F: Fn(Vector4) -> Vector4, 89 | { 90 | let mut result = TetrahedronMesh { 91 | vertices: Vec::new(), 92 | indices: Vec::new(), 93 | }; 94 | 95 | for (cell_idx, cell) in mesh.cells.iter().enumerate() { 96 | let color = color_func(cell.normal); 97 | let cell_mesh = Self::tetrahedralize_cell(mesh, cell_idx, color); 98 | result.append(cell_mesh); 99 | } 100 | 101 | result 102 | } 103 | 104 | pub fn subdivide(&self, frequency: usize) -> Self { 105 | let mut vertices = Vec::new(); 106 | let mut indices = Vec::new(); 107 | 108 | for tetrahedron in self.indices.chunks_exact(4) { 109 | if let &[a, b, c, d] = tetrahedron { 110 | let a = self.vertices[a as usize]; 111 | let b = self.vertices[b as usize]; 112 | let c = self.vertices[c as usize]; 113 | let d = self.vertices[d as usize]; 114 | 115 | let mut mapped_indices: Vec<(usize, usize, usize)> = Vec::new(); 116 | 117 | for n in 0..frequency { 118 | // loop through all the possible vertices on this layer 119 | for i in 0..(n + 1) { 120 | for j in 0..(n - i + 1) { 121 | let k = n - i - j; 122 | // insert a tetrahedron based at this vertex 123 | // x, xi, xj, xk 124 | mapped_indices.extend(&[ 125 | (i, j, k), 126 | (i + 1, j, k), 127 | (i, j + 1, k), 128 | (i, j, k + 1), 129 | ]); 130 | 131 | if n < frequency - 1 { 132 | // insert an octahedron here as well 133 | mapped_indices.extend(&[ 134 | // xi, xj, xk, xik 135 | (i + 1, j, k), 136 | (i, j + 1, k), 137 | (i, j, k + 1), 138 | (i + 1, j, k + 1), 139 | // xi, xj, xij, xik 140 | (i + 1, j, k), 141 | (i, j + 1, k), 142 | (i + 1, j + 1, k), 143 | (i + 1, j, k + 1), 144 | // xj, xk, xik, xjk 145 | (i, j + 1, k), 146 | (i, j, k + 1), 147 | (i + 1, j, k + 1), 148 | (i, j + 1, k + 1), 149 | // xj, xij, xik, xjk 150 | (i, j + 1, k), 151 | (i + 1, j + 1, k), 152 | (i + 1, j, k + 1), 153 | (i, j + 1, k + 1), 154 | ]); 155 | } 156 | 157 | if n < frequency - 2 { 158 | mapped_indices.extend(&[ 159 | // xij, xik, xjk, xijk 160 | (i + 1, j + 1, k), 161 | (i + 1, j, k + 1), 162 | (i, j + 1, k + 1), 163 | (i + 1, j + 1, k + 1), 164 | ]); 165 | } 166 | } 167 | } 168 | } 169 | 170 | let mut vertex_map = HashMap::new(); 171 | for coords in mapped_indices { 172 | let index = 173 | *vertex_map.entry(coords).or_insert_with(|| { 174 | let index = vertices.len(); 175 | 176 | let (i, j, k) = coords; 177 | let s = i as f32 / frequency as f32; 178 | let t = j as f32 / frequency as f32; 179 | let u = k as f32 / frequency as f32; 180 | let r = 1.0 - s - t - u; 181 | 182 | let position = a.position * r 183 | + b.position * s 184 | + c.position * t 185 | + d.position * u; 186 | let color = a.color * r 187 | + b.color * s 188 | + c.color * t 189 | + d.color * u; 190 | 191 | vertices.push(Vertex4 { position, color }); 192 | 193 | index 194 | }); 195 | indices.push(index as u32); 196 | } 197 | } 198 | } 199 | 200 | Self { vertices, indices } 201 | } 202 | 203 | pub fn make_geodesic(&self, frequency: usize, radius: f32) -> Self { 204 | let mut mesh = self.subdivide(frequency); 205 | mesh.vertices 206 | .iter_mut() 207 | .for_each(|v| v.position = v.position.normalize() * radius); 208 | mesh 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/mesh/todd_coxeter.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet, VecDeque}; 2 | 3 | struct CosetTable { 4 | num_gens: usize, 5 | table: Vec>>, 6 | live_map: Vec, 7 | } 8 | 9 | impl CosetTable { 10 | fn init(num_gens: usize) -> Self { 11 | let table = vec![vec![None; num_gens]]; 12 | let live_map = vec![0]; 13 | 14 | CosetTable { 15 | num_gens, 16 | table, 17 | live_map, 18 | } 19 | } 20 | 21 | fn define(&mut self, coset: usize, g: usize) { 22 | let fresh = self.table.len(); 23 | self.table.push(vec![None; self.num_gens]); 24 | self.table[coset][g] = Some(fresh); 25 | self.table[fresh][g] = Some(coset); 26 | self.live_map.push(fresh); 27 | } 28 | 29 | fn rep(&mut self, coset: usize) -> usize { 30 | let mut m = coset; 31 | while m != self.live_map[m] { 32 | m = self.live_map[m]; 33 | } 34 | 35 | let mut j = coset; 36 | while j != self.live_map[j] { 37 | let next = self.live_map[j]; 38 | self.live_map[j] = m; 39 | j = next; 40 | } 41 | 42 | m 43 | } 44 | 45 | fn merge(&mut self, queue: &mut Vec, coset1: usize, coset2: usize) { 46 | let (s, t) = (self.rep(coset1), self.rep(coset2)); 47 | if s != t { 48 | let (s, t) = (s.min(t), s.max(t)); 49 | self.live_map[t] = s; 50 | queue.push(t); 51 | } 52 | } 53 | 54 | fn coincidence(&mut self, coset1: usize, coset2: usize) { 55 | let mut queue = Vec::new(); 56 | 57 | self.merge(&mut queue, coset1, coset2); 58 | while queue.len() != 0 { 59 | let e = queue.remove(0); 60 | for g in 0..self.num_gens { 61 | if let Some(f) = self.table[e][g] { 62 | self.table[f][g] = None; 63 | 64 | let (e_, f_) = (self.rep(e), self.rep(f)); 65 | if let Some(x) = self.table[e_][g] { 66 | self.merge(&mut queue, f_, x); 67 | } else if let Some(x) = self.table[f_][g] { 68 | self.merge(&mut queue, e_, x); 69 | } else { 70 | self.table[e_][g] = Some(f_); 71 | self.table[f_][g] = Some(e_); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | fn scan_and_fill(&mut self, coset: usize, word: &[usize]) { 79 | let mut f = coset; 80 | let mut b = coset; 81 | let mut i = 0; 82 | let mut j = word.len() - 1; 83 | 84 | loop { 85 | // scan forwards as far as possible 86 | while i <= j && self.table[f][word[i]].is_some() { 87 | f = self.table[f][word[i]].unwrap(); 88 | i += 1; 89 | } 90 | 91 | if i > j { 92 | if f != b { 93 | // found a coincidence 94 | self.coincidence(f, b); 95 | } 96 | return; 97 | } 98 | 99 | while j >= i && self.table[b][word[j]].is_some() { 100 | b = self.table[b][word[j]].unwrap(); 101 | j -= 1; 102 | } 103 | 104 | if j < i { 105 | self.coincidence(f, b); 106 | return; 107 | } else if i == j { 108 | // deduction 109 | self.table[f][word[i]] = Some(b); 110 | self.table[b][word[i]] = Some(f); 111 | } else { 112 | // define a new coset, continue scanning 113 | self.define(f, word[i]); 114 | } 115 | } 116 | } 117 | } 118 | 119 | pub fn coset_table( 120 | num_gens: usize, 121 | relations: &[&[usize]], 122 | sub_gens: &[usize], 123 | ) -> Vec> { 124 | let mut table = CosetTable::init(num_gens); 125 | 126 | // fill in initial information for the first coset 127 | for g in sub_gens { 128 | table.scan_and_fill(0, &[*g]); 129 | } 130 | 131 | let mut i = 0; 132 | while i < table.table.len() { 133 | if table.live_map[i] == i { 134 | for rel in relations { 135 | table.scan_and_fill(i, rel); 136 | } 137 | 138 | for g in 0..num_gens { 139 | if table.table[i][g].is_none() { 140 | table.define(i, g); 141 | } 142 | } 143 | } 144 | 145 | i += 1; 146 | } 147 | 148 | // compress the resulting table 149 | let mut forward_map = HashMap::new(); 150 | let mut backward_map = HashMap::new(); 151 | let mut fresh = 0; 152 | for coset in 0..table.table.len() { 153 | if table.live_map[coset] == coset { 154 | forward_map.insert(coset, fresh); 155 | backward_map.insert(fresh, coset); 156 | fresh += 1; 157 | } 158 | } 159 | 160 | let mut compressed_table = Vec::new(); 161 | for i in 0..fresh { 162 | compressed_table.push( 163 | table.table[backward_map[&i]] 164 | .iter() 165 | .map(|x| forward_map[&x.unwrap()]) 166 | .collect(), 167 | ); 168 | } 169 | 170 | compressed_table 171 | } 172 | 173 | pub fn table_bfs_fold( 174 | table: &Vec>, 175 | start: usize, 176 | initial: T, 177 | f: F, 178 | ) -> Vec 179 | where 180 | T: Clone, 181 | F: Fn(T, usize) -> T, 182 | { 183 | let mut result = vec![initial.clone(); table.len()]; 184 | let mut queue = VecDeque::new(); 185 | let mut seen = HashSet::new(); 186 | 187 | queue.push_back(start); 188 | seen.insert(start); 189 | 190 | while let Some(top) = queue.pop_front() { 191 | for (g, next) in table[top].iter().enumerate() { 192 | if seen.contains(&next) { 193 | continue; 194 | } 195 | 196 | result[*next] = f(result[top].clone(), g); 197 | queue.push_back(*next); 198 | seen.insert(*next); 199 | } 200 | } 201 | 202 | result 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use super::*; 208 | 209 | #[test] 210 | fn dodecahedron_test() { 211 | let table = coset_table( 212 | 4, 213 | &[ 214 | &[0, 0], 215 | &[1, 1], 216 | &[2, 2], 217 | &[3, 3], 218 | &[0, 1].repeat(5), 219 | &[1, 2].repeat(3), 220 | &[2, 3].repeat(3), 221 | &[0, 2].repeat(2), 222 | &[0, 3].repeat(2), 223 | &[1, 3].repeat(2), 224 | ], 225 | &[0, 1, 3], 226 | ); 227 | println!("{:?}", table); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/mesh4.rs: -------------------------------------------------------------------------------- 1 | use crate::context::graphics::Vertex4; 2 | 3 | // TODO: this file is only used for generating the floor surface, replace with 4 | // better system 5 | 6 | pub struct Mesh4 { 7 | pub vertices: Vec, 8 | pub indices: Vec, 9 | } 10 | 11 | fn cube( 12 | size: f32, 13 | fixed_axis: usize, 14 | fixed_value: f32, 15 | color: [f32; 4], 16 | vertices: &mut Vec, 17 | indices: &mut Vec, 18 | ) { 19 | let vertex_size = vertices.len() as u32; 20 | 21 | for mut i in 0..8 { 22 | let mut position = [0f32; 4]; 23 | for j in 0..4 { 24 | if j == fixed_axis { 25 | position[j] = fixed_value * size; 26 | } else { 27 | position[j] = ((i & 1) as f32 * 2.0 - 1.0) * size; 28 | i /= 2; 29 | } 30 | } 31 | vertices.push(Vertex4 { 32 | position: position.into(), 33 | color: color.into(), 34 | }); 35 | } 36 | 37 | #[cfg_attr(rustfmt, rustfmt_skip)] 38 | let new_indices = vec![ 39 | 1, 2, 4, 7, 40 | 0, 1, 2, 4, 41 | 2, 4, 6, 7, 42 | 1, 2, 3, 7, 43 | 1, 4, 5, 7, 44 | ]; 45 | 46 | indices.extend(new_indices.iter().map(|x| x + vertex_size)); 47 | } 48 | 49 | pub fn floor(size: f32) -> Mesh4 { 50 | let x = size / 2.0; 51 | let color = [1.0f32; 4]; 52 | 53 | let mut vertices = Vec::new(); 54 | let mut indices = Vec::new(); 55 | 56 | cube(x, 1, 0.0, color, &mut vertices, &mut indices); 57 | 58 | Mesh4 { vertices, indices } 59 | } 60 | -------------------------------------------------------------------------------- /src/physics/body.rs: -------------------------------------------------------------------------------- 1 | use super::Collider; 2 | use crate::alg::{Bivec4, Rotor4, Vec4}; 3 | use cgmath::{InnerSpace, Vector4, Zero}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Material { 7 | pub restitution: f32, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct Velocity { 12 | pub linear: Vector4, 13 | pub angular: Bivec4, 14 | } 15 | 16 | impl Velocity { 17 | pub fn zero() -> Self { 18 | Self { 19 | linear: Vector4::zero(), 20 | angular: Bivec4::zero(), 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone)] 26 | pub struct Body { 27 | pub mass: f32, 28 | // for tesseracts it's sufficient to keep this as a scalar, but it really 29 | // should be a tensor of shape Bivec4 -> Bivec4 30 | pub moment_inertia_scalar: f32, 31 | pub material: Material, 32 | pub stationary: bool, 33 | 34 | pub pos: Vector4, 35 | pub rotation: Rotor4, 36 | 37 | pub vel: Velocity, 38 | 39 | pub collider: Collider, 40 | } 41 | 42 | impl Body { 43 | pub fn resolve_impulse( 44 | &mut self, 45 | impulse: Vector4, 46 | world_contact: Vector4, 47 | ) { 48 | if !self.stationary { 49 | let body_contact = self.world_pos_to_body(world_contact); 50 | let delta_angular_vel = self.inverse_moment_of_inertia( 51 | &Vec4::from(body_contact) 52 | .wedge_v(&self.rotation.reverse().rotate(&impulse.into())), 53 | ); 54 | 55 | self.vel.linear += impulse / self.mass; 56 | self.vel.angular = self.vel.angular + delta_angular_vel; 57 | } 58 | } 59 | 60 | pub fn step(&mut self, dt: f32) { 61 | if !self.stationary { 62 | // apply gravity 63 | self.vel.linear += Vector4::unit_y() * (-9.8 * dt); 64 | 65 | self.pos += self.vel.linear * dt; 66 | self.rotation.update(&(dt * self.vel.angular)); 67 | } 68 | } 69 | 70 | pub fn inverse_moment_of_inertia(&self, body_bivec: &Bivec4) -> Bivec4 { 71 | if self.moment_inertia_scalar <= 0.0 { 72 | return Bivec4::zero(); 73 | } 74 | 75 | 1.0 / self.moment_inertia_scalar * *body_bivec 76 | } 77 | 78 | pub fn vel_at(&self, world_pos: Vector4) -> Vector4 { 79 | let body_pos = self.world_pos_to_body(world_pos); 80 | 81 | let rot_vel = self.body_vec_to_world( 82 | Vec4::from(body_pos) 83 | .left_contract_bv(&self.vel.angular) 84 | .into(), 85 | ); 86 | 87 | self.vel.linear + rot_vel 88 | } 89 | pub fn ray_intersect( 90 | &self, 91 | start: Vector4, 92 | dir: Vector4, 93 | ) -> Option { 94 | let start = self.world_pos_to_body(start); 95 | let dir = self.world_vec_to_body(dir); 96 | 97 | match &self.collider { 98 | Collider::Mesh { mesh } => { 99 | let mut interval = (std::f32::NEG_INFINITY, std::f32::INFINITY); 100 | 101 | for cell in mesh.cells.iter() { 102 | // grab a representative vertex on the cell 103 | let v0 = mesh.vertices[mesh.edges 104 | [mesh.faces[cell.faces[0]].edges[0]] 105 | .hd_vertex]; 106 | 107 | let denom = dir.dot(cell.normal); 108 | let lambda = (v0 - start).dot(cell.normal) / denom; 109 | 110 | if denom < 0.0 { 111 | interval.0 = interval.0.max(lambda); 112 | } else { 113 | interval.1 = interval.1.min(lambda); 114 | } 115 | 116 | if interval.1 < interval.0 { 117 | return None; 118 | } 119 | } 120 | 121 | Some(interval.0) 122 | } 123 | Collider::Sphere { radius } => { 124 | // Solve a quadratic equation! 125 | let a = dir.magnitude2(); 126 | let b = 2.0 * start.dot(dir); 127 | let c = start.magnitude2() - radius * radius; 128 | 129 | let discriminant = b * b - 4.0 * a * c; 130 | if discriminant >= 0.0 { 131 | Some((-b - discriminant.sqrt()) / (2.0 * a)) 132 | } else { 133 | None 134 | } 135 | } 136 | _ => None, 137 | } 138 | } 139 | 140 | pub fn body_vec_to_world(&self, v: Vector4) -> Vector4 { 141 | self.rotation.rotate(&v.into()).into() 142 | } 143 | 144 | pub fn world_vec_to_body(&self, v: Vector4) -> Vector4 { 145 | self.rotation.reverse().rotate(&v.into()).into() 146 | } 147 | 148 | pub fn body_pos_to_world(&self, v: Vector4) -> Vector4 { 149 | let rotated: Vector4 = self.rotation.rotate(&v.into()).into(); 150 | rotated + self.pos 151 | } 152 | 153 | pub fn world_pos_to_body(&self, v: Vector4) -> Vector4 { 154 | self.rotation 155 | .reverse() 156 | .rotate(&(v - self.pos).into()) 157 | .into() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/physics/collider.rs: -------------------------------------------------------------------------------- 1 | use super::Body; 2 | use crate::mesh::{ClipMesh, Mesh}; 3 | use crate::util::EPSILON; 4 | use crate::world::ObjectKey; 5 | 6 | use cgmath::{ 7 | Array, InnerSpace, Matrix3, SquareMatrix, Vector3, Vector4, Zero, 8 | }; 9 | 10 | #[derive(Clone)] 11 | pub enum Collider { 12 | HalfSpace { normal: Vector4 }, 13 | Mesh { mesh: Mesh }, 14 | Sphere { radius: f32 }, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct CollisionManifold { 19 | pub normal: Vector4, 20 | pub depth: f32, 21 | pub contacts: Vec>, 22 | } 23 | 24 | #[derive(Copy, Clone)] 25 | pub struct MeshRef<'a> { 26 | pub body: &'a Body, 27 | pub mesh: &'a Mesh, 28 | } 29 | 30 | #[derive(Copy, Clone)] 31 | pub struct SphereRef<'a> { 32 | pub body: &'a Body, 33 | pub radius: f32, 34 | } 35 | 36 | #[derive(Debug)] 37 | struct VertexCellContact { 38 | // if true indicates that the vertex is on body b but the cell is on body a 39 | side: bool, 40 | vertex_idx: usize, 41 | cell_idx: usize, 42 | normal: Vector4, 43 | } 44 | 45 | #[derive(Debug)] 46 | struct EdgeFaceContact { 47 | // if true indicates that the edge is on body b but the face is on body a 48 | side: bool, 49 | k: Vector4, 50 | t: Vector4, 51 | s: Vector4, 52 | u: Vector4, 53 | v: Vector4, 54 | normal: Vector4, 55 | } 56 | 57 | #[derive(Debug)] 58 | enum ContactData { 59 | VertexCell(VertexCellContact), 60 | EdgeFace(EdgeFaceContact), 61 | } 62 | 63 | #[derive(Copy, Clone, Debug)] 64 | enum ContactAxis { 65 | VertexCell { 66 | side: bool, 67 | cell_idx: usize, 68 | }, 69 | EdgeFace { 70 | side: bool, 71 | edge_idx: usize, 72 | face_idx: usize, 73 | }, 74 | } 75 | 76 | #[derive(Debug)] 77 | enum AxisResult { 78 | Intersection { 79 | penetration: f32, 80 | contact: ContactData, 81 | }, 82 | NotValidAxis, 83 | LargerPenetration, 84 | NoIntersection { 85 | normal: Vector4, 86 | }, 87 | } 88 | 89 | pub struct CollisionDetection { 90 | sat_cache: lru::LruCache<(ObjectKey, ObjectKey), Vector4>, 91 | } 92 | 93 | impl CollisionDetection { 94 | pub fn new() -> Self { 95 | Self { 96 | sat_cache: lru::LruCache::new(1000), 97 | } 98 | } 99 | 100 | pub fn detect_collisions( 101 | &mut self, 102 | key: (ObjectKey, ObjectKey), 103 | a: &Body, 104 | b: &Body, 105 | ) -> Option { 106 | match (&a.collider, &b.collider) { 107 | (Collider::HalfSpace { normal }, Collider::Mesh { mesh }) => { 108 | let plane_distance = a.pos.dot(*normal); 109 | let mut max_depth = 0.0; 110 | 111 | let contacts: Vec<_> = mesh 112 | .vertices 113 | .iter() 114 | .filter_map(|position| { 115 | let pos = b.body_pos_to_world(*position); 116 | 117 | let distance = pos.dot(*normal); 118 | 119 | let depth = plane_distance - distance; 120 | if depth > 0.0 { 121 | if depth > max_depth { 122 | max_depth = depth; 123 | } 124 | Some(pos) 125 | } else { 126 | None 127 | } 128 | }) 129 | .collect(); 130 | 131 | if contacts.len() > 0 { 132 | Some(CollisionManifold { 133 | normal: *normal, 134 | depth: max_depth, 135 | contacts, 136 | }) 137 | } else { 138 | None 139 | } 140 | } 141 | (Collider::HalfSpace { normal }, Collider::Sphere { radius }) => { 142 | let plane_distance = a.pos.dot(*normal); 143 | let sphere_distance = b.pos.dot(*normal); 144 | 145 | let center_distance = sphere_distance - plane_distance; 146 | if center_distance < *radius { 147 | Some(CollisionManifold { 148 | normal: *normal, 149 | depth: radius - center_distance, 150 | contacts: vec![b.pos - *radius * normal], 151 | }) 152 | } else { 153 | None 154 | } 155 | } 156 | (Collider::Mesh { .. }, Collider::HalfSpace { .. }) => { 157 | // Just call this again with the arguments swapped 158 | let mut manifold = self.detect_collisions((key.1, key.0), b, a); 159 | if let Some(m) = &mut manifold { 160 | m.normal *= -1.0; 161 | } 162 | manifold 163 | } 164 | ( 165 | Collider::Mesh { mesh: mesh_a }, 166 | Collider::Mesh { mesh: mesh_b }, 167 | ) => { 168 | let a = MeshRef { 169 | body: a, 170 | mesh: mesh_a, 171 | }; 172 | let b = MeshRef { 173 | body: b, 174 | mesh: mesh_b, 175 | }; 176 | if let Some(contact) = self.mesh_sat(key, a, b) { 177 | // dbg!(&contact); 178 | return Some(match contact { 179 | ContactData::VertexCell(contact) => { 180 | resolve_vertex_cell_contact(a, b, contact) 181 | } 182 | ContactData::EdgeFace(contact) => { 183 | resolve_edge_face_contact(a, b, contact) 184 | } 185 | }); 186 | } 187 | None 188 | } 189 | ( 190 | Collider::Mesh { mesh: mesh_a }, 191 | Collider::Sphere { radius: radius_b }, 192 | ) => { 193 | if (a.pos - b.pos).magnitude() > mesh_a.radius + radius_b { 194 | return None; 195 | } 196 | 197 | let closest_point = a.body_pos_to_world( 198 | mesh_a.closest_point_to(a.world_pos_to_body(b.pos)), 199 | ); 200 | let displacement = closest_point - b.pos; 201 | if displacement.magnitude() < EPSILON { 202 | None 203 | } else { 204 | let depth = radius_b - displacement.magnitude(); 205 | if depth > 0.0 { 206 | Some(CollisionManifold { 207 | depth, 208 | normal: -displacement.normalize(), 209 | contacts: vec![closest_point], 210 | }) 211 | } else { 212 | None 213 | } 214 | } 215 | } 216 | (Collider::Sphere { .. }, Collider::HalfSpace { .. }) => { 217 | // Just call this again with the arguments swapped 218 | let mut manifold = self.detect_collisions((key.1, key.0), b, a); 219 | if let Some(m) = &mut manifold { 220 | m.normal *= -1.0; 221 | } 222 | manifold 223 | } 224 | (Collider::Sphere { .. }, Collider::Mesh { .. }) => { 225 | // Just call this again with the arguments swapped 226 | let mut manifold = self.detect_collisions((key.1, key.0), b, a); 227 | if let Some(m) = &mut manifold { 228 | m.normal *= -1.0; 229 | } 230 | manifold 231 | } 232 | ( 233 | Collider::Sphere { radius: radius_a }, 234 | Collider::Sphere { radius: radius_b }, 235 | ) => { 236 | let displacement = b.pos - a.pos; 237 | let depth = radius_a + radius_b - displacement.magnitude(); 238 | if depth > 0.0 { 239 | let normal = displacement.normalize(); 240 | Some(CollisionManifold { 241 | normal, 242 | depth, 243 | contacts: vec![a.pos + depth * normal], 244 | }) 245 | } else { 246 | None 247 | } 248 | } 249 | _ => None, 250 | } 251 | } 252 | 253 | fn mesh_sat( 254 | &mut self, 255 | key: (ObjectKey, ObjectKey), 256 | a: MeshRef, 257 | b: MeshRef, 258 | ) -> Option { 259 | // Bounding hypersphere check 260 | if (a.body.pos - b.body.pos).magnitude2() 261 | > (a.mesh.radius + b.mesh.radius).powi(2) 262 | { 263 | return None; 264 | } 265 | 266 | let mut min_penetration = std::f32::INFINITY; 267 | let mut curr_contact = None; 268 | 269 | let mut edge_cells_cache = None; 270 | 271 | if let Some(axis) = self.sat_cache.get(&key) { 272 | let axis = *axis; 273 | if !self.fast_check_axis(a, b, axis) { 274 | return None; 275 | } 276 | // If we got here then the cache entry is no longer useful. 277 | self.sat_cache.pop(&key); 278 | } 279 | 280 | macro_rules! axis_check { 281 | ($axis: expr) => { 282 | match Self::check_axis( 283 | a, 284 | b, 285 | $axis, 286 | min_penetration, 287 | &mut edge_cells_cache, 288 | ) { 289 | AxisResult::Intersection { 290 | penetration, 291 | contact, 292 | } => { 293 | min_penetration = penetration; 294 | curr_contact = Some(contact); 295 | } 296 | AxisResult::NoIntersection { normal } => { 297 | self.sat_cache.put(key, normal); 298 | return None; 299 | } 300 | _ => (), 301 | } 302 | }; 303 | } 304 | 305 | for cell_idx in 0..a.mesh.cells.len() { 306 | let axis = ContactAxis::VertexCell { 307 | side: true, 308 | cell_idx, 309 | }; 310 | axis_check!(axis); 311 | } 312 | 313 | for cell_idx in 0..b.mesh.cells.len() { 314 | let axis = ContactAxis::VertexCell { 315 | side: false, 316 | cell_idx, 317 | }; 318 | axis_check!(axis); 319 | } 320 | 321 | for edge_idx in 0..a.mesh.edges.len() { 322 | edge_cells_cache = None; 323 | for face_idx in 0..b.mesh.faces.len() { 324 | let axis = ContactAxis::EdgeFace { 325 | side: false, 326 | edge_idx, 327 | face_idx, 328 | }; 329 | axis_check!(axis); 330 | } 331 | } 332 | 333 | for edge_idx in 0..b.mesh.edges.len() { 334 | edge_cells_cache = None; 335 | for face_idx in 0..a.mesh.faces.len() { 336 | let axis = ContactAxis::EdgeFace { 337 | side: true, 338 | edge_idx, 339 | face_idx, 340 | }; 341 | axis_check!(axis); 342 | } 343 | } 344 | 345 | curr_contact 346 | } 347 | 348 | fn axis_span(&self, a: MeshRef, normal: Vector4) -> (f32, f32) { 349 | let mut min = std::f32::NEG_INFINITY; 350 | let mut max = std::f32::INFINITY; 351 | 352 | for v in a.mesh.vertices.iter() { 353 | let d = a.body.body_pos_to_world(*v).dot(normal); 354 | min = min.min(d); 355 | max = max.max(d); 356 | } 357 | 358 | (min, max) 359 | } 360 | 361 | fn fast_check_axis( 362 | &self, 363 | a: MeshRef, 364 | b: MeshRef, 365 | normal: Vector4, 366 | ) -> bool { 367 | let a_range = self.axis_span(a, normal); 368 | let b_range = self.axis_span(b, normal); 369 | 370 | a_range.0 <= b_range.1 && b_range.0 <= a_range.1 371 | } 372 | 373 | fn check_axis( 374 | a: MeshRef, 375 | b: MeshRef, 376 | axis: ContactAxis, 377 | min_penetration: f32, 378 | edge_cells_ref: &mut Option>>, 379 | ) -> AxisResult { 380 | match axis { 381 | ContactAxis::VertexCell { cell_idx, side } => { 382 | let (a, b) = if side { (a, b) } else { (b, a) }; 383 | Self::check_vertex_cell(a, b, cell_idx, side, min_penetration) 384 | } 385 | ContactAxis::EdgeFace { 386 | edge_idx, 387 | face_idx, 388 | side, 389 | } => { 390 | let (a, b) = if side { (b, a) } else { (a, b) }; 391 | Self::check_edge_face( 392 | a, 393 | b, 394 | edge_idx, 395 | face_idx, 396 | side, 397 | min_penetration, 398 | edge_cells_ref, 399 | ) 400 | } 401 | } 402 | } 403 | 404 | fn check_edge_face( 405 | a: MeshRef, 406 | b: MeshRef, 407 | edge_idx: usize, 408 | face_idx: usize, 409 | side: bool, 410 | min_penetration: f32, 411 | edge_cells_ref: &mut Option>>, 412 | ) -> AxisResult { 413 | let edge = &a.mesh.edges[edge_idx]; 414 | let face = &b.mesh.faces[face_idx]; 415 | 416 | let edge_cells = match edge_cells_ref { 417 | Some(cs) => cs, 418 | None => { 419 | let mut cells = Vec::new(); 420 | if let Some(face_idx) = edge.faces.first() { 421 | let face = &a.mesh.faces[*face_idx]; 422 | cells.push(face.hd_cell); 423 | cells.push(face.tl_cell); 424 | 425 | for face_idx in edge.faces.iter().skip(1) { 426 | let face = &a.mesh.faces[*face_idx]; 427 | if !cells.contains(&face.hd_cell) { 428 | cells.push(face.hd_cell); 429 | } else if !cells.contains(&face.tl_cell) { 430 | cells.push(face.tl_cell); 431 | } 432 | } 433 | }; 434 | let edge_cells = 435 | cells.into_iter().map(|i| a.mesh.cells[i].normal).collect(); 436 | *edge_cells_ref = Some(edge_cells); 437 | edge_cells_ref.as_ref().unwrap() 438 | } 439 | }; 440 | 441 | // grab a representative vertex on the edge 442 | let v0 = a.mesh.vertices[edge.hd_vertex]; 443 | // grab the edge vector 444 | let u = a.mesh.vertices[edge.tl_vertex] - v0; 445 | 446 | let c0 = a.body.world_vec_to_body( 447 | b.body.body_vec_to_world(b.mesh.cells[face.hd_cell].normal), 448 | ); 449 | let c1 = a.body.world_vec_to_body( 450 | b.body.body_vec_to_world(b.mesh.cells[face.tl_cell].normal), 451 | ); 452 | 453 | if !minkowski_edge_face_check(edge_cells, (-c0, -c1)) { 454 | return AxisResult::NotValidAxis; 455 | } 456 | 457 | // grab two edges on the face. Because of the way the face 458 | // was generated, these edges are guaranteed to be 459 | // non-parallel. 460 | let (e0, e1) = 461 | (&b.mesh.edges[face.edges[0]], &b.mesh.edges[face.edges[1]]); 462 | // grab edge vectors 463 | let v = a.body.world_vec_to_body(b.body.body_vec_to_world( 464 | b.mesh.vertices[e0.tl_vertex] - b.mesh.vertices[e0.hd_vertex], 465 | )); 466 | let w = a.body.world_vec_to_body(b.body.body_vec_to_world( 467 | b.mesh.vertices[e1.tl_vertex] - b.mesh.vertices[e1.hd_vertex], 468 | )); 469 | 470 | // grab a vector on the face also 471 | let v1 = a.body.world_pos_to_body( 472 | b.body.body_pos_to_world(b.mesh.vertices[e0.hd_vertex]), 473 | ); 474 | 475 | // grab the normal vector adjacent to all 476 | let mut n = crate::alg::triple_cross_product(u, v, w).normalize(); 477 | if !n.is_finite() { 478 | return AxisResult::NotValidAxis; 479 | } 480 | // ensure that n points from a to b 481 | let mut dist_a = n.dot(v0); 482 | if dist_a < 0.0 { 483 | n = -n; 484 | dist_a = -dist_a; 485 | } 486 | 487 | let dist_b = n.dot(v1); 488 | 489 | if dist_b < dist_a { 490 | if dist_a - dist_b < min_penetration { 491 | // Intersection along this axis 492 | AxisResult::Intersection { 493 | penetration: dist_a - dist_b, 494 | contact: ContactData::EdgeFace(EdgeFaceContact { 495 | side, 496 | k: a.body.body_pos_to_world(v0), 497 | t: a.body.body_vec_to_world(u), 498 | s: b.body 499 | .body_pos_to_world(b.mesh.vertices[e0.hd_vertex]), 500 | u: a.body.body_vec_to_world(v), 501 | v: a.body.body_vec_to_world(w), 502 | normal: a.body.body_vec_to_world(n), 503 | }), 504 | } 505 | } else { 506 | AxisResult::LargerPenetration 507 | } 508 | } else { 509 | AxisResult::NoIntersection { 510 | normal: a.body.body_vec_to_world(n), 511 | } 512 | } 513 | } 514 | 515 | fn check_vertex_cell( 516 | a: MeshRef, 517 | b: MeshRef, 518 | cell_idx: usize, 519 | side: bool, 520 | min_penetration: f32, 521 | ) -> AxisResult { 522 | let cell = &a.mesh.cells[cell_idx]; 523 | 524 | // grab a representative vertex on the cell to get the distance 525 | let v0 = a.mesh.vertices 526 | [a.mesh.edges[a.mesh.faces[cell.faces[0]].edges[0]].hd_vertex]; 527 | 528 | let dist_a = v0.dot(cell.normal); 529 | let mut min_dist_b = dist_a; 530 | let mut min_vertex_idx = 0; 531 | // loop through all the vertices on b 532 | for (vertex_idx, v) in b.mesh.vertices.iter().enumerate() { 533 | let dist_b = a 534 | .body 535 | .world_pos_to_body(b.body.body_pos_to_world(*v)) 536 | .dot(cell.normal); 537 | if dist_b < min_dist_b { 538 | min_dist_b = dist_b; 539 | min_vertex_idx = vertex_idx; 540 | } 541 | } 542 | 543 | if min_dist_b < dist_a { 544 | // Intersection along this axis 545 | if dist_a - min_dist_b < min_penetration { 546 | AxisResult::Intersection { 547 | penetration: dist_a - min_dist_b, 548 | contact: ContactData::VertexCell(VertexCellContact { 549 | side, 550 | vertex_idx: min_vertex_idx, 551 | cell_idx, 552 | normal: a.body.body_vec_to_world(cell.normal), 553 | }), 554 | } 555 | } else { 556 | AxisResult::LargerPenetration 557 | } 558 | } else { 559 | // Found a separating axis! 560 | AxisResult::NoIntersection { 561 | normal: a.body.body_vec_to_world(cell.normal), 562 | } 563 | } 564 | } 565 | } 566 | 567 | fn resolve_vertex_cell_contact( 568 | a: MeshRef, 569 | b: MeshRef, 570 | contact: VertexCellContact, 571 | ) -> CollisionManifold { 572 | if !contact.side { 573 | // just swap the meshes around in the call 574 | let mut result = resolve_vertex_cell_contact( 575 | b, 576 | a, 577 | VertexCellContact { 578 | side: true, 579 | ..contact 580 | }, 581 | ); 582 | // flip the normal as the collision resolution code expects the normal 583 | // to be oriented in a certain way 584 | result.normal *= -1.0; 585 | return result; 586 | } 587 | 588 | let reference_cell = &a.mesh.cells[contact.cell_idx]; 589 | 590 | // Need to determine incident cell - find the cell with the least dot 591 | // product with the reference normal 592 | let mut min_dot_product = 1.0; 593 | let mut incident_cell_idx = 0; 594 | for cell_idx in b.mesh.vertex_data[contact.vertex_idx].cells.iter() { 595 | let candidate_cell = &b.mesh.cells[*cell_idx]; 596 | let dot_product = b 597 | .body 598 | .body_vec_to_world(candidate_cell.normal) 599 | .dot(contact.normal); 600 | if dot_product < min_dot_product { 601 | min_dot_product = dot_product; 602 | incident_cell_idx = *cell_idx; 603 | } 604 | } 605 | 606 | // clip the incident cell against the adjacent cells of the reference cell 607 | let mut clipper = ClipMesh::from_cell(b.mesh, incident_cell_idx); 608 | let mut v0 = Vector4::zero(); 609 | for face_idx in reference_cell.faces.iter() { 610 | let face = &a.mesh.faces[*face_idx]; 611 | // grab a representative vertex 612 | v0 = a.mesh.vertices[a.mesh.edges[face.edges[0]].hd_vertex]; 613 | 614 | let cell_idx = if face.hd_cell == contact.cell_idx { 615 | face.tl_cell 616 | } else { 617 | face.hd_cell 618 | }; 619 | let clip_normal = b.body.world_vec_to_body( 620 | a.body.body_vec_to_world(-a.mesh.cells[cell_idx].normal), 621 | ); 622 | let clip_distance = clip_normal 623 | .dot(b.body.world_pos_to_body(a.body.body_pos_to_world(v0))); 624 | 625 | clipper.clip_by(clip_normal, clip_distance); 626 | } 627 | let reference_dist = v0.dot(reference_cell.normal); 628 | 629 | // keep points that are below the reference plane 630 | let mut max_depth = 0f32; 631 | let contacts = clipper 632 | .to_vertices() 633 | .into_iter() 634 | .filter_map(|b_vec| { 635 | let world_vec = b.body.body_pos_to_world(b_vec); 636 | let a_vec = a.body.world_pos_to_body(world_vec); 637 | 638 | let dist = a_vec.dot(reference_cell.normal); 639 | if dist < reference_dist { 640 | max_depth = max_depth.max(reference_dist - dist); 641 | Some(world_vec) 642 | } else { 643 | None 644 | } 645 | }) 646 | .collect(); 647 | 648 | CollisionManifold { 649 | normal: a.body.body_vec_to_world(reference_cell.normal), 650 | depth: max_depth, 651 | contacts, 652 | } 653 | } 654 | 655 | fn resolve_edge_face_contact( 656 | a: MeshRef, 657 | b: MeshRef, 658 | contact: EdgeFaceContact, 659 | ) -> CollisionManifold { 660 | if contact.side { 661 | // just swap the meshes around in the call 662 | let mut result = resolve_edge_face_contact( 663 | b, 664 | a, 665 | EdgeFaceContact { 666 | side: false, 667 | ..contact 668 | }, 669 | ); 670 | // flip the normal as the collision resolution code expects the normal 671 | // to be oriented in a certain way 672 | result.normal *= -1.0; 673 | return result; 674 | } 675 | 676 | let EdgeFaceContact { 677 | k, 678 | t, 679 | s, 680 | u, 681 | v, 682 | normal, 683 | .. 684 | } = contact; 685 | 686 | // Now we gotta solve an equation in three variables to get the closest point 687 | // form the matrix 688 | #[rustfmt::skip] 689 | let mat = Matrix3::new( 690 | t.dot(t), -t.dot(u), -t.dot(v), 691 | -t.dot(u), u.dot(u), u.dot(v), 692 | -t.dot(v), u.dot(v), v.dot(v), 693 | ); 694 | // dbg!(mat); 695 | // dbg!(mat.determinant()); 696 | let y = Vector3::new(-(k - s).dot(t), (k - s).dot(u), (k - s).dot(v)); 697 | let x = match mat.invert() { 698 | Some(m) => m, 699 | None => { 700 | // This shouldn't really happen, but as a failsafe let's return an 701 | // empty contact 702 | return CollisionManifold { 703 | normal, 704 | depth: 0.0, 705 | contacts: Vec::new(), 706 | }; 707 | } 708 | } * y; 709 | 710 | let p1 = k + x.x * t; 711 | let p2 = s + x.y * u + x.z * v; 712 | let depth = (p1 - p2).magnitude(); 713 | 714 | CollisionManifold { 715 | normal, 716 | depth, 717 | contacts: vec![(p1 + p2) / 2.0], 718 | } 719 | } 720 | 721 | fn minkowski_edge_face_check( 722 | edge_cells: &Vec>, 723 | face_cells: (Vector4, Vector4), 724 | ) -> bool { 725 | // grab the normal corresponding to the great sphere the edge lies in 726 | let normal = if let &[a, b, c, ..] = &edge_cells[..] { 727 | crate::alg::triple_cross_product(a, b, c) 728 | } else { 729 | return false; 730 | }; 731 | 732 | // intersect the plane defined by the face with the hyperplane, giving us a 733 | // line 734 | let (u, v) = face_cells; 735 | let factor = -v.dot(normal) / u.dot(normal); 736 | if !factor.is_finite() { 737 | return false; 738 | } 739 | let t = u * factor + v; 740 | // intersect t with the great sphere, giving us two points 741 | let s0 = t.normalize(); 742 | let s1 = -s0; 743 | 744 | // check that either s0 or s1 are inside the great arc 745 | let s = { 746 | let target_angle = u.dot(v).acos(); 747 | let s0_u = s0.dot(u).acos(); 748 | let s0_v = s0.dot(v).acos(); 749 | let s1_u = s1.dot(u).acos(); 750 | let s1_v = s1.dot(v).acos(); 751 | 752 | if (target_angle - s0_u - s0_v).abs() < EPSILON { 753 | s0 754 | } else if (target_angle - s1_u - s1_v).abs() < EPSILON { 755 | s1 756 | } else { 757 | // neither s0 nor s1 are in the arc 758 | return false; 759 | } 760 | }; 761 | 762 | // now check if s is actually in the spherical polygon corresponding to the edge 763 | for i in 0..edge_cells.len() { 764 | let (u, v, w) = ( 765 | edge_cells[i], 766 | edge_cells[(i + 1) % edge_cells.len()], 767 | edge_cells[(i + 2) % edge_cells.len()], 768 | ); 769 | let n = crate::alg::triple_cross_product(u, v, normal); 770 | // check that s is on the same side of the sphere as another point in the polygon, w 771 | if s.dot(n) * w.dot(n) < 0.0 { 772 | return false; 773 | } 774 | } 775 | 776 | // s is indeed an intersection! 777 | true 778 | } 779 | 780 | #[cfg(test)] 781 | mod tests { 782 | use super::*; 783 | use crate::alg::Bivec4; 784 | use crate::physics::Material; 785 | use crate::physics::Velocity; 786 | 787 | #[test] 788 | pub fn edge_edge_separation() { 789 | /* 790 | let tess = Mesh::from_schlafli_symbol(&[4, 3, 3]); 791 | 792 | let tess_a = Body { 793 | mass: 1.0, 794 | moment_inertia_scalar: 1.0 / 6.0, 795 | material: Material { restitution: 0.4 }, 796 | stationary: false, 797 | pos: Vector4::new(0.0, 0.0, 0.0, 0.0), 798 | rotation: Bivec4::new( 799 | 0.0, 800 | std::f32::consts::FRAC_PI_8, 801 | 0.0, 802 | 0.0, 803 | 0.0, 804 | 0.0, 805 | ) 806 | .exp(), 807 | vel: Velocity::zero(), 808 | collider: Collider::Mesh { mesh: tess.clone() }, 809 | }; 810 | 811 | let tess_b = Body { 812 | pos: Vector4::new(std::f32::consts::SQRT_2 - 0.1, 0.0, 0.0, 0.0), 813 | rotation: Bivec4::new( 814 | std::f32::consts::FRAC_PI_8, 815 | 0.0, 816 | 0.0, 817 | 0.0, 818 | 0.0, 819 | 0.0, 820 | ) 821 | .exp(), 822 | ..tess_a.clone() 823 | }; 824 | 825 | dbg!(detect_collisions(&tess_a, &tess_b)); 826 | */ 827 | } 828 | } 829 | -------------------------------------------------------------------------------- /src/physics/collision.rs: -------------------------------------------------------------------------------- 1 | use super::{Body, CollisionManifold}; 2 | use crate::alg::Vec4; 3 | use cgmath::{InnerSpace, Vector3, Vector4}; 4 | 5 | #[derive(Debug)] 6 | pub struct ContactState { 7 | contact: Vector4, 8 | bias: f32, 9 | normal_mass: f32, 10 | normal_impulse: f32, 11 | tangent_mass: [f32; 3], 12 | tangent_impulse: [f32; 3], 13 | } 14 | 15 | pub struct CollisionConstraint { 16 | normal: Vector4, 17 | tangents: [Vector4; 3], 18 | contacts: Vec, 19 | mu: f32, 20 | } 21 | 22 | impl CollisionConstraint { 23 | pub fn new( 24 | manifold: CollisionManifold, 25 | a: &Body, 26 | mass_adjustment_a: f32, 27 | b: &Body, 28 | mass_adjustment_b: f32, 29 | ) -> Self { 30 | let CollisionManifold { 31 | normal, 32 | depth, 33 | contacts, 34 | } = manifold; 35 | 36 | let e = a.material.restitution.min(b.material.restitution); 37 | // TODO: move this into the Material struct 38 | let mu = 0.4; 39 | 40 | let tangents = crate::util::orthonormal_basis(normal); 41 | 42 | let contacts: Vec<_> = contacts 43 | .into_iter() 44 | .map(|contact| { 45 | let rel_vel = b.vel_at(contact) - a.vel_at(contact); 46 | let rel_vel_normal = rel_vel.dot(normal); 47 | 48 | let slop = 0.01; 49 | let baumgarte = 0.2; 50 | let bias = -baumgarte * 60.0 * (slop - depth).min(0.0) 51 | + if rel_vel_normal < -1.0 { 52 | -e * rel_vel_normal 53 | } else { 54 | 0.0 55 | }; 56 | 57 | let inv_a_mass = if a.mass > 0.0 { 58 | mass_adjustment_a / a.mass 59 | } else { 60 | 0.0 61 | }; 62 | let inv_b_mass = if b.mass > 0.0 { 63 | mass_adjustment_b / b.mass 64 | } else { 65 | 0.0 66 | }; 67 | 68 | let inverse_mass_term = 69 | |body: &Body, 70 | normal: Vector4, 71 | contact: Vector4| { 72 | // n' = ~R n R 73 | let body_normal = body.world_vec_to_body(normal); 74 | let body_contact = body.world_pos_to_body(contact); 75 | 76 | // n . (R x . I_b^-1(x /\ n') ~R) 77 | normal.dot( 78 | body.body_vec_to_world( 79 | Vec4::from(body_contact) 80 | .left_contract_bv( 81 | &body.inverse_moment_of_inertia( 82 | &Vec4::from(body_contact) 83 | .wedge_v(&body_normal.into()), 84 | ), 85 | ) 86 | .into(), 87 | ), 88 | ) 89 | }; 90 | 91 | let inv_l_a = 92 | mass_adjustment_a * inverse_mass_term(a, normal, contact); 93 | let inv_l_b = 94 | mass_adjustment_b * inverse_mass_term(b, normal, contact); 95 | 96 | let normal_mass = 97 | 1.0 / (inv_a_mass + inv_b_mass + inv_l_a + inv_l_b); 98 | 99 | let mut tangent_mass = [0.0, 0.0, 0.0]; 100 | for i in 0..3 { 101 | let inv_l_t_a = mass_adjustment_a 102 | * inverse_mass_term(a, tangents[i], contact); 103 | let inv_l_t_b = mass_adjustment_b 104 | * inverse_mass_term(b, tangents[i], contact); 105 | 106 | tangent_mass[i] = 107 | 1.0 / (inv_a_mass + inv_b_mass + inv_l_t_a + inv_l_t_b); 108 | } 109 | 110 | ContactState { 111 | contact, 112 | bias, 113 | normal_mass, 114 | normal_impulse: 0.0, 115 | tangent_mass, 116 | tangent_impulse: [0.0, 0.0, 0.0], 117 | } 118 | }) 119 | .collect(); 120 | 121 | Self { 122 | normal, 123 | tangents, 124 | contacts, 125 | mu, 126 | } 127 | } 128 | 129 | pub fn solve(&mut self, a: &mut Body, b: &mut Body) { 130 | for contact_state in self.contacts.iter_mut() { 131 | let ContactState { 132 | contact, 133 | bias, 134 | normal_mass, 135 | ref mut normal_impulse, 136 | tangent_mass, 137 | ref mut tangent_impulse, 138 | } = *contact_state; 139 | 140 | let rel_vel = b.vel_at(contact) - a.vel_at(contact); 141 | 142 | // calculate friction impulse 143 | let mut new_impulses = [0f32; 3]; 144 | for i in 0..3 { 145 | let lambda = -rel_vel.dot(self.tangents[i]) * tangent_mass[i]; 146 | new_impulses[i] = tangent_impulse[i] + lambda; 147 | } 148 | 149 | // clamp the total magnitude 150 | let max_impulse = (self.mu * *normal_impulse).abs(); 151 | let mut impulse_mag2 = 0f32; 152 | new_impulses.iter().for_each(|i| impulse_mag2 += i * i); 153 | let impulse_mag = impulse_mag2.sqrt(); 154 | if impulse_mag > max_impulse { 155 | let factor = max_impulse / impulse_mag; 156 | new_impulses.iter_mut().for_each(|i| *i *= factor); 157 | } 158 | 159 | // apply the friction impulses 160 | for i in 0..3 { 161 | let impulse = 162 | self.tangents[i] * (new_impulses[i] - tangent_impulse[i]); 163 | tangent_impulse[i] = new_impulses[i]; 164 | a.resolve_impulse(-impulse, contact); 165 | b.resolve_impulse(impulse, contact); 166 | } 167 | 168 | // calculate normal impulse 169 | let rel_vel_normal = rel_vel.dot(self.normal); 170 | let lambda = normal_mass * (-rel_vel_normal + bias); 171 | let prev_impulse = *normal_impulse; 172 | *normal_impulse = (prev_impulse + lambda).max(0.0); 173 | let impulse = self.normal * (*normal_impulse - prev_impulse); 174 | a.resolve_impulse(-impulse, contact); 175 | b.resolve_impulse(impulse, contact); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/physics/gjk.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::{CollisionManifold, MeshRef, SphereRef}; 4 | use crate::alg::triple_cross_product; 5 | use crate::mesh::Mesh; 6 | use crate::util::{NotNaN, EPSILON}; 7 | use cgmath::{InnerSpace, Matrix2, SquareMatrix, Vector2, Vector4, Zero}; 8 | 9 | pub trait Support { 10 | fn support(&self, direction: Vector4) -> Vector4; 11 | } 12 | 13 | pub struct OffsetMeshRef<'a> { 14 | pub mesh_ref: MeshRef<'a>, 15 | pub offset: Vector4, 16 | } 17 | 18 | impl<'a> Support for MeshRef<'a> { 19 | fn support(&self, direction: Vector4) -> Vector4 { 20 | let body_d = self.body.world_vec_to_body(direction); 21 | self.body.body_pos_to_world( 22 | *self 23 | .mesh 24 | .vertices 25 | .iter() 26 | .max_by_key(|v| NotNaN::new(v.dot(body_d)).unwrap()) 27 | .unwrap(), 28 | ) 29 | } 30 | } 31 | 32 | impl<'a> Support for SphereRef<'a> { 33 | fn support(&self, direction: Vector4) -> Vector4 { 34 | direction.normalize() * self.radius + self.body.pos 35 | } 36 | } 37 | 38 | impl<'a> Support for OffsetMeshRef<'a> { 39 | fn support(&self, direction: Vector4) -> Vector4 { 40 | self.mesh_ref.support(direction) - self.offset 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone, Copy)] 45 | pub struct CSO 46 | where 47 | A: Support, 48 | B: Support, 49 | { 50 | a: A, 51 | b: B, 52 | } 53 | 54 | impl Support for CSO 55 | where 56 | A: Support, 57 | B: Support, 58 | { 59 | fn support(&self, direction: Vector4) -> Vector4 { 60 | self.a.support(direction) - self.b.support(-direction) 61 | } 62 | } 63 | 64 | fn support(m: MeshRef, direction: Vector4) -> Vector4 { 65 | let body_d = m.body.world_vec_to_body(direction); 66 | m.body.body_pos_to_world( 67 | *m.mesh 68 | .vertices 69 | .iter() 70 | .max_by_key(|v| NotNaN::new(v.dot(body_d)).unwrap()) 71 | .unwrap(), 72 | ) 73 | } 74 | 75 | pub struct Simplex { 76 | vertices: [Vector4; 5], 77 | length: usize, 78 | } 79 | 80 | impl Simplex { 81 | fn new(initial_point: Vector4) -> Self { 82 | let mut vertices = [Vector4::zero(); 5]; 83 | vertices[0] = initial_point; 84 | Self { 85 | vertices, 86 | length: 1, 87 | } 88 | } 89 | 90 | fn push(&mut self, point: Vector4) { 91 | if self.length >= 5 { 92 | panic!("Simplex is already full!"); 93 | } 94 | 95 | self.vertices[self.length] = point; 96 | self.length += 1; 97 | } 98 | 99 | fn remove(&mut self, index: usize) { 100 | if index >= self.length { 101 | panic!("Invalid simplex index provided to remove_at"); 102 | } 103 | if self.length == 1 { 104 | panic!("Simplex cannot have no points!"); 105 | } 106 | 107 | for i in (index + 1)..self.length { 108 | self.vertices[i - 1] = self.vertices[i]; 109 | } 110 | self.length -= 1; 111 | } 112 | 113 | // If self contains the origin, returns None. Otherwise, updates self to be 114 | // the closest simplex on self to the origin, and returns the closest point 115 | // on the updated simplex to the origin. 116 | fn nearest_simplex(&mut self) -> Option> { 117 | match self.length { 118 | 1 => { 119 | // single point case, just return it 120 | let a = self.vertices[0]; 121 | Some(a) 122 | } 123 | 2 => { 124 | // line case, return a direction perpendicular 125 | let (a, b) = (self.vertices[0], self.vertices[1]); 126 | let ab = b - a; 127 | let lambda = -a.dot(ab) / ab.magnitude2(); 128 | // lambda is now such that a + lambda * (b - a) is the point on 129 | // the defined by a and b closest to the origin. 130 | Some(a + lambda * ab) 131 | } 132 | 3 => { 133 | // triangle case, return the closest point on the triangle 134 | let (a, b, c) = 135 | (self.vertices[0], self.vertices[1], self.vertices[2]); 136 | let (ab, ac) = (b - a, c - a); 137 | 138 | // Equation of two variables, we're just going to use cgmath to 139 | // solve it 140 | #[rustfmt::skip] 141 | let mat = Matrix2::new( 142 | ab.magnitude2(), ab.dot(ac), 143 | ab.dot(ac), ac.magnitude2(), 144 | ); 145 | let y = Vector2::new(-a.dot(ab), -a.dot(ac)); 146 | let x = mat.invert().unwrap() * y; 147 | 148 | let (lambda, mu) = (x.x, x.y); 149 | 150 | Some(a + lambda * ab + mu * ac) 151 | } 152 | 4 => { 153 | // tetrahedron case, return a direction perpendicular 154 | let a = self.vertices[0]; 155 | let b = self.vertices[1]; 156 | let c = self.vertices[2]; 157 | let d = self.vertices[3]; 158 | let (ab, ac, ad) = (b - a, c - a, d - a); 159 | 160 | // We can use the triple cross product to just grab a normal to 161 | // the tetrahedron 162 | let n = triple_cross_product(ab, ac, ad); 163 | let k = a.dot(n) / n.magnitude2(); 164 | 165 | Some(k * n) 166 | } 167 | 5 => { 168 | // Now we have a full 5-cell as our simplex. To check if the 169 | // origin is inside our simplex now, we simply need to perform 170 | // 5 halfspace tests. 171 | 172 | // We can actually skip one of the halfspace tests - because we 173 | // know that on the last iteration the direction was set to the 174 | // normal of the tetrahedron abcd, the origin must be in the 175 | // halfspace defined by the tetrahedron abcd. 176 | 177 | for i in 0..4 { 178 | // tetrahedron is the 5 vertices without the ith vertex 179 | let mut j = 0; 180 | let mut tetrahedron = [Vector4::zero(); 4]; 181 | for k in 0..5 { 182 | if k != i { 183 | tetrahedron[j] = self.vertices[k]; 184 | j += 1; 185 | } 186 | } 187 | 188 | let a = tetrahedron[0]; 189 | let b = tetrahedron[1]; 190 | let c = tetrahedron[2]; 191 | let d = tetrahedron[3]; 192 | // e is the last vertex not in the tetrahedron 193 | let e = self.vertices[i]; 194 | 195 | let (ab, ac, ad) = (b - a, c - a, d - a); 196 | let normal = triple_cross_product(ab, ac, ad); 197 | 198 | // the origin has to be on the same side as e to pass the 199 | // halfspace test! 200 | let ao_dist = -a.dot(normal); 201 | if ao_dist * (e - a).dot(normal) < 0.0 { 202 | // failed the halfspace test, so we know e is on the 203 | // opposite side of the tetrahedron to the origin. 204 | // We can then remove e from the simplex and set 205 | // direction to the normal pointing towards the origin. 206 | self.remove(i); 207 | let k = -ao_dist / normal.magnitude2(); 208 | return Some(k * normal); 209 | } 210 | } 211 | 212 | // If we reach here we've passed all the halfspace tests, so 213 | // the tetrahedron does indeed contain the origin! 214 | None 215 | } 216 | _ => unreachable!("invalid simplex!"), 217 | } 218 | } 219 | } 220 | 221 | pub fn gjk_intersection( 222 | object: S, 223 | initial_direction: Vector4, 224 | ) -> Result> 225 | where 226 | S: Support, 227 | { 228 | let mut a = object.support(initial_direction); 229 | let mut s = Simplex::new(a); 230 | let mut d = -a; 231 | 232 | loop { 233 | a = object.support(d); 234 | if a.dot(d) < EPSILON { 235 | return Err(-d); 236 | } 237 | s.push(a); 238 | match s.nearest_simplex() { 239 | Some(closest) => d = -closest, 240 | None => return Ok(s), 241 | } 242 | } 243 | } 244 | 245 | struct EPCell { 246 | faces: [[usize; 3]; 4], 247 | normal: Vector4, 248 | distance: f32, 249 | } 250 | 251 | impl EPCell { 252 | fn from_faces( 253 | vertices: &Vec>, 254 | faces: [[usize; 3]; 4], 255 | ) -> Self { 256 | let mut unique_vertices = [0; 4]; 257 | let mut unique_vertices_count = 0; 258 | 'outer: for face in faces.iter() { 259 | for vertex_idx in face.iter() { 260 | if !&unique_vertices[0..unique_vertices_count] 261 | .contains(vertex_idx) 262 | { 263 | unique_vertices[unique_vertices_count] = *vertex_idx; 264 | unique_vertices_count += 1; 265 | 266 | if unique_vertices_count >= 4 { 267 | break 'outer; 268 | } 269 | } 270 | } 271 | } 272 | 273 | let a = vertices[unique_vertices[0]]; 274 | let b = vertices[unique_vertices[1]]; 275 | let c = vertices[unique_vertices[2]]; 276 | let d = vertices[unique_vertices[3]]; 277 | 278 | let mut normal = triple_cross_product(b - a, c - a, d - a); 279 | let mut distance = normal.dot(a); 280 | if distance < 0.0 { 281 | normal = -normal; 282 | distance = -distance; 283 | } 284 | 285 | Self { 286 | faces, 287 | normal, 288 | distance, 289 | } 290 | } 291 | } 292 | 293 | struct ExpandingPolytope { 294 | vertices: Vec>, 295 | cells: Vec, 296 | } 297 | 298 | impl ExpandingPolytope { 299 | fn from_simplex(simplex: Simplex) -> Self { 300 | assert!(simplex.length == 5); 301 | let vertices = simplex.vertices.to_vec(); 302 | 303 | let mut faces = Vec::new(); 304 | faces.reserve(10); 305 | for i in 0..5 { 306 | for j in (i + 1)..5 { 307 | for k in (j + 1)..5 { 308 | faces.push([i, j, k]); 309 | } 310 | } 311 | } 312 | 313 | let mut cells = Vec::new(); 314 | cells.reserve(5); 315 | for i in 0..5 { 316 | let mut cell_faces = [[0, 0, 0]; 4]; 317 | let mut cell_face_count = 0; 318 | for face in faces.iter() { 319 | if !face.contains(&i) { 320 | cell_faces[cell_face_count] = *face; 321 | cell_face_count += 1; 322 | 323 | if cell_face_count >= 4 { 324 | break; 325 | } 326 | } 327 | } 328 | 329 | cells.push(EPCell::from_faces(&vertices, cell_faces)); 330 | } 331 | 332 | Self { vertices, cells } 333 | } 334 | 335 | fn expand(&mut self, extend_point: Vector4) { 336 | let mut removed_faces = Vec::new(); 337 | 338 | let cells = &mut self.cells; 339 | let vertices = &self.vertices; 340 | 341 | cells.retain(|cell| { 342 | let v0 = vertices[cell.faces[0][0]]; 343 | if cell.normal.dot(extend_point - v0) > 0.0 { 344 | true 345 | } else { 346 | // Before returning, we need to add all the faces to the removed 347 | // faces 348 | for face in cell.faces.iter() { 349 | match removed_faces.iter().position(|f| f == face) { 350 | Some(i) => { 351 | // found a duplicate, remove it from the removed 352 | // faces 353 | removed_faces.remove(i); 354 | } 355 | None => removed_faces.push(*face), 356 | } 357 | } 358 | false 359 | } 360 | }); 361 | 362 | // push the new vertex 363 | let vertex_idx = self.vertices.len(); 364 | self.vertices.push(extend_point); 365 | 366 | // now add a new cell for each face 367 | for face in removed_faces { 368 | let mut cell_faces = [ 369 | [face[0], face[1], vertex_idx], 370 | [face[0], face[2], vertex_idx], 371 | [face[1], face[2], vertex_idx], 372 | face, 373 | ]; 374 | for f in cell_faces.iter_mut() { 375 | f.sort_unstable(); 376 | } 377 | 378 | self.cells 379 | .push(EPCell::from_faces(&self.vertices, cell_faces)); 380 | } 381 | } 382 | } 383 | 384 | fn epa(object: S, simplex: Simplex) -> Vector4 385 | where 386 | S: Support, 387 | { 388 | let mut polytope = ExpandingPolytope::from_simplex(simplex); 389 | 390 | loop { 391 | let min_cell = polytope 392 | .cells 393 | .iter() 394 | .min_by_key(|i| NotNaN::new(i.distance).unwrap()) 395 | .unwrap(); 396 | let normal = min_cell.normal; 397 | let extend_point = object.support(normal); 398 | 399 | if extend_point.dot(normal) - min_cell.distance > EPSILON { 400 | polytope.expand(extend_point); 401 | } else { 402 | return normal; 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/physics/mod.rs: -------------------------------------------------------------------------------- 1 | mod body; 2 | mod collider; 3 | mod collision; 4 | mod gjk; 5 | 6 | pub use body::*; 7 | pub use collider::*; 8 | pub use collision::*; 9 | -------------------------------------------------------------------------------- /src/shapes.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{InnerSpace, Vector4, Zero}; 2 | 3 | use crate::alg::{Bivec4, Rotor4}; 4 | use crate::context::{graphics::SlicePipeline, GraphicsContext}; 5 | use crate::mesh::{Mesh, TetrahedronMesh}; 6 | use crate::physics::{Body, Collider, Material, Velocity}; 7 | use crate::world::Object; 8 | 9 | pub enum RegularSolid { 10 | FiveCell, 11 | EightCell, 12 | SixteenCell, 13 | TwentyFourCell, 14 | OneTwentyCell, 15 | SixHundredCell, 16 | } 17 | 18 | enum ShapeSpec { 19 | RegularSolid { ty: RegularSolid }, 20 | Sphere { radius: f32 }, 21 | } 22 | 23 | pub fn create_floor( 24 | ctx: &GraphicsContext, 25 | slice_pipeline: &SlicePipeline, 26 | size: f32, 27 | material: Material, 28 | ) -> Object { 29 | let floor_mesh = crate::mesh4::floor(size); 30 | let floor_mesh_binding = slice_pipeline.create_mesh_binding( 31 | &ctx, 32 | &floor_mesh.vertices, 33 | &floor_mesh.indices, 34 | ); 35 | Object { 36 | body: Body { 37 | mass: 0.0, 38 | moment_inertia_scalar: 0.0, 39 | material, 40 | stationary: true, 41 | pos: Vector4::zero(), 42 | rotation: Rotor4::identity(), 43 | vel: Velocity::zero(), 44 | collider: Collider::HalfSpace { 45 | normal: Vector4::unit_y(), 46 | }, 47 | }, 48 | mesh_binding: Some(floor_mesh_binding), 49 | } 50 | } 51 | 52 | pub fn create_wall( 53 | position: Vector4, 54 | normal: Vector4, 55 | material: Material, 56 | ) -> Object { 57 | Object { 58 | body: Body { 59 | mass: 0.0, 60 | moment_inertia_scalar: 0.0, 61 | material, 62 | stationary: true, 63 | pos: position, 64 | rotation: Rotor4::identity(), 65 | vel: Velocity::zero(), 66 | collider: Collider::HalfSpace { 67 | normal: normal.normalize(), 68 | }, 69 | }, 70 | mesh_binding: None, 71 | } 72 | } 73 | 74 | pub struct ShapeBuilder { 75 | spec: ShapeSpec, 76 | position: Vector4, 77 | rotation: Rotor4, 78 | velocity: Velocity, 79 | mass: f32, 80 | material: Material, 81 | color: Option>, 82 | } 83 | 84 | impl ShapeBuilder { 85 | pub fn new() -> Self { 86 | Default::default() 87 | } 88 | 89 | pub fn regular_solid(mut self, ty: RegularSolid) -> Self { 90 | self.spec = ShapeSpec::RegularSolid { ty }; 91 | self 92 | } 93 | 94 | pub fn sphere(mut self, radius: f32) -> Self { 95 | self.spec = ShapeSpec::Sphere { radius }; 96 | self 97 | } 98 | 99 | pub fn position(mut self, position: Vector4) -> Self { 100 | self.position = position; 101 | self 102 | } 103 | 104 | pub fn rotation(mut self, rotation: Rotor4) -> Self { 105 | self.rotation = rotation; 106 | self 107 | } 108 | 109 | pub fn velocity(mut self, velocity: Vector4) -> Self { 110 | self.velocity.linear = velocity; 111 | self 112 | } 113 | 114 | pub fn angular_velocity(mut self, angular_velocity: Bivec4) -> Self { 115 | self.velocity.angular = angular_velocity; 116 | self 117 | } 118 | 119 | pub fn mass(mut self, mass: f32) -> Self { 120 | self.mass = mass; 121 | self 122 | } 123 | 124 | pub fn material(mut self, material: Material) -> Self { 125 | self.material = material; 126 | self 127 | } 128 | 129 | pub fn color(mut self, color: Vector4) -> Self { 130 | self.color = Some(color); 131 | self 132 | } 133 | 134 | pub fn random_color(mut self) -> Self { 135 | self.color = None; 136 | self 137 | } 138 | 139 | pub fn build( 140 | self, 141 | ctx: &GraphicsContext, 142 | slice_pipeline: &SlicePipeline, 143 | ) -> Object { 144 | use hsl::HSL; 145 | 146 | let (mesh_binding, collider) = match self.spec { 147 | ShapeSpec::RegularSolid { ty } => { 148 | let schlafli_symbol = match ty { 149 | RegularSolid::FiveCell => &[3, 3, 3], 150 | RegularSolid::EightCell => &[4, 3, 3], 151 | RegularSolid::SixteenCell => &[3, 3, 4], 152 | RegularSolid::TwentyFourCell => &[3, 4, 3], 153 | RegularSolid::OneTwentyCell => &[5, 3, 3], 154 | RegularSolid::SixHundredCell => &[3, 3, 5], 155 | }; 156 | 157 | let mesh = Mesh::from_schlafli_symbol(schlafli_symbol); 158 | let color = self.color; 159 | let tetrahedralized_mesh = 160 | TetrahedronMesh::from_mesh(&mesh, |normal| { 161 | color.unwrap_or_else(|| { 162 | let (r, g, b) = HSL { 163 | h: 180.0 164 | * (normal.z as f64 165 | + rand::random::() * 5.0 166 | - 2.5) 167 | % 360.0 168 | + 360.0, 169 | s: 0.8, 170 | l: 0.5 + rand::random::() * 0.1, 171 | } 172 | .to_rgb(); 173 | Vector4::new( 174 | r as f32 / 255.0, 175 | g as f32 / 255.0, 176 | b as f32 / 255.0, 177 | 1.0, 178 | ) 179 | }) 180 | }); 181 | let mesh_binding = slice_pipeline.create_mesh_binding( 182 | &ctx, 183 | &tetrahedralized_mesh.vertices, 184 | &tetrahedralized_mesh.indices, 185 | ); 186 | (mesh_binding, Collider::Mesh { mesh }) 187 | } 188 | ShapeSpec::Sphere { radius } => { 189 | let mesh = Mesh::from_schlafli_symbol(&[3, 3, 5]); 190 | let color = self.color.unwrap_or_else(|| { 191 | let (r, g, b) = HSL { 192 | h: 360.0 * rand::random::(), 193 | s: 1.0, 194 | l: 0.5 + rand::random::() * 0.1, 195 | } 196 | .to_rgb(); 197 | Vector4::new( 198 | r as f32 / 255.0, 199 | g as f32 / 255.0, 200 | b as f32 / 255.0, 201 | 1.0, 202 | ) 203 | }); 204 | let tetrahedralized_mesh = 205 | TetrahedronMesh::from_mesh(&mesh, |_| color) 206 | .make_geodesic(4, radius); 207 | let mesh_binding = slice_pipeline.create_mesh_binding( 208 | &ctx, 209 | &tetrahedralized_mesh.vertices, 210 | &tetrahedralized_mesh.indices, 211 | ); 212 | (mesh_binding, Collider::Sphere { radius }) 213 | } 214 | }; 215 | 216 | Object { 217 | body: Body { 218 | mass: self.mass, 219 | moment_inertia_scalar: self.mass / 6.0, // TODO! 220 | material: self.material, 221 | stationary: false, 222 | pos: self.position, 223 | rotation: self.rotation, 224 | vel: self.velocity, 225 | collider, 226 | }, 227 | mesh_binding: Some(mesh_binding), 228 | } 229 | } 230 | } 231 | 232 | impl Default for ShapeBuilder { 233 | fn default() -> Self { 234 | Self { 235 | spec: ShapeSpec::RegularSolid { 236 | ty: RegularSolid::FiveCell, 237 | }, 238 | position: Vector4::unit_y() * 5.0, 239 | rotation: Rotor4::identity(), 240 | velocity: Velocity::zero(), 241 | mass: 1.0, 242 | material: Material { restitution: 0.2 }, 243 | color: None, 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{InnerSpace, Vector3, Vector4}; 2 | use std::cmp::Ordering; 3 | use std::hint::unreachable_unchecked; 4 | 5 | pub const EPSILON: f32 = 1e-6; 6 | 7 | // Wrapper around a float that implements Ord. 8 | #[derive(PartialOrd, PartialEq, Debug, Default, Clone, Copy)] 9 | pub struct NotNaN(f32); 10 | 11 | impl NotNaN { 12 | pub fn new(f: f32) -> Option { 13 | if f.is_nan() { 14 | None 15 | } else { 16 | Some(NotNaN(f)) 17 | } 18 | } 19 | 20 | pub fn into_inner(self) -> f32 { 21 | self.0 22 | } 23 | } 24 | 25 | impl Eq for NotNaN {} 26 | 27 | impl Ord for NotNaN { 28 | fn cmp(&self, other: &Self) -> Ordering { 29 | match self.partial_cmp(other) { 30 | Some(ord) => ord, 31 | None => unsafe { unreachable_unchecked() }, 32 | } 33 | } 34 | } 35 | 36 | // Extension of https://box2d.org/posts/2014/02/computing-a-basis/ to 4 dimensions. 37 | // (Refer to https://www.geometrictools.com/Documentation/OrthonormalSets.pdf) 38 | pub fn orthonormal_basis(a: Vector4) -> [Vector4; 3] { 39 | // If a is normalized, since 1 / 4 = 0.25 at least one component of a must 40 | // be >= sqrt(0.25) 41 | let (b, c) = if a.x.abs() >= 0.5 || a.y.abs() >= 0.5 { 42 | let b = Vector4::new(a.y, -a.x, 0.0, 0.0).normalize(); 43 | let c = Vector3::new(a.x, a.y, a.z) 44 | .cross(Vector3::new(b.x, b.y, b.z)) 45 | .normalize(); 46 | (b, Vector4::new(c.x, c.y, c.z, 0.0)) 47 | } else { 48 | let b = Vector4::new(0.0, 0.0, a.w, -a.z).normalize(); 49 | let c = Vector3::new(a.y, a.z, a.w) 50 | .cross(Vector3::new(b.y, b.z, b.w)) 51 | .normalize(); 52 | (b, Vector4::new(0.0, c.x, c.y, c.z)) 53 | }; 54 | 55 | let d = crate::alg::triple_cross_product(a, b, c).normalize(); 56 | [b, c, d] 57 | } 58 | -------------------------------------------------------------------------------- /src/world.rs: -------------------------------------------------------------------------------- 1 | use slotmap::{new_key_type, DenseSlotMap}; 2 | use std::collections::HashMap; 3 | 4 | use crate::context::{ 5 | graphics::{ 6 | MeshBinding, ShadowPipeline, SlicePipeline, SlicePlane, Transform4, 7 | TriangleListPipeline, 8 | }, 9 | GraphicsContext, 10 | }; 11 | use crate::physics::{Body, CollisionConstraint, CollisionDetection}; 12 | 13 | pub struct Object { 14 | pub body: Body, 15 | pub mesh_binding: Option, 16 | } 17 | 18 | impl Object { 19 | pub fn compute( 20 | &self, 21 | graphics_ctx: &GraphicsContext, 22 | pipeline: &SlicePipeline, 23 | encoder: &mut wgpu::CommandEncoder, 24 | slice_plane: &SlicePlane, 25 | ) { 26 | if let Some(mesh_binding) = &self.mesh_binding { 27 | let transform = Transform4 { 28 | displacement: self.body.pos, 29 | transform: self.body.rotation.to_matrix(), 30 | }; 31 | pipeline.render_mesh( 32 | graphics_ctx, 33 | encoder, 34 | slice_plane, 35 | &transform, 36 | mesh_binding, 37 | ); 38 | } 39 | } 40 | 41 | pub fn render<'a: 'c, 'b, 'c>( 42 | &'a self, 43 | pipeline: &'a TriangleListPipeline, 44 | render_pass: &'b mut wgpu::RenderPass<'c>, 45 | ) { 46 | if let Some(mesh_binding) = &self.mesh_binding { 47 | pipeline.render(render_pass, mesh_binding); 48 | } 49 | } 50 | 51 | pub fn shadow_pass<'a: 'c, 'b, 'c>( 52 | &'a self, 53 | pipeline: &'a ShadowPipeline, 54 | render_pass: &'b mut wgpu::RenderPass<'c>, 55 | ) { 56 | if let Some(mesh_binding) = &self.mesh_binding { 57 | pipeline.render(render_pass, mesh_binding); 58 | } 59 | } 60 | } 61 | 62 | new_key_type! { pub struct ObjectKey; } 63 | 64 | pub struct World { 65 | pub objects: DenseSlotMap, 66 | pub collision: CollisionDetection, 67 | } 68 | 69 | fn slotmap_get_mut2( 70 | map: &mut DenseSlotMap, 71 | i: K, 72 | j: K, 73 | ) -> (&mut V, &mut V) 74 | where 75 | K: slotmap::Key + std::cmp::Eq, 76 | { 77 | assert!(i != j); 78 | 79 | unsafe { 80 | let a = std::mem::transmute::<_, _>(map.get_mut(i).unwrap()); 81 | let b = std::mem::transmute::<_, _>(map.get_mut(j).unwrap()); 82 | 83 | return (a, b); 84 | } 85 | } 86 | 87 | impl World { 88 | pub fn new() -> Self { 89 | Self { 90 | objects: DenseSlotMap::with_key(), 91 | collision: CollisionDetection::new(), 92 | } 93 | } 94 | 95 | pub fn update(&mut self, dt: f32) { 96 | let mut collisions = Vec::new(); 97 | let mut mass_adjustments = HashMap::new(); 98 | 99 | let object_keys: Vec<_> = self.objects.keys().collect(); 100 | 101 | for i in 0..object_keys.len() { 102 | for j in i + 1..object_keys.len() { 103 | let ka = object_keys[i]; 104 | let kb = object_keys[j]; 105 | let a = &self.objects[ka]; 106 | let b = &self.objects[kb]; 107 | 108 | if let Some(manifold) = 109 | self.collision.detect_collisions((ka, kb), &a.body, &b.body) 110 | { 111 | if manifold.contacts.len() == 0 { 112 | continue; 113 | } 114 | *mass_adjustments.entry(ka).or_insert(0) += 1; 115 | *mass_adjustments.entry(kb).or_insert(0) += 1; 116 | collisions.push((ka, kb, manifold)); 117 | } 118 | } 119 | } 120 | 121 | let mut constraints = Vec::new(); 122 | for (i, j, manifold) in collisions { 123 | constraints.push(( 124 | i, 125 | j, 126 | CollisionConstraint::new( 127 | manifold, 128 | &self.objects[i].body, 129 | mass_adjustments[&i] as f32, 130 | &self.objects[j].body, 131 | mass_adjustments[&j] as f32, 132 | ), 133 | )); 134 | } 135 | 136 | const SOLVER_ITERS: usize = 20; 137 | for _ in 0..SOLVER_ITERS { 138 | for (i, j, constraint) in constraints.iter_mut() { 139 | let (a, b) = slotmap_get_mut2(&mut self.objects, *i, *j); 140 | constraint.solve(&mut a.body, &mut b.body); 141 | } 142 | } 143 | 144 | for object in self.objects.values_mut() { 145 | object.body.step(dt); 146 | } 147 | } 148 | 149 | pub fn compute( 150 | &self, 151 | graphics_ctx: &GraphicsContext, 152 | pipeline: &SlicePipeline, 153 | encoder: &mut wgpu::CommandEncoder, 154 | slice_plane: &SlicePlane, 155 | ) { 156 | for i in self.objects.values() { 157 | i.compute(graphics_ctx, pipeline, encoder, slice_plane); 158 | } 159 | } 160 | 161 | pub fn render<'a: 'c, 'b, 'c>( 162 | &'a self, 163 | pipeline: &'a TriangleListPipeline, 164 | render_pass: &'b mut wgpu::RenderPass<'c>, 165 | ) { 166 | for i in self.objects.values() { 167 | i.render(pipeline, render_pass); 168 | } 169 | } 170 | 171 | pub fn shadow_pass<'a: 'c, 'b, 'c>( 172 | &'a self, 173 | pipeline: &'a ShadowPipeline, 174 | render_pass: &'b mut wgpu::RenderPass<'c>, 175 | ) { 176 | for i in self.objects.values() { 177 | i.shadow_pass(pipeline, render_pass); 178 | } 179 | } 180 | } 181 | --------------------------------------------------------------------------------