├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── colormaps ├── cool-warm-paraview.png ├── matplotlib-plasma.png ├── matplotlib-virdis.png ├── rainbow.png ├── samsel-linear-green.png └── samsel-linear-ygb-1211g.png ├── index.html ├── js ├── gl-matrix-min.js ├── marching-cubes.js ├── render.js ├── shader-srcs.js └── webgl-util.js └── src ├── error_log.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.raw 3 | /target 4 | **/*.rs.bk 5 | Cargo.lock 6 | pkg/ 7 | *.swp 8 | 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "marching-cubes" 3 | version = "0.1.0" 4 | authors = ["Will Usher "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1" 15 | wasm-bindgen = "0.2" 16 | js-sys = "0.3" 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "0.1", optional = true } 23 | 24 | [dev-dependencies] 25 | wasm-bindgen-test = "0.2" 26 | 27 | [profile.release] 28 | lto = true 29 | opt-level = 3 30 | 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Will Usher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL Marching Cubes 2 | 3 | This is a WebGL + WebASM implementation of the classic [Marching Cubes](https://en.wikipedia.org/wiki/Marching_cubes) 4 | algorithm for extracting [isosurfaces](https://en.wikipedia.org/wiki/Isosurface) from 3D volume data. 5 | An isosurface is a surface which represents points in the 3D data which all have the same value 6 | (e.g., pressure, temperature). The isosurface extraction code is implemented in Rust and compiled 7 | to WebAssembly to accelerate extraction of the surface. Depending on your browser, 8 | when compared to the pure Javascript version the WebASM version is 10-50x faster! 9 | The surface is rendered as a triangle mesh and combined with the 10 | volume during the volume raycasting step, in a manner roughly similar to shadow mapping. 11 | [Try it out online!](https://www.willusher.io/webgl-marching-cubes/) 12 | 13 | To compile the WebAssembly version you'll need [Rust](https://www.rust-lang.org/) and wasm-pack. 14 | After install Rust you can install wasm-pack with `cargo install wasm-pack`. 15 | Then build the WASM code: `wasm-pack build -t web --release`, and 16 | run a local webserver to serve the files. 17 | 18 | # Images 19 | 20 | ![images](https://i.imgur.com/2tvnaYn.png) 21 | 22 | -------------------------------------------------------------------------------- /colormaps/cool-warm-paraview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/cool-warm-paraview.png -------------------------------------------------------------------------------- /colormaps/matplotlib-plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/matplotlib-plasma.png -------------------------------------------------------------------------------- /colormaps/matplotlib-virdis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/matplotlib-virdis.png -------------------------------------------------------------------------------- /colormaps/rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/rainbow.png -------------------------------------------------------------------------------- /colormaps/samsel-linear-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/samsel-linear-green.png -------------------------------------------------------------------------------- /colormaps/samsel-linear-ygb-1211g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twinklebear/webgl-marching-cubes/4e4ac26a61ffedc37fedd69f5e441b5a5c23dd83/colormaps/samsel-linear-ygb-1211g.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | WebGL Marching Cubes 12 | 13 | 14 |
15 |
16 |
17 |

WebGL + WebASM Marching Cubes

18 | 19 |
20 |
21 |
22 | Volume: 23 |
24 |
25 | Colormap: 26 |
27 |
28 | 29 | 31 | 32 | 33 | 34 |
35 | 36 | 37 |
38 |
39 |

Isosurface info:

40 |
41 |
42 |

Controls

43 |

Desktop: Left-click + drag to rotate, scroll to zoom, 44 | right-click + drag to pan. 45 |
46 | Touch: One finger drag to rotate, pinch to zoom, two finger drag to pan. 47 |

48 |

49 |
50 |
52 |
53 | 54 |

Description

55 |

56 | This is a WebGL + WebASM implementation of the classic Marching Cubes 57 | algorithm for extracting isosurfaces from 3D volume data. 58 | An isosurface is a surface 59 | which represents points in the 3D data which all have the same value (e.g., pressure, temperature). 60 | Try changing the slider to change the selected isovalue! The isosurface extraction code 61 | is implemented in Rust and compiled to WebAssembly to accelerate extraction of the surface. Depending on your browser, 62 | when compared to the pure Javascript version the WebASM version is 10-50x faster! You can run the Javascript version 63 | by unchecking the "Use WebASM" box. The surface is rendered as a triangle mesh and combined with the 64 | volume during the volume raycasting step, in a manner roughly similar to shadow mapping. 65 | 66 | Get the code on GitHub! 67 |

68 |
69 |
70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /js/gl-matrix-min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview gl-matrix - High performance matrix and vector operations 3 | * @author Brandon Jones 4 | * @author Colin MacKenzie IV 5 | * @version 2.4.0 6 | */ 7 | 8 | /* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. */ 27 | 28 | !function(t,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var r=n();for(var a in r)("object"==typeof exports?exports:t)[a]=r[a]}}("undefined"!=typeof self?self:this,function(){return function(t){function n(a){if(r[a])return r[a].exports;var e=r[a]={i:a,l:!1,exports:{}};return t[a].call(e.exports,e,e.exports,n),e.l=!0,e.exports}var r={};return n.m=t,n.c=r,n.d=function(t,r,a){n.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:a})},n.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(r,"a",r),r},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=4)}([function(t,n,r){"use strict";function a(t){n.ARRAY_TYPE=i=t}function e(t){return t*s}function u(t,n){return Math.abs(t-n)<=o*Math.max(1,Math.abs(t),Math.abs(n))}Object.defineProperty(n,"__esModule",{value:!0}),n.setMatrixArrayType=a,n.toRadian=e,n.equals=u;var o=n.EPSILON=1e-6,i=n.ARRAY_TYPE="undefined"!=typeof Float32Array?Float32Array:Array,s=(n.RANDOM=Math.random,Math.PI/180)},function(t,n,r){"use strict";function a(){var t=new g.ARRAY_TYPE(9);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t}function e(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[4],t[4]=n[5],t[5]=n[6],t[6]=n[8],t[7]=n[9],t[8]=n[10],t}function u(t){var n=new g.ARRAY_TYPE(9);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n}function o(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t}function i(t,n,r,a,e,u,o,i,s){var c=new g.ARRAY_TYPE(9);return c[0]=t,c[1]=n,c[2]=r,c[3]=a,c[4]=e,c[5]=u,c[6]=o,c[7]=i,c[8]=s,c}function s(t,n,r,a,e,u,o,i,s,c){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t}function c(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t}function f(t,n){if(t===n){var r=n[1],a=n[2],e=n[5];t[1]=n[3],t[2]=n[6],t[3]=r,t[5]=n[7],t[6]=a,t[7]=e}else t[0]=n[0],t[1]=n[3],t[2]=n[6],t[3]=n[1],t[4]=n[4],t[5]=n[7],t[6]=n[2],t[7]=n[5],t[8]=n[8];return t}function M(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=f*o-i*c,h=-f*u+i*s,l=c*u-o*s,v=r*M+a*h+e*l;return v?(v=1/v,t[0]=M*v,t[1]=(-f*a+e*c)*v,t[2]=(i*a-e*o)*v,t[3]=h*v,t[4]=(f*r-e*s)*v,t[5]=(-i*r+e*u)*v,t[6]=l*v,t[7]=(-c*r+a*s)*v,t[8]=(o*r-a*u)*v,t):null}function h(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8];return t[0]=o*f-i*c,t[1]=e*c-a*f,t[2]=a*i-e*o,t[3]=i*s-u*f,t[4]=r*f-e*s,t[5]=e*u-r*i,t[6]=u*c-o*s,t[7]=a*s-r*c,t[8]=r*o-a*u,t}function l(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8];return n*(c*u-o*s)+r*(-c*e+o*i)+a*(s*e-u*i)}function v(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1],v=r[2],d=r[3],b=r[4],m=r[5],p=r[6],P=r[7],E=r[8];return t[0]=h*a+l*o+v*c,t[1]=h*e+l*i+v*f,t[2]=h*u+l*s+v*M,t[3]=d*a+b*o+m*c,t[4]=d*e+b*i+m*f,t[5]=d*u+b*s+m*M,t[6]=p*a+P*o+E*c,t[7]=p*e+P*i+E*f,t[8]=p*u+P*s+E*M,t}function d(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=i,t[5]=s,t[6]=h*a+l*o+c,t[7]=h*e+l*i+f,t[8]=h*u+l*s+M,t}function b(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=Math.sin(r),l=Math.cos(r);return t[0]=l*a+h*o,t[1]=l*e+h*i,t[2]=l*u+h*s,t[3]=l*o-h*a,t[4]=l*i-h*e,t[5]=l*s-h*u,t[6]=c,t[7]=f,t[8]=M,t}function m(t,n,r){var a=r[0],e=r[1];return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=e*n[3],t[4]=e*n[4],t[5]=e*n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t}function p(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=n[0],t[7]=n[1],t[8]=1,t}function P(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=-r,t[4]=a,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t}function E(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=n[1],t[5]=0,t[6]=0,t[7]=0,t[8]=1,t}function O(t,n){return t[0]=n[0],t[1]=n[1],t[2]=0,t[3]=n[2],t[4]=n[3],t[5]=0,t[6]=n[4],t[7]=n[5],t[8]=1,t}function x(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[3]=f-m,t[6]=h+b,t[1]=f+m,t[4]=1-c-v,t[7]=l-d,t[2]=h-b,t[5]=l+d,t[8]=1-c-M,t}function A(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,E=r*c-u*o,O=a*s-e*i,x=a*c-u*i,A=e*c-u*s,q=f*d-M*v,y=f*b-h*v,w=f*m-l*v,R=M*b-h*d,L=M*m-l*d,S=h*m-l*b,_=p*S-P*L+E*R+O*w-x*y+A*q;return _?(_=1/_,t[0]=(i*S-s*L+c*R)*_,t[1]=(s*w-o*S-c*y)*_,t[2]=(o*L-i*w+c*q)*_,t[3]=(e*L-a*S-u*R)*_,t[4]=(r*S-e*w+u*y)*_,t[5]=(a*w-r*L-u*q)*_,t[6]=(d*A-b*x+m*O)*_,t[7]=(b*E-v*A-m*P)*_,t[8]=(v*x-d*E+m*p)*_,t):null}function q(t,n,r){return t[0]=2/n,t[1]=0,t[2]=0,t[3]=0,t[4]=-2/r,t[5]=0,t[6]=-1,t[7]=1,t[8]=1,t}function y(t){return"mat3("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+")"}function w(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2))}function R(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t}function L(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t}function S(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t}function _(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t}function I(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]}function N(t,n){var r=t[0],a=t[1],e=t[2],u=t[3],o=t[4],i=t[5],s=t[6],c=t[7],f=t[8],M=n[0],h=n[1],l=n[2],v=n[3],d=n[4],b=n[5],m=n[6],p=n[7],P=n[8];return Math.abs(r-M)<=g.EPSILON*Math.max(1,Math.abs(r),Math.abs(M))&&Math.abs(a-h)<=g.EPSILON*Math.max(1,Math.abs(a),Math.abs(h))&&Math.abs(e-l)<=g.EPSILON*Math.max(1,Math.abs(e),Math.abs(l))&&Math.abs(u-v)<=g.EPSILON*Math.max(1,Math.abs(u),Math.abs(v))&&Math.abs(o-d)<=g.EPSILON*Math.max(1,Math.abs(o),Math.abs(d))&&Math.abs(i-b)<=g.EPSILON*Math.max(1,Math.abs(i),Math.abs(b))&&Math.abs(s-m)<=g.EPSILON*Math.max(1,Math.abs(s),Math.abs(m))&&Math.abs(c-p)<=g.EPSILON*Math.max(1,Math.abs(c),Math.abs(p))&&Math.abs(f-P)<=g.EPSILON*Math.max(1,Math.abs(f),Math.abs(P))}Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=a,n.fromMat4=e,n.clone=u,n.copy=o,n.fromValues=i,n.set=s,n.identity=c,n.transpose=f,n.invert=M,n.adjoint=h,n.determinant=l,n.multiply=v,n.translate=d,n.rotate=b,n.scale=m,n.fromTranslation=p,n.fromRotation=P,n.fromScaling=E,n.fromMat2d=O,n.fromQuat=x,n.normalFromMat4=A,n.projection=q,n.str=y,n.frob=w,n.add=R,n.subtract=L,n.multiplyScalar=S,n.multiplyScalarAndAdd=_,n.exactEquals=I,n.equals=N;var Y=r(0),g=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(Y);n.mul=v,n.sub=L},function(t,n,r){"use strict";function a(){var t=new Z.ARRAY_TYPE(3);return t[0]=0,t[1]=0,t[2]=0,t}function e(t){var n=new Z.ARRAY_TYPE(3);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n}function u(t){var n=t[0],r=t[1],a=t[2];return Math.sqrt(n*n+r*r+a*a)}function o(t,n,r){var a=new Z.ARRAY_TYPE(3);return a[0]=t,a[1]=n,a[2]=r,a}function i(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t}function s(t,n,r,a){return t[0]=n,t[1]=r,t[2]=a,t}function c(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t}function f(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t}function M(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t}function h(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t}function l(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t[2]=Math.ceil(n[2]),t}function v(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t[2]=Math.floor(n[2]),t}function d(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t}function b(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t}function m(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t[2]=Math.round(n[2]),t}function p(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t}function P(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t}function E(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return Math.sqrt(r*r+a*a+e*e)}function O(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return r*r+a*a+e*e}function x(t){var n=t[0],r=t[1],a=t[2];return n*n+r*r+a*a}function A(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t}function q(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t}function y(t,n){var r=n[0],a=n[1],e=n[2],u=r*r+a*a+e*e;return u>0&&(u=1/Math.sqrt(u),t[0]=n[0]*u,t[1]=n[1]*u,t[2]=n[2]*u),t}function w(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function R(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2];return t[0]=e*s-u*i,t[1]=u*o-a*s,t[2]=a*i-e*o,t}function L(t,n,r,a){var e=n[0],u=n[1],o=n[2];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t}function S(t,n,r,a,e,u){var o=u*u,i=o*(2*u-3)+1,s=o*(u-2)+u,c=o*(u-1),f=o*(3-2*u);return t[0]=n[0]*i+r[0]*s+a[0]*c+e[0]*f,t[1]=n[1]*i+r[1]*s+a[1]*c+e[1]*f,t[2]=n[2]*i+r[2]*s+a[2]*c+e[2]*f,t}function _(t,n,r,a,e,u){var o=1-u,i=o*o,s=u*u,c=i*o,f=3*u*i,M=3*s*o,h=s*u;return t[0]=n[0]*c+r[0]*f+a[0]*M+e[0]*h,t[1]=n[1]*c+r[1]*f+a[1]*M+e[1]*h,t[2]=n[2]*c+r[2]*f+a[2]*M+e[2]*h,t}function I(t,n){n=n||1;var r=2*Z.RANDOM()*Math.PI,a=2*Z.RANDOM()-1,e=Math.sqrt(1-a*a)*n;return t[0]=Math.cos(r)*e,t[1]=Math.sin(r)*e,t[2]=a*n,t}function N(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[3]*a+r[7]*e+r[11]*u+r[15];return o=o||1,t[0]=(r[0]*a+r[4]*e+r[8]*u+r[12])/o,t[1]=(r[1]*a+r[5]*e+r[9]*u+r[13])/o,t[2]=(r[2]*a+r[6]*e+r[10]*u+r[14])/o,t}function Y(t,n,r){var a=n[0],e=n[1],u=n[2];return t[0]=a*r[0]+e*r[3]+u*r[6],t[1]=a*r[1]+e*r[4]+u*r[7],t[2]=a*r[2]+e*r[5]+u*r[8],t}function g(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2],c=r[3],f=c*a+i*u-s*e,M=c*e+s*a-o*u,h=c*u+o*e-i*a,l=-o*a-i*e-s*u;return t[0]=f*c+l*-o+M*-s-h*-i,t[1]=M*c+l*-i+h*-o-f*-s,t[2]=h*c+l*-s+f*-i-M*-o,t}function T(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0],u[1]=e[1]*Math.cos(a)-e[2]*Math.sin(a),u[2]=e[1]*Math.sin(a)+e[2]*Math.cos(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t}function j(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[2]*Math.sin(a)+e[0]*Math.cos(a),u[1]=e[1],u[2]=e[2]*Math.cos(a)-e[0]*Math.sin(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t}function D(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0]*Math.cos(a)-e[1]*Math.sin(a),u[1]=e[0]*Math.sin(a)+e[1]*Math.cos(a),u[2]=e[2],t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t}function V(t,n){var r=o(t[0],t[1],t[2]),a=o(n[0],n[1],n[2]);y(r,r),y(a,a);var e=w(r,a);return e>1?0:e<-1?Math.PI:Math.acos(e)}function z(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"}function F(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]}function Q(t,n){var r=t[0],a=t[1],e=t[2],u=n[0],o=n[1],i=n[2];return Math.abs(r-u)<=Z.EPSILON*Math.max(1,Math.abs(r),Math.abs(u))&&Math.abs(a-o)<=Z.EPSILON*Math.max(1,Math.abs(a),Math.abs(o))&&Math.abs(e-i)<=Z.EPSILON*Math.max(1,Math.abs(e),Math.abs(i))}Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=a,n.clone=e,n.length=u,n.fromValues=o,n.copy=i,n.set=s,n.add=c,n.subtract=f,n.multiply=M,n.divide=h,n.ceil=l,n.floor=v,n.min=d,n.max=b,n.round=m,n.scale=p,n.scaleAndAdd=P,n.distance=E,n.squaredDistance=O,n.squaredLength=x,n.negate=A,n.inverse=q,n.normalize=y,n.dot=w,n.cross=R,n.lerp=L,n.hermite=S,n.bezier=_,n.random=I,n.transformMat4=N,n.transformMat3=Y,n.transformQuat=g,n.rotateX=T,n.rotateY=j,n.rotateZ=D,n.angle=V,n.str=z,n.exactEquals=F,n.equals=Q;var X=r(0),Z=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(X);n.sub=f,n.mul=M,n.div=h,n.dist=E,n.sqrDist=O,n.len=u,n.sqrLen=x,n.forEach=function(){var t=a();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=3),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i0&&(o=1/Math.sqrt(o),t[0]=r*o,t[1]=a*o,t[2]=e*o,t[3]=u*o),t}function w(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]+t[3]*n[3]}function R(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t[3]=i+a*(r[3]-i),t}function L(t,n){return n=n||1,t[0]=T.RANDOM(),t[1]=T.RANDOM(),t[2]=T.RANDOM(),t[3]=T.RANDOM(),y(t,t),m(t,t,n),t}function S(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3];return t[0]=r[0]*a+r[4]*e+r[8]*u+r[12]*o,t[1]=r[1]*a+r[5]*e+r[9]*u+r[13]*o,t[2]=r[2]*a+r[6]*e+r[10]*u+r[14]*o,t[3]=r[3]*a+r[7]*e+r[11]*u+r[15]*o,t}function _(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2],c=r[3],f=c*a+i*u-s*e,M=c*e+s*a-o*u,h=c*u+o*e-i*a,l=-o*a-i*e-s*u;return t[0]=f*c+l*-o+M*-s-h*-i,t[1]=M*c+l*-i+h*-o-f*-s,t[2]=h*c+l*-s+f*-i-M*-o,t[3]=n[3],t}function I(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"}function N(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]}function Y(t,n){var r=t[0],a=t[1],e=t[2],u=t[3],o=n[0],i=n[1],s=n[2],c=n[3];return Math.abs(r-o)<=T.EPSILON*Math.max(1,Math.abs(r),Math.abs(o))&&Math.abs(a-i)<=T.EPSILON*Math.max(1,Math.abs(a),Math.abs(i))&&Math.abs(e-s)<=T.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=T.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))}Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=a,n.clone=e,n.fromValues=u,n.copy=o,n.set=i,n.add=s,n.subtract=c,n.multiply=f,n.divide=M,n.ceil=h,n.floor=l,n.min=v,n.max=d,n.round=b,n.scale=m,n.scaleAndAdd=p,n.distance=P,n.squaredDistance=E,n.length=O,n.squaredLength=x,n.negate=A,n.inverse=q,n.normalize=y,n.dot=w,n.lerp=R,n.random=L,n.transformMat4=S,n.transformQuat=_,n.str=I,n.exactEquals=N,n.equals=Y;var g=r(0),T=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(g);n.sub=c,n.mul=f,n.div=M,n.dist=P,n.sqrDist=E,n.len=O,n.sqrLen=x,n.forEach=function(){var t=a();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=4),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i0?(a=2*Math.sqrt(r+1),t[3]=.25*a,t[0]=(n[6]-n[9])/a,t[1]=(n[8]-n[2])/a,t[2]=(n[1]-n[4])/a):n[0]>n[5]&n[0]>n[10]?(a=2*Math.sqrt(1+n[0]-n[5]-n[10]),t[3]=(n[6]-n[9])/a,t[0]=.25*a,t[1]=(n[1]+n[4])/a,t[2]=(n[8]+n[2])/a):n[5]>n[10]?(a=2*Math.sqrt(1+n[5]-n[0]-n[10]),t[3]=(n[8]-n[2])/a,t[0]=(n[1]+n[4])/a,t[1]=.25*a,t[2]=(n[6]+n[9])/a):(a=2*Math.sqrt(1+n[10]-n[0]-n[5]),t[3]=(n[1]-n[4])/a,t[0]=(n[8]+n[2])/a,t[1]=(n[6]+n[9])/a,t[2]=.25*a),t}function _(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3],s=e+e,c=u+u,f=o+o,M=e*s,h=e*c,l=e*f,v=u*c,d=u*f,b=o*f,m=i*s,p=i*c,P=i*f,E=a[0],O=a[1],x=a[2];return t[0]=(1-(v+b))*E,t[1]=(h+P)*E,t[2]=(l-p)*E,t[3]=0,t[4]=(h-P)*O,t[5]=(1-(M+b))*O,t[6]=(d+m)*O,t[7]=0,t[8]=(l+p)*x,t[9]=(d-m)*x,t[10]=(1-(M+v))*x,t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t}function I(t,n,r,a,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=u+u,f=o+o,M=i+i,h=u*c,l=u*f,v=u*M,d=o*f,b=o*M,m=i*M,p=s*c,P=s*f,E=s*M,O=a[0],x=a[1],A=a[2],q=e[0],y=e[1],w=e[2];return t[0]=(1-(d+m))*O,t[1]=(l+E)*O,t[2]=(v-P)*O,t[3]=0,t[4]=(l-E)*x,t[5]=(1-(h+m))*x,t[6]=(b+p)*x,t[7]=0,t[8]=(v+P)*A,t[9]=(b-p)*A,t[10]=(1-(h+d))*A,t[11]=0,t[12]=r[0]+q-(t[0]*q+t[4]*y+t[8]*w),t[13]=r[1]+y-(t[1]*q+t[5]*y+t[9]*w),t[14]=r[2]+w-(t[2]*q+t[6]*y+t[10]*w),t[15]=1,t}function N(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[1]=f+m,t[2]=h-b,t[3]=0,t[4]=f-m,t[5]=1-c-v,t[6]=l+d,t[7]=0,t[8]=h+b,t[9]=l-d,t[10]=1-c-M,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function Y(t,n,r,a,e,u,o){var i=1/(r-n),s=1/(e-a),c=1/(u-o);return t[0]=2*u*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=2*u*s,t[6]=0,t[7]=0,t[8]=(r+n)*i,t[9]=(e+a)*s,t[10]=(o+u)*c,t[11]=-1,t[12]=0,t[13]=0,t[14]=o*u*2*c,t[15]=0,t}function g(t,n,r,a,e){var u=1/Math.tan(n/2),o=1/(a-e);return t[0]=u/r,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=u,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=(e+a)*o,t[11]=-1,t[12]=0,t[13]=0,t[14]=2*e*a*o,t[15]=0,t}function T(t,n,r,a){var e=Math.tan(n.upDegrees*Math.PI/180),u=Math.tan(n.downDegrees*Math.PI/180),o=Math.tan(n.leftDegrees*Math.PI/180),i=Math.tan(n.rightDegrees*Math.PI/180),s=2/(o+i),c=2/(e+u);return t[0]=s,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=c,t[6]=0,t[7]=0,t[8]=-(o-i)*s*.5,t[9]=(e-u)*c*.5,t[10]=a/(r-a),t[11]=-1,t[12]=0,t[13]=0,t[14]=a*r/(r-a),t[15]=0,t}function j(t,n,r,a,e,u,o){var i=1/(n-r),s=1/(a-e),c=1/(u-o);return t[0]=-2*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=-2*s,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=2*c,t[11]=0,t[12]=(n+r)*i,t[13]=(e+a)*s,t[14]=(o+u)*c,t[15]=1,t}function D(t,n,r,a){var e=void 0,u=void 0,o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=n[0],d=n[1],b=n[2],m=a[0],p=a[1],P=a[2],E=r[0],O=r[1],x=r[2];return Math.abs(v-E)0&&(l=1/Math.sqrt(l),f*=l,M*=l,h*=l);var v=s*h-c*M,d=c*f-i*h,b=i*M-s*f;return t[0]=v,t[1]=d,t[2]=b,t[3]=0,t[4]=M*b-h*d,t[5]=h*v-f*b,t[6]=f*d-M*v,t[7]=0,t[8]=f,t[9]=M,t[10]=h,t[11]=0,t[12]=e,t[13]=u,t[14]=o,t[15]=1,t}function z(t){return"mat4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+", "+t[9]+", "+t[10]+", "+t[11]+", "+t[12]+", "+t[13]+", "+t[14]+", "+t[15]+")"}function F(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2)+Math.pow(t[9],2)+Math.pow(t[10],2)+Math.pow(t[11],2)+Math.pow(t[12],2)+Math.pow(t[13],2)+Math.pow(t[14],2)+Math.pow(t[15],2))}function Q(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t[9]=n[9]+r[9],t[10]=n[10]+r[10],t[11]=n[11]+r[11],t[12]=n[12]+r[12],t[13]=n[13]+r[13],t[14]=n[14]+r[14],t[15]=n[15]+r[15],t}function X(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t[9]=n[9]-r[9],t[10]=n[10]-r[10],t[11]=n[11]-r[11],t[12]=n[12]-r[12],t[13]=n[13]-r[13],t[14]=n[14]-r[14],t[15]=n[15]-r[15],t}function Z(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t[9]=n[9]*r,t[10]=n[10]*r,t[11]=n[11]*r,t[12]=n[12]*r,t[13]=n[13]*r,t[14]=n[14]*r,t[15]=n[15]*r,t}function k(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t[9]=n[9]+r[9]*a,t[10]=n[10]+r[10]*a,t[11]=n[11]+r[11]*a,t[12]=n[12]+r[12]*a,t[13]=n[13]+r[13]*a,t[14]=n[14]+r[14]*a,t[15]=n[15]+r[15]*a,t}function U(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]&&t[9]===n[9]&&t[10]===n[10]&&t[11]===n[11]&&t[12]===n[12]&&t[13]===n[13]&&t[14]===n[14]&&t[15]===n[15]}function W(t,n){var r=t[0],a=t[1],e=t[2],u=t[3],o=t[4],i=t[5],s=t[6],c=t[7],f=t[8],M=t[9],h=t[10],l=t[11],v=t[12],d=t[13],b=t[14],m=t[15],p=n[0],P=n[1],E=n[2],O=n[3],x=n[4],A=n[5],q=n[6],y=n[7],w=n[8],R=n[9],L=n[10],S=n[11],_=n[12],I=n[13],N=n[14],Y=n[15];return Math.abs(r-p)<=C.EPSILON*Math.max(1,Math.abs(r),Math.abs(p))&&Math.abs(a-P)<=C.EPSILON*Math.max(1,Math.abs(a),Math.abs(P))&&Math.abs(e-E)<=C.EPSILON*Math.max(1,Math.abs(e),Math.abs(E))&&Math.abs(u-O)<=C.EPSILON*Math.max(1,Math.abs(u),Math.abs(O))&&Math.abs(o-x)<=C.EPSILON*Math.max(1,Math.abs(o),Math.abs(x))&&Math.abs(i-A)<=C.EPSILON*Math.max(1,Math.abs(i),Math.abs(A))&&Math.abs(s-q)<=C.EPSILON*Math.max(1,Math.abs(s),Math.abs(q))&&Math.abs(c-y)<=C.EPSILON*Math.max(1,Math.abs(c),Math.abs(y))&&Math.abs(f-w)<=C.EPSILON*Math.max(1,Math.abs(f),Math.abs(w))&&Math.abs(M-R)<=C.EPSILON*Math.max(1,Math.abs(M),Math.abs(R))&&Math.abs(h-L)<=C.EPSILON*Math.max(1,Math.abs(h),Math.abs(L))&&Math.abs(l-S)<=C.EPSILON*Math.max(1,Math.abs(l),Math.abs(S))&&Math.abs(v-_)<=C.EPSILON*Math.max(1,Math.abs(v),Math.abs(_))&&Math.abs(d-I)<=C.EPSILON*Math.max(1,Math.abs(d),Math.abs(I))&&Math.abs(b-N)<=C.EPSILON*Math.max(1,Math.abs(b),Math.abs(N))&&Math.abs(m-Y)<=C.EPSILON*Math.max(1,Math.abs(m),Math.abs(Y))}Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=a,n.clone=e,n.copy=u,n.fromValues=o,n.set=i,n.identity=s,n.transpose=c,n.invert=f,n.adjoint=M,n.determinant=h,n.multiply=l,n.translate=v,n.scale=d,n.rotate=b,n.rotateX=m,n.rotateY=p,n.rotateZ=P,n.fromTranslation=E,n.fromScaling=O,n.fromRotation=x,n.fromXRotation=A,n.fromYRotation=q,n.fromZRotation=y,n.fromRotationTranslation=w,n.getTranslation=R,n.getScaling=L,n.getRotation=S,n.fromRotationTranslationScale=_,n.fromRotationTranslationScaleOrigin=I,n.fromQuat=N,n.frustum=Y,n.perspective=g,n.perspectiveFromFieldOfView=T,n.ortho=j,n.lookAt=D,n.targetTo=V,n.str=z,n.frob=F,n.add=Q,n.subtract=X,n.multiplyScalar=Z,n.multiplyScalarAndAdd=k,n.exactEquals=U,n.equals=W;var B=r(0),C=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(B);n.mul=l,n.sub=X},function(t,n,r){"use strict";function a(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function e(){var t=new E.ARRAY_TYPE(4);return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t}function u(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t}function o(t,n,r){r*=.5;var a=Math.sin(r);return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=Math.cos(r),t}function i(t,n){var r=2*Math.acos(n[3]),a=Math.sin(r/2);return 0!=a?(t[0]=n[0]/a,t[1]=n[1]/a,t[2]=n[2]/a):(t[0]=1,t[1]=0,t[2]=0),r}function s(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*f+o*i+e*c-u*s,t[1]=e*f+o*s+u*i-a*c,t[2]=u*f+o*c+a*s-e*i,t[3]=o*f-a*i-e*s-u*c,t}function c(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+o*i,t[1]=e*s+u*i,t[2]=u*s-e*i,t[3]=o*s-a*i,t}function f(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s-u*i,t[1]=e*s+o*i,t[2]=u*s+a*i,t[3]=o*s-e*i,t}function M(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+e*i,t[1]=e*s-a*i,t[2]=u*s+o*i,t[3]=o*s-u*i,t}function h(t,n){var r=n[0],a=n[1],e=n[2];return t[0]=r,t[1]=a,t[2]=e,t[3]=Math.sqrt(Math.abs(1-r*r-a*a-e*e)),t}function l(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3],s=r[0],c=r[1],f=r[2],M=r[3],h=void 0,l=void 0,v=void 0,d=void 0,b=void 0;return l=e*s+u*c+o*f+i*M,l<0&&(l=-l,s=-s,c=-c,f=-f,M=-M),1-l>1e-6?(h=Math.acos(l),v=Math.sin(h),d=Math.sin((1-a)*h)/v,b=Math.sin(a*h)/v):(d=1-a,b=a),t[0]=d*e+b*s,t[1]=d*u+b*c,t[2]=d*o+b*f,t[3]=d*i+b*M,t}function v(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u,i=o?1/o:0;return t[0]=-r*i,t[1]=-a*i,t[2]=-e*i,t[3]=u*i,t}function d(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t}function b(t,n){var r=n[0]+n[4]+n[8],a=void 0;if(r>0)a=Math.sqrt(r+1),t[3]=.5*a,a=.5/a,t[0]=(n[5]-n[7])*a,t[1]=(n[6]-n[2])*a,t[2]=(n[1]-n[3])*a;else{var e=0;n[4]>n[0]&&(e=1),n[8]>n[3*e+e]&&(e=2);var u=(e+1)%3,o=(e+2)%3;a=Math.sqrt(n[3*e+e]-n[3*u+u]-n[3*o+o]+1),t[e]=.5*a,a=.5/a,t[3]=(n[3*u+o]-n[3*o+u])*a,t[u]=(n[3*u+e]+n[3*e+u])*a,t[o]=(n[3*o+e]+n[3*e+o])*a}return t}function m(t,n,r,a){var e=.5*Math.PI/180;n*=e,r*=e,a*=e;var u=Math.sin(n),o=Math.cos(n),i=Math.sin(r),s=Math.cos(r),c=Math.sin(a),f=Math.cos(a);return t[0]=u*s*f-o*i*c,t[1]=o*i*f+u*s*c,t[2]=o*s*c-u*i*f,t[3]=o*s*f+u*i*c,t}function p(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"}Object.defineProperty(n,"__esModule",{value:!0}),n.setAxes=n.sqlerp=n.rotationTo=n.equals=n.exactEquals=n.normalize=n.sqrLen=n.squaredLength=n.len=n.length=n.lerp=n.dot=n.scale=n.mul=n.add=n.set=n.copy=n.fromValues=n.clone=void 0,n.create=e,n.identity=u,n.setAxisAngle=o,n.getAxisAngle=i,n.multiply=s,n.rotateX=c,n.rotateY=f,n.rotateZ=M,n.calculateW=h,n.slerp=l,n.invert=v,n.conjugate=d,n.fromMat3=b,n.fromEuler=m,n.str=p;var P=r(0),E=a(P),O=r(1),x=a(O),A=r(2),q=a(A),y=r(3),w=a(y),R=(n.clone=w.clone,n.fromValues=w.fromValues,n.copy=w.copy,n.set=w.set,n.add=w.add,n.mul=s,n.scale=w.scale,n.dot=w.dot,n.lerp=w.lerp,n.length=w.length),L=(n.len=R,n.squaredLength=w.squaredLength),S=(n.sqrLen=L,n.normalize=w.normalize);n.exactEquals=w.exactEquals,n.equals=w.equals,n.rotationTo=function(){var t=q.create(),n=q.fromValues(1,0,0),r=q.fromValues(0,1,0);return function(a,e,u){var i=q.dot(e,u);return i<-.999999?(q.cross(t,n,e),q.len(t)<1e-6&&q.cross(t,r,e),q.normalize(t,t),o(a,t,Math.PI),a):i>.999999?(a[0]=0,a[1]=0,a[2]=0,a[3]=1,a):(q.cross(t,e,u),a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=1+i,S(a,a))}}(),n.sqlerp=function(){var t=e(),n=e();return function(r,a,e,u,o,i){return l(t,a,o,i),l(n,e,u,i),l(r,t,n,2*i*(1-i)),r}}(),n.setAxes=function(){var t=x.create();return function(n,r,a,e){return t[0]=a[0],t[3]=a[1],t[6]=a[2],t[1]=e[0],t[4]=e[1],t[7]=e[2],t[2]=-r[0],t[5]=-r[1],t[8]=-r[2],S(n,b(n,t))}}()},function(t,n,r){"use strict";function a(){var t=new V.ARRAY_TYPE(2);return t[0]=0,t[1]=0,t}function e(t){var n=new V.ARRAY_TYPE(2);return n[0]=t[0],n[1]=t[1],n}function u(t,n){var r=new V.ARRAY_TYPE(2);return r[0]=t,r[1]=n,r}function o(t,n){return t[0]=n[0],t[1]=n[1],t}function i(t,n,r){return t[0]=n,t[1]=r,t}function s(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t}function c(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t}function f(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t}function M(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t}function h(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t}function l(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t}function v(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t}function d(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t}function b(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t}function m(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t}function p(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t}function P(t,n){var r=n[0]-t[0],a=n[1]-t[1];return Math.sqrt(r*r+a*a)}function E(t,n){var r=n[0]-t[0],a=n[1]-t[1];return r*r+a*a}function O(t){var n=t[0],r=t[1];return Math.sqrt(n*n+r*r)}function x(t){var n=t[0],r=t[1];return n*n+r*r}function A(t,n){return t[0]=-n[0],t[1]=-n[1],t}function q(t,n){return t[0]=1/n[0],t[1]=1/n[1],t}function y(t,n){var r=n[0],a=n[1],e=r*r+a*a;return e>0&&(e=1/Math.sqrt(e),t[0]=n[0]*e,t[1]=n[1]*e),t}function w(t,n){return t[0]*n[0]+t[1]*n[1]}function R(t,n,r){var a=n[0]*r[1]-n[1]*r[0];return t[0]=t[1]=0,t[2]=a,t}function L(t,n,r,a){var e=n[0],u=n[1];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t}function S(t,n){n=n||1;var r=2*V.RANDOM()*Math.PI;return t[0]=Math.cos(r)*n,t[1]=Math.sin(r)*n,t}function _(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e,t[1]=r[1]*a+r[3]*e,t}function I(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e+r[4],t[1]=r[1]*a+r[3]*e+r[5],t}function N(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[3]*e+r[6],t[1]=r[1]*a+r[4]*e+r[7],t}function Y(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[4]*e+r[12],t[1]=r[1]*a+r[5]*e+r[13],t}function g(t){return"vec2("+t[0]+", "+t[1]+")"}function T(t,n){return t[0]===n[0]&&t[1]===n[1]}function j(t,n){var r=t[0],a=t[1],e=n[0],u=n[1];return Math.abs(r-e)<=V.EPSILON*Math.max(1,Math.abs(r),Math.abs(e))&&Math.abs(a-u)<=V.EPSILON*Math.max(1,Math.abs(a),Math.abs(u))}Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.sqrDist=n.dist=n.div=n.mul=n.sub=n.len=void 0,n.create=a,n.clone=e,n.fromValues=u,n.copy=o,n.set=i,n.add=s,n.subtract=c,n.multiply=f,n.divide=M,n.ceil=h,n.floor=l,n.min=v,n.max=d,n.round=b,n.scale=m,n.scaleAndAdd=p,n.distance=P,n.squaredDistance=E,n.length=O,n.squaredLength=x,n.negate=A,n.inverse=q,n.normalize=y,n.dot=w,n.cross=R,n.lerp=L,n.random=S,n.transformMat2=_,n.transformMat2d=I,n.transformMat3=N,n.transformMat4=Y,n.str=g,n.exactEquals=T,n.equals=j;var D=r(0),V=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(D);n.len=O,n.sub=c,n.mul=f,n.div=M,n.dist=P,n.sqrDist=E,n.sqrLen=x,n.forEach=function(){var t=a();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=2),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i 0) { 183 | surfaceShader.use(gl) 184 | gl.disable(gl.CULL_FACE); 185 | gl.uniform1f(surfaceShader.uniforms["isovalue"], currentIsovalue / 255.0); 186 | gl.uniform3iv(surfaceShader.uniforms["volume_dims"], volDims); 187 | gl.uniform3fv(surfaceShader.uniforms["volume_scale"], volScale); 188 | gl.uniform3fv(surfaceShader.uniforms["eye_pos"], eye); 189 | gl.uniformMatrix4fv(surfaceShader.uniforms["proj_view"], false, projView); 190 | 191 | gl.disable(gl.CULL_FACE); 192 | gl.bindVertexArray(surfaceVao); 193 | gl.drawArrays(gl.TRIANGLES, 0, isosurfaceNumVerts); 194 | gl.enable(gl.CULL_FACE); 195 | } 196 | 197 | // Render the volume on top of the isosurface 198 | if (showVolume.checked) { 199 | gl.disable(gl.DEPTH_TEST); 200 | gl.cullFace(gl.FRONT); 201 | gl.bindFramebuffer(gl.FRAMEBUFFER, colorFbo); 202 | gl.bindVertexArray(volumeVao); 203 | volumeShader.use(gl); 204 | gl.uniform3iv(volumeShader.uniforms["volume_dims"], volDims); 205 | gl.uniform3fv(volumeShader.uniforms["volume_scale"], volScale); 206 | gl.uniform3fv(volumeShader.uniforms["eye_pos"], eye); 207 | gl.uniform1f(volumeShader.uniforms["dt_scale"], samplingRate); 208 | gl.uniformMatrix4fv(volumeShader.uniforms["proj_view"], false, projView); 209 | gl.uniformMatrix4fv(volumeShader.uniforms["inv_proj"], false, invProj); 210 | gl.uniformMatrix4fv(volumeShader.uniforms["inv_view"], false, invView); 211 | 212 | gl.bindVertexArray(volumeVao); 213 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, cubeStrip.length / 3); 214 | } 215 | 216 | // Perform final blit to the actual framebuffer, as we can't do a 217 | // blit framebuffer if the image is multisampled 218 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 219 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 220 | gl.disable(gl.BLEND); 221 | gl.disable(gl.CULL_FACE); 222 | blitImageShader.use(gl); 223 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 224 | gl.enable(gl.CULL_FACE); 225 | gl.enable(gl.BLEND); 226 | 227 | // Wait for rendering to actually finish 228 | gl.finish(); 229 | var endTime = new Date(); 230 | var renderTime = endTime - startTime; 231 | var targetSamplingRate = renderTime / targetFrameTime; 232 | 233 | // If we're dropping frames, decrease the sampling rate 234 | if (!newVolumeUpload && targetSamplingRate > samplingRate) { 235 | samplingRate = 0.8 * samplingRate + 0.2 * targetSamplingRate; 236 | } 237 | 238 | newVolumeUpload = false; 239 | startTime = endTime; 240 | } 241 | 242 | var selectVolume = function() { 243 | var selection = document.getElementById("volumeList").value; 244 | history.replaceState(history.state, "#" + selection, "#" + selection); 245 | 246 | loadVolume(selection, function(file, dataBuffer) { 247 | var m = file.match(fileRegex); 248 | volDims = [parseInt(m[2]), parseInt(m[3]), parseInt(m[4])]; 249 | 250 | var tex = gl.createTexture(); 251 | gl.activeTexture(gl.TEXTURE0); 252 | gl.bindTexture(gl.TEXTURE_3D, tex); 253 | gl.texStorage3D(gl.TEXTURE_3D, 1, gl.R8, volDims[0], volDims[1], volDims[2]); 254 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 255 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 256 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); 257 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 258 | gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 259 | gl.texSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, 260 | volDims[0], volDims[1], volDims[2], 261 | gl.RED, gl.UNSIGNED_BYTE, dataBuffer); 262 | 263 | var longestAxis = Math.max(volDims[0], Math.max(volDims[1], volDims[2])); 264 | volScale = [volDims[0] / longestAxis, volDims[1] / longestAxis, 265 | volDims[2] / longestAxis]; 266 | 267 | marchingCubes.set_volume(dataBuffer, volDims[0], volDims[1], volDims[2]); 268 | 269 | volumeData = dataBuffer; 270 | newVolumeUpload = true; 271 | if (!volumeTexture) { 272 | volumeTexture = tex; 273 | setInterval(renderLoop, targetFrameTime); 274 | } else { 275 | gl.deleteTexture(volumeTexture); 276 | volumeTexture = tex; 277 | } 278 | }); 279 | } 280 | 281 | var selectColormap = function() { 282 | var selection = document.getElementById("colormapList").value; 283 | var colormapImage = new Image(); 284 | colormapImage.onload = function() { 285 | gl.activeTexture(gl.TEXTURE1); 286 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 180, 1, 287 | gl.RGBA, gl.UNSIGNED_BYTE, colormapImage); 288 | }; 289 | colormapImage.src = colormaps[selection]; 290 | } 291 | 292 | var run = function(){ 293 | fillVolumeSelector(); 294 | fillcolormapSelector(); 295 | 296 | isovalue = document.getElementById("isovalue"); 297 | showVolume = document.getElementById("showVolume"); 298 | showVolume.checked = true; 299 | 300 | useWebASM = document.getElementById("useWebASM"); 301 | useWebASM.checked = true; 302 | 303 | isosurfaceInfo = document.getElementById("isosurfaceInfo"); 304 | 305 | canvas = document.getElementById("glcanvas"); 306 | gl = canvas.getContext("webgl2"); 307 | if (!gl) { 308 | alert("Unable to initialize WebGL2. Your browser may not support it"); 309 | return; 310 | } 311 | WIDTH = canvas.getAttribute("width"); 312 | HEIGHT = canvas.getAttribute("height"); 313 | 314 | proj = mat4.perspective(mat4.create(), 60 * Math.PI / 180.0, 315 | WIDTH / HEIGHT, 0.1, 100); 316 | invProj = mat4.invert(mat4.create(), proj); 317 | 318 | camera = new ArcballCamera(defaultEye, center, up, 1, [WIDTH, HEIGHT]); 319 | projView = mat4.create(); 320 | invView = mat4.create(); 321 | 322 | // Register mouse and touch listeners 323 | var controller = new Controller(); 324 | controller.mousemove = function(prev, cur, evt) { 325 | if (evt.buttons == 1) { 326 | camera.rotate(prev, cur); 327 | 328 | } else if (evt.buttons == 2) { 329 | camera.pan([cur[0] - prev[0], prev[1] - cur[1]]); 330 | } 331 | }; 332 | controller.wheel = function(amt) { camera.zoom(amt); }; 333 | controller.pinch = controller.wheel; 334 | controller.twoFingerDrag = function(drag) { camera.pan(drag); }; 335 | 336 | controller.registerForCanvas(canvas); 337 | 338 | // Setup VAO and VBO to render the cube to run the raymarching shader 339 | volumeVao = gl.createVertexArray(); 340 | gl.bindVertexArray(volumeVao); 341 | 342 | var vbo = gl.createBuffer(); 343 | gl.bindBuffer(gl.ARRAY_BUFFER, vbo); 344 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeStrip), gl.STATIC_DRAW); 345 | 346 | gl.enableVertexAttribArray(0); 347 | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); 348 | 349 | volumeShader = new Shader(gl, vertShader, fragShader); 350 | volumeShader.use(gl); 351 | gl.uniform1i(volumeShader.uniforms["volume"], 0); 352 | gl.uniform1i(volumeShader.uniforms["colormap"], 1); 353 | gl.uniform1i(volumeShader.uniforms["depth"], 4); 354 | gl.uniform1f(volumeShader.uniforms["dt_scale"], 1.0); 355 | gl.uniform2iv(volumeShader.uniforms["canvas_dims"], [WIDTH, HEIGHT]); 356 | 357 | surfaceVao = gl.createVertexArray(); 358 | surfaceVbo = gl.createBuffer(); 359 | gl.bindVertexArray(surfaceVao); 360 | gl.bindBuffer(gl.ARRAY_BUFFER, surfaceVbo); 361 | gl.enableVertexAttribArray(0); 362 | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); 363 | 364 | surfaceShader = new Shader(gl, isosurfaceVertShader, isosurfaceFragShader); 365 | surfaceShader.use(gl); 366 | gl.uniform1i(surfaceShader.uniforms["colormap"], 1); 367 | 368 | blitImageShader = new Shader(gl, quadVertShader, quadFragShader); 369 | blitImageShader.use(gl); 370 | // Final colors will be on texture unit 3 371 | gl.uniform1i(blitImageShader.uniforms["colors"], 3); 372 | 373 | // Setup required OpenGL state for drawing the back faces and 374 | // composting with the background color 375 | gl.enable(gl.CULL_FACE); 376 | gl.cullFace(gl.FRONT); 377 | gl.enable(gl.BLEND); 378 | gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 379 | 380 | // See if we were linked to a datset 381 | if (window.location.hash) { 382 | var linkedDataset = decodeURI(window.location.hash.substr(1)); 383 | if (linkedDataset in volumes) { 384 | document.getElementById("volumeList").value = linkedDataset; 385 | } 386 | } 387 | 388 | // Setup the framebuffers for opaque geometry pass and volume composite 389 | renderTargets = [gl.createTexture(), gl.createTexture()] 390 | gl.bindTexture(gl.TEXTURE_2D, renderTargets[0]); 391 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, WIDTH, HEIGHT); 392 | 393 | gl.bindTexture(gl.TEXTURE_2D, renderTargets[1]); 394 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT32F, WIDTH, HEIGHT); 395 | 396 | for (var i = 0; i < 2; ++i) { 397 | gl.bindTexture(gl.TEXTURE_2D, renderTargets[i]); 398 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 399 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 400 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 401 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 402 | } 403 | 404 | gl.activeTexture(gl.TEXTURE3); 405 | gl.bindTexture(gl.TEXTURE_2D, renderTargets[0]); 406 | gl.activeTexture(gl.TEXTURE4); 407 | gl.bindTexture(gl.TEXTURE_2D, renderTargets[1]); 408 | 409 | depthColorFbo = gl.createFramebuffer(); 410 | gl.bindFramebuffer(gl.FRAMEBUFFER, depthColorFbo); 411 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, 412 | gl.TEXTURE_2D, renderTargets[0], 0); 413 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, 414 | gl.TEXTURE_2D, renderTargets[1], 0); 415 | gl.drawBuffers([gl.COLOR_ATTACHMENT0]); 416 | 417 | colorFbo = gl.createFramebuffer(); 418 | gl.bindFramebuffer(gl.FRAMEBUFFER, colorFbo); 419 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, 420 | gl.TEXTURE_2D, renderTargets[0], 0); 421 | gl.drawBuffers([gl.COLOR_ATTACHMENT0]); 422 | 423 | // Load the default colormap and upload it, after which we 424 | // load the default volume. 425 | var colormapImage = new Image(); 426 | colormapImage.onload = function() { 427 | var colormap = gl.createTexture(); 428 | gl.activeTexture(gl.TEXTURE1); 429 | gl.bindTexture(gl.TEXTURE_2D, colormap); 430 | gl.texStorage2D(gl.TEXTURE_2D, 1, gl.SRGB8_ALPHA8, 180, 1); 431 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 432 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 433 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE); 434 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 435 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 180, 1, 436 | gl.RGBA, gl.UNSIGNED_BYTE, colormapImage); 437 | 438 | selectVolume(); 439 | }; 440 | colormapImage.src = "colormaps/cool-warm-paraview.png"; 441 | } 442 | 443 | var fillVolumeSelector = function() { 444 | var selector = document.getElementById("volumeList"); 445 | for (var v in volumes) { 446 | var opt = document.createElement("option"); 447 | opt.value = v; 448 | opt.innerHTML = v; 449 | selector.appendChild(opt); 450 | } 451 | selector.addEventListener("change", selectVolume); 452 | } 453 | 454 | var fillcolormapSelector = function() { 455 | var selector = document.getElementById("colormapList"); 456 | for (var p in colormaps) { 457 | var opt = document.createElement("option"); 458 | opt.value = p; 459 | opt.innerHTML = p; 460 | selector.appendChild(opt); 461 | } 462 | selector.addEventListener("change", selectColormap); 463 | } 464 | 465 | window.onload = function() { 466 | init("pkg/marching_cubes_bg.wasm").then(() => { 467 | marchingCubes = MarchingCubes.new(); 468 | run(); 469 | }); 470 | } 471 | 472 | -------------------------------------------------------------------------------- /js/shader-srcs.js: -------------------------------------------------------------------------------- 1 | var vertShader = 2 | `#version 300 es 3 | #line 4 4 | layout(location=0) in vec3 pos; 5 | uniform mat4 proj_view; 6 | uniform vec3 eye_pos; 7 | uniform vec3 volume_scale; 8 | 9 | out vec3 vray_dir; 10 | flat out vec3 transformed_eye; 11 | 12 | void main(void) { 13 | // TODO: For non-uniform size volumes we need to transform them differently as well 14 | // to center them properly 15 | vec3 volume_translation = vec3(0.5) - volume_scale * 0.5; 16 | gl_Position = proj_view * vec4(pos * volume_scale + volume_translation, 1); 17 | transformed_eye = (eye_pos - volume_translation) / volume_scale; 18 | vray_dir = pos - transformed_eye; 19 | }`; 20 | 21 | var fragShader = 22 | `#version 300 es 23 | #line 24 24 | precision highp int; 25 | precision highp float; 26 | uniform highp sampler3D volume; 27 | uniform highp sampler2D colormap; 28 | uniform highp sampler2D depth; 29 | uniform ivec3 volume_dims; 30 | uniform float dt_scale; 31 | uniform ivec2 canvas_dims; 32 | uniform vec3 volume_scale; 33 | uniform mat4 inv_view; 34 | uniform mat4 inv_proj; 35 | 36 | in vec3 vray_dir; 37 | flat in vec3 transformed_eye; 38 | out vec4 color; 39 | 40 | vec2 intersect_box(vec3 orig, vec3 dir) { 41 | const vec3 box_min = vec3(0); 42 | const vec3 box_max = vec3(1); 43 | vec3 inv_dir = 1.0 / dir; 44 | vec3 tmin_tmp = (box_min - orig) * inv_dir; 45 | vec3 tmax_tmp = (box_max - orig) * inv_dir; 46 | vec3 tmin = min(tmin_tmp, tmax_tmp); 47 | vec3 tmax = max(tmin_tmp, tmax_tmp); 48 | float t0 = max(tmin.x, max(tmin.y, tmin.z)); 49 | float t1 = min(tmax.x, min(tmax.y, tmax.z)); 50 | return vec2(t0, t1); 51 | } 52 | 53 | // Pseudo-random number gen from 54 | // http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/ 55 | // with some tweaks for the range of values 56 | float wang_hash(int seed) { 57 | seed = (seed ^ 61) ^ (seed >> 16); 58 | seed *= 9; 59 | seed = seed ^ (seed >> 4); 60 | seed *= 0x27d4eb2d; 61 | seed = seed ^ (seed >> 15); 62 | return float(seed % 2147483647) / float(2147483647); 63 | } 64 | 65 | // Linearize the depth value passed in 66 | float linearize(float d) { 67 | float near = 0.0; 68 | float far = 1.0; 69 | return (2.f * d - near - far) / (far - near); 70 | } 71 | 72 | // Reconstruct the view-space position 73 | vec4 compute_view_pos(float z) { 74 | // TODO: We don't really care about the full view position here 75 | vec4 pos = vec4(gl_FragCoord.xy / vec2(canvas_dims) * 2.f - 1.f, z, 1.f); 76 | pos = inv_proj * pos; 77 | return pos / pos.w; 78 | } 79 | 80 | void main(void) { 81 | vec3 ray_dir = normalize(vray_dir); 82 | vec2 t_hit = intersect_box(transformed_eye, ray_dir); 83 | if (t_hit.x > t_hit.y) { 84 | discard; 85 | } 86 | t_hit.x = max(t_hit.x, 0.0); 87 | 88 | vec3 dt_vec = 1.0 / (vec3(volume_dims) * abs(ray_dir)); 89 | float dt = dt_scale * min(dt_vec.x, min(dt_vec.y, dt_vec.z)); 90 | float dt_correction = dt_scale; 91 | float offset = wang_hash(int(gl_FragCoord.x + float(canvas_dims.x) * gl_FragCoord.y)); 92 | 93 | // Composite with the rendered geometry 94 | float z = linearize(texelFetch(depth, ivec2(gl_FragCoord), 0).x); 95 | if (z < 1.0) { 96 | vec3 volume_translation = vec3(0.5) - volume_scale * 0.5; 97 | vec3 geom_pos = (inv_view * compute_view_pos(z)).xyz; 98 | geom_pos = (geom_pos - volume_translation) / volume_scale; 99 | float geom_t = length(geom_pos - transformed_eye); 100 | 101 | // We want to adjust the sampling rate to still take a reasonable 102 | // number of samples in the volume up to the surface 103 | float samples = 1.f / dt; 104 | float newdt = (geom_t - t_hit.x) / samples; 105 | dt_correction = dt_scale * newdt / dt; 106 | dt = newdt; 107 | t_hit.y = geom_t; 108 | } 109 | 110 | vec3 p = transformed_eye + (t_hit.x + offset * dt) * ray_dir; 111 | float t; 112 | for (t = t_hit.x; t < t_hit.y; t += dt) { 113 | float val = texture(volume, p).r; 114 | vec4 val_color = vec4(texture(colormap, vec2(val, 0.5)).rgb, val); 115 | // Opacity correction 116 | val_color.a = 1.0 - pow(1.0 - val_color.a, dt_correction); 117 | color.rgb += (1.0 - color.a) * val_color.a * val_color.rgb; 118 | color.a += (1.0 - color.a) * val_color.a; 119 | if (color.a >= 0.99) { 120 | break; 121 | } 122 | p += ray_dir * dt; 123 | } 124 | // If we have the surface, take a final sample at the surface point 125 | if (z < 1.f) { 126 | p = transformed_eye + t_hit.y * ray_dir; 127 | float val = texture(volume, p).r; 128 | vec4 val_color = vec4(texture(colormap, vec2(val, 0.5)).rgb, val); 129 | // Opacity correction 130 | val_color.a = 1.0 - pow(1.0 - val_color.a, (t_hit.y - t) * dt_scale); 131 | color.rgb += (1.0 - color.a) * val_color.a * val_color.rgb; 132 | color.a += (1.0 - color.a) * val_color.a; 133 | } 134 | }`; 135 | 136 | var isosurfaceVertShader = 137 | `#version 300 es 138 | #line 119 139 | layout(location=0) in vec3 pos; 140 | uniform mat4 proj_view; 141 | uniform vec3 eye_pos; 142 | uniform vec3 volume_scale; 143 | uniform ivec3 volume_dims; 144 | 145 | out vec3 vpos; 146 | 147 | void main(void) { 148 | vec3 volume_translation = vec3(0.5) - volume_scale * 0.5; 149 | // The isosurface vertices are in the volume grid space, so transform to [0, 1] first, 150 | // then apply the volume transform to line up with the volume 151 | // TODO: This should still be fine for computing the normal right? 152 | vpos = pos / vec3(volume_dims) * volume_scale + volume_translation; 153 | gl_Position = proj_view * vec4(vpos, 1.f); 154 | }`; 155 | 156 | var isosurfaceFragShader = 157 | `#version 300 es 158 | #line 139 159 | precision highp int; 160 | precision highp float; 161 | uniform highp sampler2D colormap; 162 | uniform float isovalue; 163 | uniform vec3 eye_pos; 164 | 165 | in vec3 vpos; 166 | 167 | out vec4 color; 168 | 169 | void main(void) { 170 | vec3 v = -normalize(vpos - eye_pos); 171 | //vec3 light_dir = normalize(v + vec3(0.5, 0.5, 0.5)); 172 | vec3 light_dir = v; 173 | vec3 n = normalize(cross(dFdx(vpos), dFdy(vpos))); 174 | //vec3 base_color = (n + 1.f) * 0.5f; 175 | vec3 base_color = texture(colormap, vec2(isovalue, 0.5)).xyz; 176 | vec3 h = normalize(v + light_dir); 177 | // Just some Blinn-Phong shading 178 | color.xyz = base_color * 0.2f; 179 | color.xyz += 0.6 * clamp(dot(light_dir, n), 0.f, 1.f) * base_color; 180 | color.xyz += 0.4 * pow(clamp(dot(n, h), 0.f, 1.f), 25.f); 181 | 182 | color.a = 1.0; 183 | }`; 184 | 185 | var quadVertShader = 186 | `#version 300 es 187 | #line 162 188 | const vec4 pos[4] = vec4[4]( 189 | vec4(-1, 1, 0.5, 1), 190 | vec4(-1, -1, 0.5, 1), 191 | vec4(1, 1, 0.5, 1), 192 | vec4(1, -1, 0.5, 1) 193 | ); 194 | void main(void){ 195 | gl_Position = pos[gl_VertexID]; 196 | }`; 197 | 198 | var quadFragShader = 199 | `#version 300 es 200 | #line 175 201 | precision highp int; 202 | precision highp float; 203 | 204 | uniform sampler2D colors; 205 | out vec4 color; 206 | 207 | float linear_to_srgb(float x) { 208 | if (x <= 0.0031308f) { 209 | return 12.92f * x; 210 | } 211 | return 1.055f * pow(x, 1.f / 2.4f) - 0.055f; 212 | } 213 | 214 | void main(void){ 215 | ivec2 uv = ivec2(gl_FragCoord.xy); 216 | color = texelFetch(colors, uv, 0); 217 | color.r = linear_to_srgb(color.r); 218 | color.g = linear_to_srgb(color.g); 219 | color.b = linear_to_srgb(color.b); 220 | }`; 221 | 222 | -------------------------------------------------------------------------------- /js/webgl-util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Compute the view frustum in world space from the provided 3 | // column major projection * view matrix 4 | var Frustum = function(projView) { 5 | var rows = [vec4.create(), vec4.create(), vec4.create(), vec4.create()]; 6 | for (var i = 0; i < rows.length; ++i) { 7 | rows[i] = vec4.set(rows[i], projView[i], projView[4 + i], 8 | projView[8 + i], projView[12 + i]); 9 | } 10 | 11 | this.planes = [ 12 | // -x plane 13 | vec4.add(vec4.create(), rows[3], rows[0]), 14 | // +x plane 15 | vec4.sub(vec4.create(), rows[3], rows[0]), 16 | // -y plane 17 | vec4.add(vec4.create(), rows[3], rows[1]), 18 | // +y plane 19 | vec4.sub(vec4.create(), rows[3], rows[1]), 20 | // -z plane 21 | vec4.add(vec4.create(), rows[3], rows[2]), 22 | // +z plane 23 | vec4.sub(vec4.create(), rows[3], rows[2]) 24 | ]; 25 | 26 | // Normalize the planes 27 | for (var i = 0; i < this.planes.length; ++i) { 28 | var s = 1.0 / Math.sqrt(this.planes[i][0] * this.planes[i][0] + 29 | this.planes[i][1] * this.planes[i][1] + this.planes[i][2] * this.planes[i][2]); 30 | this.planes[i][0] *= s; 31 | this.planes[i][1] *= s; 32 | this.planes[i][2] *= s; 33 | this.planes[i][3] *= s; 34 | } 35 | 36 | // Compute the frustum points as well 37 | var invProjView = mat4.invert(mat4.create(), projView); 38 | this.points = [ 39 | // x_l, y_l, z_l 40 | vec4.set(vec4.create(), -1, -1, -1, 1), 41 | // x_h, y_l, z_l 42 | vec4.set(vec4.create(), 1, -1, -1, 1), 43 | // x_l, y_h, z_l 44 | vec4.set(vec4.create(), -1, 1, -1, 1), 45 | // x_h, y_h, z_l 46 | vec4.set(vec4.create(), 1, 1, -1, 1), 47 | // x_l, y_l, z_h 48 | vec4.set(vec4.create(), -1, -1, 1, 1), 49 | // x_h, y_l, z_h 50 | vec4.set(vec4.create(), 1, -1, 1, 1), 51 | // x_l, y_h, z_h 52 | vec4.set(vec4.create(), -1, 1, 1, 1), 53 | // x_h, y_h, z_h 54 | vec4.set(vec4.create(), 1, 1, 1, 1) 55 | ]; 56 | for (var i = 0; i < 8; ++i) { 57 | this.points[i] = vec4.transformMat4(this.points[i], this.points[i], invProjView); 58 | this.points[i][0] /= this.points[i][3]; 59 | this.points[i][1] /= this.points[i][3]; 60 | this.points[i][2] /= this.points[i][3]; 61 | this.points[i][3] = 1.0; 62 | } 63 | } 64 | 65 | // Check if the box is contained in the Frustum 66 | // The box should be [x_lower, y_lower, z_lower, x_upper, y_upper, z_upper] 67 | // This is done using Inigo Quilez's approach to help with large 68 | // bounds: https://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm 69 | Frustum.prototype.containsBox = function(box) { 70 | // Test the box against each plane 71 | var vec = vec4.create(); 72 | var out = 0; 73 | for (var i = 0; i < this.planes.length; ++i) { 74 | out = 0; 75 | // x_l, y_l, z_l 76 | vec4.set(vec, box[0], box[1], box[2], 1.0); 77 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 78 | // x_h, y_l, z_l 79 | vec4.set(vec, box[3], box[1], box[2], 1.0); 80 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 81 | // x_l, y_h, z_l 82 | vec4.set(vec, box[0], box[4], box[2], 1.0); 83 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 84 | // x_h, y_h, z_l 85 | vec4.set(vec, box[3], box[4], box[2], 1.0); 86 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 87 | // x_l, y_l, z_h 88 | vec4.set(vec, box[0], box[1], box[5], 1.0); 89 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 90 | // x_h, y_l, z_h 91 | vec4.set(vec, box[3], box[1], box[5], 1.0); 92 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 93 | // x_l, y_h, z_h 94 | vec4.set(vec, box[0], box[4], box[5], 1.0); 95 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 96 | // x_h, y_h, z_h 97 | vec4.set(vec, box[3], box[4], box[5], 1.0); 98 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 99 | 100 | if (out == 8) { 101 | return false; 102 | } 103 | } 104 | 105 | // Test the frustum against the box 106 | out = 0; 107 | for (var i = 0; i < 8; ++i) { 108 | out += this.points[i][0] > box[3] ? 1 : 0; 109 | } 110 | if (out == 8) { 111 | return false; 112 | } 113 | 114 | out = 0; 115 | for (var i = 0; i < 8; ++i) { 116 | out += this.points[i][0] < box[0] ? 1 : 0; 117 | } 118 | if (out == 8) { 119 | return false; 120 | } 121 | 122 | out = 0; 123 | for (var i = 0; i < 8; ++i) { 124 | out += this.points[i][1] > box[4] ? 1 : 0; 125 | } 126 | if (out == 8) { 127 | return false; 128 | } 129 | 130 | out = 0; 131 | for (var i = 0; i < 8; ++i) { 132 | out += this.points[i][1] < box[1] ? 1 : 0; 133 | } 134 | if (out == 8) { 135 | return false; 136 | } 137 | 138 | out = 0; 139 | for (var i = 0; i < 8; ++i) { 140 | out += this.points[i][2] > box[5] ? 1 : 0; 141 | } 142 | if (out == 8) { 143 | return false; 144 | } 145 | 146 | out = 0; 147 | for (var i = 0; i < 8; ++i) { 148 | out += this.points[i][2] < box[2] ? 1 : 0; 149 | } 150 | if (out == 8) { 151 | return false; 152 | } 153 | return true; 154 | } 155 | 156 | 157 | var Shader = function(gl, vertexSrc, fragmentSrc) { 158 | var self = this; 159 | this.program = compileShader(gl, vertexSrc, fragmentSrc); 160 | 161 | var regexUniform = /uniform[^;]+[ ](\w+);/g 162 | var matchUniformName = /uniform[^;]+[ ](\w+);/ 163 | 164 | this.uniforms = {}; 165 | 166 | var vertexUnifs = vertexSrc.match(regexUniform); 167 | var fragUnifs = fragmentSrc.match(regexUniform); 168 | 169 | if (vertexUnifs) { 170 | vertexUnifs.forEach(function(unif) { 171 | var m = unif.match(matchUniformName); 172 | self.uniforms[m[1]] = -1; 173 | }); 174 | } 175 | if (fragUnifs) { 176 | fragUnifs.forEach(function(unif) { 177 | var m = unif.match(matchUniformName); 178 | self.uniforms[m[1]] = -1; 179 | }); 180 | } 181 | 182 | for (var unif in this.uniforms) { 183 | this.uniforms[unif] = gl.getUniformLocation(this.program, unif); 184 | } 185 | } 186 | 187 | Shader.prototype.use = function(gl) { 188 | gl.useProgram(this.program); 189 | } 190 | 191 | // Compile and link the shaders vert and frag. vert and frag should contain 192 | // the shader source code for the vertex and fragment shaders respectively 193 | // Returns the compiled and linked program, or null if compilation or linking failed 194 | var compileShader = function(gl, vert, frag){ 195 | var vs = gl.createShader(gl.VERTEX_SHADER); 196 | gl.shaderSource(vs, vert); 197 | gl.compileShader(vs); 198 | if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)){ 199 | alert("Vertex shader failed to compile, see console for log"); 200 | console.log(gl.getShaderInfoLog(vs)); 201 | return null; 202 | } 203 | 204 | var fs = gl.createShader(gl.FRAGMENT_SHADER); 205 | gl.shaderSource(fs, frag); 206 | gl.compileShader(fs); 207 | if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)){ 208 | alert("Fragment shader failed to compile, see console for log"); 209 | console.log(gl.getShaderInfoLog(fs)); 210 | return null; 211 | } 212 | 213 | var program = gl.createProgram(); 214 | gl.attachShader(program, vs); 215 | gl.attachShader(program, fs); 216 | gl.linkProgram(program); 217 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)){ 218 | alert("Shader failed to link, see console for log"); 219 | console.log(gl.getProgramInfoLog(program)); 220 | return null; 221 | } 222 | return program; 223 | } 224 | 225 | var getGLExtension = function(ext) { 226 | if (!gl.getExtension(ext)) { 227 | alert("Missing " + ext + " WebGL extension"); 228 | return false; 229 | } 230 | return true; 231 | } 232 | 233 | /* The arcball camera will be placed at the position 'eye', rotating 234 | * around the point 'center', with the up vector 'up'. 'screenDims' 235 | * should be the dimensions of the canvas or region taking mouse input 236 | * so the mouse positions can be normalized into [-1, 1] from the pixel 237 | * coordinates. 238 | */ 239 | var ArcballCamera = function(eye, center, up, zoomSpeed, screenDims) { 240 | var veye = vec3.set(vec3.create(), eye[0], eye[1], eye[2]); 241 | var vcenter = vec3.set(vec3.create(), center[0], center[1], center[2]); 242 | var vup = vec3.set(vec3.create(), up[0], up[1], up[2]); 243 | vec3.normalize(vup, vup); 244 | 245 | var zAxis = vec3.sub(vec3.create(), vcenter, veye); 246 | var viewDist = vec3.len(zAxis); 247 | vec3.normalize(zAxis, zAxis); 248 | 249 | var xAxis = vec3.cross(vec3.create(), zAxis, vup); 250 | vec3.normalize(xAxis, xAxis); 251 | 252 | var yAxis = vec3.cross(vec3.create(), xAxis, zAxis); 253 | vec3.normalize(yAxis, yAxis); 254 | 255 | vec3.cross(xAxis, zAxis, yAxis); 256 | vec3.normalize(xAxis, xAxis); 257 | 258 | this.zoomSpeed = zoomSpeed; 259 | this.invScreen = [1.0 / screenDims[0], 1.0 / screenDims[1]]; 260 | 261 | this.centerTranslation = mat4.fromTranslation(mat4.create(), center); 262 | mat4.invert(this.centerTranslation, this.centerTranslation); 263 | 264 | var vt = vec3.set(vec3.create(), 0, 0, -1.0 * viewDist); 265 | this.translation = mat4.fromTranslation(mat4.create(), vt); 266 | 267 | var rotMat = mat3.fromValues(xAxis[0], xAxis[1], xAxis[2], 268 | yAxis[0], yAxis[1], yAxis[2], 269 | -zAxis[0], -zAxis[1], -zAxis[2]); 270 | mat3.transpose(rotMat, rotMat); 271 | this.rotation = quat.fromMat3(quat.create(), rotMat); 272 | quat.normalize(this.rotation, this.rotation); 273 | 274 | this.camera = mat4.create(); 275 | this.invCamera = mat4.create(); 276 | this.updateCameraMatrix(); 277 | } 278 | 279 | ArcballCamera.prototype.rotate = function(prevMouse, curMouse) { 280 | var mPrev = vec2.set(vec2.create(), 281 | clamp(prevMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0), 282 | clamp(1.0 - prevMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)); 283 | 284 | var mCur = vec2.set(vec2.create(), 285 | clamp(curMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0), 286 | clamp(1.0 - curMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)); 287 | 288 | var mPrevBall = screenToArcball(mPrev); 289 | var mCurBall = screenToArcball(mCur); 290 | // rotation = curBall * prevBall * rotation 291 | this.rotation = quat.mul(this.rotation, mPrevBall, this.rotation); 292 | this.rotation = quat.mul(this.rotation, mCurBall, this.rotation); 293 | 294 | this.updateCameraMatrix(); 295 | } 296 | 297 | ArcballCamera.prototype.zoom = function(amount) { 298 | var vt = vec3.set(vec3.create(), 0.0, 0.0, amount * this.invScreen[1] * this.zoomSpeed); 299 | var t = mat4.fromTranslation(mat4.create(), vt); 300 | this.translation = mat4.mul(this.translation, t, this.translation); 301 | if (this.translation[14] >= -0.2) { 302 | this.translation[14] = -0.2; 303 | } 304 | this.updateCameraMatrix(); 305 | } 306 | 307 | ArcballCamera.prototype.pan = function(mouseDelta) { 308 | var delta = vec4.set(vec4.create(), mouseDelta[0] * this.invScreen[0] * Math.abs(this.translation[14]), 309 | mouseDelta[1] * this.invScreen[1] * Math.abs(this.translation[14]), 0, 0); 310 | var worldDelta = vec4.transformMat4(vec4.create(), delta, this.invCamera); 311 | var translation = mat4.fromTranslation(mat4.create(), worldDelta); 312 | this.centerTranslation = mat4.mul(this.centerTranslation, translation, this.centerTranslation); 313 | this.updateCameraMatrix(); 314 | } 315 | 316 | ArcballCamera.prototype.updateCameraMatrix = function() { 317 | // camera = translation * rotation * centerTranslation 318 | var rotMat = mat4.fromQuat(mat4.create(), this.rotation); 319 | this.camera = mat4.mul(this.camera, rotMat, this.centerTranslation); 320 | this.camera = mat4.mul(this.camera, this.translation, this.camera); 321 | this.invCamera = mat4.invert(this.invCamera, this.camera); 322 | } 323 | 324 | ArcballCamera.prototype.eyePos = function() { 325 | return [camera.invCamera[12], camera.invCamera[13], camera.invCamera[14]]; 326 | } 327 | 328 | ArcballCamera.prototype.eyeDir = function() { 329 | var dir = vec4.set(vec4.create(), 0.0, 0.0, -1.0, 0.0); 330 | dir = vec4.transformMat4(dir, dir, this.invCamera); 331 | dir = vec4.normalize(dir, dir); 332 | return [dir[0], dir[1], dir[2]]; 333 | } 334 | 335 | ArcballCamera.prototype.upDir = function() { 336 | var dir = vec4.set(vec4.create(), 0.0, 1.0, 0.0, 0.0); 337 | dir = vec4.transformMat4(dir, dir, this.invCamera); 338 | dir = vec4.normalize(dir, dir); 339 | return [dir[0], dir[1], dir[2]]; 340 | } 341 | 342 | var screenToArcball = function(p) { 343 | var dist = vec2.dot(p, p); 344 | if (dist <= 1.0) { 345 | return quat.set(quat.create(), p[0], p[1], Math.sqrt(1.0 - dist), 0); 346 | } else { 347 | var unitP = vec2.normalize(vec2.create(), p); 348 | // cgmath is w, x, y, z 349 | // glmatrix is x, y, z, w 350 | return quat.set(quat.create(), unitP[0], unitP[1], 0, 0); 351 | } 352 | } 353 | var clamp = function(a, min, max) { 354 | return a < min ? min : a > max ? max : a; 355 | } 356 | 357 | var pointDist = function(a, b) { 358 | var v = [b[0] - a[0], b[1] - a[1]]; 359 | return Math.sqrt(Math.pow(v[0], 2.0) + Math.pow(v[1], 2.0)); 360 | } 361 | 362 | var Buffer = function(capacity, dtype) { 363 | this.len = 0; 364 | this.capacity = capacity; 365 | if (dtype == "uint8") { 366 | this.buffer = new Uint8Array(capacity); 367 | } else if (dtype == "int8") { 368 | this.buffer = new Int8Array(capacity); 369 | } else if (dtype == "uint16") { 370 | this.buffer = new Uint16Array(capacity); 371 | } else if (dtype == "int16") { 372 | this.buffer = new Int16Array(capacity); 373 | } else if (dtype == "uint32") { 374 | this.buffer = new Uint32Array(capacity); 375 | } else if (dtype == "int32") { 376 | this.buffer = new Int32Array(capacity); 377 | } else if (dtype == "float32") { 378 | this.buffer = new Float32Array(capacity); 379 | } else if (dtype == "float64") { 380 | this.buffer = new Float64Array(capacity); 381 | } else { 382 | console.log("ERROR: unsupported type " + dtype); 383 | } 384 | } 385 | 386 | Buffer.prototype.append = function(buf) { 387 | if (this.len + buf.length >= this.capacity) { 388 | var newCap = Math.floor(this.capacity * 1.5); 389 | var tmp = new (this.buffer.constructor)(newCap); 390 | tmp.set(this.buffer); 391 | 392 | this.capacity = newCap; 393 | this.buffer = tmp; 394 | } 395 | this.buffer.set(buf, this.len); 396 | this.len += buf.length; 397 | } 398 | 399 | Buffer.prototype.clear = function() { 400 | this.len = 0; 401 | } 402 | 403 | Buffer.prototype.stride = function() { 404 | return this.buffer.BYTES_PER_ELEMENT; 405 | } 406 | 407 | Buffer.prototype.view = function(offset, length) { 408 | return new (this.buffer.constructor)(this.buffer.buffer, offset, length); 409 | } 410 | 411 | // Various utilities that don't really fit anywhere else 412 | 413 | // Parse the hex string to RGB values in [0, 255] 414 | var hexToRGB = function(hex) { 415 | var val = parseInt(hex.substr(1), 16); 416 | var r = (val >> 16) & 255; 417 | var g = (val >> 8) & 255; 418 | var b = val & 255; 419 | return [r, g, b]; 420 | } 421 | 422 | // Parse the hex string to RGB values in [0, 1] 423 | var hexToRGBf = function(hex) { 424 | var c = hexToRGB(hex); 425 | return [c[0] / 255.0, c[1] / 255.0, c[2] / 255.0]; 426 | } 427 | 428 | /* The controller can register callbacks for various events on a canvas: 429 | * 430 | * mousemove: function(prevMouse, curMouse, evt) 431 | * receives both regular mouse events, and single-finger drags (sent as a left-click), 432 | * 433 | * press: function(curMouse, evt) 434 | * receives mouse click and touch start events 435 | * 436 | * wheel: function(amount) 437 | * mouse wheel scrolling 438 | * 439 | * pinch: function(amount) 440 | * two finger pinch, receives the distance change between the fingers 441 | * 442 | * twoFingerDrag: function(dragVector) 443 | * two finger drag, receives the drag movement amount 444 | */ 445 | var Controller = function() { 446 | this.mousemove = null; 447 | this.press = null; 448 | this.wheel = null; 449 | this.twoFingerDrag = null; 450 | this.pinch = null; 451 | } 452 | 453 | Controller.prototype.registerForCanvas = function(canvas) { 454 | var prevMouse = null; 455 | var mouseState = [false, false]; 456 | var self = this; 457 | canvas.addEventListener("mousemove", function(evt) { 458 | evt.preventDefault(); 459 | var rect = canvas.getBoundingClientRect(); 460 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 461 | if (!prevMouse) { 462 | prevMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 463 | } else if (self.mousemove) { 464 | self.mousemove(prevMouse, curMouse, evt); 465 | } 466 | prevMouse = curMouse; 467 | }); 468 | 469 | canvas.addEventListener("mousedown", function(evt) { 470 | evt.preventDefault(); 471 | var rect = canvas.getBoundingClientRect(); 472 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 473 | if (self.press) { 474 | self.press(curMouse, evt); 475 | } 476 | }); 477 | 478 | canvas.addEventListener("wheel", function(evt) { 479 | evt.preventDefault(); 480 | if (self.wheel) { 481 | self.wheel(-evt.deltaY); 482 | } 483 | }); 484 | 485 | canvas.oncontextmenu = function (evt) { 486 | evt.preventDefault(); 487 | }; 488 | 489 | var touches = {}; 490 | canvas.addEventListener("touchstart", function(evt) { 491 | var rect = canvas.getBoundingClientRect(); 492 | evt.preventDefault(); 493 | for (var i = 0; i < evt.changedTouches.length; ++i) { 494 | var t = evt.changedTouches[i]; 495 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 496 | if (evt.changedTouches.length == 1 && self.press) { 497 | self.press(touches[t.identifier], evt); 498 | } 499 | } 500 | }); 501 | 502 | canvas.addEventListener("touchmove", function(evt) { 503 | evt.preventDefault(); 504 | var rect = canvas.getBoundingClientRect(); 505 | var numTouches = Object.keys(touches).length; 506 | // Single finger to rotate the camera 507 | if (numTouches == 1) { 508 | if (self.mousemove) { 509 | var t = evt.changedTouches[0]; 510 | var prevTouch = touches[t.identifier]; 511 | var curTouch = [t.clientX - rect.left, t.clientY - rect.top]; 512 | evt.buttons = 1; 513 | self.mousemove(prevTouch, curTouch, evt); 514 | } 515 | } else { 516 | var curTouches = {}; 517 | for (var i = 0; i < evt.changedTouches.length; ++i) { 518 | var t = evt.changedTouches[i]; 519 | curTouches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 520 | } 521 | 522 | // If some touches didn't change make sure we have them in 523 | // our curTouches list to compute the pinch distance 524 | // Also get the old touch points to compute the distance here 525 | var oldTouches = []; 526 | for (t in touches) { 527 | if (!(t in curTouches)) { 528 | curTouches[t] = touches[t]; 529 | } 530 | oldTouches.push(touches[t]); 531 | } 532 | 533 | var newTouches = []; 534 | for (t in curTouches) { 535 | newTouches.push(curTouches[t]); 536 | } 537 | 538 | // Determine if the user is pinching or panning 539 | var motionVectors = [ 540 | vec2.set(vec2.create(), newTouches[0][0] - oldTouches[0][0], 541 | newTouches[0][1] - oldTouches[0][1]), 542 | vec2.set(vec2.create(), newTouches[1][0] - oldTouches[1][0], 543 | newTouches[1][1] - oldTouches[1][1]) 544 | ]; 545 | var motionDirs = [vec2.create(), vec2.create()]; 546 | vec2.normalize(motionDirs[0], motionVectors[0]); 547 | vec2.normalize(motionDirs[1], motionVectors[1]); 548 | 549 | var pinchAxis = vec2.set(vec2.create(), oldTouches[1][0] - oldTouches[0][0], 550 | oldTouches[1][1] - oldTouches[0][1]); 551 | vec2.normalize(pinchAxis, pinchAxis); 552 | 553 | var panAxis = vec2.lerp(vec2.create(), motionVectors[0], motionVectors[1], 0.5); 554 | vec2.normalize(panAxis, panAxis); 555 | 556 | var pinchMotion = [ 557 | vec2.dot(pinchAxis, motionDirs[0]), 558 | vec2.dot(pinchAxis, motionDirs[1]) 559 | ]; 560 | var panMotion = [ 561 | vec2.dot(panAxis, motionDirs[0]), 562 | vec2.dot(panAxis, motionDirs[1]) 563 | ]; 564 | 565 | // If we're primarily moving along the pinching axis and in the opposite direction with 566 | // the fingers, then the user is zooming. 567 | // Otherwise, if the fingers are moving along the same direction they're panning 568 | if (self.pinch && Math.abs(pinchMotion[0]) > 0.5 && Math.abs(pinchMotion[1]) > 0.5 569 | && Math.sign(pinchMotion[0]) != Math.sign(pinchMotion[1])) 570 | { 571 | // Pinch distance change for zooming 572 | var oldDist = pointDist(oldTouches[0], oldTouches[1]); 573 | var newDist = pointDist(newTouches[0], newTouches[1]); 574 | self.pinch(newDist - oldDist); 575 | } else if (self.twoFingerDrag && Math.abs(panMotion[0]) > 0.5 && Math.abs(panMotion[1]) > 0.5 576 | && Math.sign(panMotion[0]) == Math.sign(panMotion[1])) 577 | { 578 | // Pan by the average motion of the two fingers 579 | var panAmount = vec2.lerp(vec2.create(), motionVectors[0], motionVectors[1], 0.5); 580 | panAmount[1] = -panAmount[1]; 581 | self.twoFingerDrag(panAmount); 582 | } 583 | } 584 | 585 | // Update the existing list of touches with the current positions 586 | for (var i = 0; i < evt.changedTouches.length; ++i) { 587 | var t = evt.changedTouches[i]; 588 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 589 | } 590 | }); 591 | 592 | var touchEnd = function(evt) { 593 | evt.preventDefault(); 594 | for (var i = 0; i < evt.changedTouches.length; ++i) { 595 | var t = evt.changedTouches[i]; 596 | delete touches[t.identifier]; 597 | } 598 | } 599 | canvas.addEventListener("touchcancel", touchEnd); 600 | canvas.addEventListener("touchend", touchEnd); 601 | } 602 | 603 | -------------------------------------------------------------------------------- /src/error_log.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | pub use console_error_panic_hook::set_once as set_panic_hook; 12 | } else { 13 | #[inline] 14 | pub fn set_panic_hook() {} 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate wasm_bindgen; 3 | extern crate js_sys; 4 | 5 | mod error_log; 6 | 7 | use std::f32; 8 | use wasm_bindgen::prelude::*; 9 | 10 | #[wasm_bindgen] 11 | extern "C" { 12 | #[wasm_bindgen(js_namespace = console)] 13 | fn log(a: &str); 14 | } 15 | 16 | macro_rules! console_log { 17 | ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) 18 | } 19 | 20 | static TRI_TABLE: [[i32; 16]; 256] = [ 21 | [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 22 | [ 0, 8, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 23 | [ 1, 9, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 24 | [ 8, 1, 9, 8, 3, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 25 | [ 2, 10, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 26 | [ 0, 8, 3, 1, 2, 10, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | [ 9, 2, 10, 9, 0, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 28 | [ 3, 2, 10, 3, 10, 8, 8, 10, 9, -1, 0, 0, 0, 0, 0, 0], 29 | [ 2, 3, 11, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 30 | [11, 0, 8, 11, 2, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 31 | [ 1, 9, 0, 2, 3, 11, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 32 | [ 2, 1, 9, 2, 9, 11, 11, 9, 8, -1, 0, 0, 0, 0, 0, 0], 33 | [ 3, 10, 1, 3, 11, 10, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 34 | [ 1, 0, 8, 1, 8, 10, 10, 8, 11, -1, 0, 0, 0, 0, 0, 0], 35 | [ 0, 3, 11, 0, 11, 9, 9, 11, 10, -1, 0, 0, 0, 0, 0, 0], 36 | [11, 10, 9, 11, 9, 8, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 37 | [ 4, 7, 8, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 38 | [ 4, 3, 0, 4, 7, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 39 | [ 4, 7, 8, 9, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 40 | [ 9, 4, 7, 9, 7, 1, 1, 7, 3, -1, 0, 0, 0, 0, 0, 0], 41 | [ 4, 7, 8, 1, 2, 10, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 42 | [ 4, 3, 0, 4, 7, 3, 2, 10, 1, -1, 0, 0, 0, 0, 0, 0], 43 | [ 2, 9, 0, 2, 10, 9, 4, 7, 8, -1, 0, 0, 0, 0, 0, 0], 44 | [ 3, 2, 7, 7, 9, 4, 7, 2, 9, 9, 2, 10, -1, 0, 0, 0], 45 | [ 8, 4, 7, 3, 11, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 46 | [ 7, 11, 2, 7, 2, 4, 4, 2, 0, -1, 0, 0, 0, 0, 0, 0], 47 | [ 2, 3, 11, 1, 9, 0, 8, 4, 7, -1, 0, 0, 0, 0, 0, 0], 48 | [ 2, 1, 9, 2, 9, 4, 2, 4, 11, 11, 4, 7, -1, 0, 0, 0], 49 | [10, 3, 11, 10, 1, 3, 8, 4, 7, -1, 0, 0, 0, 0, 0, 0], 50 | [ 4, 7, 0, 0, 10, 1, 7, 10, 0, 7, 11, 10, -1, 0, 0, 0], 51 | [ 8, 4, 7, 0, 3, 11, 0, 11, 9, 9, 11, 10, -1, 0, 0, 0], 52 | [ 7, 9, 4, 7, 11, 9, 9, 11, 10, -1, 0, 0, 0, 0, 0, 0], 53 | [ 4, 9, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 54 | [ 8, 3, 0, 4, 9, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 55 | [ 0, 5, 4, 0, 1, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 56 | [ 4, 8, 3, 4, 3, 5, 5, 3, 1, -1, 0, 0, 0, 0, 0, 0], 57 | [ 1, 2, 10, 9, 5, 4, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 58 | [ 4, 9, 5, 8, 3, 0, 1, 2, 10, -1, 0, 0, 0, 0, 0, 0], 59 | [10, 5, 4, 10, 4, 2, 2, 4, 0, -1, 0, 0, 0, 0, 0, 0], 60 | [ 4, 8, 3, 4, 3, 2, 4, 2, 5, 5, 2, 10, -1, 0, 0, 0], 61 | [ 2, 3, 11, 5, 4, 9, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 62 | [11, 0, 8, 11, 2, 0, 9, 5, 4, -1, 0, 0, 0, 0, 0, 0], 63 | [ 5, 0, 1, 5, 4, 0, 3, 11, 2, -1, 0, 0, 0, 0, 0, 0], 64 | [11, 2, 8, 8, 5, 4, 2, 5, 8, 2, 1, 5, -1, 0, 0, 0], 65 | [ 3, 10, 1, 3, 11, 10, 5, 4, 9, -1, 0, 0, 0, 0, 0, 0], 66 | [ 9, 5, 4, 1, 0, 8, 1, 8, 10, 10, 8, 11, -1, 0, 0, 0], 67 | [10, 5, 11, 11, 0, 3, 11, 5, 0, 0, 5, 4, -1, 0, 0, 0], 68 | [ 4, 10, 5, 4, 8, 10, 10, 8, 11, -1, 0, 0, 0, 0, 0, 0], 69 | [ 7, 9, 5, 7, 8, 9, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 70 | [ 0, 9, 5, 0, 5, 3, 3, 5, 7, -1, 0, 0, 0, 0, 0, 0], 71 | [ 8, 0, 1, 8, 1, 7, 7, 1, 5, -1, 0, 0, 0, 0, 0, 0], 72 | [ 3, 1, 5, 3, 5, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 73 | [ 7, 9, 5, 7, 8, 9, 1, 2, 10, -1, 0, 0, 0, 0, 0, 0], 74 | [ 1, 2, 10, 0, 9, 5, 0, 5, 3, 3, 5, 7, -1, 0, 0, 0], 75 | [ 7, 8, 5, 5, 2, 10, 8, 2, 5, 8, 0, 2, -1, 0, 0, 0], 76 | [10, 3, 2, 10, 5, 3, 3, 5, 7, -1, 0, 0, 0, 0, 0, 0], 77 | [ 9, 7, 8, 9, 5, 7, 11, 2, 3, -1, 0, 0, 0, 0, 0, 0], 78 | [ 0, 9, 2, 2, 7, 11, 2, 9, 7, 7, 9, 5, -1, 0, 0, 0], 79 | [ 3, 11, 2, 8, 0, 1, 8, 1, 7, 7, 1, 5, -1, 0, 0, 0], 80 | [ 2, 7, 11, 2, 1, 7, 7, 1, 5, -1, 0, 0, 0, 0, 0, 0], 81 | [11, 1, 3, 11, 10, 1, 7, 8, 9, 7, 9, 5, -1, 0, 0, 0], 82 | [11, 10, 1, 11, 1, 7, 7, 1, 0, 7, 0, 9, 7, 9, 5, -1], 83 | [ 5, 7, 8, 5, 8, 10, 10, 8, 0, 10, 0, 3, 10, 3, 11, -1], 84 | [11, 10, 5, 11, 5, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 85 | [10, 6, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 86 | [ 0, 8, 3, 10, 6, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 87 | [ 9, 0, 1, 5, 10, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 88 | [ 8, 1, 9, 8, 3, 1, 10, 6, 5, -1, 0, 0, 0, 0, 0, 0], 89 | [ 6, 1, 2, 6, 5, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 90 | [ 6, 1, 2, 6, 5, 1, 0, 8, 3, -1, 0, 0, 0, 0, 0, 0], 91 | [ 5, 9, 0, 5, 0, 6, 6, 0, 2, -1, 0, 0, 0, 0, 0, 0], 92 | [ 6, 5, 2, 2, 8, 3, 5, 8, 2, 5, 9, 8, -1, 0, 0, 0], 93 | [ 2, 3, 11, 10, 6, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 94 | [ 0, 11, 2, 0, 8, 11, 6, 5, 10, -1, 0, 0, 0, 0, 0, 0], 95 | [ 0, 1, 9, 3, 11, 2, 10, 6, 5, -1, 0, 0, 0, 0, 0, 0], 96 | [10, 6, 5, 2, 1, 9, 2, 9, 11, 11, 9, 8, -1, 0, 0, 0], 97 | [11, 6, 5, 11, 5, 3, 3, 5, 1, -1, 0, 0, 0, 0, 0, 0], 98 | [11, 6, 8, 8, 1, 0, 8, 6, 1, 1, 6, 5, -1, 0, 0, 0], 99 | [ 0, 3, 11, 0, 11, 6, 0, 6, 9, 9, 6, 5, -1, 0, 0, 0], 100 | [ 5, 11, 6, 5, 9, 11, 11, 9, 8, -1, 0, 0, 0, 0, 0, 0], 101 | [ 7, 8, 4, 6, 5, 10, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 102 | [ 3, 4, 7, 3, 0, 4, 5, 10, 6, -1, 0, 0, 0, 0, 0, 0], 103 | [ 6, 5, 10, 7, 8, 4, 9, 0, 1, -1, 0, 0, 0, 0, 0, 0], 104 | [ 5, 10, 6, 9, 4, 7, 9, 7, 1, 1, 7, 3, -1, 0, 0, 0], 105 | [ 1, 6, 5, 1, 2, 6, 7, 8, 4, -1, 0, 0, 0, 0, 0, 0], 106 | [ 7, 0, 4, 7, 3, 0, 6, 5, 1, 6, 1, 2, -1, 0, 0, 0], 107 | [ 4, 7, 8, 5, 9, 0, 5, 0, 6, 6, 0, 2, -1, 0, 0, 0], 108 | [ 2, 6, 5, 2, 5, 3, 3, 5, 9, 3, 9, 4, 3, 4, 7, -1], 109 | [ 4, 7, 8, 5, 10, 6, 11, 2, 3, -1, 0, 0, 0, 0, 0, 0], 110 | [ 6, 5, 10, 7, 11, 2, 7, 2, 4, 4, 2, 0, -1, 0, 0, 0], 111 | [ 4, 7, 8, 9, 0, 1, 6, 5, 10, 3, 11, 2, -1, 0, 0, 0], 112 | [ 6, 5, 10, 11, 4, 7, 11, 2, 4, 4, 2, 9, 9, 2, 1, -1], 113 | [ 7, 8, 4, 11, 6, 5, 11, 5, 3, 3, 5, 1, -1, 0, 0, 0], 114 | [ 0, 4, 7, 0, 7, 1, 1, 7, 11, 1, 11, 6, 1, 6, 5, -1], 115 | [ 4, 7, 8, 9, 6, 5, 9, 0, 6, 6, 0, 11, 11, 0, 3, -1], 116 | [ 7, 11, 4, 11, 9, 4, 11, 5, 9, 11, 6, 5, -1, 0, 0, 0], 117 | [10, 4, 9, 10, 6, 4, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 118 | [10, 4, 9, 10, 6, 4, 8, 3, 0, -1, 0, 0, 0, 0, 0, 0], 119 | [ 1, 10, 6, 1, 6, 0, 0, 6, 4, -1, 0, 0, 0, 0, 0, 0], 120 | [ 4, 8, 6, 6, 1, 10, 6, 8, 1, 1, 8, 3, -1, 0, 0, 0], 121 | [ 9, 1, 2, 9, 2, 4, 4, 2, 6, -1, 0, 0, 0, 0, 0, 0], 122 | [ 0, 8, 3, 9, 1, 2, 9, 2, 4, 4, 2, 6, -1, 0, 0, 0], 123 | [ 0, 2, 6, 0, 6, 4, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 124 | [ 3, 4, 8, 3, 2, 4, 4, 2, 6, -1, 0, 0, 0, 0, 0, 0], 125 | [ 4, 10, 6, 4, 9, 10, 2, 3, 11, -1, 0, 0, 0, 0, 0, 0], 126 | [ 8, 2, 0, 8, 11, 2, 4, 9, 10, 4, 10, 6, -1, 0, 0, 0], 127 | [ 2, 3, 11, 1, 10, 6, 1, 6, 0, 0, 6, 4, -1, 0, 0, 0], 128 | [ 8, 11, 2, 8, 2, 4, 4, 2, 1, 4, 1, 10, 4, 10, 6, -1], 129 | [ 3, 11, 1, 1, 4, 9, 11, 4, 1, 11, 6, 4, -1, 0, 0, 0], 130 | [ 6, 4, 9, 6, 9, 11, 11, 9, 1, 11, 1, 0, 11, 0, 8, -1], 131 | [11, 0, 3, 11, 6, 0, 0, 6, 4, -1, 0, 0, 0, 0, 0, 0], 132 | [ 8, 11, 6, 8, 6, 4, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 133 | [ 6, 7, 8, 6, 8, 10, 10, 8, 9, -1, 0, 0, 0, 0, 0, 0], 134 | [ 3, 0, 7, 7, 10, 6, 0, 10, 7, 0, 9, 10, -1, 0, 0, 0], 135 | [ 1, 10, 6, 1, 6, 7, 1, 7, 0, 0, 7, 8, -1, 0, 0, 0], 136 | [ 6, 1, 10, 6, 7, 1, 1, 7, 3, -1, 0, 0, 0, 0, 0, 0], 137 | [ 9, 1, 8, 8, 6, 7, 8, 1, 6, 6, 1, 2, -1, 0, 0, 0], 138 | [ 7, 3, 0, 7, 0, 6, 6, 0, 9, 6, 9, 1, 6, 1, 2, -1], 139 | [ 8, 6, 7, 8, 0, 6, 6, 0, 2, -1, 0, 0, 0, 0, 0, 0], 140 | [ 2, 6, 7, 2, 7, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 141 | [11, 2, 3, 6, 7, 8, 6, 8, 10, 10, 8, 9, -1, 0, 0, 0], 142 | [ 9, 10, 6, 9, 6, 0, 0, 6, 7, 0, 7, 11, 0, 11, 2, -1], 143 | [ 3, 11, 2, 0, 7, 8, 0, 1, 7, 7, 1, 6, 6, 1, 10, -1], 144 | [ 6, 7, 10, 7, 1, 10, 7, 2, 1, 7, 11, 2, -1, 0, 0, 0], 145 | [ 1, 3, 11, 1, 11, 9, 9, 11, 6, 9, 6, 7, 9, 7, 8, -1], 146 | [ 6, 7, 11, 9, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 147 | [ 8, 0, 7, 0, 6, 7, 0, 11, 6, 0, 3, 11, -1, 0, 0, 0], 148 | [ 6, 7, 11, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 149 | [ 6, 11, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 150 | [ 3, 0, 8, 11, 7, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 151 | [ 6, 11, 7, 9, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 152 | [ 1, 8, 3, 1, 9, 8, 7, 6, 11, -1, 0, 0, 0, 0, 0, 0], 153 | [11, 7, 6, 2, 10, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 154 | [ 1, 2, 10, 0, 8, 3, 11, 7, 6, -1, 0, 0, 0, 0, 0, 0], 155 | [ 9, 2, 10, 9, 0, 2, 11, 7, 6, -1, 0, 0, 0, 0, 0, 0], 156 | [11, 7, 6, 3, 2, 10, 3, 10, 8, 8, 10, 9, -1, 0, 0, 0], 157 | [ 2, 7, 6, 2, 3, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 158 | [ 8, 7, 6, 8, 6, 0, 0, 6, 2, -1, 0, 0, 0, 0, 0, 0], 159 | [ 7, 2, 3, 7, 6, 2, 1, 9, 0, -1, 0, 0, 0, 0, 0, 0], 160 | [ 8, 7, 9, 9, 2, 1, 9, 7, 2, 2, 7, 6, -1, 0, 0, 0], 161 | [ 6, 10, 1, 6, 1, 7, 7, 1, 3, -1, 0, 0, 0, 0, 0, 0], 162 | [ 6, 10, 1, 6, 1, 0, 6, 0, 7, 7, 0, 8, -1, 0, 0, 0], 163 | [ 7, 6, 3, 3, 9, 0, 6, 9, 3, 6, 10, 9, -1, 0, 0, 0], 164 | [ 6, 8, 7, 6, 10, 8, 8, 10, 9, -1, 0, 0, 0, 0, 0, 0], 165 | [ 8, 6, 11, 8, 4, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 166 | [11, 3, 0, 11, 0, 6, 6, 0, 4, -1, 0, 0, 0, 0, 0, 0], 167 | [ 6, 8, 4, 6, 11, 8, 0, 1, 9, -1, 0, 0, 0, 0, 0, 0], 168 | [ 1, 9, 3, 3, 6, 11, 9, 6, 3, 9, 4, 6, -1, 0, 0, 0], 169 | [ 8, 6, 11, 8, 4, 6, 10, 1, 2, -1, 0, 0, 0, 0, 0, 0], 170 | [ 2, 10, 1, 11, 3, 0, 11, 0, 6, 6, 0, 4, -1, 0, 0, 0], 171 | [11, 4, 6, 11, 8, 4, 2, 10, 9, 2, 9, 0, -1, 0, 0, 0], 172 | [ 4, 6, 11, 4, 11, 9, 9, 11, 3, 9, 3, 2, 9, 2, 10, -1], 173 | [ 3, 8, 4, 3, 4, 2, 2, 4, 6, -1, 0, 0, 0, 0, 0, 0], 174 | [ 2, 0, 4, 2, 4, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 175 | [ 0, 1, 9, 3, 8, 4, 3, 4, 2, 2, 4, 6, -1, 0, 0, 0], 176 | [ 9, 2, 1, 9, 4, 2, 2, 4, 6, -1, 0, 0, 0, 0, 0, 0], 177 | [ 6, 10, 4, 4, 3, 8, 4, 10, 3, 3, 10, 1, -1, 0, 0, 0], 178 | [ 1, 6, 10, 1, 0, 6, 6, 0, 4, -1, 0, 0, 0, 0, 0, 0], 179 | [10, 9, 0, 10, 0, 6, 6, 0, 3, 6, 3, 8, 6, 8, 4, -1], 180 | [10, 9, 4, 10, 4, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 181 | [ 6, 11, 7, 5, 4, 9, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 182 | [ 0, 8, 3, 9, 5, 4, 7, 6, 11, -1, 0, 0, 0, 0, 0, 0], 183 | [ 0, 5, 4, 0, 1, 5, 6, 11, 7, -1, 0, 0, 0, 0, 0, 0], 184 | [ 7, 6, 11, 4, 8, 3, 4, 3, 5, 5, 3, 1, -1, 0, 0, 0], 185 | [ 2, 10, 1, 11, 7, 6, 5, 4, 9, -1, 0, 0, 0, 0, 0, 0], 186 | [ 0, 8, 3, 1, 2, 10, 4, 9, 5, 11, 7, 6, -1, 0, 0, 0], 187 | [ 6, 11, 7, 10, 5, 4, 10, 4, 2, 2, 4, 0, -1, 0, 0, 0], 188 | [ 6, 11, 7, 5, 2, 10, 5, 4, 2, 2, 4, 3, 3, 4, 8, -1], 189 | [ 2, 7, 6, 2, 3, 7, 4, 9, 5, -1, 0, 0, 0, 0, 0, 0], 190 | [ 4, 9, 5, 8, 7, 6, 8, 6, 0, 0, 6, 2, -1, 0, 0, 0], 191 | [ 3, 6, 2, 3, 7, 6, 0, 1, 5, 0, 5, 4, -1, 0, 0, 0], 192 | [ 1, 5, 4, 1, 4, 2, 2, 4, 8, 2, 8, 7, 2, 7, 6, -1], 193 | [ 5, 4, 9, 6, 10, 1, 6, 1, 7, 7, 1, 3, -1, 0, 0, 0], 194 | [ 4, 9, 5, 7, 0, 8, 7, 6, 0, 0, 6, 1, 1, 6, 10, -1], 195 | [ 3, 7, 6, 3, 6, 0, 0, 6, 10, 0, 10, 5, 0, 5, 4, -1], 196 | [ 4, 8, 5, 8, 10, 5, 8, 6, 10, 8, 7, 6, -1, 0, 0, 0], 197 | [ 5, 6, 11, 5, 11, 9, 9, 11, 8, -1, 0, 0, 0, 0, 0, 0], 198 | [ 0, 9, 5, 0, 5, 6, 0, 6, 3, 3, 6, 11, -1, 0, 0, 0], 199 | [ 8, 0, 11, 11, 5, 6, 11, 0, 5, 5, 0, 1, -1, 0, 0, 0], 200 | [11, 5, 6, 11, 3, 5, 5, 3, 1, -1, 0, 0, 0, 0, 0, 0], 201 | [10, 1, 2, 5, 6, 11, 5, 11, 9, 9, 11, 8, -1, 0, 0, 0], 202 | [ 2, 10, 1, 3, 6, 11, 3, 0, 6, 6, 0, 5, 5, 0, 9, -1], 203 | [ 0, 2, 10, 0, 10, 8, 8, 10, 5, 8, 5, 6, 8, 6, 11, -1], 204 | [11, 3, 6, 3, 5, 6, 3, 10, 5, 3, 2, 10, -1, 0, 0, 0], 205 | [ 2, 3, 6, 6, 9, 5, 3, 9, 6, 3, 8, 9, -1, 0, 0, 0], 206 | [ 5, 0, 9, 5, 6, 0, 0, 6, 2, -1, 0, 0, 0, 0, 0, 0], 207 | [ 6, 2, 3, 6, 3, 5, 5, 3, 8, 5, 8, 0, 5, 0, 1, -1], 208 | [ 6, 2, 1, 6, 1, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 209 | [ 8, 9, 5, 8, 5, 3, 3, 5, 6, 3, 6, 10, 3, 10, 1, -1], 210 | [ 1, 0, 10, 0, 6, 10, 0, 5, 6, 0, 9, 5, -1, 0, 0, 0], 211 | [ 0, 3, 8, 10, 5, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 212 | [10, 5, 6, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 213 | [11, 5, 10, 11, 7, 5, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 214 | [ 5, 11, 7, 5, 10, 11, 3, 0, 8, -1, 0, 0, 0, 0, 0, 0], 215 | [11, 5, 10, 11, 7, 5, 9, 0, 1, -1, 0, 0, 0, 0, 0, 0], 216 | [ 9, 3, 1, 9, 8, 3, 5, 10, 11, 5, 11, 7, -1, 0, 0, 0], 217 | [ 2, 11, 7, 2, 7, 1, 1, 7, 5, -1, 0, 0, 0, 0, 0, 0], 218 | [ 3, 0, 8, 2, 11, 7, 2, 7, 1, 1, 7, 5, -1, 0, 0, 0], 219 | [ 2, 11, 0, 0, 5, 9, 0, 11, 5, 5, 11, 7, -1, 0, 0, 0], 220 | [ 9, 8, 3, 9, 3, 5, 5, 3, 2, 5, 2, 11, 5, 11, 7, -1], 221 | [10, 2, 3, 10, 3, 5, 5, 3, 7, -1, 0, 0, 0, 0, 0, 0], 222 | [ 5, 10, 7, 7, 0, 8, 10, 0, 7, 10, 2, 0, -1, 0, 0, 0], 223 | [ 1, 9, 0, 10, 2, 3, 10, 3, 5, 5, 3, 7, -1, 0, 0, 0], 224 | [ 7, 5, 10, 7, 10, 8, 8, 10, 2, 8, 2, 1, 8, 1, 9, -1], 225 | [ 7, 5, 1, 7, 1, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 226 | [ 8, 1, 0, 8, 7, 1, 1, 7, 5, -1, 0, 0, 0, 0, 0, 0], 227 | [ 0, 5, 9, 0, 3, 5, 5, 3, 7, -1, 0, 0, 0, 0, 0, 0], 228 | [ 7, 5, 9, 7, 9, 8, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 229 | [ 4, 5, 10, 4, 10, 8, 8, 10, 11, -1, 0, 0, 0, 0, 0, 0], 230 | [11, 3, 10, 10, 4, 5, 10, 3, 4, 4, 3, 0, -1, 0, 0, 0], 231 | [ 9, 0, 1, 4, 5, 10, 4, 10, 8, 8, 10, 11, -1, 0, 0, 0], 232 | [ 3, 1, 9, 3, 9, 11, 11, 9, 4, 11, 4, 5, 11, 5, 10, -1], 233 | [ 8, 4, 11, 11, 1, 2, 4, 1, 11, 4, 5, 1, -1, 0, 0, 0], 234 | [ 5, 1, 2, 5, 2, 4, 4, 2, 11, 4, 11, 3, 4, 3, 0, -1], 235 | [11, 8, 4, 11, 4, 2, 2, 4, 5, 2, 5, 9, 2, 9, 0, -1], 236 | [ 2, 11, 3, 5, 9, 4, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 237 | [ 4, 5, 10, 4, 10, 2, 4, 2, 8, 8, 2, 3, -1, 0, 0, 0], 238 | [10, 4, 5, 10, 2, 4, 4, 2, 0, -1, 0, 0, 0, 0, 0, 0], 239 | [ 0, 1, 9, 8, 2, 3, 8, 4, 2, 2, 4, 10, 10, 4, 5, -1], 240 | [10, 2, 5, 2, 4, 5, 2, 9, 4, 2, 1, 9, -1, 0, 0, 0], 241 | [ 4, 3, 8, 4, 5, 3, 3, 5, 1, -1, 0, 0, 0, 0, 0, 0], 242 | [ 0, 4, 5, 0, 5, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 243 | [ 0, 3, 9, 3, 5, 9, 3, 4, 5, 3, 8, 4, -1, 0, 0, 0], 244 | [ 4, 5, 9, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 245 | [ 7, 4, 9, 7, 9, 11, 11, 9, 10, -1, 0, 0, 0, 0, 0, 0], 246 | [ 8, 3, 0, 7, 4, 9, 7, 9, 11, 11, 9, 10, -1, 0, 0, 0], 247 | [ 0, 1, 4, 4, 11, 7, 1, 11, 4, 1, 10, 11, -1, 0, 0, 0], 248 | [10, 11, 7, 10, 7, 1, 1, 7, 4, 1, 4, 8, 1, 8, 3, -1], 249 | [ 2, 11, 7, 2, 7, 4, 2, 4, 1, 1, 4, 9, -1, 0, 0, 0], 250 | [ 0, 8, 3, 1, 4, 9, 1, 2, 4, 4, 2, 7, 7, 2, 11, -1], 251 | [ 7, 2, 11, 7, 4, 2, 2, 4, 0, -1, 0, 0, 0, 0, 0, 0], 252 | [ 7, 4, 11, 4, 2, 11, 4, 3, 2, 4, 8, 3, -1, 0, 0, 0], 253 | [ 7, 4, 3, 3, 10, 2, 3, 4, 10, 10, 4, 9, -1, 0, 0, 0], 254 | [ 2, 0, 8, 2, 8, 10, 10, 8, 7, 10, 7, 4, 10, 4, 9, -1], 255 | [ 4, 0, 1, 4, 1, 7, 7, 1, 10, 7, 10, 2, 7, 2, 3, -1], 256 | [ 4, 8, 7, 1, 10, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 257 | [ 9, 7, 4, 9, 1, 7, 7, 1, 3, -1, 0, 0, 0, 0, 0, 0], 258 | [ 8, 7, 0, 7, 1, 0, 7, 9, 1, 7, 4, 9, -1, 0, 0, 0], 259 | [ 4, 0, 3, 4, 3, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 260 | [ 4, 8, 7, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 261 | [ 8, 9, 10, 8, 10, 11, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 262 | [ 0, 11, 3, 0, 9, 11, 11, 9, 10, -1, 0, 0, 0, 0, 0, 0], 263 | [ 1, 8, 0, 1, 10, 8, 8, 10, 11, -1, 0, 0, 0, 0, 0, 0], 264 | [ 3, 1, 10, 3, 10, 11, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 265 | [ 2, 9, 1, 2, 11, 9, 9, 11, 8, -1, 0, 0, 0, 0, 0, 0], 266 | [ 0, 9, 3, 9, 11, 3, 9, 2, 11, 9, 1, 2, -1, 0, 0, 0], 267 | [11, 8, 0, 11, 0, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 268 | [ 2, 11, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 269 | [ 3, 10, 2, 3, 8, 10, 10, 8, 9, -1, 0, 0, 0, 0, 0, 0], 270 | [ 9, 10, 2, 9, 2, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 271 | [ 3, 8, 2, 8, 10, 2, 8, 1, 10, 8, 0, 1, -1, 0, 0, 0], 272 | [ 2, 1, 10, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 273 | [ 8, 9, 1, 8, 1, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 274 | [ 1, 0, 9, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 275 | [ 0, 3, 8, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 276 | [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 277 | ]; 278 | static INDEX_TO_VERTEX: [[u32; 3]; 8] = [ 279 | [0, 0, 0], 280 | [1, 0, 0], 281 | [1, 1, 0], 282 | [0, 1, 0], 283 | [0, 0, 1], 284 | [1, 0, 1], 285 | [1, 1, 1], 286 | [0, 1, 1], 287 | ]; 288 | static EDGE_VERTICES: [[usize; 2]; 12] = [ 289 | [0, 1], 290 | [1, 2], 291 | [2, 3], 292 | [3, 0], 293 | [4, 5], 294 | [6, 5], 295 | [6, 7], 296 | [7, 4], 297 | [0, 4], 298 | [1, 5], 299 | [2, 6], 300 | [3, 7], 301 | ]; 302 | 303 | fn lerp_verts(va: &[u32; 3], vb: &[u32; 3], fa: f32, fb: f32, isoval: f32, v: &mut [f32; 3]) { 304 | let t = 305 | if f32::abs(fa - fb) < 0.0001 { 306 | 0.0 307 | } else { 308 | (isoval - fa) / (fb - fa) 309 | }; 310 | 311 | v[0] = va[0] as f32 + (vb[0] as f32 - va[0] as f32) * t; 312 | v[1] = va[1] as f32 + (vb[1] as f32 - va[1] as f32) * t; 313 | v[2] = va[2] as f32 + (vb[2] as f32 - va[2] as f32) * t; 314 | } 315 | 316 | #[wasm_bindgen] 317 | pub struct MarchingCubes { 318 | dims: [u32; 3], 319 | // The input volume, stored on the WASM side 320 | volume: Vec, 321 | // The computed triangles, stored on the WASM side 322 | triangles: Vec, 323 | } 324 | 325 | impl MarchingCubes { 326 | // Compute the vertex values of the cell given the ID of its bottom vertex 327 | fn compute_vertex_values(&self, cell_i: u32, cell_j: u32, cell_k: u32, values: &mut [f32; 8]) { 328 | for (i, ref v) in INDEX_TO_VERTEX.iter().enumerate() { 329 | // We want to swap the order we go when on the top of the cube, 330 | // due to how the indices are labeled in the paper. 331 | let voxel = ((cell_k + v[2]) * self.dims[1] + cell_j + v[1]) * self.dims[0] + cell_i + v[0]; 332 | values[i] = self.volume[voxel as usize] as f32 / 255.0; 333 | }; 334 | } 335 | } 336 | 337 | #[wasm_bindgen] 338 | impl MarchingCubes { 339 | pub fn new() -> MarchingCubes { 340 | MarchingCubes { 341 | dims: [0, 0, 0], 342 | volume: Vec::new(), 343 | triangles: Vec::new(), 344 | } 345 | } 346 | 347 | pub fn set_volume(&mut self, volume: Vec, dims_x: u32, dims_y: u32, dims_z: u32) { 348 | self.volume = volume; 349 | self.dims[0] = dims_x; 350 | self.dims[1] = dims_y; 351 | self.dims[2] = dims_z; 352 | } 353 | 354 | // Run the Marching Cubes algorithm on the volume to compute 355 | // the isosurface at the desired value, and return a reference to the triangle data to JS 356 | pub fn marching_cubes(&mut self, isovalue: f32) -> js_sys::Float32Array { 357 | self.triangles.clear(); 358 | let mut vals = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; 359 | let mut vert = [0.0, 0.0, 0.0]; 360 | for k in 0..self.dims[2] - 1 { 361 | for j in 0..self.dims[1] - 1 { 362 | for i in 0..self.dims[0] - 1 { 363 | self.compute_vertex_values(i, j, k, &mut vals); 364 | let mut index = 0; 365 | for v in 0..8 { 366 | if vals[v] <= isovalue { 367 | index |= 1 << v; 368 | } 369 | } 370 | 371 | /* The cube vertex and edge indices for base rotation: 372 | * 373 | * v7------e6------v6 374 | * / | /| 375 | * e11 | e10| 376 | * / e7 / | 377 | * / | / e5 378 | * v3------e2-------v2 | 379 | * | | | | 380 | * | v4------e4---|---v5 381 | * e3 / e1 / 382 | * | e8 | e9 383 | * | / | / y z 384 | * |/ |/ |/ 385 | * v0------e0-------v1 O--x 386 | */ 387 | 388 | // The triangle table gives us the mapping from index to actual 389 | // triangles to return for this configuration 390 | for t in TRI_TABLE[index].iter().take_while(|t| **t >= 0) { 391 | let v_idx = *t as usize; 392 | let v0 = EDGE_VERTICES[v_idx][0]; 393 | let v1 = EDGE_VERTICES[v_idx][1]; 394 | 395 | lerp_verts(&INDEX_TO_VERTEX[v0], &INDEX_TO_VERTEX[v1], 396 | vals[v0], vals[v1], isovalue, &mut vert); 397 | 398 | // Note: The vertex positions need to be placed on the dual grid, 399 | // since that's where the isosurface is computed and defined. 400 | self.triangles.push(vert[0] + i as f32 + 0.5); 401 | self.triangles.push(vert[1] + j as f32 + 0.5); 402 | self.triangles.push(vert[2] + k as f32 + 0.5); 403 | } 404 | } 405 | } 406 | } 407 | unsafe { js_sys::Float32Array::view(&self.triangles[..]) } 408 | } 409 | } 410 | 411 | --------------------------------------------------------------------------------