├── .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 | 
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 |
--------------------------------------------------------------------------------