├── .gitignore ├── README.md ├── frontend ├── index.html └── index.ts ├── package.json ├── screenshot └── demo.png ├── shell.nix ├── snowpack.config.js ├── tsconfig.json └── wasm ├── Cargo.toml └── src ├── camera ├── matrix.rs └── mod.rs ├── geometry ├── mod.rs ├── noise.rs ├── octree.rs ├── random.rs └── vec_3d.rs ├── lib.rs └── webgl.rs /.gitignore: -------------------------------------------------------------------------------- 1 | wasm/target/ 2 | node_modules/ 3 | build/ 4 | Cargo.lock 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 3d Art in your browser ! 2 | ======================== 3 | 4 | ![](screenshot/demo.png) 5 | 6 | # Description 7 | This repository is an experiment showing what you can do with wasm, rust and webgl, a powerfull 3d rasterization engine. 8 | It's gpu accelerated ! 9 | 10 | # Running the project 11 | 12 | ## Prerequisites: 13 | You first need the rust language tooling installed on your system (look for `rustup` or `rust` in your repos). There is also the [official page](https://www.rust-lang.org/tools/install) 14 | 15 | After you've done that, run: 16 | ```bash 17 | cargo install cargo-watch 18 | ``` 19 | And on linux, make sure the folder `~/.cargo/bin` is in your PATH variable 20 | 21 | Then, if you use yarn (:+1:) 22 | 23 | ``` 24 | git clone https://github.com/blixor/rust-web3-3d 25 | cd web-3d 26 | yarn install 27 | yarn start 28 | ``` 29 | 30 | If you use npm (:-1:) 31 | ``` 32 | git clone https://github.com/blixor/rust-web3-3d 33 | cd web-3d 34 | npm install 35 | npm start 36 | ``` 37 | 38 | ## Nix 39 | 40 | There is a nix-shell provided 41 | 42 | If you have nix installed, just type `nix-shell` in the repo's folder. 43 | After that you can `yarn install` the same way 44 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3D 6 | 19 | 20 | 21 |

Artificial Universe

22 |
wait for the shape to appear, then use the arrows to move
23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/index.ts: -------------------------------------------------------------------------------- 1 | // see in tsconfig.json what path is wasm 2 | import init, {Universe} from '@wasm'; 3 | 4 | 5 | let canvas = document.getElementById("canvas"); 6 | let gl = canvas.getContext('webgl'); 7 | 8 | 9 | // _ _ 10 | // ___| |__ __ _ __| | ___ _ __ ___ 11 | /// __| '_ \ / _` |/ _` |/ _ \ '__/ __| 12 | //\__ \ | | | (_| | (_| | __/ | \__ \ 13 | //|___/_| |_|\__,_|\__,_|\___|_| |___/ 14 | 15 | let fragmentShaderCode = ` 16 | precision mediump float; 17 | varying vec4 v_color; 18 | void main(void) { 19 | gl_FragColor = v_color; 20 | } 21 | ` 22 | let vertexShaderCode = ` 23 | attribute vec4 coordinates; 24 | uniform mat4 projection; 25 | uniform float time; 26 | 27 | attribute vec3 color; 28 | attribute vec3 ondulation_vec; 29 | attribute float frequency; 30 | attribute float phase; 31 | 32 | varying vec4 v_color; 33 | 34 | void main() { 35 | vec4 shift = vec4(cos(time*frequency+phase) * ondulation_vec, 1.0); 36 | gl_Position = projection * (coordinates + shift); 37 | v_color = vec4(color, 0.5); 38 | } 39 | ` 40 | 41 | // setup color 42 | gl.clearColor(0.,0.,0., 0); 43 | gl.clear(gl.COLOR_BUFFER_BIT); 44 | gl.enable(gl.CULL_FACE); 45 | gl.enable(gl.DEPTH_TEST); 46 | 47 | 48 | // SHADERS 49 | var vertShader = gl.createShader(gl.VERTEX_SHADER); 50 | gl.shaderSource(vertShader, vertexShaderCode); 51 | gl.compileShader(vertShader); 52 | 53 | var fragShader = gl.createShader(gl.FRAGMENT_SHADER); 54 | gl.shaderSource(fragShader, fragmentShaderCode); 55 | gl.compileShader(fragShader); 56 | 57 | var shaderProgram = gl.createProgram(); 58 | gl.attachShader(shaderProgram, vertShader); 59 | gl.attachShader(shaderProgram, fragShader); 60 | gl.linkProgram(shaderProgram); 61 | gl.useProgram(shaderProgram); 62 | 63 | 64 | 65 | // _ __ __ 66 | //| |__ _ _ / _|/ _| ___ _ __ ___ 67 | //| '_ \| | | | |_| |_ / _ \ '__/ __| 68 | //| |_) | |_| | _| _| __/ | \__ \ 69 | //|_.__/ \__,_|_| |_| \___|_| |___/ 70 | 71 | // index buffer 72 | var index_buffer = gl.createBuffer(); 73 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); 74 | 75 | 76 | 77 | // point buffer 78 | let point_buffer = gl.createBuffer(); 79 | gl.bindBuffer(gl.ARRAY_BUFFER, point_buffer); 80 | 81 | 82 | const SIZE_VERTEX = 12; 83 | /* The POINT buffer has 12 elements: 84 | * x y z | r g b | dx dy dz | frequency | phase | other 85 | * position color ondulation vector other params of vertex 86 | */ 87 | let offset = 0; 88 | function define_float_buffer(name: string, size:number) { 89 | let loc = gl.getAttribLocation(shaderProgram, name); 90 | gl.vertexAttribPointer( 91 | loc, size, gl.FLOAT, false, 92 | SIZE_VERTEX * Float32Array.BYTES_PER_ELEMENT, 93 | offset * Float32Array.BYTES_PER_ELEMENT 94 | ); 95 | 96 | gl.enableVertexAttribArray(loc); 97 | offset += size; 98 | } 99 | 100 | // 3 coordinates by vertex 101 | define_float_buffer("coordinates", 3); 102 | // r,g,b for each vertex 103 | define_float_buffer("color", 3); 104 | // 3 coordinates for ondulation vector 105 | define_float_buffer("ondulation_vec", 3); 106 | // 1 number for frequency 107 | define_float_buffer("phase", 1); 108 | // 1 number for phase 109 | define_float_buffer("frequency", 1); 110 | 111 | 112 | 113 | // uniforms (values passed to vertex shader) 114 | let trans_loc = gl.getUniformLocation(shaderProgram, "projection"); 115 | let time_loc = gl.getUniformLocation(shaderProgram, "time"); 116 | 117 | 118 | 119 | // key manager 120 | enum Keys { 121 | Left = 37, 122 | Right = 39, 123 | Down = 40, 124 | Up = 38, 125 | Space = 32, 126 | Shift = 9, 127 | }; 128 | 129 | var pressedKeys: Record = {}; 130 | // set instead 131 | 132 | window.onkeyup = (e: KeyboardEvent) => { console.log(e.key); pressedKeys[e.keyCode] = false; } 133 | window.onkeydown = (e: KeyboardEvent) => { pressedKeys[e.keyCode] = true; } 134 | 135 | // time manager 136 | const initialTime = Date.now(); 137 | const FPS_THROTTLE = 1000.0 / 20.0; 138 | let lastDrawTime = -1; 139 | 140 | 141 | // controlls : left arrow, right arrow, down arrow, up arrow, space, shift 142 | function get_controlls() : [boolean, boolean, boolean, boolean, boolean, boolean] 143 | { 144 | return [ 145 | pressedKeys[Keys.Left] || false, 146 | pressedKeys[Keys.Right] || false, 147 | pressedKeys[Keys.Down] || false, 148 | pressedKeys[Keys.Up] || false, 149 | pressedKeys[Keys.Space] || false, 150 | pressedKeys[Keys.Shift] || false, 151 | ]; 152 | }; 153 | 154 | function create_universe_loop(universe: Universe) { 155 | setInterval(() => universe.update(lastDrawTime, ...get_controlls()), FPS_THROTTLE/3); 156 | render(universe); 157 | } 158 | 159 | function render(universe: Universe) { 160 | const currTime = Date.now(); 161 | 162 | if (currTime >= lastDrawTime + FPS_THROTTLE) { 163 | lastDrawTime = currTime; 164 | 165 | let width = 0.9*window.innerWidth - 25; 166 | let height = 0.8*window.innerHeight - 30; 167 | 168 | if (width != canvas.width || height != canvas.height){ 169 | canvas.width = width; canvas.height = height; 170 | gl.viewport(0, 0, width, height); 171 | } 172 | 173 | let t = currTime - initialTime; 174 | 175 | // render without changing environment 176 | universe.render(t); 177 | } 178 | 179 | 180 | requestAnimationFrame(() => render(universe)); 181 | } 182 | 183 | init().then(() => {create_universe_loop(new Universe(gl, trans_loc, time_loc, Date.now()))}) 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "snowpack build && snowpack dev" 4 | }, 5 | "devDependencies": { 6 | "snowpack": "^3.7.1", 7 | "snowpack-plugin-wasm-pack": "^1.1.0", 8 | "wasm-pack": "^0.9.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /screenshot/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor/rust-web3-3d/7ddf91b6ecc5e14b5f2d9dd8161ca92086f4ad72/screenshot/demo.png -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import {}}: 2 | 3 | pkgs.mkShell { 4 | buildInputs = with pkgs; [ 5 | rustup 6 | cargo-watch 7 | yarn 8 | clippy 9 | ]; 10 | } 11 | -------------------------------------------------------------------------------- /snowpack.config.js: -------------------------------------------------------------------------------- 1 | // Snowpack Configuration File 2 | // See all supported options: https://www.snowpack.dev/reference/configuration 3 | 4 | /** @type {import("snowpack").SnowpackUserConfig } */ 5 | module.exports = { 6 | alias: { 7 | '@wasm': './wasm/pkg', 8 | }, 9 | mount: { 10 | "frontend": "/", 11 | }, 12 | plugins: [ 13 | [ 14 | 'snowpack-plugin-wasm-pack', 15 | { 16 | projectPath: './wasm', 17 | }, 18 | ], 19 | ], 20 | packageOptions: { 21 | /* ... */ 22 | }, 23 | devOptions: { 24 | /* ... */ 25 | }, 26 | buildOptions: { 27 | /* ... */ 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@wasm": ["./wasm/pkg/"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web-3d" 3 | version = "0.1.0" 4 | authors = ["rambip"] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [unstable] 11 | features = [ 12 | 'array_map', 13 | 'array_zip', 14 | ] 15 | 16 | [dependencies] 17 | js-sys = "0.3.35" 18 | wasm-bindgen = "0.2.72" 19 | getrandom = { version = "0.2", features = ["js"] } 20 | array-init = "2.0.0" 21 | 22 | [dependencies.web-sys] 23 | version = "0.3.4" 24 | features = [ 25 | 'WebGlRenderingContext', 26 | 'WebGlUniformLocation', 27 | 'console', 28 | ] 29 | -------------------------------------------------------------------------------- /wasm/src/camera/matrix.rs: -------------------------------------------------------------------------------- 1 | type M4 = [f32; 16]; 2 | 3 | 4 | pub fn mult(a: M4, b: M4) -> M4 { 5 | [ 6 | b[0] *a[0] + b[1] *a[4] + b[2] *a[8] + b[3] *a[12], 7 | b[0] *a[1] + b[1] *a[5] + b[2] *a[9] + b[3] *a[13], 8 | b[0] *a[2] + b[1] *a[6] + b[2] *a[10] + b[3] *a[14], 9 | b[0] *a[3] + b[1] *a[7] + b[2] *a[11] + b[3] *a[15], 10 | b[4] *a[0] + b[5] *a[4] + b[6] *a[8] + b[7] *a[12], 11 | b[4] *a[1] + b[5] *a[5] + b[6] *a[9] + b[7] *a[13], 12 | b[4] *a[2] + b[5] *a[6] + b[6] *a[10] + b[7] *a[14], 13 | b[4] *a[3] + b[5] *a[7] + b[6] *a[11] + b[7] *a[15], 14 | b[8] *a[0] + b[9] *a[4] + b[10]*a[8] + b[11]*a[12], 15 | b[8] *a[1] + b[9] *a[5] + b[10]*a[9] + b[11]*a[13], 16 | b[8] *a[2] + b[9] *a[6] + b[10]*a[10] + b[11]*a[14], 17 | b[8] *a[3] + b[9] *a[7] + b[10]*a[11] + b[11]*a[15], 18 | b[12]*a[0] + b[13]*a[4] + b[14]*a[8] + b[15]*a[12], 19 | b[12]*a[1] + b[13]*a[5] + b[14]*a[9] + b[15]*a[13], 20 | b[12]*a[2] + b[13]*a[6] + b[14]*a[10] + b[15]*a[14], 21 | b[12]*a[3] + b[13]*a[7] + b[14]*a[11] + b[15]*a[15] 22 | ] 23 | } 24 | 25 | 26 | 27 | pub fn projection(a: f32, fov: f32, z_near: f32, z_far: f32) -> M4 { 28 | 29 | /* 30 | * a is aspect ratio 31 | * fov is the field of view (not in radian, in proportion) 32 | * z_near is the distance to the nearest plane of the frustrum 33 | * z_far is the distance to the furthest plane of the frustrum 34 | */ 35 | 36 | let r = 1.0 / (z_near - z_far); 37 | 38 | [ 39 | fov/a, 0.0, 0.0, 0.0, 40 | 0.0, fov, 0.0, 0.0, 41 | 0.0, 0.0, (z_near + z_far)*r, -1.0, 42 | 0.0, 0.0, 2.0*z_near*z_far*r, 0.0 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /wasm/src/camera/mod.rs: -------------------------------------------------------------------------------- 1 | mod matrix; 2 | 3 | 4 | pub struct Camera { 5 | pub x: f32, 6 | pub y: f32, 7 | pub z: f32, 8 | pub angle: f32, 9 | } 10 | 11 | 12 | impl Camera { 13 | pub fn rotate(&mut self, d_angle: f32){ 14 | self.angle += d_angle 15 | } 16 | 17 | pub fn forward(&mut self, d: f32){ 18 | self.x += self.angle.cos()*d; 19 | self.y += self.angle.sin()*d; 20 | } 21 | pub fn up(&mut self, d: f32) { 22 | self.z += d; 23 | } 24 | pub fn get_info(&self) -> String { 25 | format!("x = {}, y = {}, z = {}", self.x, self.y, self.z) 26 | } 27 | 28 | pub fn get_transform(&self, width: u32, height: u32) -> [f32; 16] { 29 | let a = (width as f32) / (height as f32); 30 | 31 | let cos = (-self.angle).cos(); 32 | let sin = (-self.angle).sin(); 33 | 34 | 35 | let inv_cam = [ 36 | -sin, 0.0, -cos, 0.0, 37 | -cos, 0.0, sin, 0.0, 38 | 0.0, 1.0, 0.0, 0.0, 39 | 40 | // last column: 41 | self.x*sin+self.y*cos, 42 | -self.z, 43 | self.x*cos-self.y*sin, 44 | 1.0, 45 | ]; 46 | /* to generate this matrix, we use these 2 transformations: 47 | 48 | let translation = [ // move the scene to the position of the camera 49 | 1.0, 0.0, 0.0, 0.0, 50 | 0.0, 1.0, 0.0, 0.0, 51 | 0.0, 0.0, 1.0, 0.0, 52 | -self.x, -self.y, -self.z, 1.0, 53 | ]; 54 | 55 | let rot = [ // rotate with angle and transpose 56 | -sin, 0.0,-cos, 0.0, 57 | -cos, 0.0, sin, 0.0, 58 | 0.0, 1.0, 0.0, 0.0, 59 | 0.0, 0.0, 0.0, 1.0, 60 | ]; 61 | 62 | let inv_cam = matrix::mult(rot, translation); 63 | */ 64 | 65 | matrix::mult( 66 | matrix::projection(a, 1.5, 0.1, 100.0), 67 | inv_cam) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /wasm/src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! push_index { 2 | // ex. push_index!(indices, [9, 3, 2, 42]) 3 | ($array: ident, [$($x: expr),*]) => { 4 | $( 5 | $array.push($x as u16); 6 | )* 7 | }; 8 | // ex. push_index!(indices, tuple.[0, 2, 1, 2, 0, 1]) 9 | ($array: ident, $arr: ident.[$($x: tt),*]) => { 10 | $( 11 | $array.push($arr[$x] as u16); 12 | )* 13 | }; 14 | } 15 | 16 | // TODO: convert that to function or export to other scripts 17 | macro_rules! push_point { 18 | ($array:ident) => {}; 19 | ($array:ident, [$a:expr, $b:expr, $c:expr] $(,$tail:tt)*) => { 20 | $array.push($a as f32); 21 | $array.push($b as f32); 22 | $array.push($c as f32); 23 | push_point!($array $(,$tail)*) 24 | }; 25 | ($array:ident, $v:expr $(,$tail:tt)*) => { 26 | $array.push($v.x as f32); 27 | $array.push($v.y as f32); 28 | $array.push($v.z as f32); 29 | push_point!($array $(,$tail)*) 30 | }; 31 | } 32 | 33 | use super::console; 34 | 35 | mod noise; 36 | mod random; 37 | #[macro_use] 38 | mod octree; 39 | 40 | mod vec_3d; 41 | use vec_3d::V3; 42 | use vec_3d::Range; 43 | use vec_3d::Dist; 44 | 45 | use octree::Octree; 46 | 47 | 48 | // a vertex currently has 12 values: x, y, z | r, g, b and so on 49 | const SIZE_VERTEX : usize = 12; 50 | 51 | 52 | fn get_point(points: &Vec, i: u16) -> V3 { 53 | let i = i as usize*SIZE_VERTEX; 54 | V3::new( 55 | points[i], 56 | points[i+1], 57 | points[i+2] 58 | ) 59 | } 60 | 61 | fn set_point(points: &mut Vec, i:u16, p: V3) { 62 | let i = i as usize*SIZE_VERTEX; 63 | points[i ] = p.x; 64 | points[i+1] = p.y; 65 | points[i+2] = p.z; 66 | } 67 | 68 | 69 | fn pseudo_sphere(points: &mut Vec, indices: &mut Vec, center: V3, radius: f32, color: (f32, f32, f32)) { 70 | let frequency = 0.3+random::rand_float(); 71 | let i0 = points.len()/SIZE_VERTEX; 72 | let n = 30usize; 73 | let pi = 3.15; 74 | 75 | let range = Range::new(V3::new(-2.1, -2.1, -2.1), V3::new(2.1, 2.1, 2.1)); 76 | let shape_noise = noise::Perlin::new(range, (4, 4, 4), 2.4); 77 | let phase_noise = noise::Perlin::new(range, (8, 8, 8), 1.5); 78 | 79 | // create points 80 | for long in 0..n { 81 | for lat in 0..n { 82 | let a1 = lat as f32 / (n as f32) * 2.0 * pi; 83 | let a2 = long as f32 / (n as f32 -1.0) * pi; 84 | 85 | let p = V3::new( 86 | a1.cos()*a2.sin(), 87 | a1.sin()*a2.sin(), 88 | a2.cos() , 89 | ); 90 | 91 | let rad_vector = p.scale(radius+shape_noise.noise(p)); 92 | 93 | let v = p.scale(0.2)+random::rand_v3().scale(0.3); 94 | 95 | push_point!(points, 96 | (center+rad_vector), // coordinates 97 | (V3::from(color)), // color 98 | v, // 99 | [frequency, phase_noise.noise(p), 0.0] 100 | ); 101 | } 102 | } 103 | 104 | // create triangles 105 | for long in 0..n-1 { 106 | for lat in 0..n-1 { 107 | let i = i0 + long*n+lat; 108 | let p = [i, i+1, i+n, i+n+1]; 109 | push_index!(indices, p.[0, 3, 1, 0, 2, 3]); 110 | } 111 | } 112 | for long in 0..n-2 { 113 | let i = i0 + long*n; 114 | let p = [i+n-1, i+n, i+n+n-1, i+n+n]; 115 | push_index!(indices, p.[0, 3, 1, 0, 2, 3]); 116 | } 117 | } 118 | 119 | pub fn test_sphere(points: &mut Vec, indices: &mut Vec) { 120 | use random::rand_float; 121 | for _ in 0..30 { 122 | let v = random::rand_v3().scale(20.0+rand_float()*40.0); 123 | let center = V3::new(v.x, v.y, 2.0+rand_float()*8.0); 124 | let color = (rand_float(), rand_float(), rand_float()); 125 | pseudo_sphere(points, indices, center, 0.5+rand_float(), color); 126 | } 127 | } 128 | 129 | pub fn test_octree_shape(points: &mut Vec, indices: &mut Vec) { 130 | let range = Range::new( 131 | V3::new(-3.0, -3.0, -3.0), 132 | V3::new(3.0, 3.0, 3.0) 133 | ); 134 | let k = 0.7; 135 | let clamp = |v, min, max| if v < min {min} else if v > max {max} else {v}; 136 | let smooth_min = |a:f32, b:f32| { 137 | let h = clamp(0.5+0.5*(a-b)/k, 0.0, 1.0); 138 | a*(1.0-h) + h*b -k*h*(1.0-h) 139 | }; 140 | let dist_function = 141 | |p: V3| f32::min( 142 | f32::min( 143 | (p.x*p.x + p.y*p.y).sqrt() - 0.6, 144 | (p.z*p.z + p.y*p.y).sqrt() - 0.6, 145 | ), 146 | (p.x*p.x+p.y*p.y + p.z*p.z).sqrt() - 1.0, 147 | ) - 0.2; 148 | let oct = Octree::new_from_dist(dist_function, range, 7); 149 | oct.triangulate(points, indices); 150 | } 151 | 152 | 153 | 154 | pub fn rand_surface(points: &mut Vec, indices: &mut Vec) { 155 | // we generate fractal noise with 2d slices of 3d perlin noise 156 | let range = Range::new(V3::new(-100.0, -100.0, -1.0), V3::new(100.0, 100.0, 1.0)); 157 | let perlin_1 = noise::Perlin::new(range, (5, 5, 3), 15.0); 158 | let perlin_2 = noise::Perlin::new(range, (30, 30, 3), 5.5); 159 | 160 | let phase_noise = noise::Perlin::new(range, (30, 30, 3), 6.28); 161 | 162 | let i0 = points.len()/SIZE_VERTEX; 163 | let n = 100usize; 164 | for x in 0..n { 165 | for y in 0..n { 166 | let x = x as f32-50.0+0.5; 167 | let y = y as f32-50.0; 168 | let v = V3::new(x, y, 0.0); 169 | let z = perlin_1.noise(v)+perlin_2.noise(v)-4.0; 170 | // position 171 | push_point!( 172 | points, 173 | [x, y, z], 174 | [0.7, 0.4, 0.3], 175 | [0.0, 0.0, 0.3], 176 | [1.0, phase_noise.noise(v), 0.0]); 177 | } 178 | } 179 | 180 | for x in 0..n-1 { 181 | for y in 0..n-1 { 182 | let i = i0 + y*n+x; 183 | // corners of square 184 | let p = [i, i+1, i+n, i+n+1]; 185 | // 3 triangles 186 | push_index!(indices, p.[0, 3, 1, 0, 2, 3]); 187 | } 188 | } 189 | } 190 | 191 | 192 | 193 | 194 | // shading algorithm 195 | pub fn shade(points: &mut Vec, indices: &Vec) { 196 | let light_dir : V3 = V3::new(0.3, 0.3, 0.3); 197 | 198 | // this vector will store the average normal of each point 199 | let mut normals_by_point = vec![V3::null(); points.len()/SIZE_VERTEX]; 200 | 201 | for i in (0..indices.len()).step_by(3) { 202 | let p1 = get_point(points, indices[i ]); 203 | let p2 = get_point(points, indices[i+1]); 204 | let p3 = get_point(points, indices[i+2]); 205 | 206 | // compute normal of each triangle 207 | let normal = V3::cross(p1-p3, p1-p2).normalize(); 208 | 209 | // then add this vector to the 3 points making up this triangle 210 | normals_by_point[indices[i ] as usize] += normal; 211 | normals_by_point[indices[i+1] as usize] += normal; 212 | normals_by_point[indices[i+2] as usize] += normal; 213 | } 214 | 215 | for (i, &v) in normals_by_point.iter().enumerate() { 216 | let brightness = V3::dot(v.normalize(), light_dir); // ⚠ normalize 217 | points[i*SIZE_VERTEX+3] += brightness; 218 | points[i*SIZE_VERTEX+4] += brightness; 219 | points[i*SIZE_VERTEX+5] += brightness; 220 | } 221 | } 222 | 223 | -------------------------------------------------------------------------------- /wasm/src/geometry/noise.rs: -------------------------------------------------------------------------------- 1 | use super::random; 2 | use super::V3; 3 | use super::Range; 4 | 5 | pub struct Perlin { 6 | values: Vec, 7 | range: Range, 8 | resol: (usize, usize, usize), 9 | scale_x: f32, 10 | scale_y: f32, 11 | scale_z: f32, 12 | amplitude: f32, 13 | } 14 | 15 | 16 | impl Perlin { 17 | pub fn new(range: Range, resol: (usize, usize, usize), amplitude: f32) -> Self { 18 | let values = (0..resol.0*resol.1*resol.2) 19 | .map(|_| random::rand_v3()) 20 | .collect(); 21 | 22 | let diag = range.diagonal(); 23 | let scale_x = (resol.0 as f32 - 1.0)/diag.x; 24 | let scale_y = (resol.1 as f32 - 1.0)/diag.y; 25 | let scale_z = (resol.2 as f32 - 1.0)/diag.z; 26 | 27 | Self {values, range, resol, scale_x, scale_y, scale_z, amplitude} 28 | } 29 | 30 | fn grad(&self, x: usize, y: usize, z: usize) -> V3 { 31 | self.values[ 32 | z*self.resol.0*self.resol.1 33 | +y*self.resol.0 34 | +x] 35 | } 36 | 37 | pub fn noise(&self, v: V3) -> f32 { 38 | // map point to grid space 39 | let v = V3::new( 40 | (v.x - self.range.smaller_corner.x)*self.scale_x, 41 | (v.y - self.range.smaller_corner.y)*self.scale_y, 42 | (v.z - self.range.smaller_corner.z)*self.scale_z, 43 | ); 44 | 45 | // first corner of the cell 46 | let c0 = V3::new( 47 | v.x.floor(), 48 | v.y.floor(), 49 | v.z.floor(), 50 | ); 51 | 52 | // second corner of the cell 53 | let c1 = V3::new( 54 | c0.x+1.0, 55 | c0.y+1.0, 56 | c0.z+1.0, 57 | ); 58 | 59 | let ux0 = c0.x as usize; let ux1 = c1.x as usize; 60 | let uy0 = c0.y as usize; let uy1 = c1.y as usize; 61 | let uz0 = c0.z as usize; let uz1 = c1.z as usize; 62 | 63 | let t0 = v-c0; // vector between the point and the first corner 64 | let t1 = v-c1; // vector between the point and the other corner 65 | 66 | // interpolation function 67 | let interpolate = |a0, a1, w| (a1-a0)*(3.0-2.0*w)*w*w+a0; 68 | 69 | // calculate a value for each corner 70 | let c000 = V3::dot(V3::new(t0.x, t0.y, t0.z), self.grad(ux0, uy0, uz0)); 71 | let c001 = V3::dot(V3::new(t0.x, t0.y, t1.z), self.grad(ux0, uy0, uz1)); 72 | let c010 = V3::dot(V3::new(t0.x, t1.y, t0.z), self.grad(ux0, uy1, uz0)); 73 | let c011 = V3::dot(V3::new(t0.x, t1.y, t1.z), self.grad(ux0, uy1, uz1)); 74 | let c100 = V3::dot(V3::new(t1.x, t0.y, t0.z), self.grad(ux1, uy0, uz0)); 75 | let c101 = V3::dot(V3::new(t1.x, t0.y, t1.z), self.grad(ux1, uy0, uz1)); 76 | let c110 = V3::dot(V3::new(t1.x, t1.y, t0.z), self.grad(ux1, uy1, uz0)); 77 | let c111 = V3::dot(V3::new(t1.x, t1.y, t1.z), self.grad(ux1, uy1, uz1)); 78 | 79 | // and combine them 80 | interpolate( 81 | interpolate( 82 | interpolate(c000, c001, t0.z), 83 | interpolate(c010, c011, t0.z), 84 | t0.y), 85 | interpolate( 86 | interpolate(c100, c101, t0.z), 87 | interpolate(c110, c111, t0.z), 88 | t0.y), 89 | t0.x) * self.amplitude 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /wasm/src/geometry/octree.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Index, IndexMut}; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | use array_init::array_init; 5 | 6 | use super::V3; 7 | use super::Dist; 8 | use super::Range; 9 | use super::random::rand_v3; 10 | 11 | use super::console; 12 | 13 | // bool structure: intersection, union and negation 14 | trait BoolLike { 15 | fn union(a: Self, b: Self) -> Self; 16 | fn inter(a: Self, b: Self) -> Self; 17 | fn not(self) -> Self; 18 | } 19 | 20 | 21 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 22 | struct CubeCorner (usize); 23 | 24 | impl CubeCorner { 25 | fn bools(self) -> [bool; 3] { 26 | self.into() 27 | } 28 | } 29 | 30 | impl From<[bool; 3]> for CubeCorner { 31 | fn from(c: [bool; 3]) -> CubeCorner { 32 | CubeCorner( 33 | c.iter() 34 | .enumerate() 35 | .map(|(i, &b)| if b {1< for [bool; 3] { 42 | fn from(t: CubeCorner) -> [bool; 3] { 43 | let id = t.0; 44 | array_init::array_init( 45 | |i| id>>i & 1 != 0 46 | ) 47 | } 48 | } 49 | 50 | impl Index for [T; 8] { 51 | type Output = T; 52 | fn index(&self, i: CubeCorner) -> &Self::Output { 53 | self.index(i.0) 54 | } 55 | } 56 | 57 | 58 | 59 | impl IndexMut for [T; 8] { 60 | fn index_mut(&mut self, i: CubeCorner) -> &mut T { 61 | self.index_mut(i.0) 62 | } 63 | } 64 | 65 | pub fn sub_corner(corner_pos: V3, half_size: V3, corner: [bool; 3]) -> V3 { 66 | corner_pos + V3::new( 67 | if corner[0] {half_size.x} else {0.0}, 68 | if corner[1] {half_size.y} else {0.0}, 69 | if corner[2] {half_size.z} else {0.0}, 70 | ) 71 | } 72 | 73 | // _ 74 | // _ _ ___ __| |___ 75 | //| ' \/ _ \/ _` / -_) 76 | //|_||_\___/\__,_\___| 77 | 78 | type NodeIndex = [i32; 3]; 79 | 80 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 81 | enum NodeState { 82 | Inside, 83 | Outside 84 | } 85 | 86 | use NodeState::{Inside, Outside}; 87 | 88 | /// NodeState: can be inside or outside 89 | impl NodeState { 90 | fn opposite(self) -> Self { 91 | match self { 92 | Inside => Outside, 93 | Outside => Inside, 94 | } 95 | } 96 | } 97 | 98 | impl Default for NodeState { 99 | fn default() -> Self {Outside} 100 | } 101 | 102 | #[derive(Debug)] 103 | struct CellInfo<'octree> { 104 | cube_indices: RefCell<[Option; 8]>, 105 | neighbourgs: [[Result<&'octree CellInfo<'octree>, NodeState>; 2]; 3], 106 | } 107 | 108 | impl<'octree> Default for CellInfo<'octree> { 109 | fn default() -> CellInfo<'octree> { 110 | let neighbourgs = array_init(|_| array_init(|_| Err(Outside))); 111 | CellInfo {cube_indices:RefCell::new([None; 8]), neighbourgs} 112 | } 113 | } 114 | 115 | /// The Node object for the octree. 116 | /// it can be either: 117 | /// - a Cell (or leaf) that contain a refcell to a struct. This is the end of the recursion, at max 118 | /// depth 119 | /// - a State, `Inside` or `Outside`. 120 | /// That means that this region of space is completely inside the shape or outside the shape 121 | /// - 8 Subcubes (a cube is splited into 2 in the 3 directions of space) 122 | enum Node<'octree> { 123 | Sub([Box>; 8]), 124 | Cell(CellInfo<'octree>), 125 | Completely(NodeState), 126 | } 127 | 128 | impl <'octree> Node<'octree> where { 129 | /// Get state of the cell. 130 | /// if completely outside or inside, return it. 131 | /// otherwise null 132 | fn get_state(&self) -> Option { 133 | if let Node::Completely(s) = self { 134 | Some(*s) 135 | } 136 | else {None} 137 | } 138 | 139 | /// index cell with 3 numbers 140 | fn index(&self, id: NodeIndex, depth: u8) -> Result<&'octree CellInfo, NodeState> { 141 | match self { 142 | Node::Sub(cubes) => { 143 | let m = 1 << (depth-1); 144 | let corner = map_array(id, |v| v >= m); 145 | cubes[CubeCorner::from(corner)] 146 | .index( 147 | map_array(id, |x| x%m), 148 | depth-1 149 | ) 150 | } 151 | Node::Cell(x) => Ok(x), 152 | Node::Completely(s) => Err(*s), 153 | } 154 | } 155 | 156 | /// index cell with 3 numbers 157 | fn index_mut(&mut self, id: NodeIndex, depth: u8) -> Result<&'octree mut CellInfo, NodeState>{ 158 | match self { 159 | Node::Sub(cubes) => { 160 | let m = 1 << (depth-1); 161 | let corner = map_array(id, |v| v >= m); 162 | cubes[CubeCorner::from(corner)] 163 | .index_mut( 164 | map_array(id, |x| x%m), 165 | depth-1 166 | ) 167 | } 168 | Node::Cell(x) => Ok(x), 169 | Node::Completely(s) => Err(*s), 170 | } 171 | } 172 | /// get all references to the cells with corresponding index 173 | /// useless ? 174 | fn get_cells_with_indices(&'octree self, id: NodeIndex, depth: u8, result: &mut Vec<(NodeIndex, &'octree CellInfo<'octree>)>) { 175 | match self { 176 | Node::Sub(cubes) => { 177 | let m = 1 << (depth-1); 178 | for i in 0..8 { 179 | let corner = CubeCorner(i).bools(); 180 | let new_id = array_init(|d| id[d] + if corner[d] {m} else {0}); 181 | cubes[i].get_cells_with_indices(new_id, depth-1, result); 182 | } 183 | } 184 | Node::Cell(x) => result.push((id, x)), 185 | _ => (), 186 | } 187 | } 188 | 189 | /// approximate a distance function. 190 | /// TODO: use a Range instead of 2 V3 191 | fn approximate(corner: V3, half_size: V3, shape: &impl Dist, depth: u8) -> Self { 192 | // approximate distance function with octree 193 | let center = corner + half_size; 194 | // calculate the distance from the center to the nearest point of the shape 195 | let dist = shape.dist(center); 196 | 197 | // calculate the size of the diagonal (from the center to a corner) 198 | let diag = half_size.norm(); 199 | 200 | if dist > diag { 201 | // if distance is greater than the diagonal, it is outside 202 | Node::Completely(Outside) 203 | } 204 | else if dist < -diag { 205 | // same thing with opposite sign: we are inside 206 | Node::Completely(Inside) 207 | } 208 | else { 209 | if depth == 0 { 210 | // if max depth, add the center of the cell as a leaf 211 | Node::Cell(CellInfo::default()) 212 | } 213 | else { 214 | // Otherwise, generate 8 subcubes 215 | Node::Sub(array_init(|i| 216 | Box::new(Node::approximate( 217 | sub_corner(corner, half_size, CubeCorner(i).into()), 218 | half_size.scale(0.5), 219 | shape, 220 | depth-1) 221 | ) 222 | )) 223 | } 224 | } 225 | } 226 | } 227 | 228 | 229 | /// zip 2 arrays with a function. 230 | /// Soon, map and zip will be part of stable-rust !!! 231 | fn zip_array_with(a: [T; N], b: [T; N], mut f: F) -> [S; N] 232 | where F: FnMut(T, T) -> S 233 | { 234 | use array_init::from_iter; 235 | use std::array::IntoIter; 236 | let zip = IntoIter::new(a).zip(IntoIter::new(b)); 237 | from_iter(zip 238 | .map(|(a, b)| f(a, b)) 239 | ).unwrap() 240 | } 241 | 242 | /// map an array with a function. 243 | /// this will also be part of rust soon 244 | fn map_array(a: [T; N], f: F) -> [S; N] 245 | where F: FnMut(T) -> S 246 | { 247 | use array_init::from_iter; 248 | use std::array::IntoIter; 249 | let iter = IntoIter::new(a); 250 | from_iter(iter 251 | .map(f) 252 | ).unwrap() 253 | } 254 | 255 | 256 | impl<'octree> BoolLike for Node<'octree> 257 | { 258 | fn union(a: Self, b: Self) -> Self { 259 | // union of 2 octree nodes at the same location 260 | match (a, b) { 261 | (Node::Sub(cubes_a), Node::Sub(cubes_b)) => { 262 | // calculate the union of the sub_cubes 263 | let cubes = zip_array_with( 264 | cubes_a, 265 | cubes_b, 266 | |a, b| Box::new(BoolLike::union(*a, *b)) 267 | ); 268 | 269 | let mut fusion = cubes[0].get_state(); 270 | for c in &cubes { 271 | fusion = match (fusion, c.get_state()) { 272 | (Some(Outside), Some(Outside)) => Some(Outside), 273 | (Some(Inside), Some(Inside)) => Some(Inside), 274 | _ => None, 275 | }; 276 | } 277 | 278 | // if the new contain only empty or full blocks, return one of them 279 | match fusion { 280 | Some(state) => Node::Completely(state.opposite()), 281 | None => Node::Sub(cubes) 282 | } 283 | }, 284 | // if one is empty, return the other 285 | (Node::Completely(Outside), b) => b, 286 | (a, Node::Completely(Outside)) => a, 287 | // if one is full, return full 288 | (Node::Completely(Inside), _) => Node::Completely(Inside), 289 | (_, Node::Completely(Inside)) => Node::Completely(Inside), 290 | // if one is point, return it 291 | (Node::Cell(x), _) => Node::Cell(x), 292 | (_, Node::Cell(x)) => Node::Cell(x), 293 | } 294 | } 295 | 296 | fn inter(a: Self, b: Self) -> Self { 297 | // intersection of 2 octree nodes at the same location 298 | match (a, b) { 299 | (Node::Sub(cubes_a), Node::Sub(cubes_b)) => { 300 | // calculate the intersection of the sub_cubes 301 | let cubes = zip_array_with( 302 | cubes_a, 303 | cubes_b, 304 | |a, b| Box::new(BoolLike::inter(*a, *b)) 305 | ); 306 | 307 | let mut fusion = cubes[0].get_state(); 308 | for c in &cubes { 309 | fusion = match (fusion, c.get_state()) { 310 | (Some(Outside), Some(Outside)) => Some(Outside), 311 | (Some(Inside), Some(Inside)) => Some(Inside), 312 | _ => None, 313 | }; 314 | } 315 | 316 | // if the new contain only empty or full blocks, return one of them 317 | match fusion { 318 | Some(state) => Node::Completely(state.opposite()), 319 | None => Node::Sub(cubes) 320 | } 321 | }, 322 | // if one is full, return the other 323 | (Node::Completely(Inside), b) => b, 324 | (a, Node::Completely(Inside)) => a, 325 | // if one is empty, return empty 326 | (Node::Completely(Outside), _) => Node::Completely(Outside), 327 | (_, Node::Completely(Outside)) => Node::Completely(Outside), 328 | // if one is leaf, return it 329 | (Node::Cell(x), _) => Node::Cell(x), 330 | (_, Node::Cell(x)) => Node::Cell(x), 331 | } 332 | } 333 | 334 | fn not(self) -> Self { 335 | match self { 336 | Node::Sub(cubes) => Node::Sub(map_array(cubes, |c| Box::new(c.not()))), 337 | Node::Cell(x) => Node::Cell(x), 338 | Node::Completely(state) => Node::Completely(state.opposite()), 339 | } 340 | } 341 | } 342 | 343 | 344 | pub struct Octree<'octree> { 345 | range: Range, 346 | depth: u8, 347 | root: Node<'octree>, 348 | scale: V3, 349 | } 350 | 351 | 352 | impl <'octree> Octree<'octree> { 353 | fn index(&'octree self, id: NodeIndex) -> Result<&'octree CellInfo<'octree>, NodeState> { 354 | let max_id = 1 << self.depth; 355 | if id.iter().all(|&n| n >= 0 && n < max_id) 356 | { 357 | // if index is inside the octree 358 | self.root.index(id, self.depth) 359 | } 360 | else { 361 | // otherwise return error 362 | Err(Outside) 363 | } 364 | } 365 | 366 | fn index_mut(&'octree mut self, id: NodeIndex) -> Result<&'octree mut CellInfo<'octree>, NodeState> { 367 | let max_id = 1 << self.depth; 368 | if id.iter().all(|&n| n >= 0 && n < max_id) 369 | { 370 | // if index is inside the octree 371 | self.root.index_mut(id, self.depth) 372 | } 373 | else { 374 | // otherwise return error 375 | Err(Outside) 376 | } 377 | } 378 | 379 | /// give the position in space that correspond to an index in this octree 380 | fn index_to_point(&self, pos: NodeIndex) -> V3 { 381 | let vec_from_corner = V3::new( 382 | (pos[0] as f32) * self.scale.x, 383 | (pos[1] as f32) * self.scale.y, 384 | (pos[2] as f32) * self.scale.z, 385 | ); 386 | vec_from_corner + self.range.smaller_corner 387 | } 388 | 389 | /// get all cells and indices inside the octree. 390 | fn get_cells_with_indices(&'octree self) -> Vec<(NodeIndex, &'octree CellInfo<'octree>)> { 391 | let mut result = Vec::new(); 392 | self.root.get_cells_with_indices([0, 0, 0], self.depth, &mut result); 393 | result 394 | } 395 | 396 | /// approximate a distance function with an octree. 397 | /// `d`: struct that implement a distance function 398 | /// `range`: range of the octree (region of space in a tile) 399 | /// `depth`: depth you want (maximum 8) 400 | pub fn new_from_dist(d: impl Dist, range: Range, depth: u8) -> Self { 401 | let size = range.diagonal(); 402 | let root = Node::approximate(range.smaller_corner, size.scale(0.5), &d, depth); 403 | let max_index = (1<, index_array: &mut Vec) { 432 | // get all cells in octree with the corresponding indices 433 | let all_cells = self.get_cells_with_indices(); 434 | 435 | log!("number of cells: {:?}", all_cells.len()); 436 | // for each cell in the octree, set index of all the corners 437 | for (cell_pos, x) in &all_cells { 438 | 439 | // modify temp value with new indices 440 | let indices = x.cube_indices.borrow_mut(); 441 | let neighbourgs = x.neighbourgs; 442 | 443 | // (*temp_ref) = Some(array_init( 444 | // |i| { 445 | // let mut tmp_corner_index = None; 446 | 447 | // for d in 0..3 { 448 | // // look at 3 neighbourgs cells (like up, down and front) 449 | // tmp_corner_index = tmp_corner_index.or( 450 | // // if there is a value defined, 451 | // neighbourgs[d][if CubeCorner(i).bools()[d] {1} else {0}].as_ref() 452 | // .ok() 453 | // .and_then(|ref_corner| ref_corner.borrow().cube_indices) 454 | // .map(|corners| corners[i^(1< id, 461 | // // otherwise, create a new id 462 | // None => { 463 | // let t = point_array.len()/12; 464 | // let col = V3::new(0.3, 0.3, 0.3)+rand_v3().scale(0.1); 465 | // push_point!(point_array, self.index_to_point(*cell_pos), col, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]); 466 | // t 467 | // } 468 | // } 469 | // })); 470 | } 471 | 472 | // for (pos, x) in &all_cells { 473 | // // create triangles for each cell 474 | // for d in 0..3 { 475 | // for &side in &[0, 1] { 476 | // let pos_in_this_dir = { 477 | // let mut new_pos = pos.clone(); 478 | // new_pos[d] += side*2-1; 479 | // new_pos 480 | // }; 481 | 482 | // if let Err(Outside) = self.index(pos_in_this_dir){ 483 | // // if neighbourg cell in this direction is outside, create triangles: 484 | // let points_id_in_cube = x.borrow().cube_indices.unwrap(); 485 | // let corners_squares = (0..8).filter(|&c| ((c as usize)>>d&1)==side as usize); 486 | // let points_id_in_square: Vec<_> = corners_squares.map(|i| points_id_in_cube[i]).collect(); 487 | // push_index!( index_array, points_id_in_square.[0, 1, 2, 1, 3, 2]); 488 | // } 489 | // } 490 | // } 491 | // } 492 | } 493 | } 494 | 495 | #[cfg(test)] 496 | mod tests { 497 | use super::{CubeCorner, Octree, Range, V3, map_array}; 498 | use getrandom; 499 | 500 | #[test] 501 | fn conversions() { 502 | for i in 0..8 { 503 | let corner = CubeCorner(i); 504 | assert_eq!(corner, corner.bools().into()) 505 | } 506 | } 507 | 508 | #[test] 509 | fn octree_indexing() { 510 | let range = Range::new( 511 | V3::new(-1.0, -1.0, -1.0), 512 | V3::new( 1.0, 1.0, 1.0), 513 | ); 514 | let oct = Octree::new_from_dist(|v: V3| v.x*v.x+v.y+v.y-0.5, range, 4); 515 | 516 | for (_pos, ref_value) in oct.get_cells_with_indices() { 517 | let random_numbers = { 518 | let mut tmp = [0; 8]; 519 | getrandom::getrandom(&mut tmp).unwrap(); 520 | map_array(tmp, |x| x as usize) 521 | }; 522 | ref_value.borrow_mut().cube_indices = Some(random_numbers); 523 | } 524 | 525 | for (pos, ref_value) in oct.get_cells_with_indices() { 526 | assert_eq!( 527 | oct.index(pos).unwrap().borrow().cube_indices, 528 | ref_value.cube_indices 529 | ); 530 | } 531 | } 532 | 533 | #[test] 534 | fn octree_triangulation() { 535 | let range = Range::new( 536 | V3::new(-1.0, -1.0, -1.0), 537 | V3::new( 1.0, 1.0, 1.0), 538 | ); 539 | let oct = Octree::new_from_dist(|v: V3| v.x*v.x+v.y+v.y-0.5, range, 5); 540 | oct.triangulate(&mut Vec::new(), &mut Vec::new()) 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /wasm/src/geometry/random.rs: -------------------------------------------------------------------------------- 1 | use getrandom; 2 | use super::V3; 3 | 4 | 5 | pub fn rand_float() -> f32 { 6 | const SCALE_FACTOR: f32 = 1.0/65536.0; 7 | 8 | let mut buff = [0; 2]; 9 | getrandom::getrandom(&mut buff).unwrap(); 10 | let (a, b) = (buff[0] as f32, buff[1] as f32); 11 | (a*256.0+b) * SCALE_FACTOR 12 | } 13 | 14 | pub fn rand_v3() -> V3 { 15 | const SCALE_FACTOR: f32 = 2.0/65536.0; 16 | let mut buff = [0; 6]; 17 | getrandom::getrandom(&mut buff).unwrap(); 18 | let x = buff[0] as f32 * 256.0 + buff[1] as f32; 19 | let y = buff[2] as f32 * 256.0 + buff[3] as f32; 20 | let z = buff[4] as f32 * 256.0 + buff[5] as f32; 21 | 22 | (V3{x, y, z}.scale(SCALE_FACTOR)-V3::new(1.0, 1.0, 1.0)).normalize() 23 | } 24 | -------------------------------------------------------------------------------- /wasm/src/geometry/vec_3d.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Sub}; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub struct V3 { 5 | pub x: f32, 6 | pub y: f32, 7 | pub z: f32, 8 | } 9 | 10 | impl AddAssign for V3 { 11 | fn add_assign(&mut self, other: Self) { 12 | self.x += other.x; 13 | self.y += other.y; 14 | self.z += other.z; 15 | } 16 | } 17 | 18 | 19 | impl Add for V3 { 20 | type Output = Self; 21 | 22 | fn add(self, other: Self) -> Self { 23 | Self::new( 24 | self.x+other.x, 25 | self.y+other.y, 26 | self.z+other.z) 27 | } 28 | } 29 | 30 | impl Sub for V3 { 31 | type Output = Self; 32 | 33 | fn sub(self, other: Self) -> Self { 34 | Self::new( 35 | self.x-other.x, 36 | self.y-other.y, 37 | self.z-other.z) 38 | } 39 | } 40 | 41 | impl From<(f32, f32, f32)> for V3 { 42 | fn from(t: (f32, f32, f32)) -> V3 { 43 | Self {x: t.0, y: t.1, z: t.2} 44 | } 45 | } 46 | impl From for (f32, f32, f32) { 47 | fn from(p: V3) -> (f32, f32, f32) { 48 | (p.x, p.y, p.z) 49 | } 50 | } 51 | 52 | impl V3 { 53 | pub fn new(x: f32, y: f32, z: f32) -> Self { 54 | Self {x, y, z} 55 | } 56 | pub fn null() -> Self { 57 | Self {x: 0.0, y: 0.0, z: 0.0} 58 | } 59 | pub fn scale(self, k: f32) -> Self { 60 | Self {x: self.x * k, y: self.y * k, z: self.z * k} 61 | } 62 | pub fn norm(self) -> f32 { 63 | V3::dot(self, self).sqrt() 64 | } 65 | pub fn normalize(self) -> Self { 66 | let r = fast_inverse_square_root( 67 | self.x*self.x+ 68 | self.y*self.y+ 69 | self.z*self.z); 70 | 71 | self.scale(r) 72 | } 73 | pub fn cross(a: V3, b: V3) -> V3 { 74 | V3::new( 75 | a.y*b.z - a.z*b.y, 76 | a.z*b.x - a.x*b.z, 77 | a.x*b.y - a.y*b.x, 78 | ) 79 | } 80 | pub fn dot(a: V3, b: V3) -> f32 { 81 | a.x*b.x+a.y*b.y+a.z*b.z 82 | } 83 | pub fn map(&self, f: impl Fn(f32) -> f32) -> Self{ 84 | // consume the vector 85 | Self::new(f(self.x), f(self.y), f(self.z)) 86 | } 87 | } 88 | 89 | 90 | fn fast_inverse_square_root(x: f32) -> f32 { 91 | let i = x.to_bits(); 92 | let i = 0x5f3759df - (i >> 1); 93 | let y = f32::from_bits(i); 94 | 95 | y * (1.5 - 0.5 * x * y * y) 96 | } 97 | 98 | 99 | #[derive(Copy, Clone)] 100 | pub struct Range { 101 | pub smaller_corner: V3, 102 | pub greater_corner: V3, 103 | } 104 | 105 | impl Range { 106 | pub fn new(smaller_corner: V3, greater_corner: V3) -> Self { 107 | Range {smaller_corner, greater_corner} 108 | } 109 | pub fn contain(&self, p: V3) -> bool { 110 | let x_good = self.smaller_corner.x < p.x && p.x < self.greater_corner.x; 111 | let y_good = self.smaller_corner.y < p.y && p.y < self.greater_corner.y; 112 | let z_good = self.smaller_corner.z < p.z && p.z < self.greater_corner.z; 113 | 114 | x_good && y_good && z_good 115 | } 116 | pub fn intersection(&self, other: &Self) -> Option { 117 | let a_s = self.smaller_corner; 118 | let a_g = self.greater_corner; 119 | let b_s = other.smaller_corner; 120 | let b_g = other.greater_corner; 121 | 122 | // new smaller corner 123 | let s_x = f32::max(a_s.x, b_s.x); 124 | let s_y = f32::max(a_s.y, b_s.y); 125 | let s_z = f32::max(a_s.z, b_s.z); 126 | 127 | // new greater corner 128 | let b_x = f32::max(a_g.x, b_g.x); 129 | let b_y = f32::max(a_g.y, b_g.y); 130 | let b_z = f32::max(a_g.z, b_g.z); 131 | 132 | if s_x < b_x && s_y < b_y && s_z < b_z { 133 | Some( 134 | Self::new( 135 | V3::new(s_x, s_y, s_z), 136 | V3::new(b_x, b_y, b_z), 137 | ) 138 | ) 139 | } 140 | else { 141 | None 142 | } 143 | } 144 | pub fn diagonal(&self) -> V3 { 145 | self.greater_corner - self.smaller_corner 146 | } 147 | } 148 | 149 | 150 | 151 | pub trait Dist { 152 | // signed distance function 153 | fn dist(&self, point: V3) -> f32; 154 | } 155 | 156 | impl Dist for F where F: Fn(V3) -> f32 { 157 | fn dist(&self, point: V3) -> f32 { 158 | self(point) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use web_sys::console; 4 | 5 | macro_rules! log { 6 | ($($msg: tt)*) => { 7 | unsafe { 8 | console::log_1(&format!($($msg)*).into()) 9 | } 10 | } 11 | } 12 | use web_sys::WebGlRenderingContext as GL; 13 | use web_sys::WebGlUniformLocation; 14 | 15 | mod webgl; 16 | use webgl::Engine; 17 | 18 | mod camera; 19 | use camera::Camera; 20 | 21 | mod geometry; 22 | 23 | #[wasm_bindgen] 24 | pub struct Universe { 25 | engine: Engine, 26 | camera: Camera, 27 | n_update: u32, 28 | last_update: u32, 29 | } 30 | 31 | #[wasm_bindgen] 32 | impl Universe { 33 | #[wasm_bindgen(constructor)] 34 | pub fn new(gl: GL, trans_location: WebGlUniformLocation, time_location: WebGlUniformLocation, t: u32) -> Self { 35 | let camera = Camera {x:2.0, y:-2.0, z:0.0, angle:1.80}; 36 | let engine = Engine {gl, trans_location, time_location, n_indices: 0i32}; 37 | Self {engine, camera, n_update: 0, last_update: t} 38 | } 39 | 40 | pub fn update(&mut self, t: u32, left: bool, right: bool, down: bool, up: bool, space: bool, shift: bool) { 41 | 42 | let dt = (t - self.last_update) as f32 / 1000.0; 43 | 44 | 45 | 46 | if left {self.camera.rotate( 2.0 * dt);}; 47 | if right {self.camera.rotate( 2.0 * -dt);}; 48 | if down {self.camera.forward(2.0 * -dt);}; 49 | if up {self.camera.forward(2.0 * dt);}; 50 | if space {self.camera.up(2.0 * dt);}; 51 | if shift {self.camera.up(-2.0*dt);}; 52 | 53 | if self.n_update % 3000 == 0 { 54 | // update landscape 55 | let mut points = Vec::with_capacity(10000000); 56 | let mut indices = Vec::with_capacity(10000000); 57 | //geometry::test_sphere(&mut points, &mut indices); 58 | geometry::test_octree_shape(&mut points, &mut indices); 59 | // geometry::rand_surface(&mut points, &mut indices); 60 | geometry::shade(&mut points, &mut indices); 61 | 62 | self.engine.update_triangles(points, indices); 63 | } 64 | 65 | self.n_update += 1; 66 | self.last_update = t; 67 | 68 | // debug log 69 | if self.n_update % 10 == 0 { 70 | log!("{}", self.camera.get_info()); 71 | }; 72 | 73 | } 74 | 75 | pub fn render(&mut self, t: u32){ 76 | let time = (t as f32) * 0.001; 77 | 78 | self.engine.render(self.camera.get_transform( 79 | self.engine.width(), 80 | self.engine.height()), 81 | time 82 | ); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /wasm/src/webgl.rs: -------------------------------------------------------------------------------- 1 | use web_sys::WebGlRenderingContext as GL; 2 | use web_sys::WebGlUniformLocation; 3 | 4 | use js_sys::*; 5 | 6 | pub struct Engine { 7 | pub gl: GL, 8 | pub trans_location: WebGlUniformLocation, 9 | pub time_location: WebGlUniformLocation, 10 | pub n_indices: i32, 11 | } 12 | 13 | impl Engine { 14 | pub fn update_triangles(&mut self, point_data: Vec, index_data: Vec) { 15 | unsafe { 16 | let vert_array = Float32Array::view(&point_data[..]); 17 | let index_array = Uint16Array::view(&index_data[..]); 18 | 19 | self.gl.buffer_data_with_array_buffer_view( 20 | GL::ARRAY_BUFFER, 21 | &vert_array, 22 | GL::DYNAMIC_DRAW); 23 | 24 | self.gl.buffer_data_with_array_buffer_view( 25 | GL::ELEMENT_ARRAY_BUFFER, 26 | &index_array, 27 | GL::DYNAMIC_DRAW); 28 | } 29 | 30 | self.n_indices = index_data.len() as i32; 31 | } 32 | 33 | pub fn render(&self, transform: [f32; 16], time: f32) { 34 | self.gl.uniform_matrix4fv_with_f32_array( 35 | Some(&self.trans_location), 36 | false, 37 | &transform, 38 | ); 39 | 40 | self.gl.uniform1f( 41 | Some(&self.time_location), 42 | time 43 | ); 44 | 45 | self.gl.clear(GL::COLOR_BUFFER_BIT); 46 | self.gl.draw_elements_with_i32(GL::TRIANGLES, self.n_indices, GL::UNSIGNED_SHORT, 0) 47 | } 48 | pub fn width(&self) -> u32 {self.gl.drawing_buffer_width() as u32} 49 | pub fn height(&self) -> u32 {self.gl.drawing_buffer_height() as u32} 50 | } 51 | 52 | --------------------------------------------------------------------------------