├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── assets │ ├── index.92c60a2d.js │ └── vendor.351ef72a.js └── index.html ├── example ├── index.html ├── main.ts ├── render-bounds.ts └── types.d.ts ├── lib ├── index.d.ts ├── index.d.ts.map ├── index.es.js ├── index.umd.js ├── isosurface.d.ts ├── isosurface.d.ts.map ├── primitives.d.ts ├── primitives.d.ts.map ├── sdf.d.ts ├── sdf.d.ts.map ├── util.d.ts └── util.d.ts.map ├── media ├── round.png ├── screenshot.png ├── smooth-intersect.png ├── smooth-subtract.png ├── smooth-union.png └── user-data.png ├── package-lock.json ├── package.json ├── src ├── index.ts ├── isosurface.ts ├── primitives.ts ├── sdf.ts └── util.ts ├── tsconfig.json ├── vite.example-config.js └── vite.lib-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ./docs 2 | ./example 3 | ./media 4 | .vscode 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdf-csg 2 | 3 | Generate meshes from signed distance functions and constructive solid geometry operations. This library is heavily based upon [Inigo Quilez's](https://iquilezles.org/index.html) [3D SDFs](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm) article. 4 | 5 | ## Example 6 | 7 | ```ts 8 | import { Box, Sphere, CappedCylinder } from "sdf-csg"; 9 | 10 | const sdf = new Sphere(1) 11 | .setUserData([0, 0, 1]) 12 | .smoothIntersect(new Box(0.75, 0.75, 0.75).setUserData([1, 0, 0]), 0.1) 13 | .smoothSubtract( 14 | new CappedCylinder(1, 0.5) 15 | .smoothUnion(new CappedCylinder(1, 0.5).rotateX(Math.PI / 2), 0.1) 16 | .smoothUnion(new CappedCylinder(1, 0.5).rotateZ(Math.PI / 2), 0.1) 17 | .setUserData([0, 1, 0]), 18 | 0.1 19 | ); 20 | 21 | const mesh = sdf.generateMesh([64, 64, 64], 0.2); 22 | ``` 23 | 24 |

25 | 26 |

27 | 28 | [Live Demo](https://wwwtyro.github.io/sdf-csg) 29 | 30 | > Note: this project is very much a work in progress. There may be significant changes to the API until the 1.0.0 semantic version is released. 31 | 32 | ## Primitives 33 | 34 | All of these return a new SDF. 35 | 36 | ```ts 37 | const sdf = new Box(width: number, height: number, depth: number) 38 | const sdf = new Sphere(radius: number) 39 | const sdf = new BoxFrame(width: number, height: number, depth: number, edge: number) 40 | const sdf = new Torus(majorRadius: number, minorRadius: number) 41 | const sdf = new CappedTorus(majorRadius: number, minorRadius: number, angle: number) 42 | const sdf = new Link(majorRadius: number, minorRadius: number, length: number) 43 | const sdf = new Cone(angle: number, height: number) 44 | const sdf = new HexagonalPrism(radius: number, length: number) 45 | const sdf = new Capsule(pointA: number[], pointB: number[], radius: number) 46 | const sdf = new CappedCylinder(length: number, radius: number) 47 | const sdf = new CappedCone(length: number, radius1: number, radius2: number) 48 | const sdf = new SolidAngle(angle: number, radius: number) 49 | ``` 50 | 51 | ## Operations 52 | 53 | All of these return a new SDF. 54 | 55 | ```ts 56 | sdf.union(sdf: SDF) 57 | sdf.subtract(sdf: SDF) 58 | sdf.intersect(sdf: SDF) 59 | sdf.smoothUnion(sdf: SDF, smoothness: number) 60 | sdf.smoothSubtract(sdf: SDF, smoothness: number) 61 | sdf.smoothIntersect(sdf: SDF, smoothness: number) 62 | sdf.translate(x: number, y: number, z: number) 63 | sdf.rotate(quat: number[]) 64 | sdf.rotateX(radians: number) 65 | sdf.rotateY(radians: number) 66 | sdf.rotateZ(radians: number) 67 | sdf.scale(amount: number) 68 | sdf.round(amount: number) 69 | ``` 70 | 71 | ### Example: `smoothUnion` 72 | 73 | ```ts 74 | const sdf = new Sphere(1) 75 | .translate(-0.5, 0, 0) 76 | .smoothUnion(new Sphere(1).translate(0.5, 0, 0), 0.1) 77 | .setUserData([1, 0.5, 1]); 78 | ``` 79 | 80 |

81 | 82 |

83 | 84 | ### Example: `smoothSubtract` 85 | 86 | ```ts 87 | const sdf = new Sphere(1) 88 | .translate(-0.5, 0, 0) 89 | .smoothSubtract(new Sphere(1).translate(0.5, 0, 0), 0.2) 90 | .setUserData([1, 0.5, 1]); 91 | ``` 92 | 93 |

94 | 95 |

96 | 97 | ### Example: `smoothIntersect` 98 | 99 | ```ts 100 | const sdf = new Sphere(1) 101 | .translate(-0.5, 0, 0) 102 | .smoothIntersect(new Sphere(1).translate(0.5, 0, 0), 0.2) 103 | .setUserData([1, 0.5, 1]); 104 | ``` 105 | 106 |

107 | 108 |

109 | 110 | ### Example: `round` 111 | 112 | ```ts 113 | const sdf = new HexagonalPrism(0.25, 1) 114 | .translate(-0.75, 0, 0) 115 | .union(new HexagonalPrism(0.25, 1).translate(0.75, 0, 0).round(0.1)) 116 | .setUserData([1, 0.5, 1]); 117 | ``` 118 | 119 |

120 | 121 |

122 | 123 | ## User data 124 | 125 | You can assign "user data" - an array of numbers - to components of your SDF. These are interpolated across the SDF and returned by the `generateMesh` function. Here's an example using them to interpolate color across the surface of the mesh, but you could imagine using them for other purposes, such as values for use with physically based rendering, etc. 126 | 127 | ```ts 128 | const sdf = new Sphere(1) 129 | .setUserData([1, 0.5, 0.5]) 130 | .translate(-0.75, 0, 0) 131 | .smoothUnion(new Sphere(1).setUserData([0.5, 0.5, 1]).translate(0.75, 0, 0), 0.5); 132 | ``` 133 | 134 |

135 | 136 |

137 | 138 | ## Mesh generation 139 | 140 | ```ts 141 | const mesh = sdf.generateMesh(resolution: [number, number, number], padding: number); 142 | ``` 143 | 144 | Returns a `Mesh`: 145 | 146 | ```ts 147 | interface Mesh { 148 | positions: number[][]; 149 | normals: number[][]; 150 | cells: number[][]; 151 | userdata: number[][] | null; 152 | } 153 | ``` 154 | 155 | ## Road map 156 | 157 | - The current isosurface extractor generates poor results for edges and corners. Write or use a different one for better results. 158 | - Isosurface extraction is slow. Speed it up (perhaps the SDF data could be used to skip large chunks of the grid?). 159 | - The bounds of the SDF are determined analytically, and it's a little buggy (e.g., when using the "smooth" operators), requiring the use of padding when generating the mesh. Some options: 160 | - Fix the bugs. 161 | - Determine the bounds numerically, using the value of the SDF to accelerate it. 162 | - Add more primitives. 163 | - Add more operators. 164 | 165 | ## Credits 166 | 167 | This library is heavily based upon [Inigo Quilez's](https://iquilezles.org/index.html) [3D SDFs](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm) article. 168 | -------------------------------------------------------------------------------- /docs/assets/index.92c60a2d.js: -------------------------------------------------------------------------------- 1 | var st=Object.defineProperty,nt=Object.defineProperties;var et=Object.getOwnPropertyDescriptors;var Z=Object.getOwnPropertySymbols;var ot=Object.prototype.hasOwnProperty,it=Object.prototype.propertyIsEnumerable;var A=(h,t,s)=>t in h?st(h,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):h[t]=s,F=(h,t)=>{for(var s in t||(t={}))ot.call(t,s)&&A(h,s,t[s]);if(Z)for(var s of Z(t))it.call(t,s)&&A(h,s,t[s]);return h},G=(h,t)=>nt(h,et(t));var x=(h,t,s)=>(A(h,typeof t!="symbol"?t+"":t,s),s);import{f as C,n as rt,c as b,s as N,a as P,m as at,b as L,r as ut,d as ht,e as ct,g as j,h as U,i as v,j as dt,k as q,l as V,o as K,t as S,p as lt,q as H,u as J,v as O,w as mt,x as Q,y as W,z as R,A as ft,R as pt,B as bt,C as gt,D as xt}from"./vendor.351ef72a.js";const Dt=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))e(n);new MutationObserver(n=>{for(const o of n)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&e(a)}).observe(document,{childList:!0,subtree:!0});function s(n){const o={};return n.integrity&&(o.integrity=n.integrity),n.referrerpolicy&&(o.referrerPolicy=n.referrerpolicy),n.crossorigin==="use-credentials"?o.credentials="include":n.crossorigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function e(n){if(n.ep)return;n.ep=!0;const o=s(n);fetch(n.href,o)}};Dt();function wt(h,t){return h[0]=Math.abs(t[0]),h[1]=Math.abs(t[1]),h[2]=Math.abs(t[2]),h}function Mt(h,t){return h[0]=Math.abs(t[0]),h[1]=Math.abs(t[1]),h}function B(h,t,s){return Math.max(t,Math.min(s,h))}function T(h,t,s){return h*(1-s)+t*s}const y={points:[[0,0,0],[1,0,0],[0,1,0],[1,1,0],[0,0,1],[1,0,1],[0,1,1],[1,1,1]],edges:[[0,1],[0,2],[0,4],[1,3],[1,5],[2,3],[2,6],[3,7],[4,5],[4,6],[5,7],[6,7]]};function vt(h,t){const s=h.shape[0],e=h.shape[1],n=h.shape[2],o=[],a={};function c(r,i,u){if([r,i,u].toString()in a)return a[[r,i,u].toString()];const m=[];y.points.forEach(function(l){m.push(h.get(r+l[0],i+l[1],u+l[2]))});let f=[0,0,0],I=0;return y.edges.forEach(function(l){if(m[l[0]]=t&&m[l[1]]>=t)return;const g=m[l[1]]-m[l[0]],w=(t-m[l[0]])/g,p=[y.points[l[1]][0]-y.points[l[0]][0],y.points[l[1]][1]-y.points[l[0]][1],y.points[l[1]][2]-y.points[l[0]][2]],M=[y.points[l[0]][0]+p[0]*w,y.points[l[0]][1]+p[1]*w,y.points[l[0]][2]+p[2]*w];f=[f[0]+M[0]+r,f[1]+M[1]+i,f[2]+M[2]+u],I++}),o.push([f[0]/I,f[1]/I,f[2]/I]),a[[r,i,u].toString()]=o.length-1,a[[r,i,u].toString()]}const d=[];for(let r=0;r=t?1:0,f=h.get(r+1,i+0,u+0)>=t?1:0,I=h.get(r+0,i+1,u+0)>=t?1:0,l=h.get(r+0,i+0,u+1)>=t?1:0;if(m+f===1&&i>0&&u>0){const g=c(r+0,i-1,u-1),w=c(r+0,i-1,u+0),p=c(r+0,i+0,u+0),M=c(r+0,i+0,u-1);f0&&u>0){const g=c(r-1,i+0,u-1),w=c(r+0,i+0,u-1),p=c(r+0,i+0,u+0),M=c(r-1,i+0,u+0);I0&&i>0){const g=c(r-1,i-1,u+0),w=c(r+0,i-1,u+0),p=c(r+0,i+0,u+0),M=c(r-1,i+0,u+0);l>m?(d.push([g,w,p]),d.push([g,p,M])):(d.push([g,p,w]),d.push([g,M,p]))}}return{positions:o,cells:d}}class _{constructor(){x(this,"_userData",null);x(this,"bounds",{min:C(0,0,0),max:C(0,0,0)})}normal(t,s,e){const n=.001,o=this.density(t,s,e),a=this.density(t+n,s,e),c=this.density(t,s+n,e),d=this.density(t,s,e+n),r=C((a-o)/n,(c-o)/n,(d-o)/n);return rt(b(),r)}generateMesh(t,s){let e=performance.now();const n=this.generateGrid(t,s);console.log(`Grid: ${Math.round(performance.now()-e)} ms`),e=performance.now();const o=vt(n,0);console.log(`Isosurface extraction: ${Math.round(performance.now()-e)} ms`);const a=N(b(),this.bounds.min,[s,s,s]),c=P(b(),this.bounds.max,[s,s,s]),d=N(b(),c,a);for(const u of o.positions)at(u,u,[d[0]/t[0],d[1]/t[1],d[2]/t[2]]),P(u,u,a);const r=[];for(const u of o.positions)r.push(this.normal(u[0],u[1],u[2]));let i=null;if(this.getUserData(0,0,0)!==null){i=[];for(const u of o.positions)i.push(this.getUserData(u[0],u[1],u[2]))}return G(F({},o),{normals:r,userdata:i})}generateGrid(t,s){const e=new Float32Array((t[0]+1)*(t[1]+1)*(t[2]+1)),n=N(b(),this.bounds.min,[s,s,s]),o=P(b(),this.bounds.max,[s,s,s]),a=(o[0]-n[0])/t[0],c=(o[1]-n[1])/t[1],d=(o[2]-n[2])/t[2];for(let r=0;re[t[0]*t[2]*i+t[0]*u+r],shape:[t[0]+1,t[1]+1,t[2]+1]}}setUserData(t){return this._userData=t.slice(),this}getUserData(t,s,e){return this._userData}union(t){return new yt(this,t)}subtract(t){return new Ut(t,this)}intersect(t){return new _t(t,this)}smoothUnion(t,s){return new It(this,t,s)}smoothSubtract(t,s){return new St(t,this,s)}smoothIntersect(t,s){return new Ct(this,t,s)}translate(t,s,e){return new $(this,[t,s,e],L(),1)}rotate(t){return new $(this,[0,0,0],t,1)}rotateX(t){return new $(this,[0,0,0],ut(L(),L(),t),1)}rotateY(t){return new $(this,[0,0,0],ht(L(),L(),t),1)}rotateZ(t){return new $(this,[0,0,0],ct(L(),L(),t),1)}scale(t){return new $(this,[0,0,0],L(),t)}round(t){return new Lt(this,t)}}class Ut extends _{constructor(t,s){super();x(this,"density",(t,s,e)=>{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e);return Math.max(-n,o)});this.sdf1=t,this.sdf2=s,j(this.bounds.min,s.bounds.min),j(this.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e);return Math.min(n,o)});this.sdf1=t,this.sdf2=s,U(this.bounds.min,t.bounds.min,s.bounds.min),v(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e);return Math.max(n,o)});this.sdf1=t,this.sdf2=s,v(this.bounds.min,t.bounds.min,s.bounds.min),U(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e),a=B(.5-.5*(o+n)/this.smoothness,0,1);return T(o,-n,a)+this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=e,j(this.bounds.min,s.bounds.min),j(this.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e),a=B(.5+.5*(o-n)/this.smoothness,0,1);return T(o,n,a)-this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=e,U(this.bounds.min,t.bounds.min,s.bounds.min),v(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=this.sdf1.density(t,s,e),o=this.sdf2.density(t,s,e),a=B(.5-.5*(o-n)/this.smoothness,0,1);return T(o,n,a)+this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=e,v(this.bounds.min,t.bounds.min,s.bounds.min),U(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=this.sdf1.getUserData(t,s,e),o=this.sdf2.getUserData(t,s,e);if(n!==null&&o===null)return n;if(n===null&&o!==null)return o;if(n===null&&o===null)return null;const a=Math.abs(this.sdf1.density(t,s,e)),c=Math.abs(this.sdf2.density(t,s,e)),d=a/(a+c),r=[];for(let i=0;i{const n=C(t,s,e);return S(n,n,this.matrix),this.sdf.density(n[0],n[1],n[2])});this.sdf=t,this.matrix=dt(q(),e,s,C(n,n,n));const o=V(this.sdf.bounds.min),a=V(this.sdf.bounds.max),c=K(b(),a,o),d=P(b(),o,[c[0],0,0]),r=P(b(),o,[c[0],c[1],0]),i=P(b(),o,[0,c[1],0]),u=N(b(),a,[c[0],0,0]),m=N(b(),a,[c[0],c[1],0]),f=N(b(),a,[0,c[1],0]);S(o,o,this.matrix),S(a,a,this.matrix),S(d,d,this.matrix),S(r,r,this.matrix),S(i,i,this.matrix),S(u,u,this.matrix),S(m,m,this.matrix),S(f,f,this.matrix),U(this.bounds.min,o,a),U(this.bounds.min,this.bounds.min,d),U(this.bounds.min,this.bounds.min,r),U(this.bounds.min,this.bounds.min,i),U(this.bounds.min,this.bounds.min,u),U(this.bounds.min,this.bounds.min,m),U(this.bounds.min,this.bounds.min,f),v(this.bounds.max,o,a),v(this.bounds.max,this.bounds.max,d),v(this.bounds.max,this.bounds.max,r),v(this.bounds.max,this.bounds.max,i),v(this.bounds.max,this.bounds.max,u),v(this.bounds.max,this.bounds.max,m),v(this.bounds.max,this.bounds.max,f),lt(this.matrix,this.matrix)}getUserData(t,s,e){if(this._userData!==null)return this._userData;const n=S(b(),[t,s,e],this.matrix);return this.sdf.getUserData(n[0],n[1],n[2])}}class Lt extends _{constructor(t,s){super();x(this,"density",(t,s,e)=>this.sdf.density(t,s,e)-this.radius);this.sdf=t,this.radius=s,N(this.bounds.min,t.bounds.min,[s,s,s]),P(this.bounds.max,t.bounds.max,[s,s,s])}getUserData(t,s,e){return this._userData!==null?this._userData:this.sdf.getUserData(t,s,e)}}class Nt extends _{constructor(t,s,e){super();x(this,"radii");x(this,"density",(t,s,e)=>{const n=C(t,s,e),o=wt(b(),n),a=K(b(),o,this.radii),c=v(b(),a,C(0,0,0));return J(c)+Math.min(Math.max(a[0],Math.max(a[1],a[2])),0)});this.radii=C(t,s,e),H(this.bounds.min,this.radii),j(this.bounds.max,this.radii)}}class Pt extends _{constructor(t){super();x(this,"density",(t,s,e)=>{const n=C(t,s,e);return J(n)-this.radius});this.radius=t,H(this.bounds.min,[this.radius,this.radius,this.radius]),O(this.bounds.max,this.radius,this.radius,this.radius)}}class X extends _{constructor(t,s){super();x(this,"density",(t,s,e)=>{const n=mt(R(),Mt(R(),Q(W([t,e]),s)),Q(this.radius,this.length));return Math.min(Math.max(n[0],n[1]),0)+W(ft(R(),n,[0,0]))});this.length=t,this.radius=s,O(this.bounds.min,-s,-t,-s),O(this.bounds.max,s,t,s)}}const D=pt({extensions:["ANGLE_instanced_arrays","OES_element_index_uint"]}),$t=new Pt(1).setUserData([0,0,1]).smoothIntersect(new Nt(.75,.75,.75).setUserData([1,0,0]),.1).smoothSubtract(new X(1,.5).smoothUnion(new X(1,.5).rotateX(Math.PI/2),.1).smoothUnion(new X(1,.5).rotateZ(Math.PI/2),.1).setUserData([0,1,0]),.1),jt=.2,k=$t.generateMesh([64,64,64],jt),Et={positions:D.buffer(k.positions),colors:D.buffer(k.userdata),normals:D.buffer(k.normals),cells:D.elements(k.cells)},kt=D({vert:` 2 | precision highp float; 3 | attribute vec3 position, color, normal; 4 | uniform mat4 model, view, projection; 5 | varying vec3 vNormal, vColor; 6 | 7 | void main() { 8 | gl_Position = projection * view * model * vec4(position, 1.0); 9 | vNormal = vec3(model * vec4(normal, 1.0)); 10 | vColor = color; 11 | } 12 | `,frag:` 13 | precision highp float; 14 | varying vec3 vNormal, vColor; 15 | 16 | void main() { 17 | vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); 18 | float light = 0.5 + 0.5 * dot(normalize(vNormal), lightDir); 19 | gl_FragColor = vec4(light * vColor,1); 20 | } 21 | `,attributes:{position:D.prop("positions"),color:D.prop("colors"),normal:D.prop("normals")},uniforms:{model:D.prop("model"),view:D.prop("view"),projection:D.prop("projection")},viewport:D.prop("viewport"),elements:D.prop("cells"),cull:{enable:!0,face:"back"}}),E=document.getElementsByTagName("canvas")[0];var z=new bt(E,{drag:.01});z.spin(Math.random()*32-16,Math.random()*32-16);let Y=3;window.addEventListener("wheel",h=>{h.deltaY<0?Y*=.95:Y/=.95});function tt(){D.clear({color:[0,0,0,0],depth:1});const h=gt(q(),[0,0,Y],[0,0,0],[0,1,0]),t=xt(q(),Math.PI/4,E.width/E.height,.1,1e3),s={x:0,y:0,width:E.width,height:E.height};kt(G(F({},Et),{model:z.rotation,view:h,projection:t,viewport:s})),requestAnimationFrame(tt)}tt();console.log("done"); 22 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sdf-csg 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sdf-csg 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/main.ts: -------------------------------------------------------------------------------- 1 | import REGL from "regl"; 2 | import Trackball from "trackball-controller"; 3 | import { mat4 } from "gl-matrix"; 4 | 5 | import { Box, Sphere, CappedCylinder } from "../src"; 6 | // import { createBoundsRenderer } from "./render-bounds"; 7 | 8 | const regl = REGL({ 9 | extensions: ["ANGLE_instanced_arrays", "OES_element_index_uint"], 10 | }); 11 | 12 | // const renderBounds = createBoundsRenderer(regl); 13 | 14 | const sdf = new Sphere(1) 15 | .setUserData([0, 0, 1]) 16 | .smoothIntersect(new Box(0.75, 0.75, 0.75).setUserData([1, 0, 0]), 0.1) 17 | .smoothSubtract( 18 | new CappedCylinder(1, 0.5) 19 | .smoothUnion(new CappedCylinder(1, 0.5).rotateX(Math.PI / 2), 0.1) 20 | .smoothUnion(new CappedCylinder(1, 0.5).rotateZ(Math.PI / 2), 0.1) 21 | .setUserData([0, 1, 0]), 22 | 0.1 23 | ); 24 | 25 | const padding = 0.2; 26 | 27 | const mesh = sdf.generateMesh([64, 64, 64], padding); 28 | 29 | const buffers = { 30 | positions: regl.buffer(mesh.positions), 31 | colors: regl.buffer(mesh.userdata), 32 | normals: regl.buffer(mesh.normals), 33 | cells: regl.elements(mesh.cells), 34 | }; 35 | 36 | const render = regl({ 37 | vert: ` 38 | precision highp float; 39 | attribute vec3 position, color, normal; 40 | uniform mat4 model, view, projection; 41 | varying vec3 vNormal, vColor; 42 | 43 | void main() { 44 | gl_Position = projection * view * model * vec4(position, 1.0); 45 | vNormal = vec3(model * vec4(normal, 1.0)); 46 | vColor = color; 47 | } 48 | `, 49 | frag: ` 50 | precision highp float; 51 | varying vec3 vNormal, vColor; 52 | 53 | void main() { 54 | vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); 55 | float light = 0.5 + 0.5 * dot(normalize(vNormal), lightDir); 56 | gl_FragColor = vec4(light * vColor,1); 57 | } 58 | `, 59 | attributes: { 60 | position: regl.prop("positions"), 61 | color: regl.prop("colors"), 62 | normal: regl.prop("normals"), 63 | }, 64 | uniforms: { 65 | model: regl.prop("model"), 66 | view: regl.prop("view"), 67 | projection: regl.prop("projection"), 68 | }, 69 | viewport: regl.prop("viewport"), 70 | elements: regl.prop("cells"), 71 | 72 | cull: { 73 | enable: true, 74 | face: "back", 75 | }, 76 | }); 77 | 78 | const canvas = document.getElementsByTagName("canvas")[0]; 79 | 80 | var trackball = new Trackball(canvas, { 81 | drag: 0.01, 82 | }); 83 | trackball.spin(Math.random() * 32 - 16, Math.random() * 32 - 16); 84 | 85 | let zoom = 3; 86 | 87 | window.addEventListener("wheel", (e) => { 88 | if (e.deltaY < 0) { 89 | zoom *= 0.95; 90 | } else { 91 | zoom /= 0.95; 92 | } 93 | }); 94 | 95 | function renderLoop() { 96 | regl.clear({ color: [0, 0, 0, 0], depth: 1 }); 97 | const view = mat4.lookAt(mat4.create(), [0, 0, zoom], [0, 0, 0], [0, 1, 0]); 98 | const projection = mat4.perspective(mat4.create(), Math.PI / 4, canvas.width / canvas.height, 0.1, 1000); 99 | const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }; 100 | render({ 101 | ...buffers, 102 | model: trackball.rotation, 103 | view, 104 | projection, 105 | viewport, 106 | }); 107 | // renderBounds(sdf.bounds, [canvas.width, canvas.height], trackball.rotation, view, projection); 108 | // const padded = { 109 | // min: vec3.sub(vec3.create(), sdf.bounds.min, vec3.fromValues(padding, padding, padding)), 110 | // max: vec3.add(vec3.create(), sdf.bounds.max, vec3.fromValues(padding, padding, padding)), 111 | // }; 112 | // renderBounds(padded, [canvas.width, canvas.height], trackball.rotation, view, projection); 113 | requestAnimationFrame(renderLoop); 114 | } 115 | 116 | renderLoop(); 117 | 118 | console.log("done"); 119 | -------------------------------------------------------------------------------- /example/render-bounds.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from "gl-matrix"; 2 | import { Regl } from "regl"; 3 | import { Bounds } from "../src"; 4 | 5 | function roundCapJoinGeometry(regl: Regl, resolution: number) { 6 | const instanceRoundRound = [ 7 | [0, -0.5, 0], 8 | [0, -0.5, 1], 9 | [0, 0.5, 1], 10 | [0, -0.5, 0], 11 | [0, 0.5, 1], 12 | [0, 0.5, 0], 13 | ]; 14 | // Add the left cap. 15 | for (let step = 0; step < resolution; step++) { 16 | const theta0 = Math.PI / 2 + ((step + 0) * Math.PI) / resolution; 17 | const theta1 = Math.PI / 2 + ((step + 1) * Math.PI) / resolution; 18 | instanceRoundRound.push([0, 0, 0]); 19 | instanceRoundRound.push([0.5 * Math.cos(theta0), 0.5 * Math.sin(theta0), 0]); 20 | instanceRoundRound.push([0.5 * Math.cos(theta1), 0.5 * Math.sin(theta1), 0]); 21 | } 22 | // Add the right cap. 23 | for (let step = 0; step < resolution; step++) { 24 | const theta0 = (3 * Math.PI) / 2 + ((step + 0) * Math.PI) / resolution; 25 | const theta1 = (3 * Math.PI) / 2 + ((step + 1) * Math.PI) / resolution; 26 | instanceRoundRound.push([0, 0, 1]); 27 | instanceRoundRound.push([0.5 * Math.cos(theta0), 0.5 * Math.sin(theta0), 1]); 28 | instanceRoundRound.push([0.5 * Math.cos(theta1), 0.5 * Math.sin(theta1), 1]); 29 | } 30 | return { 31 | buffer: regl.buffer(instanceRoundRound), 32 | count: instanceRoundRound.length, 33 | }; 34 | } 35 | 36 | export function createBoundsRenderer(regl: Regl) { 37 | const roundCapJoin = roundCapJoinGeometry(regl, 8); 38 | 39 | const command = regl({ 40 | vert: ` 41 | precision highp float; 42 | attribute vec3 position; 43 | attribute vec3 pointA, pointB; 44 | 45 | uniform float width; 46 | uniform vec2 resolution; 47 | uniform mat4 model, view, projection; 48 | 49 | varying vec3 vColor; 50 | 51 | void main() { 52 | vec4 clip0 = projection * view * model * vec4(pointA, 1.0); 53 | vec4 clip1 = projection * view * model * vec4(pointB, 1.0); 54 | vec2 screen0 = resolution * (0.5 * clip0.xy/clip0.w + 0.5); 55 | vec2 screen1 = resolution * (0.5 * clip1.xy/clip1.w + 0.5); 56 | vec2 xBasis = normalize(screen1 - screen0); 57 | vec2 yBasis = vec2(-xBasis.y, xBasis.x); 58 | vec2 pt0 = screen0 + width * (position.x * xBasis + position.y * yBasis); 59 | vec2 pt1 = screen1 + width * (position.x * xBasis + position.y * yBasis); 60 | vec2 pt = mix(pt0, pt1, position.z); 61 | vec4 clip = mix(clip0, clip1, position.z); 62 | gl_Position = vec4(clip.w * (2.0 * pt/resolution - 1.0), clip.z, clip.w); 63 | }`, 64 | 65 | frag: ` 66 | precision highp float; 67 | 68 | void main() { 69 | gl_FragColor = vec4(0.9,0.9,0.9,1); 70 | }`, 71 | 72 | attributes: { 73 | position: { 74 | buffer: roundCapJoin.buffer, 75 | divisor: 0, 76 | }, 77 | pointA: { 78 | buffer: regl.prop("points"), 79 | divisor: 1, 80 | offset: Float32Array.BYTES_PER_ELEMENT * 0, 81 | stride: Float32Array.BYTES_PER_ELEMENT * 6, 82 | }, 83 | pointB: { 84 | buffer: regl.prop("points"), 85 | divisor: 1, 86 | offset: Float32Array.BYTES_PER_ELEMENT * 3, 87 | stride: Float32Array.BYTES_PER_ELEMENT * 6, 88 | }, 89 | }, 90 | 91 | uniforms: { 92 | width: regl.prop("width"), 93 | model: regl.prop("model"), 94 | view: regl.prop("view"), 95 | projection: regl.prop("projection"), 96 | resolution: regl.prop("resolution"), 97 | }, 98 | 99 | // cull: { 100 | // enable: true, 101 | // face: "back", 102 | // }, 103 | 104 | count: roundCapJoin.count, 105 | instances: regl.prop("segments"), 106 | viewport: regl.prop("viewport"), 107 | }); 108 | 109 | return function renderBounds(bounds: Bounds, resolution: number[], model: mat4, view: mat4, projection: mat4) { 110 | const bmin = vec3.clone(bounds.min); 111 | const bmax = vec3.clone(bounds.max); 112 | const db = vec3.subtract(vec3.create(), bmax, bmin); 113 | const t0 = vec3.add(vec3.create(), bmin, [db[0], 0, 0]); 114 | const t1 = vec3.add(vec3.create(), bmin, [db[0], db[1], 0]); 115 | const t2 = vec3.add(vec3.create(), bmin, [0, db[1], 0]); 116 | const t3 = vec3.sub(vec3.create(), bmax, [db[0], 0, 0]); 117 | const t4 = vec3.sub(vec3.create(), bmax, [db[0], db[1], 0]); 118 | const t5 = vec3.sub(vec3.create(), bmax, [0, db[1], 0]); 119 | const points: vec3[] = []; 120 | points.push(bmin, t0, bmin, t2, t0, t1, t2, t1); 121 | points.push(bmax, t3, bmax, t5, t3, t4, t5, t4); 122 | points.push(t1, bmax, t0, t5, bmin, t4, t2, t3); 123 | command({ 124 | points, 125 | width: 2, 126 | segments: points.length / 2, 127 | resolution, 128 | model, 129 | view, 130 | projection, 131 | viewport: { x: 0, y: 0, width: resolution[0], height: resolution[1] }, 132 | }); 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /example/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "trackball-controller"; 2 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./primitives"; 2 | export type { Bounds } from "./sdf"; 3 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /lib/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC"} -------------------------------------------------------------------------------- /lib/index.es.js: -------------------------------------------------------------------------------- 1 | var __defProp = Object.defineProperty; 2 | var __defProps = Object.defineProperties; 3 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors; 4 | var __getOwnPropSymbols = Object.getOwnPropertySymbols; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __propIsEnum = Object.prototype.propertyIsEnumerable; 7 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 8 | var __spreadValues = (a, b) => { 9 | for (var prop in b || (b = {})) 10 | if (__hasOwnProp.call(b, prop)) 11 | __defNormalProp(a, prop, b[prop]); 12 | if (__getOwnPropSymbols) 13 | for (var prop of __getOwnPropSymbols(b)) { 14 | if (__propIsEnum.call(b, prop)) 15 | __defNormalProp(a, prop, b[prop]); 16 | } 17 | return a; 18 | }; 19 | var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); 20 | var __publicField = (obj, key, value) => { 21 | __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 22 | return value; 23 | }; 24 | var EPSILON = 1e-6; 25 | var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; 26 | if (!Math.hypot) 27 | Math.hypot = function() { 28 | var y = 0, i = arguments.length; 29 | while (i--) { 30 | y += arguments[i] * arguments[i]; 31 | } 32 | return Math.sqrt(y); 33 | }; 34 | function create$5() { 35 | var out = new ARRAY_TYPE(9); 36 | if (ARRAY_TYPE != Float32Array) { 37 | out[1] = 0; 38 | out[2] = 0; 39 | out[3] = 0; 40 | out[5] = 0; 41 | out[6] = 0; 42 | out[7] = 0; 43 | } 44 | out[0] = 1; 45 | out[4] = 1; 46 | out[8] = 1; 47 | return out; 48 | } 49 | function create$4() { 50 | var out = new ARRAY_TYPE(16); 51 | if (ARRAY_TYPE != Float32Array) { 52 | out[1] = 0; 53 | out[2] = 0; 54 | out[3] = 0; 55 | out[4] = 0; 56 | out[6] = 0; 57 | out[7] = 0; 58 | out[8] = 0; 59 | out[9] = 0; 60 | out[11] = 0; 61 | out[12] = 0; 62 | out[13] = 0; 63 | out[14] = 0; 64 | } 65 | out[0] = 1; 66 | out[5] = 1; 67 | out[10] = 1; 68 | out[15] = 1; 69 | return out; 70 | } 71 | function invert(out, a) { 72 | var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; 73 | var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; 74 | var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; 75 | var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; 76 | var b00 = a00 * a11 - a01 * a10; 77 | var b01 = a00 * a12 - a02 * a10; 78 | var b02 = a00 * a13 - a03 * a10; 79 | var b03 = a01 * a12 - a02 * a11; 80 | var b04 = a01 * a13 - a03 * a11; 81 | var b05 = a02 * a13 - a03 * a12; 82 | var b06 = a20 * a31 - a21 * a30; 83 | var b07 = a20 * a32 - a22 * a30; 84 | var b08 = a20 * a33 - a23 * a30; 85 | var b09 = a21 * a32 - a22 * a31; 86 | var b10 = a21 * a33 - a23 * a31; 87 | var b11 = a22 * a33 - a23 * a32; 88 | var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; 89 | if (!det) { 90 | return null; 91 | } 92 | det = 1 / det; 93 | out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; 94 | out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; 95 | out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; 96 | out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; 97 | out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; 98 | out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; 99 | out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; 100 | out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; 101 | out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; 102 | out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; 103 | out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; 104 | out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; 105 | out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; 106 | out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; 107 | out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; 108 | out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; 109 | return out; 110 | } 111 | function fromRotationTranslationScale(out, q, v, s) { 112 | var x = q[0], y = q[1], z = q[2], w = q[3]; 113 | var x2 = x + x; 114 | var y2 = y + y; 115 | var z2 = z + z; 116 | var xx = x * x2; 117 | var xy = x * y2; 118 | var xz = x * z2; 119 | var yy = y * y2; 120 | var yz = y * z2; 121 | var zz = z * z2; 122 | var wx = w * x2; 123 | var wy = w * y2; 124 | var wz = w * z2; 125 | var sx = s[0]; 126 | var sy = s[1]; 127 | var sz = s[2]; 128 | out[0] = (1 - (yy + zz)) * sx; 129 | out[1] = (xy + wz) * sx; 130 | out[2] = (xz - wy) * sx; 131 | out[3] = 0; 132 | out[4] = (xy - wz) * sy; 133 | out[5] = (1 - (xx + zz)) * sy; 134 | out[6] = (yz + wx) * sy; 135 | out[7] = 0; 136 | out[8] = (xz + wy) * sz; 137 | out[9] = (yz - wx) * sz; 138 | out[10] = (1 - (xx + yy)) * sz; 139 | out[11] = 0; 140 | out[12] = v[0]; 141 | out[13] = v[1]; 142 | out[14] = v[2]; 143 | out[15] = 1; 144 | return out; 145 | } 146 | function create$3() { 147 | var out = new ARRAY_TYPE(3); 148 | if (ARRAY_TYPE != Float32Array) { 149 | out[0] = 0; 150 | out[1] = 0; 151 | out[2] = 0; 152 | } 153 | return out; 154 | } 155 | function clone(a) { 156 | var out = new ARRAY_TYPE(3); 157 | out[0] = a[0]; 158 | out[1] = a[1]; 159 | out[2] = a[2]; 160 | return out; 161 | } 162 | function length$1(a) { 163 | var x = a[0]; 164 | var y = a[1]; 165 | var z = a[2]; 166 | return Math.hypot(x, y, z); 167 | } 168 | function fromValues$1(x, y, z) { 169 | var out = new ARRAY_TYPE(3); 170 | out[0] = x; 171 | out[1] = y; 172 | out[2] = z; 173 | return out; 174 | } 175 | function copy(out, a) { 176 | out[0] = a[0]; 177 | out[1] = a[1]; 178 | out[2] = a[2]; 179 | return out; 180 | } 181 | function set(out, x, y, z) { 182 | out[0] = x; 183 | out[1] = y; 184 | out[2] = z; 185 | return out; 186 | } 187 | function add$1(out, a, b) { 188 | out[0] = a[0] + b[0]; 189 | out[1] = a[1] + b[1]; 190 | out[2] = a[2] + b[2]; 191 | return out; 192 | } 193 | function subtract$1(out, a, b) { 194 | out[0] = a[0] - b[0]; 195 | out[1] = a[1] - b[1]; 196 | out[2] = a[2] - b[2]; 197 | return out; 198 | } 199 | function multiply$1(out, a, b) { 200 | out[0] = a[0] * b[0]; 201 | out[1] = a[1] * b[1]; 202 | out[2] = a[2] * b[2]; 203 | return out; 204 | } 205 | function min(out, a, b) { 206 | out[0] = Math.min(a[0], b[0]); 207 | out[1] = Math.min(a[1], b[1]); 208 | out[2] = Math.min(a[2], b[2]); 209 | return out; 210 | } 211 | function max$1(out, a, b) { 212 | out[0] = Math.max(a[0], b[0]); 213 | out[1] = Math.max(a[1], b[1]); 214 | out[2] = Math.max(a[2], b[2]); 215 | return out; 216 | } 217 | function scale$1(out, a, b) { 218 | out[0] = a[0] * b; 219 | out[1] = a[1] * b; 220 | out[2] = a[2] * b; 221 | return out; 222 | } 223 | function negate(out, a) { 224 | out[0] = -a[0]; 225 | out[1] = -a[1]; 226 | out[2] = -a[2]; 227 | return out; 228 | } 229 | function normalize$2(out, a) { 230 | var x = a[0]; 231 | var y = a[1]; 232 | var z = a[2]; 233 | var len2 = x * x + y * y + z * z; 234 | if (len2 > 0) { 235 | len2 = 1 / Math.sqrt(len2); 236 | } 237 | out[0] = a[0] * len2; 238 | out[1] = a[1] * len2; 239 | out[2] = a[2] * len2; 240 | return out; 241 | } 242 | function dot$1(a, b) { 243 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; 244 | } 245 | function cross(out, a, b) { 246 | var ax = a[0], ay = a[1], az = a[2]; 247 | var bx = b[0], by = b[1], bz = b[2]; 248 | out[0] = ay * bz - az * by; 249 | out[1] = az * bx - ax * bz; 250 | out[2] = ax * by - ay * bx; 251 | return out; 252 | } 253 | function transformMat4(out, a, m) { 254 | var x = a[0], y = a[1], z = a[2]; 255 | var w = m[3] * x + m[7] * y + m[11] * z + m[15]; 256 | w = w || 1; 257 | out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; 258 | out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; 259 | out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; 260 | return out; 261 | } 262 | var sub$1 = subtract$1; 263 | var mul$1 = multiply$1; 264 | var len = length$1; 265 | (function() { 266 | var vec = create$3(); 267 | return function(a, stride, offset, count, fn, arg) { 268 | var i, l; 269 | if (!stride) { 270 | stride = 3; 271 | } 272 | if (!offset) { 273 | offset = 0; 274 | } 275 | if (count) { 276 | l = Math.min(count * stride + offset, a.length); 277 | } else { 278 | l = a.length; 279 | } 280 | for (i = offset; i < l; i += stride) { 281 | vec[0] = a[i]; 282 | vec[1] = a[i + 1]; 283 | vec[2] = a[i + 2]; 284 | fn(vec, vec, arg); 285 | a[i] = vec[0]; 286 | a[i + 1] = vec[1]; 287 | a[i + 2] = vec[2]; 288 | } 289 | return a; 290 | }; 291 | })(); 292 | function create$2() { 293 | var out = new ARRAY_TYPE(4); 294 | if (ARRAY_TYPE != Float32Array) { 295 | out[0] = 0; 296 | out[1] = 0; 297 | out[2] = 0; 298 | out[3] = 0; 299 | } 300 | return out; 301 | } 302 | function normalize$1(out, a) { 303 | var x = a[0]; 304 | var y = a[1]; 305 | var z = a[2]; 306 | var w = a[3]; 307 | var len2 = x * x + y * y + z * z + w * w; 308 | if (len2 > 0) { 309 | len2 = 1 / Math.sqrt(len2); 310 | } 311 | out[0] = x * len2; 312 | out[1] = y * len2; 313 | out[2] = z * len2; 314 | out[3] = w * len2; 315 | return out; 316 | } 317 | (function() { 318 | var vec = create$2(); 319 | return function(a, stride, offset, count, fn, arg) { 320 | var i, l; 321 | if (!stride) { 322 | stride = 4; 323 | } 324 | if (!offset) { 325 | offset = 0; 326 | } 327 | if (count) { 328 | l = Math.min(count * stride + offset, a.length); 329 | } else { 330 | l = a.length; 331 | } 332 | for (i = offset; i < l; i += stride) { 333 | vec[0] = a[i]; 334 | vec[1] = a[i + 1]; 335 | vec[2] = a[i + 2]; 336 | vec[3] = a[i + 3]; 337 | fn(vec, vec, arg); 338 | a[i] = vec[0]; 339 | a[i + 1] = vec[1]; 340 | a[i + 2] = vec[2]; 341 | a[i + 3] = vec[3]; 342 | } 343 | return a; 344 | }; 345 | })(); 346 | function create$1() { 347 | var out = new ARRAY_TYPE(4); 348 | if (ARRAY_TYPE != Float32Array) { 349 | out[0] = 0; 350 | out[1] = 0; 351 | out[2] = 0; 352 | } 353 | out[3] = 1; 354 | return out; 355 | } 356 | function setAxisAngle(out, axis, rad) { 357 | rad = rad * 0.5; 358 | var s = Math.sin(rad); 359 | out[0] = s * axis[0]; 360 | out[1] = s * axis[1]; 361 | out[2] = s * axis[2]; 362 | out[3] = Math.cos(rad); 363 | return out; 364 | } 365 | function rotateX(out, a, rad) { 366 | rad *= 0.5; 367 | var ax = a[0], ay = a[1], az = a[2], aw = a[3]; 368 | var bx = Math.sin(rad), bw = Math.cos(rad); 369 | out[0] = ax * bw + aw * bx; 370 | out[1] = ay * bw + az * bx; 371 | out[2] = az * bw - ay * bx; 372 | out[3] = aw * bw - ax * bx; 373 | return out; 374 | } 375 | function rotateY(out, a, rad) { 376 | rad *= 0.5; 377 | var ax = a[0], ay = a[1], az = a[2], aw = a[3]; 378 | var by = Math.sin(rad), bw = Math.cos(rad); 379 | out[0] = ax * bw - az * by; 380 | out[1] = ay * bw + aw * by; 381 | out[2] = az * bw + ax * by; 382 | out[3] = aw * bw - ay * by; 383 | return out; 384 | } 385 | function rotateZ(out, a, rad) { 386 | rad *= 0.5; 387 | var ax = a[0], ay = a[1], az = a[2], aw = a[3]; 388 | var bz = Math.sin(rad), bw = Math.cos(rad); 389 | out[0] = ax * bw + ay * bz; 390 | out[1] = ay * bw - ax * bz; 391 | out[2] = az * bw + aw * bz; 392 | out[3] = aw * bw - az * bz; 393 | return out; 394 | } 395 | function slerp(out, a, b, t) { 396 | var ax = a[0], ay = a[1], az = a[2], aw = a[3]; 397 | var bx = b[0], by = b[1], bz = b[2], bw = b[3]; 398 | var omega, cosom, sinom, scale0, scale1; 399 | cosom = ax * bx + ay * by + az * bz + aw * bw; 400 | if (cosom < 0) { 401 | cosom = -cosom; 402 | bx = -bx; 403 | by = -by; 404 | bz = -bz; 405 | bw = -bw; 406 | } 407 | if (1 - cosom > EPSILON) { 408 | omega = Math.acos(cosom); 409 | sinom = Math.sin(omega); 410 | scale0 = Math.sin((1 - t) * omega) / sinom; 411 | scale1 = Math.sin(t * omega) / sinom; 412 | } else { 413 | scale0 = 1 - t; 414 | scale1 = t; 415 | } 416 | out[0] = scale0 * ax + scale1 * bx; 417 | out[1] = scale0 * ay + scale1 * by; 418 | out[2] = scale0 * az + scale1 * bz; 419 | out[3] = scale0 * aw + scale1 * bw; 420 | return out; 421 | } 422 | function fromMat3(out, m) { 423 | var fTrace = m[0] + m[4] + m[8]; 424 | var fRoot; 425 | if (fTrace > 0) { 426 | fRoot = Math.sqrt(fTrace + 1); 427 | out[3] = 0.5 * fRoot; 428 | fRoot = 0.5 / fRoot; 429 | out[0] = (m[5] - m[7]) * fRoot; 430 | out[1] = (m[6] - m[2]) * fRoot; 431 | out[2] = (m[1] - m[3]) * fRoot; 432 | } else { 433 | var i = 0; 434 | if (m[4] > m[0]) 435 | i = 1; 436 | if (m[8] > m[i * 3 + i]) 437 | i = 2; 438 | var j = (i + 1) % 3; 439 | var k = (i + 2) % 3; 440 | fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1); 441 | out[i] = 0.5 * fRoot; 442 | fRoot = 0.5 / fRoot; 443 | out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; 444 | out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; 445 | out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; 446 | } 447 | return out; 448 | } 449 | var normalize = normalize$1; 450 | (function() { 451 | var tmpvec3 = create$3(); 452 | var xUnitVec3 = fromValues$1(1, 0, 0); 453 | var yUnitVec3 = fromValues$1(0, 1, 0); 454 | return function(out, a, b) { 455 | var dot2 = dot$1(a, b); 456 | if (dot2 < -0.999999) { 457 | cross(tmpvec3, xUnitVec3, a); 458 | if (len(tmpvec3) < 1e-6) 459 | cross(tmpvec3, yUnitVec3, a); 460 | normalize$2(tmpvec3, tmpvec3); 461 | setAxisAngle(out, tmpvec3, Math.PI); 462 | return out; 463 | } else if (dot2 > 0.999999) { 464 | out[0] = 0; 465 | out[1] = 0; 466 | out[2] = 0; 467 | out[3] = 1; 468 | return out; 469 | } else { 470 | cross(tmpvec3, a, b); 471 | out[0] = tmpvec3[0]; 472 | out[1] = tmpvec3[1]; 473 | out[2] = tmpvec3[2]; 474 | out[3] = 1 + dot2; 475 | return normalize(out, out); 476 | } 477 | }; 478 | })(); 479 | (function() { 480 | var temp1 = create$1(); 481 | var temp2 = create$1(); 482 | return function(out, a, b, c, d, t) { 483 | slerp(temp1, a, d, t); 484 | slerp(temp2, b, c, t); 485 | slerp(out, temp1, temp2, 2 * t * (1 - t)); 486 | return out; 487 | }; 488 | })(); 489 | (function() { 490 | var matr = create$5(); 491 | return function(out, view, right, up) { 492 | matr[0] = right[0]; 493 | matr[3] = right[1]; 494 | matr[6] = right[2]; 495 | matr[1] = up[0]; 496 | matr[4] = up[1]; 497 | matr[7] = up[2]; 498 | matr[2] = -view[0]; 499 | matr[5] = -view[1]; 500 | matr[8] = -view[2]; 501 | return normalize(out, fromMat3(out, matr)); 502 | }; 503 | })(); 504 | function create() { 505 | var out = new ARRAY_TYPE(2); 506 | if (ARRAY_TYPE != Float32Array) { 507 | out[0] = 0; 508 | out[1] = 0; 509 | } 510 | return out; 511 | } 512 | function fromValues(x, y) { 513 | var out = new ARRAY_TYPE(2); 514 | out[0] = x; 515 | out[1] = y; 516 | return out; 517 | } 518 | function add(out, a, b) { 519 | out[0] = a[0] + b[0]; 520 | out[1] = a[1] + b[1]; 521 | return out; 522 | } 523 | function subtract(out, a, b) { 524 | out[0] = a[0] - b[0]; 525 | out[1] = a[1] - b[1]; 526 | return out; 527 | } 528 | function multiply(out, a, b) { 529 | out[0] = a[0] * b[0]; 530 | out[1] = a[1] * b[1]; 531 | return out; 532 | } 533 | function max(out, a, b) { 534 | out[0] = Math.max(a[0], b[0]); 535 | out[1] = Math.max(a[1], b[1]); 536 | return out; 537 | } 538 | function scale(out, a, b) { 539 | out[0] = a[0] * b; 540 | out[1] = a[1] * b; 541 | return out; 542 | } 543 | function length(a) { 544 | var x = a[0], y = a[1]; 545 | return Math.hypot(x, y); 546 | } 547 | function dot(a, b) { 548 | return a[0] * b[0] + a[1] * b[1]; 549 | } 550 | var sub = subtract; 551 | var mul = multiply; 552 | (function() { 553 | var vec = create(); 554 | return function(a, stride, offset, count, fn, arg) { 555 | var i, l; 556 | if (!stride) { 557 | stride = 2; 558 | } 559 | if (!offset) { 560 | offset = 0; 561 | } 562 | if (count) { 563 | l = Math.min(count * stride + offset, a.length); 564 | } else { 565 | l = a.length; 566 | } 567 | for (i = offset; i < l; i += stride) { 568 | vec[0] = a[i]; 569 | vec[1] = a[i + 1]; 570 | fn(vec, vec, arg); 571 | a[i] = vec[0]; 572 | a[i + 1] = vec[1]; 573 | } 574 | return a; 575 | }; 576 | })(); 577 | function vec3abs(out, p) { 578 | out[0] = Math.abs(p[0]); 579 | out[1] = Math.abs(p[1]); 580 | out[2] = Math.abs(p[2]); 581 | return out; 582 | } 583 | function vec2abs(out, p) { 584 | out[0] = Math.abs(p[0]); 585 | out[1] = Math.abs(p[1]); 586 | return out; 587 | } 588 | function clamp(n, min2, max2) { 589 | return Math.max(min2, Math.min(max2, n)); 590 | } 591 | function mix(n0, n1, frac) { 592 | return n0 * (1 - frac) + n1 * frac; 593 | } 594 | const unitCube = { 595 | points: [ 596 | [0, 0, 0], 597 | [1, 0, 0], 598 | [0, 1, 0], 599 | [1, 1, 0], 600 | [0, 0, 1], 601 | [1, 0, 1], 602 | [0, 1, 1], 603 | [1, 1, 1] 604 | ], 605 | edges: [ 606 | [0, 1], 607 | [0, 2], 608 | [0, 4], 609 | [1, 3], 610 | [1, 5], 611 | [2, 3], 612 | [2, 6], 613 | [3, 7], 614 | [4, 5], 615 | [4, 6], 616 | [5, 7], 617 | [6, 7] 618 | ] 619 | }; 620 | function isosurfaceGenerator(density, level) { 621 | const width = density.shape[0]; 622 | const height = density.shape[1]; 623 | const depth = density.shape[2]; 624 | const featurePoints = []; 625 | const featurePointIndex = {}; 626 | function getFeaturePointIndex(x, y, z) { 627 | if ([x, y, z].toString() in featurePointIndex) 628 | return featurePointIndex[[x, y, z].toString()]; 629 | const values = []; 630 | unitCube.points.forEach(function(v) { 631 | values.push(density.get(x + v[0], y + v[1], z + v[2])); 632 | }); 633 | let p = [0, 0, 0]; 634 | let sum = 0; 635 | unitCube.edges.forEach(function(e) { 636 | if (values[e[0]] < level && values[e[1]] < level) 637 | return; 638 | if (values[e[0]] >= level && values[e[1]] >= level) 639 | return; 640 | const dv = values[e[1]] - values[e[0]]; 641 | const dr = (level - values[e[0]]) / dv; 642 | const r = [ 643 | unitCube.points[e[1]][0] - unitCube.points[e[0]][0], 644 | unitCube.points[e[1]][1] - unitCube.points[e[0]][1], 645 | unitCube.points[e[1]][2] - unitCube.points[e[0]][2] 646 | ]; 647 | const interp = [ 648 | unitCube.points[e[0]][0] + r[0] * dr, 649 | unitCube.points[e[0]][1] + r[1] * dr, 650 | unitCube.points[e[0]][2] + r[2] * dr 651 | ]; 652 | p = [p[0] + interp[0] + x, p[1] + interp[1] + y, p[2] + interp[2] + z]; 653 | sum++; 654 | }); 655 | featurePoints.push([p[0] / sum, p[1] / sum, p[2] / sum]); 656 | featurePointIndex[[x, y, z].toString()] = featurePoints.length - 1; 657 | return featurePointIndex[[x, y, z].toString()]; 658 | } 659 | const cells = []; 660 | for (let x = 0; x < width - 1; x++) { 661 | for (let y = 0; y < height - 1; y++) { 662 | for (let z = 0; z < depth - 1; z++) { 663 | const p0 = density.get(x + 0, y + 0, z + 0) >= level ? 1 : 0; 664 | const px = density.get(x + 1, y + 0, z + 0) >= level ? 1 : 0; 665 | const py = density.get(x + 0, y + 1, z + 0) >= level ? 1 : 0; 666 | const pz = density.get(x + 0, y + 0, z + 1) >= level ? 1 : 0; 667 | if (p0 + px === 1 && y > 0 && z > 0) { 668 | const a = getFeaturePointIndex(x + 0, y - 1, z - 1); 669 | const b = getFeaturePointIndex(x + 0, y - 1, z + 0); 670 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 671 | const d = getFeaturePointIndex(x + 0, y + 0, z - 1); 672 | if (px < p0) { 673 | cells.push([a, b, c]); 674 | cells.push([a, c, d]); 675 | } else { 676 | cells.push([a, c, b]); 677 | cells.push([a, d, c]); 678 | } 679 | } 680 | if (p0 + py === 1 && x > 0 && z > 0) { 681 | const a = getFeaturePointIndex(x - 1, y + 0, z - 1); 682 | const b = getFeaturePointIndex(x + 0, y + 0, z - 1); 683 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 684 | const d = getFeaturePointIndex(x - 1, y + 0, z + 0); 685 | if (py < p0) { 686 | cells.push([a, b, c]); 687 | cells.push([a, c, d]); 688 | } else { 689 | cells.push([a, c, b]); 690 | cells.push([a, d, c]); 691 | } 692 | } 693 | if (p0 + pz === 1 && x > 0 && y > 0) { 694 | const a = getFeaturePointIndex(x - 1, y - 1, z + 0); 695 | const b = getFeaturePointIndex(x + 0, y - 1, z + 0); 696 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 697 | const d = getFeaturePointIndex(x - 1, y + 0, z + 0); 698 | if (pz > p0) { 699 | cells.push([a, b, c]); 700 | cells.push([a, c, d]); 701 | } else { 702 | cells.push([a, c, b]); 703 | cells.push([a, d, c]); 704 | } 705 | } 706 | } 707 | } 708 | } 709 | return { 710 | positions: featurePoints, 711 | cells 712 | }; 713 | } 714 | class SDF { 715 | constructor() { 716 | __publicField(this, "_userData", null); 717 | __publicField(this, "bounds", { 718 | min: fromValues$1(0, 0, 0), 719 | max: fromValues$1(0, 0, 0) 720 | }); 721 | } 722 | normal(x, y, z) { 723 | const dr = 1e-3; 724 | const p0 = this.density(x, y, z); 725 | const px = this.density(x + dr, y, z); 726 | const py = this.density(x, y + dr, z); 727 | const pz = this.density(x, y, z + dr); 728 | const n0 = fromValues$1((px - p0) / dr, (py - p0) / dr, (pz - p0) / dr); 729 | return normalize$2(create$3(), n0); 730 | } 731 | generateMesh(resolution, padding) { 732 | let t0 = performance.now(); 733 | const grid = this.generateGrid(resolution, padding); 734 | console.log(`Grid: ${Math.round(performance.now() - t0)} ms`); 735 | t0 = performance.now(); 736 | const mesh = isosurfaceGenerator(grid, 0); 737 | console.log(`Isosurface extraction: ${Math.round(performance.now() - t0)} ms`); 738 | const min2 = sub$1(create$3(), this.bounds.min, [padding, padding, padding]); 739 | const max2 = add$1(create$3(), this.bounds.max, [padding, padding, padding]); 740 | const dm = sub$1(create$3(), max2, min2); 741 | for (const position of mesh.positions) { 742 | mul$1(position, position, [dm[0] / resolution[0], dm[1] / resolution[1], dm[2] / resolution[2]]); 743 | add$1(position, position, min2); 744 | } 745 | const normals = []; 746 | for (const p of mesh.positions) { 747 | normals.push(this.normal(p[0], p[1], p[2])); 748 | } 749 | let userdata = null; 750 | if (this.getUserData(0, 0, 0) !== null) { 751 | userdata = []; 752 | for (const p of mesh.positions) { 753 | userdata.push(this.getUserData(p[0], p[1], p[2])); 754 | } 755 | } 756 | return __spreadProps(__spreadValues({}, mesh), { 757 | normals, 758 | userdata 759 | }); 760 | } 761 | generateGrid(resolution, padding) { 762 | const grid = new Float32Array((resolution[0] + 1) * (resolution[1] + 1) * (resolution[2] + 1)); 763 | const min2 = sub$1(create$3(), this.bounds.min, [padding, padding, padding]); 764 | const max2 = add$1(create$3(), this.bounds.max, [padding, padding, padding]); 765 | const dx = (max2[0] - min2[0]) / resolution[0]; 766 | const dy = (max2[1] - min2[1]) / resolution[1]; 767 | const dz = (max2[2] - min2[2]) / resolution[2]; 768 | for (let i = 0; i < resolution[0] + 1; i++) { 769 | const x = i * dx + min2[0]; 770 | for (let j = 0; j < resolution[1] + 1; j++) { 771 | const y = j * dy + min2[1]; 772 | for (let k = 0; k < resolution[2] + 1; k++) { 773 | const z = k * dz + min2[2]; 774 | const index = resolution[0] * resolution[2] * j + resolution[0] * k + i; 775 | grid[index] = this.density(x, y, z); 776 | } 777 | } 778 | } 779 | return { 780 | get: (i, j, k) => grid[resolution[0] * resolution[2] * j + resolution[0] * k + i], 781 | shape: [resolution[0] + 1, resolution[1] + 1, resolution[2] + 1] 782 | }; 783 | } 784 | setUserData(data) { 785 | this._userData = data.slice(); 786 | return this; 787 | } 788 | getUserData(x, y, z) { 789 | return this._userData; 790 | } 791 | union(sdf) { 792 | return new Union(this, sdf); 793 | } 794 | subtract(sdf) { 795 | return new Subtraction(sdf, this); 796 | } 797 | intersect(sdf) { 798 | return new Intersection(sdf, this); 799 | } 800 | smoothUnion(sdf, smoothness) { 801 | return new SmoothUnion(this, sdf, smoothness); 802 | } 803 | smoothSubtract(sdf, smoothness) { 804 | return new SmoothSubtraction(sdf, this, smoothness); 805 | } 806 | smoothIntersect(sdf, smoothness) { 807 | return new SmoothIntersection(this, sdf, smoothness); 808 | } 809 | translate(x, y, z) { 810 | return new Transform(this, [x, y, z], create$1(), 1); 811 | } 812 | rotate(quat2) { 813 | return new Transform(this, [0, 0, 0], quat2, 1); 814 | } 815 | rotateX(radians) { 816 | return new Transform(this, [0, 0, 0], rotateX(create$1(), create$1(), radians), 1); 817 | } 818 | rotateY(radians) { 819 | return new Transform(this, [0, 0, 0], rotateY(create$1(), create$1(), radians), 1); 820 | } 821 | rotateZ(radians) { 822 | return new Transform(this, [0, 0, 0], rotateZ(create$1(), create$1(), radians), 1); 823 | } 824 | scale(amount) { 825 | return new Transform(this, [0, 0, 0], create$1(), amount); 826 | } 827 | round(amount) { 828 | return new Round(this, amount); 829 | } 830 | } 831 | class Subtraction extends SDF { 832 | constructor(sdf1, sdf2) { 833 | super(); 834 | __publicField(this, "density", (x, y, z) => { 835 | const d1 = this.sdf1.density(x, y, z); 836 | const d2 = this.sdf2.density(x, y, z); 837 | return Math.max(-d1, d2); 838 | }); 839 | this.sdf1 = sdf1; 840 | this.sdf2 = sdf2; 841 | copy(this.bounds.min, sdf2.bounds.min); 842 | copy(this.bounds.max, sdf2.bounds.max); 843 | } 844 | getUserData(x, y, z) { 845 | if (this._userData !== null) { 846 | return this._userData; 847 | } 848 | const ud1 = this.sdf1.getUserData(x, y, z); 849 | const ud2 = this.sdf2.getUserData(x, y, z); 850 | if (ud1 !== null && ud2 === null) { 851 | return ud1; 852 | } else if (ud1 === null && ud2 !== null) { 853 | return ud2; 854 | } else if (ud1 === null && ud2 === null) { 855 | return null; 856 | } 857 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 858 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 859 | const frac = d1 / (d1 + d2); 860 | const lerp = []; 861 | for (let i = 0; i < ud1.length; i++) { 862 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 863 | } 864 | return lerp; 865 | } 866 | } 867 | class Union extends SDF { 868 | constructor(sdf1, sdf2) { 869 | super(); 870 | __publicField(this, "density", (x, y, z) => { 871 | const d1 = this.sdf1.density(x, y, z); 872 | const d2 = this.sdf2.density(x, y, z); 873 | return Math.min(d1, d2); 874 | }); 875 | this.sdf1 = sdf1; 876 | this.sdf2 = sdf2; 877 | min(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 878 | max$1(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 879 | } 880 | getUserData(x, y, z) { 881 | if (this._userData !== null) { 882 | return this._userData; 883 | } 884 | const ud1 = this.sdf1.getUserData(x, y, z); 885 | const ud2 = this.sdf2.getUserData(x, y, z); 886 | if (ud1 !== null && ud2 === null) { 887 | return ud1; 888 | } else if (ud1 === null && ud2 !== null) { 889 | return ud2; 890 | } else if (ud1 === null && ud2 === null) { 891 | return null; 892 | } 893 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 894 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 895 | const frac = d1 / (d1 + d2); 896 | const lerp = []; 897 | for (let i = 0; i < ud1.length; i++) { 898 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 899 | } 900 | return lerp; 901 | } 902 | } 903 | class Intersection extends SDF { 904 | constructor(sdf1, sdf2) { 905 | super(); 906 | __publicField(this, "density", (x, y, z) => { 907 | const d1 = this.sdf1.density(x, y, z); 908 | const d2 = this.sdf2.density(x, y, z); 909 | return Math.max(d1, d2); 910 | }); 911 | this.sdf1 = sdf1; 912 | this.sdf2 = sdf2; 913 | max$1(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 914 | min(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 915 | } 916 | getUserData(x, y, z) { 917 | if (this._userData !== null) { 918 | return this._userData; 919 | } 920 | const ud1 = this.sdf1.getUserData(x, y, z); 921 | const ud2 = this.sdf2.getUserData(x, y, z); 922 | if (ud1 !== null && ud2 === null) { 923 | return ud1; 924 | } else if (ud1 === null && ud2 !== null) { 925 | return ud2; 926 | } else if (ud1 === null && ud2 === null) { 927 | return null; 928 | } 929 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 930 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 931 | const frac = d1 / (d1 + d2); 932 | const lerp = []; 933 | for (let i = 0; i < ud1.length; i++) { 934 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 935 | } 936 | return lerp; 937 | } 938 | } 939 | class SmoothSubtraction extends SDF { 940 | constructor(sdf1, sdf2, smoothness) { 941 | super(); 942 | __publicField(this, "density", (x, y, z) => { 943 | const d1 = this.sdf1.density(x, y, z); 944 | const d2 = this.sdf2.density(x, y, z); 945 | const h = clamp(0.5 - 0.5 * (d2 + d1) / this.smoothness, 0, 1); 946 | return mix(d2, -d1, h) + this.smoothness * h * (1 - h); 947 | }); 948 | this.sdf1 = sdf1; 949 | this.sdf2 = sdf2; 950 | this.smoothness = smoothness; 951 | copy(this.bounds.min, sdf2.bounds.min); 952 | copy(this.bounds.max, sdf2.bounds.max); 953 | } 954 | getUserData(x, y, z) { 955 | if (this._userData !== null) { 956 | return this._userData; 957 | } 958 | const ud1 = this.sdf1.getUserData(x, y, z); 959 | const ud2 = this.sdf2.getUserData(x, y, z); 960 | if (ud1 !== null && ud2 === null) { 961 | return ud1; 962 | } else if (ud1 === null && ud2 !== null) { 963 | return ud2; 964 | } else if (ud1 === null && ud2 === null) { 965 | return null; 966 | } 967 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 968 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 969 | const frac = d1 / (d1 + d2); 970 | const lerp = []; 971 | for (let i = 0; i < ud1.length; i++) { 972 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 973 | } 974 | return lerp; 975 | } 976 | } 977 | class SmoothUnion extends SDF { 978 | constructor(sdf1, sdf2, smoothness) { 979 | super(); 980 | __publicField(this, "density", (x, y, z) => { 981 | const d1 = this.sdf1.density(x, y, z); 982 | const d2 = this.sdf2.density(x, y, z); 983 | const h = clamp(0.5 + 0.5 * (d2 - d1) / this.smoothness, 0, 1); 984 | return mix(d2, d1, h) - this.smoothness * h * (1 - h); 985 | }); 986 | this.sdf1 = sdf1; 987 | this.sdf2 = sdf2; 988 | this.smoothness = smoothness; 989 | min(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 990 | max$1(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 991 | } 992 | getUserData(x, y, z) { 993 | if (this._userData !== null) { 994 | return this._userData; 995 | } 996 | const ud1 = this.sdf1.getUserData(x, y, z); 997 | const ud2 = this.sdf2.getUserData(x, y, z); 998 | if (ud1 !== null && ud2 === null) { 999 | return ud1; 1000 | } else if (ud1 === null && ud2 !== null) { 1001 | return ud2; 1002 | } else if (ud1 === null && ud2 === null) { 1003 | return null; 1004 | } 1005 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 1006 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 1007 | const frac = d1 / (d1 + d2); 1008 | const lerp = []; 1009 | for (let i = 0; i < ud1.length; i++) { 1010 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 1011 | } 1012 | return lerp; 1013 | } 1014 | } 1015 | class SmoothIntersection extends SDF { 1016 | constructor(sdf1, sdf2, smoothness) { 1017 | super(); 1018 | __publicField(this, "density", (x, y, z) => { 1019 | const d1 = this.sdf1.density(x, y, z); 1020 | const d2 = this.sdf2.density(x, y, z); 1021 | const h = clamp(0.5 - 0.5 * (d2 - d1) / this.smoothness, 0, 1); 1022 | return mix(d2, d1, h) + this.smoothness * h * (1 - h); 1023 | }); 1024 | this.sdf1 = sdf1; 1025 | this.sdf2 = sdf2; 1026 | this.smoothness = smoothness; 1027 | max$1(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 1028 | min(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 1029 | } 1030 | getUserData(x, y, z) { 1031 | if (this._userData !== null) { 1032 | return this._userData; 1033 | } 1034 | const ud1 = this.sdf1.getUserData(x, y, z); 1035 | const ud2 = this.sdf2.getUserData(x, y, z); 1036 | if (ud1 !== null && ud2 === null) { 1037 | return ud1; 1038 | } else if (ud1 === null && ud2 !== null) { 1039 | return ud2; 1040 | } else if (ud1 === null && ud2 === null) { 1041 | return null; 1042 | } 1043 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 1044 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 1045 | const frac = d1 / (d1 + d2); 1046 | const lerp = []; 1047 | for (let i = 0; i < ud1.length; i++) { 1048 | lerp.push(ud1[i] + frac * (ud2[i] - ud1[i])); 1049 | } 1050 | return lerp; 1051 | } 1052 | } 1053 | class Transform extends SDF { 1054 | constructor(sdf, translation, rotation, scale2) { 1055 | super(); 1056 | __publicField(this, "matrix"); 1057 | __publicField(this, "density", (x, y, z) => { 1058 | const point = fromValues$1(x, y, z); 1059 | transformMat4(point, point, this.matrix); 1060 | return this.sdf.density(point[0], point[1], point[2]); 1061 | }); 1062 | this.sdf = sdf; 1063 | this.matrix = fromRotationTranslationScale(create$4(), rotation, translation, fromValues$1(scale2, scale2, scale2)); 1064 | const bmin = clone(this.sdf.bounds.min); 1065 | const bmax = clone(this.sdf.bounds.max); 1066 | const db = subtract$1(create$3(), bmax, bmin); 1067 | const t0 = add$1(create$3(), bmin, [db[0], 0, 0]); 1068 | const t1 = add$1(create$3(), bmin, [db[0], db[1], 0]); 1069 | const t2 = add$1(create$3(), bmin, [0, db[1], 0]); 1070 | const t3 = sub$1(create$3(), bmax, [db[0], 0, 0]); 1071 | const t4 = sub$1(create$3(), bmax, [db[0], db[1], 0]); 1072 | const t5 = sub$1(create$3(), bmax, [0, db[1], 0]); 1073 | transformMat4(bmin, bmin, this.matrix); 1074 | transformMat4(bmax, bmax, this.matrix); 1075 | transformMat4(t0, t0, this.matrix); 1076 | transformMat4(t1, t1, this.matrix); 1077 | transformMat4(t2, t2, this.matrix); 1078 | transformMat4(t3, t3, this.matrix); 1079 | transformMat4(t4, t4, this.matrix); 1080 | transformMat4(t5, t5, this.matrix); 1081 | min(this.bounds.min, bmin, bmax); 1082 | min(this.bounds.min, this.bounds.min, t0); 1083 | min(this.bounds.min, this.bounds.min, t1); 1084 | min(this.bounds.min, this.bounds.min, t2); 1085 | min(this.bounds.min, this.bounds.min, t3); 1086 | min(this.bounds.min, this.bounds.min, t4); 1087 | min(this.bounds.min, this.bounds.min, t5); 1088 | max$1(this.bounds.max, bmin, bmax); 1089 | max$1(this.bounds.max, this.bounds.max, t0); 1090 | max$1(this.bounds.max, this.bounds.max, t1); 1091 | max$1(this.bounds.max, this.bounds.max, t2); 1092 | max$1(this.bounds.max, this.bounds.max, t3); 1093 | max$1(this.bounds.max, this.bounds.max, t4); 1094 | max$1(this.bounds.max, this.bounds.max, t5); 1095 | invert(this.matrix, this.matrix); 1096 | } 1097 | getUserData(x, y, z) { 1098 | if (this._userData !== null) { 1099 | return this._userData; 1100 | } 1101 | const p = transformMat4(create$3(), [x, y, z], this.matrix); 1102 | return this.sdf.getUserData(p[0], p[1], p[2]); 1103 | } 1104 | } 1105 | class Round extends SDF { 1106 | constructor(sdf, radius) { 1107 | super(); 1108 | __publicField(this, "density", (x, y, z) => { 1109 | return this.sdf.density(x, y, z) - this.radius; 1110 | }); 1111 | this.sdf = sdf; 1112 | this.radius = radius; 1113 | sub$1(this.bounds.min, sdf.bounds.min, [radius, radius, radius]); 1114 | add$1(this.bounds.max, sdf.bounds.max, [radius, radius, radius]); 1115 | } 1116 | getUserData(x, y, z) { 1117 | if (this._userData !== null) { 1118 | return this._userData; 1119 | } 1120 | return this.sdf.getUserData(x, y, z); 1121 | } 1122 | } 1123 | class Box extends SDF { 1124 | constructor(width, height, depth) { 1125 | super(); 1126 | __publicField(this, "radii"); 1127 | __publicField(this, "density", (x, y, z) => { 1128 | const p = fromValues$1(x, y, z); 1129 | const absp = vec3abs(create$3(), p); 1130 | const q = subtract$1(create$3(), absp, this.radii); 1131 | const posq = max$1(create$3(), q, fromValues$1(0, 0, 0)); 1132 | return length$1(posq) + Math.min(Math.max(q[0], Math.max(q[1], q[2])), 0); 1133 | }); 1134 | this.radii = fromValues$1(width, height, depth); 1135 | negate(this.bounds.min, this.radii); 1136 | copy(this.bounds.max, this.radii); 1137 | } 1138 | } 1139 | class Sphere extends SDF { 1140 | constructor(radius) { 1141 | super(); 1142 | __publicField(this, "density", (x, y, z) => { 1143 | const p = fromValues$1(x, y, z); 1144 | return length$1(p) - this.radius; 1145 | }); 1146 | this.radius = radius; 1147 | negate(this.bounds.min, [this.radius, this.radius, this.radius]); 1148 | set(this.bounds.max, this.radius, this.radius, this.radius); 1149 | } 1150 | } 1151 | class BoxFrame extends SDF { 1152 | constructor(width, height, depth, edge) { 1153 | super(); 1154 | __publicField(this, "radii"); 1155 | __publicField(this, "density", (x, y, z) => { 1156 | const p = subtract$1(create$3(), vec3abs(create$3(), fromValues$1(x, y, z)), this.radii); 1157 | const e = fromValues$1(this.edge, this.edge, this.edge); 1158 | const q = sub$1(create$3(), vec3abs(create$3(), add$1(create$3(), p, e)), e); 1159 | return Math.min(Math.min(length$1(max$1(create$3(), [p[0], q[1], q[2]], [0, 0, 0])) + Math.min(Math.max(p[0], Math.max(q[1], q[2])), 0), length$1(max$1(create$3(), [q[0], p[1], q[2]], [0, 0, 0])) + Math.min(Math.max(q[0], Math.max(p[1], q[2])), 0)), length$1(max$1(create$3(), [q[0], q[1], p[2]], [0, 0, 0])) + Math.min(Math.max(q[0], Math.max(q[1], p[2])), 0)); 1160 | }); 1161 | this.edge = edge; 1162 | this.radii = fromValues$1(width, height, depth); 1163 | negate(this.bounds.min, this.radii); 1164 | copy(this.bounds.max, this.radii); 1165 | } 1166 | } 1167 | class Torus extends SDF { 1168 | constructor(majorRadius, minorRadius) { 1169 | super(); 1170 | __publicField(this, "density", (x, y, z) => { 1171 | const q = fromValues(length(fromValues(x, y)) - this.majorRadius, z); 1172 | return length(q) - this.minorRadius; 1173 | }); 1174 | this.majorRadius = majorRadius; 1175 | this.minorRadius = minorRadius; 1176 | set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius, -minorRadius); 1177 | set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius, +minorRadius); 1178 | } 1179 | } 1180 | class CappedTorus extends SDF { 1181 | constructor(majorRadius, minorRadius, angle) { 1182 | super(); 1183 | __publicField(this, "density", (x, y, z) => { 1184 | const sc = fromValues(Math.sin(this.angle), Math.cos(this.angle)); 1185 | x = Math.abs(x); 1186 | const k = sc[1] * x > sc[0] * y ? dot([x, y], sc) : length([x, y]); 1187 | const p = fromValues$1(x, y, z); 1188 | return Math.sqrt(dot$1(p, p) + this.majorRadius * this.majorRadius - 2 * this.majorRadius * k) - this.minorRadius; 1189 | }); 1190 | this.majorRadius = majorRadius; 1191 | this.minorRadius = minorRadius; 1192 | this.angle = angle; 1193 | set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius, -minorRadius); 1194 | set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius, +minorRadius); 1195 | } 1196 | } 1197 | class Link extends SDF { 1198 | constructor(majorRadius, minorRadius, length2) { 1199 | super(); 1200 | __publicField(this, "density", (x, y, z) => { 1201 | const q = fromValues$1(x, Math.max(Math.abs(y) - this.length, 0), z); 1202 | return length(fromValues(length([q[0], q[1]]) - this.majorRadius, q[2])) - this.minorRadius; 1203 | }); 1204 | this.majorRadius = majorRadius; 1205 | this.minorRadius = minorRadius; 1206 | this.length = length2; 1207 | set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius - length2, -minorRadius); 1208 | set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius + length2, +minorRadius); 1209 | } 1210 | } 1211 | class Cone extends SDF { 1212 | constructor(angle, height) { 1213 | super(); 1214 | __publicField(this, "density", (x, y, z) => { 1215 | const c = [Math.sin(this.angle), Math.cos(this.angle)]; 1216 | const q = scale(create(), fromValues(c[0] / c[1], -1), this.height); 1217 | const w = fromValues(length([x, z]), y); 1218 | const a = sub(create(), w, scale(create(), q, clamp(dot(w, q) / dot(q, q), 0, 1))); 1219 | const b = sub(create(), w, mul(create(), q, fromValues(clamp(w[0] / q[0], 0, 1), 1))); 1220 | const k = Math.sign(q[1]); 1221 | const d = Math.min(dot(a, a), dot(b, b)); 1222 | const s = Math.max(k * (w[0] * q[1] - w[1] * q[0]), k * (w[1] - q[1])); 1223 | return Math.sqrt(d) * Math.sign(s); 1224 | }); 1225 | this.angle = angle; 1226 | this.height = height; 1227 | const sigma = height * Math.tan(angle); 1228 | set(this.bounds.min, -sigma, -height, -sigma); 1229 | set(this.bounds.max, sigma, 0, sigma); 1230 | } 1231 | } 1232 | class HexagonalPrism extends SDF { 1233 | constructor(radius, length2) { 1234 | super(); 1235 | __publicField(this, "density", (x, y, z) => { 1236 | const k = fromValues$1(-0.8660254, 0.5, 0.57735); 1237 | const p = fromValues$1(x, y, z); 1238 | vec3abs(p, p); 1239 | const q = scale(create(), [k[0], k[1]], 2 * Math.min(dot([k[0], k[1]], [p[0], p[1]]), 0)); 1240 | p[0] -= q[0]; 1241 | p[1] -= q[1]; 1242 | const d = fromValues(length(sub(create(), [p[0], p[1]], fromValues(clamp(p[0], -k[2] * this.radius, k[2] * this.radius), this.radius))) * Math.sign(p[1] - this.radius), p[2] - this.length); 1243 | return Math.min(Math.max(d[0], d[1]), 0) + length(max(create(), d, [0, 0])); 1244 | }); 1245 | this.radius = radius; 1246 | this.length = length2; 1247 | const h = radius / Math.cos(Math.PI / 6); 1248 | set(this.bounds.min, -h, -radius, -length2); 1249 | set(this.bounds.max, h, radius, length2); 1250 | } 1251 | } 1252 | class Capsule extends SDF { 1253 | constructor(pointA, pointB, radius) { 1254 | super(); 1255 | __publicField(this, "density", (x, y, z) => { 1256 | const p = fromValues$1(x, y, z); 1257 | const pa = sub$1(create$3(), p, this.pointA); 1258 | const ba = sub$1(create$3(), this.pointB, this.pointA); 1259 | const h = clamp(dot$1(pa, ba) / dot$1(ba, ba), 0, 1); 1260 | return length$1(sub$1(create$3(), pa, scale$1(create$3(), ba, h))) - this.radius; 1261 | }); 1262 | this.pointA = pointA; 1263 | this.pointB = pointB; 1264 | this.radius = radius; 1265 | const min$1 = min(create$3(), pointA, pointB); 1266 | const max2 = max$1(create$3(), pointA, pointB); 1267 | sub$1(this.bounds.min, min$1, [radius, radius, radius]); 1268 | add$1(this.bounds.max, max2, [radius, radius, radius]); 1269 | } 1270 | } 1271 | class CappedCylinder extends SDF { 1272 | constructor(length2, radius) { 1273 | super(); 1274 | __publicField(this, "density", (x, y, z) => { 1275 | const d = sub(create(), vec2abs(create(), fromValues(length([x, z]), y)), fromValues(this.radius, this.length)); 1276 | return Math.min(Math.max(d[0], d[1]), 0) + length(max(create(), d, [0, 0])); 1277 | }); 1278 | this.length = length2; 1279 | this.radius = radius; 1280 | set(this.bounds.min, -radius, -length2, -radius); 1281 | set(this.bounds.max, radius, length2, radius); 1282 | } 1283 | } 1284 | class CappedCone extends SDF { 1285 | constructor(length2, radius1, radius2) { 1286 | super(); 1287 | __publicField(this, "density", (x, y, z) => { 1288 | const q = fromValues(length([x, z]), y); 1289 | const k1 = fromValues(this.radius2, this.length); 1290 | const k2 = fromValues(this.radius2 - this.radius1, 2 * this.length); 1291 | const ca = fromValues(q[0] - Math.min(q[0], q[1] < 0 ? this.radius1 : this.radius2), Math.abs(q[1]) - this.length); 1292 | const qk1 = sub(create(), k1, q); 1293 | const cb = add(create(), sub(create(), q, k1), scale(create(), k2, clamp(dot(qk1, k2) / dot(k2, k2), 0, 1))); 1294 | const s = cb[0] < 0 && ca[1] < 0 ? -1 : 1; 1295 | return s * Math.sqrt(Math.min(dot(ca, ca), dot(cb, cb))); 1296 | }); 1297 | this.length = length2; 1298 | this.radius1 = radius1; 1299 | this.radius2 = radius2; 1300 | const r = Math.max(radius1, radius2); 1301 | set(this.bounds.min, -r, -length2, -r); 1302 | set(this.bounds.max, r, length2, r); 1303 | } 1304 | } 1305 | class SolidAngle extends SDF { 1306 | constructor(angle, radius) { 1307 | super(); 1308 | __publicField(this, "density", (x, y, z) => { 1309 | const c = fromValues(Math.sin(this.angle), Math.cos(this.angle)); 1310 | const q = fromValues(length([x, z]), y); 1311 | const l = length(q) - this.radius; 1312 | const m = length(sub(create(), q, scale(create(), c, clamp(dot(q, c), 0, this.radius)))); 1313 | return Math.max(l, m * Math.sign(c[1] * q[0] - c[0] * q[1])); 1314 | }); 1315 | this.angle = angle; 1316 | this.radius = radius; 1317 | const h = radius * Math.sin(angle); 1318 | set(this.bounds.min, -h, 0, -h); 1319 | set(this.bounds.max, h, radius, h); 1320 | } 1321 | } 1322 | class TriangularPrism_ extends SDF { 1323 | constructor(radius, length2) { 1324 | super(); 1325 | __publicField(this, "density", (x, y, z) => { 1326 | const q = vec3abs(create$3(), [x, y, z]); 1327 | return Math.max(q[2] - this.length, Math.max(q[0] * 0.866025 + y * 0.5, -y) - this.radius * 0.5); 1328 | }); 1329 | this.radius = radius; 1330 | this.length = length2; 1331 | const h0 = 0.5 * radius / Math.cos(Math.PI / 3); 1332 | const h1 = 0.5 * radius * Math.tan(Math.PI / 3); 1333 | set(this.bounds.min, -h1, -0.5 * radius, -length2); 1334 | set(this.bounds.max, h1, h0, length2); 1335 | } 1336 | } 1337 | export { Box, BoxFrame, CappedCone, CappedCylinder, CappedTorus, Capsule, Cone, HexagonalPrism, Link, SolidAngle, Sphere, Torus, TriangularPrism_ }; 1338 | -------------------------------------------------------------------------------- /lib/index.umd.js: -------------------------------------------------------------------------------- 1 | var is=Object.defineProperty,es=Object.defineProperties;var as=Object.getOwnPropertyDescriptors;var mt=Object.getOwnPropertySymbols;var hs=Object.prototype.hasOwnProperty,os=Object.prototype.propertyIsEnumerable;var ct=(p,D,x)=>D in p?is(p,D,{enumerable:!0,configurable:!0,writable:!0,value:x}):p[D]=x,bt=(p,D)=>{for(var x in D||(D={}))hs.call(D,x)&&ct(p,x,D[x]);if(mt)for(var x of mt(D))os.call(D,x)&&ct(p,x,D[x]);return p},Mt=(p,D)=>es(p,as(D));var g=(p,D,x)=>(ct(p,typeof D!="symbol"?D+"":D,x),x);(function(p,D){typeof exports=="object"&&typeof module!="undefined"?D(exports):typeof define=="function"&&define.amd?define(["exports"],D):(p=typeof globalThis!="undefined"?globalThis:p||self,D(p["sdf-csg"]={}))})(this,function(p){"use strict";var D=1e-6,x=typeof Float32Array!="undefined"?Float32Array:Array;Math.hypot||(Math.hypot=function(){for(var n=0,t=arguments.length;t--;)n+=arguments[t]*arguments[t];return Math.sqrt(n)});function pt(){var n=new x(9);return x!=Float32Array&&(n[1]=0,n[2]=0,n[3]=0,n[5]=0,n[6]=0,n[7]=0),n[0]=1,n[4]=1,n[8]=1,n}function xt(){var n=new x(16);return x!=Float32Array&&(n[1]=0,n[2]=0,n[3]=0,n[4]=0,n[6]=0,n[7]=0,n[8]=0,n[9]=0,n[11]=0,n[12]=0,n[13]=0,n[14]=0),n[0]=1,n[5]=1,n[10]=1,n[15]=1,n}function vt(n,t){var s=t[0],r=t[1],i=t[2],e=t[3],a=t[4],h=t[5],c=t[6],u=t[7],o=t[8],d=t[9],f=t[10],m=t[11],y=t[12],l=t[13],M=t[14],w=t[15],v=s*h-r*a,_=s*c-i*a,L=s*u-e*a,H=r*c-i*h,O=r*u-e*h,W=i*u-e*c,z=o*l-d*y,j=o*M-f*y,R=o*w-m*y,tt=d*M-f*l,st=d*w-m*l,nt=f*w-m*M,A=v*nt-_*st+L*tt+H*R-O*j+W*z;return A?(A=1/A,n[0]=(h*nt-c*st+u*tt)*A,n[1]=(i*st-r*nt-e*tt)*A,n[2]=(l*W-M*O+w*H)*A,n[3]=(f*O-d*W-m*H)*A,n[4]=(c*R-a*nt-u*j)*A,n[5]=(s*nt-i*R+e*j)*A,n[6]=(M*L-y*W-w*_)*A,n[7]=(o*W-f*L+m*_)*A,n[8]=(a*st-h*R+u*z)*A,n[9]=(r*R-s*st-e*z)*A,n[10]=(y*O-l*L+w*v)*A,n[11]=(d*L-o*O-m*v)*A,n[12]=(h*j-a*tt-c*z)*A,n[13]=(s*tt-r*j+i*z)*A,n[14]=(l*_-y*H-M*v)*A,n[15]=(o*H-d*_+f*v)*A,n):null}function gt(n,t,s,r){var i=t[0],e=t[1],a=t[2],h=t[3],c=i+i,u=e+e,o=a+a,d=i*c,f=i*u,m=i*o,y=e*u,l=e*o,M=a*o,w=h*c,v=h*u,_=h*o,L=r[0],H=r[1],O=r[2];return n[0]=(1-(y+M))*L,n[1]=(f+_)*L,n[2]=(m-v)*L,n[3]=0,n[4]=(f-_)*H,n[5]=(1-(d+M))*H,n[6]=(l+w)*H,n[7]=0,n[8]=(m+v)*O,n[9]=(l-w)*O,n[10]=(1-(d+y))*O,n[11]=0,n[12]=s[0],n[13]=s[1],n[14]=s[2],n[15]=1,n}function b(){var n=new x(3);return x!=Float32Array&&(n[0]=0,n[1]=0,n[2]=0),n}function ut(n){var t=new x(3);return t[0]=n[0],t[1]=n[1],t[2]=n[2],t}function X(n){var t=n[0],s=n[1],r=n[2];return Math.hypot(t,s,r)}function $(n,t,s){var r=new x(3);return r[0]=n,r[1]=t,r[2]=s,r}function N(n,t){return n[0]=t[0],n[1]=t[1],n[2]=t[2],n}function S(n,t,s,r){return n[0]=t,n[1]=s,n[2]=r,n}function Y(n,t,s){return n[0]=t[0]+s[0],n[1]=t[1]+s[1],n[2]=t[2]+s[2],n}function rt(n,t,s){return n[0]=t[0]-s[0],n[1]=t[1]-s[1],n[2]=t[2]-s[2],n}function yt(n,t,s){return n[0]=t[0]*s[0],n[1]=t[1]*s[1],n[2]=t[2]*s[2],n}function T(n,t,s){return n[0]=Math.min(t[0],s[0]),n[1]=Math.min(t[1],s[1]),n[2]=Math.min(t[2],s[2]),n}function P(n,t,s){return n[0]=Math.max(t[0],s[0]),n[1]=Math.max(t[1],s[1]),n[2]=Math.max(t[2],s[2]),n}function Dt(n,t,s){return n[0]=t[0]*s,n[1]=t[1]*s,n[2]=t[2]*s,n}function et(n,t){return n[0]=-t[0],n[1]=-t[1],n[2]=-t[2],n}function dt(n,t){var s=t[0],r=t[1],i=t[2],e=s*s+r*r+i*i;return e>0&&(e=1/Math.sqrt(e)),n[0]=t[0]*e,n[1]=t[1]*e,n[2]=t[2]*e,n}function it(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function at(n,t,s){var r=t[0],i=t[1],e=t[2],a=s[0],h=s[1],c=s[2];return n[0]=i*c-e*h,n[1]=e*a-r*c,n[2]=r*h-i*a,n}function G(n,t,s){var r=t[0],i=t[1],e=t[2],a=s[3]*r+s[7]*i+s[11]*e+s[15];return a=a||1,n[0]=(s[0]*r+s[4]*i+s[8]*e+s[12])/a,n[1]=(s[1]*r+s[5]*i+s[9]*e+s[13])/a,n[2]=(s[2]*r+s[6]*i+s[10]*e+s[14])/a,n}var I=rt,wt=yt,Ut=X;(function(){var n=b();return function(t,s,r,i,e,a){var h,c;for(s||(s=3),r||(r=0),i?c=Math.min(i*s+r,t.length):c=t.length,h=r;h0&&(a=1/Math.sqrt(a)),n[0]=s*a,n[1]=r*a,n[2]=i*a,n[3]=e*a,n}(function(){var n=_t();return function(t,s,r,i,e,a){var h,c;for(s||(s=4),r||(r=0),i?c=Math.min(i*s+r,t.length):c=t.length,h=r;hD?(f=Math.acos(m),y=Math.sin(f),l=Math.sin((1-r)*f)/y,M=Math.sin(r*f)/y):(l=1-r,M=r),n[0]=l*i+M*c,n[1]=l*e+M*u,n[2]=l*a+M*o,n[3]=l*h+M*d,n}function Pt(n,t){var s=t[0]+t[4]+t[8],r;if(s>0)r=Math.sqrt(s+1),n[3]=.5*r,r=.5/r,n[0]=(t[5]-t[7])*r,n[1]=(t[6]-t[2])*r,n[2]=(t[1]-t[3])*r;else{var i=0;t[4]>t[0]&&(i=1),t[8]>t[i*3+i]&&(i=2);var e=(i+1)%3,a=(i+2)%3;r=Math.sqrt(t[i*3+i]-t[e*3+e]-t[a*3+a]+1),n[i]=.5*r,r=.5/r,n[3]=(t[e*3+a]-t[a*3+e])*r,n[e]=(t[e*3+i]+t[i*3+e])*r,n[a]=(t[a*3+i]+t[i*3+a])*r}return n}var lt=$t;(function(){var n=b(),t=$(1,0,0),s=$(0,1,0);return function(r,i,e){var a=it(i,e);return a<-.999999?(at(n,t,i),Ut(n)<1e-6&&at(n,s,i),dt(n,n),St(r,n,Math.PI),r):a>.999999?(r[0]=0,r[1]=0,r[2]=0,r[3]=1,r):(at(n,i,e),r[0]=n[0],r[1]=n[1],r[2]=n[2],r[3]=1+a,lt(r,r))}})(),function(){var n=V(),t=V();return function(s,r,i,e,a,h){return ht(n,r,a,h),ht(t,i,e,h),ht(s,n,t,2*h*(1-h)),s}}(),function(){var n=pt();return function(t,s,r,i){return n[0]=r[0],n[3]=r[1],n[6]=r[2],n[1]=i[0],n[4]=i[1],n[7]=i[2],n[2]=-s[0],n[5]=-s[1],n[8]=-s[2],lt(t,Pt(t,n))}}();function q(){var n=new x(2);return x!=Float32Array&&(n[0]=0,n[1]=0),n}function C(n,t){var s=new x(2);return s[0]=n,s[1]=t,s}function Ft(n,t,s){return n[0]=t[0]+s[0],n[1]=t[1]+s[1],n}function Tt(n,t,s){return n[0]=t[0]-s[0],n[1]=t[1]-s[1],n}function It(n,t,s){return n[0]=t[0]*s[0],n[1]=t[1]*s[1],n}function ft(n,t,s){return n[0]=Math.max(t[0],s[0]),n[1]=Math.max(t[1],s[1]),n}function K(n,t,s){return n[0]=t[0]*s,n[1]=t[1]*s,n}function F(n){var t=n[0],s=n[1];return Math.hypot(t,s)}function B(n,t){return n[0]*t[0]+n[1]*t[1]}var Z=Tt,Bt=It;(function(){var n=q();return function(t,s,r,i,e,a){var h,c;for(s||(s=2),r||(r=0),i?c=Math.min(i*s+r,t.length):c=t.length,h=r;h=t&&f[l[1]]>=t)return;const M=f[l[1]]-f[l[0]],w=(t-f[l[0]])/M,v=[E.points[l[1]][0]-E.points[l[0]][0],E.points[l[1]][1]-E.points[l[0]][1],E.points[l[1]][2]-E.points[l[0]][2]],_=[E.points[l[0]][0]+v[0]*w,E.points[l[0]][1]+v[1]*w,E.points[l[0]][2]+v[2]*w];m=[m[0]+_[0]+u,m[1]+_[1]+o,m[2]+_[2]+d],y++}),e.push([m[0]/y,m[1]/y,m[2]/y]),a[[u,o,d].toString()]=e.length-1,a[[u,o,d].toString()]}const c=[];for(let u=0;u=t?1:0,m=n.get(u+1,o+0,d+0)>=t?1:0,y=n.get(u+0,o+1,d+0)>=t?1:0,l=n.get(u+0,o+0,d+1)>=t?1:0;if(f+m===1&&o>0&&d>0){const M=h(u+0,o-1,d-1),w=h(u+0,o-1,d+0),v=h(u+0,o+0,d+0),_=h(u+0,o+0,d-1);m0&&d>0){const M=h(u-1,o+0,d-1),w=h(u+0,o+0,d-1),v=h(u+0,o+0,d+0),_=h(u-1,o+0,d+0);y0&&o>0){const M=h(u-1,o-1,d+0),w=h(u+0,o-1,d+0),v=h(u+0,o+0,d+0),_=h(u-1,o+0,d+0);l>f?(c.push([M,w,v]),c.push([M,v,_])):(c.push([M,v,w]),c.push([M,_,v]))}}return{positions:e,cells:c}}class U{constructor(){g(this,"_userData",null);g(this,"bounds",{min:$(0,0,0),max:$(0,0,0)})}normal(t,s,r){const i=.001,e=this.density(t,s,r),a=this.density(t+i,s,r),h=this.density(t,s+i,r),c=this.density(t,s,r+i),u=$((a-e)/i,(h-e)/i,(c-e)/i);return dt(b(),u)}generateMesh(t,s){let r=performance.now();const i=this.generateGrid(t,s);console.log(`Grid: ${Math.round(performance.now()-r)} ms`),r=performance.now();const e=Gt(i,0);console.log(`Isosurface extraction: ${Math.round(performance.now()-r)} ms`);const a=I(b(),this.bounds.min,[s,s,s]),h=Y(b(),this.bounds.max,[s,s,s]),c=I(b(),h,a);for(const d of e.positions)wt(d,d,[c[0]/t[0],c[1]/t[1],c[2]/t[2]]),Y(d,d,a);const u=[];for(const d of e.positions)u.push(this.normal(d[0],d[1],d[2]));let o=null;if(this.getUserData(0,0,0)!==null){o=[];for(const d of e.positions)o.push(this.getUserData(d[0],d[1],d[2]))}return Mt(bt({},e),{normals:u,userdata:o})}generateGrid(t,s){const r=new Float32Array((t[0]+1)*(t[1]+1)*(t[2]+1)),i=I(b(),this.bounds.min,[s,s,s]),e=Y(b(),this.bounds.max,[s,s,s]),a=(e[0]-i[0])/t[0],h=(e[1]-i[1])/t[1],c=(e[2]-i[2])/t[2];for(let u=0;ur[t[0]*t[2]*o+t[0]*d+u],shape:[t[0]+1,t[1]+1,t[2]+1]}}setUserData(t){return this._userData=t.slice(),this}getUserData(t,s,r){return this._userData}union(t){return new Yt(this,t)}subtract(t){return new Vt(t,this)}intersect(t){return new kt(t,this)}smoothUnion(t,s){return new Ht(this,t,s)}smoothSubtract(t,s){return new Lt(t,this,s)}smoothIntersect(t,s){return new Ot(this,t,s)}translate(t,s,r){return new J(this,[t,s,r],V(),1)}rotate(t){return new J(this,[0,0,0],t,1)}rotateX(t){return new J(this,[0,0,0],At(V(),V(),t),1)}rotateY(t){return new J(this,[0,0,0],qt(V(),V(),t),1)}rotateZ(t){return new J(this,[0,0,0],Ct(V(),V(),t),1)}scale(t){return new J(this,[0,0,0],V(),t)}round(t){return new Xt(this,t)}}class Vt extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r);return Math.max(-i,e)});this.sdf1=t,this.sdf2=s,N(this.bounds.min,s.bounds.min),N(this.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r);return Math.min(i,e)});this.sdf1=t,this.sdf2=s,T(this.bounds.min,t.bounds.min,s.bounds.min),P(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r);return Math.max(i,e)});this.sdf1=t,this.sdf2=s,P(this.bounds.min,t.bounds.min,s.bounds.min),T(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r),a=k(.5-.5*(e+i)/this.smoothness,0,1);return ot(e,-i,a)+this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=r,N(this.bounds.min,s.bounds.min),N(this.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r),a=k(.5+.5*(e-i)/this.smoothness,0,1);return ot(e,i,a)-this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=r,T(this.bounds.min,t.bounds.min,s.bounds.min),P(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=this.sdf1.density(t,s,r),e=this.sdf2.density(t,s,r),a=k(.5-.5*(e-i)/this.smoothness,0,1);return ot(e,i,a)+this.smoothness*a*(1-a)});this.sdf1=t,this.sdf2=s,this.smoothness=r,P(this.bounds.min,t.bounds.min,s.bounds.min),T(this.bounds.max,t.bounds.max,s.bounds.max)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=this.sdf1.getUserData(t,s,r),e=this.sdf2.getUserData(t,s,r);if(i!==null&&e===null)return i;if(i===null&&e!==null)return e;if(i===null&&e===null)return null;const a=Math.abs(this.sdf1.density(t,s,r)),h=Math.abs(this.sdf2.density(t,s,r)),c=a/(a+h),u=[];for(let o=0;o{const i=$(t,s,r);return G(i,i,this.matrix),this.sdf.density(i[0],i[1],i[2])});this.sdf=t,this.matrix=gt(xt(),r,s,$(i,i,i));const e=ut(this.sdf.bounds.min),a=ut(this.sdf.bounds.max),h=rt(b(),a,e),c=Y(b(),e,[h[0],0,0]),u=Y(b(),e,[h[0],h[1],0]),o=Y(b(),e,[0,h[1],0]),d=I(b(),a,[h[0],0,0]),f=I(b(),a,[h[0],h[1],0]),m=I(b(),a,[0,h[1],0]);G(e,e,this.matrix),G(a,a,this.matrix),G(c,c,this.matrix),G(u,u,this.matrix),G(o,o,this.matrix),G(d,d,this.matrix),G(f,f,this.matrix),G(m,m,this.matrix),T(this.bounds.min,e,a),T(this.bounds.min,this.bounds.min,c),T(this.bounds.min,this.bounds.min,u),T(this.bounds.min,this.bounds.min,o),T(this.bounds.min,this.bounds.min,d),T(this.bounds.min,this.bounds.min,f),T(this.bounds.min,this.bounds.min,m),P(this.bounds.max,e,a),P(this.bounds.max,this.bounds.max,c),P(this.bounds.max,this.bounds.max,u),P(this.bounds.max,this.bounds.max,o),P(this.bounds.max,this.bounds.max,d),P(this.bounds.max,this.bounds.max,f),P(this.bounds.max,this.bounds.max,m),vt(this.matrix,this.matrix)}getUserData(t,s,r){if(this._userData!==null)return this._userData;const i=G(b(),[t,s,r],this.matrix);return this.sdf.getUserData(i[0],i[1],i[2])}}class Xt extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>this.sdf.density(t,s,r)-this.radius);this.sdf=t,this.radius=s,I(this.bounds.min,t.bounds.min,[s,s,s]),Y(this.bounds.max,t.bounds.max,[s,s,s])}getUserData(t,s,r){return this._userData!==null?this._userData:this.sdf.getUserData(t,s,r)}}class Zt extends U{constructor(t,s,r){super();g(this,"radii");g(this,"density",(t,s,r)=>{const i=$(t,s,r),e=Q(b(),i),a=rt(b(),e,this.radii),h=P(b(),a,$(0,0,0));return X(h)+Math.min(Math.max(a[0],Math.max(a[1],a[2])),0)});this.radii=$(t,s,r),et(this.bounds.min,this.radii),N(this.bounds.max,this.radii)}}class Nt extends U{constructor(t){super();g(this,"density",(t,s,r)=>{const i=$(t,s,r);return X(i)-this.radius});this.radius=t,et(this.bounds.min,[this.radius,this.radius,this.radius]),S(this.bounds.max,this.radius,this.radius,this.radius)}}class Jt extends U{constructor(t,s,r,i){super();g(this,"radii");g(this,"density",(t,s,r)=>{const i=rt(b(),Q(b(),$(t,s,r)),this.radii),e=$(this.edge,this.edge,this.edge),a=I(b(),Q(b(),Y(b(),i,e)),e);return Math.min(Math.min(X(P(b(),[i[0],a[1],a[2]],[0,0,0]))+Math.min(Math.max(i[0],Math.max(a[1],a[2])),0),X(P(b(),[a[0],i[1],a[2]],[0,0,0]))+Math.min(Math.max(a[0],Math.max(i[1],a[2])),0)),X(P(b(),[a[0],a[1],i[2]],[0,0,0]))+Math.min(Math.max(a[0],Math.max(a[1],i[2])),0))});this.edge=i,this.radii=$(t,s,r),et(this.bounds.min,this.radii),N(this.bounds.max,this.radii)}}class Kt extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=C(F(C(t,s))-this.majorRadius,r);return F(i)-this.minorRadius});this.majorRadius=t,this.minorRadius=s,S(this.bounds.min,-t-s,-t-s,-s),S(this.bounds.max,t+s,t+s,+s)}}class Qt extends U{constructor(t,s,r){super();g(this,"density",(t,s,r)=>{const i=C(Math.sin(this.angle),Math.cos(this.angle));t=Math.abs(t);const e=i[1]*t>i[0]*s?B([t,s],i):F([t,s]),a=$(t,s,r);return Math.sqrt(it(a,a)+this.majorRadius*this.majorRadius-2*this.majorRadius*e)-this.minorRadius});this.majorRadius=t,this.minorRadius=s,this.angle=r,S(this.bounds.min,-t-s,-t-s,-s),S(this.bounds.max,t+s,t+s,+s)}}class Wt extends U{constructor(t,s,r){super();g(this,"density",(t,s,r)=>{const i=$(t,Math.max(Math.abs(s)-this.length,0),r);return F(C(F([i[0],i[1]])-this.majorRadius,i[2]))-this.minorRadius});this.majorRadius=t,this.minorRadius=s,this.length=r,S(this.bounds.min,-t-s,-t-s-r,-s),S(this.bounds.max,t+s,t+s+r,+s)}}class zt extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=[Math.sin(this.angle),Math.cos(this.angle)],e=K(q(),C(i[0]/i[1],-1),this.height),a=C(F([t,r]),s),h=Z(q(),a,K(q(),e,k(B(a,e)/B(e,e),0,1))),c=Z(q(),a,Bt(q(),e,C(k(a[0]/e[0],0,1),1))),u=Math.sign(e[1]),o=Math.min(B(h,h),B(c,c)),d=Math.max(u*(a[0]*e[1]-a[1]*e[0]),u*(a[1]-e[1]));return Math.sqrt(o)*Math.sign(d)});this.angle=t,this.height=s;const r=s*Math.tan(t);S(this.bounds.min,-r,-s,-r),S(this.bounds.max,r,0,r)}}class jt extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=$(-.8660254,.5,.57735),e=$(t,s,r);Q(e,e);const a=K(q(),[i[0],i[1]],2*Math.min(B([i[0],i[1]],[e[0],e[1]]),0));e[0]-=a[0],e[1]-=a[1];const h=C(F(Z(q(),[e[0],e[1]],C(k(e[0],-i[2]*this.radius,i[2]*this.radius),this.radius)))*Math.sign(e[1]-this.radius),e[2]-this.length);return Math.min(Math.max(h[0],h[1]),0)+F(ft(q(),h,[0,0]))});this.radius=t,this.length=s;const r=t/Math.cos(Math.PI/6);S(this.bounds.min,-r,-t,-s),S(this.bounds.max,r,t,s)}}class Rt extends U{constructor(t,s,r){super();g(this,"density",(t,s,r)=>{const i=$(t,s,r),e=I(b(),i,this.pointA),a=I(b(),this.pointB,this.pointA),h=k(it(e,a)/it(a,a),0,1);return X(I(b(),e,Dt(b(),a,h)))-this.radius});this.pointA=t,this.pointB=s,this.radius=r;const i=T(b(),t,s),e=P(b(),t,s);I(this.bounds.min,i,[r,r,r]),Y(this.bounds.max,e,[r,r,r])}}class ts extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=Z(q(),Et(q(),C(F([t,r]),s)),C(this.radius,this.length));return Math.min(Math.max(i[0],i[1]),0)+F(ft(q(),i,[0,0]))});this.length=t,this.radius=s,S(this.bounds.min,-s,-t,-s),S(this.bounds.max,s,t,s)}}class ss extends U{constructor(t,s,r){super();g(this,"density",(t,s,r)=>{const i=C(F([t,r]),s),e=C(this.radius2,this.length),a=C(this.radius2-this.radius1,2*this.length),h=C(i[0]-Math.min(i[0],i[1]<0?this.radius1:this.radius2),Math.abs(i[1])-this.length),c=Z(q(),e,i),u=Ft(q(),Z(q(),i,e),K(q(),a,k(B(c,a)/B(a,a),0,1)));return(u[0]<0&&h[1]<0?-1:1)*Math.sqrt(Math.min(B(h,h),B(u,u)))});this.length=t,this.radius1=s,this.radius2=r;const i=Math.max(s,r);S(this.bounds.min,-i,-t,-i),S(this.bounds.max,i,t,i)}}class ns extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=C(Math.sin(this.angle),Math.cos(this.angle)),e=C(F([t,r]),s),a=F(e)-this.radius,h=F(Z(q(),e,K(q(),i,k(B(e,i),0,this.radius))));return Math.max(a,h*Math.sign(i[1]*e[0]-i[0]*e[1]))});this.angle=t,this.radius=s;const r=s*Math.sin(t);S(this.bounds.min,-r,0,-r),S(this.bounds.max,r,s,r)}}class rs extends U{constructor(t,s){super();g(this,"density",(t,s,r)=>{const i=Q(b(),[t,s,r]);return Math.max(i[2]-this.length,Math.max(i[0]*.866025+s*.5,-s)-this.radius*.5)});this.radius=t,this.length=s;const r=.5*t/Math.cos(Math.PI/3),i=.5*t*Math.tan(Math.PI/3);S(this.bounds.min,-i,-.5*t,-s),S(this.bounds.max,i,r,s)}}p.Box=Zt,p.BoxFrame=Jt,p.CappedCone=ss,p.CappedCylinder=ts,p.CappedTorus=Qt,p.Capsule=Rt,p.Cone=zt,p.HexagonalPrism=jt,p.Link=Wt,p.SolidAngle=ns,p.Sphere=Nt,p.Torus=Kt,p.TriangularPrism_=rs,Object.defineProperty(p,"__esModule",{value:!0}),p[Symbol.toStringTag]="Module"}); 2 | -------------------------------------------------------------------------------- /lib/isosurface.d.ts: -------------------------------------------------------------------------------- 1 | interface Grid { 2 | get(x: number, y: number, z: number): number; 3 | shape: number[]; 4 | } 5 | export declare function isosurfaceGenerator(density: Grid, level: number): { 6 | positions: number[][]; 7 | cells: number[][]; 8 | }; 9 | export {}; 10 | //# sourceMappingURL=isosurface.d.ts.map -------------------------------------------------------------------------------- /lib/isosurface.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"isosurface.d.ts","sourceRoot":"","sources":["../src/isosurface.ts"],"names":[],"mappings":"AA2BA,UAAU,IAAI;IACZ,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;;;EAqG/D"} -------------------------------------------------------------------------------- /lib/primitives.d.ts: -------------------------------------------------------------------------------- 1 | import { SDF } from "./sdf"; 2 | export declare class Box extends SDF { 3 | private radii; 4 | constructor(width: number, height: number, depth: number); 5 | density: (x: number, y: number, z: number) => number; 6 | } 7 | export declare class Sphere extends SDF { 8 | private radius; 9 | constructor(radius: number); 10 | density: (x: number, y: number, z: number) => number; 11 | } 12 | export declare class BoxFrame extends SDF { 13 | private edge; 14 | private radii; 15 | constructor(width: number, height: number, depth: number, edge: number); 16 | density: (x: number, y: number, z: number) => number; 17 | } 18 | export declare class Torus extends SDF { 19 | private majorRadius; 20 | private minorRadius; 21 | constructor(majorRadius: number, minorRadius: number); 22 | density: (x: number, y: number, z: number) => number; 23 | } 24 | export declare class CappedTorus extends SDF { 25 | private majorRadius; 26 | private minorRadius; 27 | private angle; 28 | constructor(majorRadius: number, minorRadius: number, angle: number); 29 | density: (x: number, y: number, z: number) => number; 30 | } 31 | export declare class Link extends SDF { 32 | private majorRadius; 33 | private minorRadius; 34 | private length; 35 | constructor(majorRadius: number, minorRadius: number, length: number); 36 | density: (x: number, y: number, z: number) => number; 37 | } 38 | export declare class Cone extends SDF { 39 | private angle; 40 | private height; 41 | constructor(angle: number, height: number); 42 | density: (x: number, y: number, z: number) => number; 43 | } 44 | export declare class HexagonalPrism extends SDF { 45 | private radius; 46 | private length; 47 | constructor(radius: number, length: number); 48 | density: (x: number, y: number, z: number) => number; 49 | } 50 | export declare class Capsule extends SDF { 51 | private pointA; 52 | private pointB; 53 | private radius; 54 | constructor(pointA: number[], pointB: number[], radius: number); 55 | density: (x: number, y: number, z: number) => number; 56 | } 57 | export declare class CappedCylinder extends SDF { 58 | private length; 59 | private radius; 60 | constructor(length: number, radius: number); 61 | density: (x: number, y: number, z: number) => number; 62 | } 63 | export declare class CappedCone extends SDF { 64 | private length; 65 | private radius1; 66 | private radius2; 67 | constructor(length: number, radius1: number, radius2: number); 68 | density: (x: number, y: number, z: number) => number; 69 | } 70 | export declare class SolidAngle extends SDF { 71 | private angle; 72 | private radius; 73 | constructor(angle: number, radius: number); 74 | density: (x: number, y: number, z: number) => number; 75 | } 76 | export declare class TriangularPrism_ extends SDF { 77 | private radius; 78 | private length; 79 | constructor(radius: number, length: number); 80 | density: (x: number, y: number, z: number) => number; 81 | } 82 | //# sourceMappingURL=primitives.d.ts.map -------------------------------------------------------------------------------- /lib/primitives.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAG5B,qBAAa,GAAI,SAAQ,GAAG;IAC1B,OAAO,CAAC,KAAK,CAAC;gBAEF,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAOjD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAM/C;CACH;AAED,qBAAa,MAAO,SAAQ,GAAG;IACjB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAM3B,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAG/C;CACH;AAED,qBAAa,QAAS,SAAQ,GAAG;IAE2B,OAAO,CAAC,IAAI;IADtE,OAAO,CAAC,KAAK,CAAC;gBACF,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAU,IAAI,EAAE,MAAM;IAOvE,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAc/C;CACH;AAED,qBAAa,KAAM,SAAQ,GAAG;IAChB,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,WAAW;gBAAxC,WAAW,EAAE,MAAM,EAAU,WAAW,EAAE,MAAM;IAM7D,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAG/C;CACH;AAED,qBAAa,WAAY,SAAQ,GAAG;IACtB,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,KAAK;gBAA/D,WAAW,EAAE,MAAM,EAAU,WAAW,EAAE,MAAM,EAAU,KAAK,EAAE,MAAM;IAMpF,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAQ/C;CACH;AAED,qBAAa,IAAK,SAAQ,GAAG;IACf,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,MAAM;gBAAhE,WAAW,EAAE,MAAM,EAAU,WAAW,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAMrF,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAG/C;CACH;AAED,qBAAa,IAAK,SAAQ,GAAG;IACf,OAAO,CAAC,KAAK;IAAU,OAAO,CAAC,MAAM;gBAA7B,KAAK,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAOlD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAmB/C;CACH;AAED,qBAAa,cAAe,SAAQ,GAAG;IACzB,OAAO,CAAC,MAAM;IAAU,OAAO,CAAC,MAAM;gBAA9B,MAAM,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAOnD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAmB/C;CACH;AAED,qBAAa,OAAQ,SAAQ,GAAG;IAClB,OAAO,CAAC,MAAM;IAAY,OAAO,CAAC,MAAM;IAAY,OAAO,CAAC,MAAM;gBAA1D,MAAM,EAAE,MAAM,EAAE,EAAU,MAAM,EAAE,MAAM,EAAE,EAAU,MAAM,EAAE,MAAM;IAQ/E,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAM/C;CACH;AAED,qBAAa,cAAe,SAAQ,GAAG;IACzB,OAAO,CAAC,MAAM;IAAU,OAAO,CAAC,MAAM;gBAA9B,MAAM,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAMnD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAO/C;CACH;AAED,qBAAa,UAAW,SAAQ,GAAG;IACrB,OAAO,CAAC,MAAM;IAAU,OAAO,CAAC,OAAO;IAAU,OAAO,CAAC,OAAO;gBAAxD,MAAM,EAAE,MAAM,EAAU,OAAO,EAAE,MAAM,EAAU,OAAO,EAAE,MAAM;IAO7E,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAgB/C;CACH;AAED,qBAAa,UAAW,SAAQ,GAAG;IACrB,OAAO,CAAC,KAAK;IAAU,OAAO,CAAC,MAAM;gBAA7B,KAAK,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAOlD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAQ/C;CACH;AAED,qBAAa,gBAAiB,SAAQ,GAAG;IAC3B,OAAO,CAAC,MAAM;IAAU,OAAO,CAAC,MAAM;gBAA9B,MAAM,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM;IAQnD,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAG/C;CACH"} -------------------------------------------------------------------------------- /lib/sdf.d.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | export interface Bounds { 3 | min: vec3; 4 | max: vec3; 5 | } 6 | interface Mesh { 7 | positions: number[][]; 8 | normals: number[][]; 9 | cells: number[][]; 10 | userdata: number[][] | null; 11 | } 12 | export declare abstract class SDF { 13 | protected _userData: number[] | null; 14 | bounds: Bounds; 15 | abstract density(x: number, y: number, z: number): number; 16 | normal(x: number, y: number, z: number): vec3; 17 | generateMesh(resolution: number[], padding: number): Mesh; 18 | private generateGrid; 19 | setUserData(data: number[]): this; 20 | getUserData(x: number, y: number, z: number): number[] | null; 21 | union(sdf: SDF): Union; 22 | subtract(sdf: SDF): Subtraction; 23 | intersect(sdf: SDF): Intersection; 24 | smoothUnion(sdf: SDF, smoothness: number): SmoothUnion; 25 | smoothSubtract(sdf: SDF, smoothness: number): SmoothSubtraction; 26 | smoothIntersect(sdf: SDF, smoothness: number): SmoothIntersection; 27 | translate(x: number, y: number, z: number): Transform; 28 | rotate(quat: number[]): Transform; 29 | rotateX(radians: number): Transform; 30 | rotateY(radians: number): Transform; 31 | rotateZ(radians: number): Transform; 32 | scale(amount: number): Transform; 33 | round(amount: number): Round; 34 | } 35 | export declare class Subtraction extends SDF { 36 | private sdf1; 37 | private sdf2; 38 | constructor(sdf1: SDF, sdf2: SDF); 39 | density: (x: number, y: number, z: number) => number; 40 | getUserData(x: number, y: number, z: number): number[] | null; 41 | } 42 | export declare class Union extends SDF { 43 | private sdf1; 44 | private sdf2; 45 | constructor(sdf1: SDF, sdf2: SDF); 46 | density: (x: number, y: number, z: number) => number; 47 | getUserData(x: number, y: number, z: number): number[] | null; 48 | } 49 | export declare class Intersection extends SDF { 50 | private sdf1; 51 | private sdf2; 52 | constructor(sdf1: SDF, sdf2: SDF); 53 | density: (x: number, y: number, z: number) => number; 54 | getUserData(x: number, y: number, z: number): number[] | null; 55 | } 56 | export declare class SmoothSubtraction extends SDF { 57 | private sdf1; 58 | private sdf2; 59 | private smoothness; 60 | constructor(sdf1: SDF, sdf2: SDF, smoothness: number); 61 | density: (x: number, y: number, z: number) => number; 62 | getUserData(x: number, y: number, z: number): number[] | null; 63 | } 64 | export declare class SmoothUnion extends SDF { 65 | private sdf1; 66 | private sdf2; 67 | private smoothness; 68 | constructor(sdf1: SDF, sdf2: SDF, smoothness: number); 69 | density: (x: number, y: number, z: number) => number; 70 | getUserData(x: number, y: number, z: number): number[] | null; 71 | } 72 | export declare class SmoothIntersection extends SDF { 73 | private sdf1; 74 | private sdf2; 75 | private smoothness; 76 | constructor(sdf1: SDF, sdf2: SDF, smoothness: number); 77 | density: (x: number, y: number, z: number) => number; 78 | getUserData(x: number, y: number, z: number): number[] | null; 79 | } 80 | export declare class Transform extends SDF { 81 | private sdf; 82 | private matrix; 83 | constructor(sdf: SDF, translation: number[], rotation: number[], scale: number); 84 | density: (x: number, y: number, z: number) => number; 85 | getUserData(x: number, y: number, z: number): number[] | null; 86 | } 87 | export declare class Round extends SDF { 88 | private sdf; 89 | private radius; 90 | constructor(sdf: SDF, radius: number); 91 | density: (x: number, y: number, z: number) => number; 92 | getUserData(x: number, y: number, z: number): number[] | null; 93 | } 94 | export {}; 95 | //# sourceMappingURL=sdf.d.ts.map -------------------------------------------------------------------------------- /lib/sdf.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sdf.d.ts","sourceRoot":"","sources":["../src/sdf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,IAAI,EAAE,MAAM,WAAW,CAAC;AAI7C,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,IAAI,CAAC;CACX;AAED,UAAU,IAAI;IACZ,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC;CAC7B;AAED,8BAAsB,GAAG;IACvB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAQ;IAErC,MAAM,EAAE,MAAM,CAGnB;IAEF,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAElD,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAUtC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAgChE,OAAO,CAAC,YAAY;IAwBb,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE;IAK1B,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAI3C,KAAK,CAAC,GAAG,EAAE,GAAG;IAId,QAAQ,CAAC,GAAG,EAAE,GAAG;IAIjB,SAAS,CAAC,GAAG,EAAE,GAAG;IAIlB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;IAIxC,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;IAI3C,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;IAI5C,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAIzC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE;IAIrB,OAAO,CAAC,OAAO,EAAE,MAAM;IAIvB,OAAO,CAAC,OAAO,EAAE,MAAM;IAIvB,OAAO,CAAC,OAAO,EAAE,MAAM;IAIvB,KAAK,CAAC,MAAM,EAAE,MAAM;IAIpB,KAAK,CAAC,MAAM,EAAE,MAAM;CAG5B;AAED,qBAAa,WAAY,SAAQ,GAAG;IACtB,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;gBAAvB,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG;IAMzC,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAI/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,KAAM,SAAQ,GAAG;IAChB,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;gBAAvB,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG;IAMzC,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAI/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,YAAa,SAAQ,GAAG;IACvB,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;gBAAvB,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG;IAMzC,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAI/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,iBAAkB,SAAQ,GAAG;IAC5B,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,UAAU;gBAAhD,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG,EAAU,UAAU,EAAE,MAAM;IAMrE,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAK/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,WAAY,SAAQ,GAAG;IACtB,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,UAAU;gBAAhD,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG,EAAU,UAAU,EAAE,MAAM;IAMrE,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAK/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,kBAAmB,SAAQ,GAAG;IAC7B,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,IAAI;IAAO,OAAO,CAAC,UAAU;gBAAhD,IAAI,EAAE,GAAG,EAAU,IAAI,EAAE,GAAG,EAAU,UAAU,EAAE,MAAM;IAMrE,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAK/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAsBnD;AAED,qBAAa,SAAU,SAAQ,GAAG;IAGpB,OAAO,CAAC,GAAG;IAFvB,OAAO,CAAC,MAAM,CAAO;gBAED,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM;IA0C/E,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAI/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAOnD;AAED,qBAAa,KAAM,SAAQ,GAAG;IAChB,OAAO,CAAC,GAAG;IAAO,OAAO,CAAC,MAAM;gBAAxB,GAAG,EAAE,GAAG,EAAU,MAAM,EAAE,MAAM;IAM7C,OAAO,MAAO,MAAM,KAAK,MAAM,KAAK,MAAM,YAE/C;IAEK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;CAMnD"} -------------------------------------------------------------------------------- /lib/util.d.ts: -------------------------------------------------------------------------------- 1 | import { vec2, vec3 } from "gl-matrix"; 2 | export declare function vec3abs(out: vec3, p: vec3): vec3; 3 | export declare function vec2abs(out: vec2, p: vec2): vec2; 4 | export declare function clamp(n: number, min: number, max: number): number; 5 | export declare function mix(n0: number, n1: number, frac: number): number; 6 | //# sourceMappingURL=util.d.ts.map -------------------------------------------------------------------------------- /lib/util.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEvC,wBAAgB,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,QAKzC;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,QAIzC;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAExD;AAED,wBAAgB,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAEvD"} -------------------------------------------------------------------------------- /media/round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/round.png -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/screenshot.png -------------------------------------------------------------------------------- /media/smooth-intersect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/smooth-intersect.png -------------------------------------------------------------------------------- /media/smooth-subtract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/smooth-subtract.png -------------------------------------------------------------------------------- /media/smooth-union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/smooth-union.png -------------------------------------------------------------------------------- /media/user-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wwwtyro/sdf-csg/2c14e99f50f7d072a913f7cef0193c0550c51ac8/media/user-data.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdf-csg", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "clone": { 8 | "version": "1.0.4", 9 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 10 | "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", 11 | "dev": true 12 | }, 13 | "defaults": { 14 | "version": "1.0.3", 15 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", 16 | "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", 17 | "dev": true, 18 | "requires": { 19 | "clone": "^1.0.2" 20 | } 21 | }, 22 | "esbuild": { 23 | "version": "0.13.4", 24 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.4.tgz", 25 | "integrity": "sha512-wMA5eUwpavTBiNl+It6j8OQuKVh69l6z4DKDLzoTIqC+gChnPpcmqdA8WNHptUHRnfyML+mKEQPlW7Mybj8gHg==", 26 | "dev": true, 27 | "requires": { 28 | "esbuild-android-arm64": "0.13.4", 29 | "esbuild-darwin-64": "0.13.4", 30 | "esbuild-darwin-arm64": "0.13.4", 31 | "esbuild-freebsd-64": "0.13.4", 32 | "esbuild-freebsd-arm64": "0.13.4", 33 | "esbuild-linux-32": "0.13.4", 34 | "esbuild-linux-64": "0.13.4", 35 | "esbuild-linux-arm": "0.13.4", 36 | "esbuild-linux-arm64": "0.13.4", 37 | "esbuild-linux-mips64le": "0.13.4", 38 | "esbuild-linux-ppc64le": "0.13.4", 39 | "esbuild-openbsd-64": "0.13.4", 40 | "esbuild-sunos-64": "0.13.4", 41 | "esbuild-windows-32": "0.13.4", 42 | "esbuild-windows-64": "0.13.4", 43 | "esbuild-windows-arm64": "0.13.4" 44 | } 45 | }, 46 | "esbuild-android-arm64": { 47 | "version": "0.13.4", 48 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.4.tgz", 49 | "integrity": "sha512-elDJt+jNyoHFId0/dKsuVYUPke3EcquIyUwzJCH17a3ERglN3A9aMBI5zbz+xNZ+FbaDNdpn0RaJHCFLbZX+fA==", 50 | "dev": true, 51 | "optional": true 52 | }, 53 | "esbuild-darwin-64": { 54 | "version": "0.13.4", 55 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.4.tgz", 56 | "integrity": "sha512-zJQGyHRAdZUXlRzbN7W+7ykmEiGC+bq3Gc4GxKYjjWTgDRSEly98ym+vRNkDjXwXYD3gGzSwvH35+MiHAtWvLA==", 57 | "dev": true, 58 | "optional": true 59 | }, 60 | "esbuild-darwin-arm64": { 61 | "version": "0.13.4", 62 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.4.tgz", 63 | "integrity": "sha512-r8oYvAtqSGq8HNTZCAx4TdLE7jZiGhX9ooGi5AQAey37MA6XNaP8ZNlw9OCpcgpx3ryU2WctXwIqPzkHO7a8dg==", 64 | "dev": true, 65 | "optional": true 66 | }, 67 | "esbuild-freebsd-64": { 68 | "version": "0.13.4", 69 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.4.tgz", 70 | "integrity": "sha512-u9DRGkn09EN8+lCh6z7FKle7awi17PJRBuAKdRNgSo5ZrH/3m+mYaJK2PR2URHMpAfXiwJX341z231tSdVe3Yw==", 71 | "dev": true, 72 | "optional": true 73 | }, 74 | "esbuild-freebsd-arm64": { 75 | "version": "0.13.4", 76 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.4.tgz", 77 | "integrity": "sha512-q3B2k68Uf6gfjATjcK16DqxvjqRQkHL8aPoOfj4op+lSqegdXvBacB1d8jw8PxbWJ8JHpdTLdAVUYU80kotQXA==", 78 | "dev": true, 79 | "optional": true 80 | }, 81 | "esbuild-linux-32": { 82 | "version": "0.13.4", 83 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.4.tgz", 84 | "integrity": "sha512-UUYJPHSiKAO8KoN3Ls/iZtgDLZvK5HarES96aolDPWZnq9FLx4dIHM/x2z4Rxv9IYqQ/DxlPoE2Co1UPBIYYeA==", 85 | "dev": true, 86 | "optional": true 87 | }, 88 | "esbuild-linux-64": { 89 | "version": "0.13.4", 90 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.4.tgz", 91 | "integrity": "sha512-+RnohAKiiUW4UHLGRkNR1AnENW1gCuDWuygEtd4jxTNPIoeC7lbXGor7rtgjj9AdUzFgOEvAXyNNX01kJ8NueQ==", 92 | "dev": true, 93 | "optional": true 94 | }, 95 | "esbuild-linux-arm": { 96 | "version": "0.13.4", 97 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.4.tgz", 98 | "integrity": "sha512-BH5gKve4jglS7UPSsfwHSX79I5agC/lm4eKoRUEyo8lwQs89frQSRp2Xup+6SFQnxt3md5EsKcd2Dbkqeb3gPA==", 99 | "dev": true, 100 | "optional": true 101 | }, 102 | "esbuild-linux-arm64": { 103 | "version": "0.13.4", 104 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.4.tgz", 105 | "integrity": "sha512-+A188cAdd6QuSRxMIwRrWLjgphQA0LDAQ/ECVlrPVJwnx+1i64NjDZivoqPYLOTkSPIKntiWwMhhf0U5/RrPHQ==", 106 | "dev": true, 107 | "optional": true 108 | }, 109 | "esbuild-linux-mips64le": { 110 | "version": "0.13.4", 111 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.4.tgz", 112 | "integrity": "sha512-0xkwtPaUkG5xMTFGaQPe1AadSe5QAiQuD4Gix1O9k5Xo/U8xGIkw9UFUTvfEUeu71vFb6ZgsIacfP1NLoFjWNw==", 113 | "dev": true, 114 | "optional": true 115 | }, 116 | "esbuild-linux-ppc64le": { 117 | "version": "0.13.4", 118 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.4.tgz", 119 | "integrity": "sha512-E1+oJPP7A+j23GPo3CEpBhGwG1bni4B8IbTA3/3rvzjURwUMZdcN3Fhrz24rnjzdLSHmULtOE4VsbT42h1Om4Q==", 120 | "dev": true, 121 | "optional": true 122 | }, 123 | "esbuild-openbsd-64": { 124 | "version": "0.13.4", 125 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.4.tgz", 126 | "integrity": "sha512-xEkI1o5HYxDzbv9jSox0EsDxpwraG09SRiKKv0W8pH6O3bt+zPSlnoK7+I7Q69tkvONkpIq5n2o+c55uq0X7cw==", 127 | "dev": true, 128 | "optional": true 129 | }, 130 | "esbuild-sunos-64": { 131 | "version": "0.13.4", 132 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.4.tgz", 133 | "integrity": "sha512-bjXUMcODMnB6hQicLBBmmnBl7OMDyVpFahKvHGXJfDChIi5udiIRKCmFUFIRn+AUAKVlfrofRKdyPC7kBsbvGQ==", 134 | "dev": true, 135 | "optional": true 136 | }, 137 | "esbuild-windows-32": { 138 | "version": "0.13.4", 139 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.4.tgz", 140 | "integrity": "sha512-z4CH07pfyVY0XF98TCsGmLxKCl0kyvshKDbdpTekW9f2d+dJqn5mmoUyWhpSVJ0SfYWJg86FoD9nMbbaMVyGdg==", 141 | "dev": true, 142 | "optional": true 143 | }, 144 | "esbuild-windows-64": { 145 | "version": "0.13.4", 146 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.4.tgz", 147 | "integrity": "sha512-uVL11vORRPjocGLYam67rwFLd0LvkrHEs+JG+1oJN4UD9MQmNGZPa4gBHo6hDpF+kqRJ9kXgQSeDqUyRy0tj/Q==", 148 | "dev": true, 149 | "optional": true 150 | }, 151 | "esbuild-windows-arm64": { 152 | "version": "0.13.4", 153 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.4.tgz", 154 | "integrity": "sha512-vA6GLvptgftRcDcWngD5cMlL4f4LbL8JjU2UMT9yJ0MT5ra6hdZNFWnOeOoEtY4GtJ6OjZ0i+81sTqhAB0fMkg==", 155 | "dev": true, 156 | "optional": true 157 | }, 158 | "fsevents": { 159 | "version": "2.3.2", 160 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 161 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 162 | "dev": true, 163 | "optional": true 164 | }, 165 | "function-bind": { 166 | "version": "1.1.1", 167 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 168 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 169 | "dev": true 170 | }, 171 | "gl-matrix": { 172 | "version": "3.4.3", 173 | "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", 174 | "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" 175 | }, 176 | "has": { 177 | "version": "1.0.3", 178 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 179 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 180 | "dev": true, 181 | "requires": { 182 | "function-bind": "^1.1.1" 183 | } 184 | }, 185 | "is-core-module": { 186 | "version": "2.7.0", 187 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", 188 | "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", 189 | "dev": true, 190 | "requires": { 191 | "has": "^1.0.3" 192 | } 193 | }, 194 | "nanoid": { 195 | "version": "3.1.29", 196 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz", 197 | "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==", 198 | "dev": true 199 | }, 200 | "path-parse": { 201 | "version": "1.0.7", 202 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 203 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 204 | "dev": true 205 | }, 206 | "pepjs": { 207 | "version": "0.4.3", 208 | "resolved": "https://registry.npmjs.org/pepjs/-/pepjs-0.4.3.tgz", 209 | "integrity": "sha1-FggOlwqud5kTdWwtrviOqnSG30E=", 210 | "dev": true 211 | }, 212 | "picocolors": { 213 | "version": "0.2.1", 214 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", 215 | "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", 216 | "dev": true 217 | }, 218 | "postcss": { 219 | "version": "8.3.9", 220 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz", 221 | "integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==", 222 | "dev": true, 223 | "requires": { 224 | "nanoid": "^3.1.28", 225 | "picocolors": "^0.2.1", 226 | "source-map-js": "^0.6.2" 227 | } 228 | }, 229 | "regl": { 230 | "version": "2.1.0", 231 | "resolved": "https://registry.npmjs.org/regl/-/regl-2.1.0.tgz", 232 | "integrity": "sha512-oWUce/aVoEvW5l2V0LK7O5KJMzUSKeiOwFuJehzpSFd43dO5spP9r+sSUfhKtsky4u6MCqWJaRL+abzExynfTg==", 233 | "dev": true 234 | }, 235 | "resolve": { 236 | "version": "1.20.0", 237 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", 238 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", 239 | "dev": true, 240 | "requires": { 241 | "is-core-module": "^2.2.0", 242 | "path-parse": "^1.0.6" 243 | } 244 | }, 245 | "rollup": { 246 | "version": "2.58.0", 247 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.58.0.tgz", 248 | "integrity": "sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==", 249 | "dev": true, 250 | "requires": { 251 | "fsevents": "~2.3.2" 252 | } 253 | }, 254 | "source-map-js": { 255 | "version": "0.6.2", 256 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", 257 | "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", 258 | "dev": true 259 | }, 260 | "trackball-controller": { 261 | "version": "2.0.0", 262 | "resolved": "https://registry.npmjs.org/trackball-controller/-/trackball-controller-2.0.0.tgz", 263 | "integrity": "sha512-GNm5CZNKbXlWo1twjDbta3+T8+DthM5wLruMg7ZXk0nYBTgSQzYHh7ih2SmfDk1lvk6k+qQ0pfbyLCMn/n/uwg==", 264 | "dev": true, 265 | "requires": { 266 | "defaults": "^1.0.3", 267 | "gl-matrix": "^2.7.1", 268 | "pepjs": "^0.4.3" 269 | }, 270 | "dependencies": { 271 | "gl-matrix": { 272 | "version": "2.8.1", 273 | "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", 274 | "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==", 275 | "dev": true 276 | } 277 | } 278 | }, 279 | "typescript": { 280 | "version": "4.4.3", 281 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", 282 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", 283 | "dev": true 284 | }, 285 | "vite": { 286 | "version": "2.6.5", 287 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.6.5.tgz", 288 | "integrity": "sha512-vavXMChDUb4Oh4YunrK9BrH5Ox74cu0eOp0VuyI/iqFz1FqbWD72So2c9I87lLL2n0+6tFPV5ijow60KrtxuZg==", 289 | "dev": true, 290 | "requires": { 291 | "esbuild": "^0.13.2", 292 | "fsevents": "~2.3.2", 293 | "postcss": "^8.3.8", 294 | "resolve": "^1.20.0", 295 | "rollup": "^2.57.0" 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdf-csg", 3 | "version": "0.0.0", 4 | "description": "SDF-based constructive solid geometry", 5 | "keywords": [ 6 | "sdf", 7 | "csg", 8 | "procedural", 9 | "geometry", 10 | "mesh", 11 | "3d", 12 | "webgl" 13 | ], 14 | "author": { 15 | "name": "Rye Terrell", 16 | "email": "ryeterrell@ryeterrell.net", 17 | "url": "https://wwwtyro.net" 18 | }, 19 | "homepage": "https://github.com/wwwtyro/sdf-csg", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/wwwtyro/sdf-csg.git" 23 | }, 24 | "license": "Unlicense", 25 | "files": [ 26 | "lib" 27 | ], 28 | "main": "./lib/index.umd.js", 29 | "module": "./lib/index.es.js", 30 | "types": "./lib/index", 31 | "scripts": { 32 | "start": "vite --config vite.example-config.js --host 0.0.0.0", 33 | "tsc": "tsc --watch --noEmit --emitDeclarationOnly false", 34 | "build-lib": "tsc && vite --config vite.lib-config.js build", 35 | "build-example": "tsc && vite --config vite.example-config.js build", 36 | "clean": "rm -rf lib docs", 37 | "build": "npm run clean && npm run build-lib && npm run build-example" 38 | }, 39 | "exports": { 40 | ".": { 41 | "import": "./lib/index.es.js", 42 | "require": "./lib/index.umd.js" 43 | } 44 | }, 45 | "dependencies": { 46 | "gl-matrix": "^3.4.3" 47 | }, 48 | "devDependencies": { 49 | "regl": "^2.1.0", 50 | "trackball-controller": "^2.0.0", 51 | "typescript": "^4.3.2", 52 | "vite": "^2.5.4" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./primitives"; 2 | export type { Bounds } from "./sdf"; 3 | -------------------------------------------------------------------------------- /src/isosurface.ts: -------------------------------------------------------------------------------- 1 | const unitCube = { 2 | points: [ 3 | [0, 0, 0], 4 | [1, 0, 0], 5 | [0, 1, 0], 6 | [1, 1, 0], 7 | [0, 0, 1], 8 | [1, 0, 1], 9 | [0, 1, 1], 10 | [1, 1, 1], 11 | ], 12 | edges: [ 13 | [0, 1], 14 | [0, 2], 15 | [0, 4], 16 | [1, 3], 17 | [1, 5], 18 | [2, 3], 19 | [2, 6], 20 | [3, 7], 21 | [4, 5], 22 | [4, 6], 23 | [5, 7], 24 | [6, 7], 25 | ], 26 | }; 27 | 28 | interface Grid { 29 | get(x: number, y: number, z: number): number; 30 | shape: number[]; 31 | } 32 | 33 | export function isosurfaceGenerator(density: Grid, level: number) { 34 | const width = density.shape[0]; 35 | const height = density.shape[1]; 36 | const depth = density.shape[2]; 37 | 38 | const featurePoints: number[][] = []; 39 | const featurePointIndex: Record = {}; 40 | 41 | function getFeaturePointIndex(x: number, y: number, z: number) { 42 | if ([x, y, z].toString() in featurePointIndex) return featurePointIndex[[x, y, z].toString()]; 43 | const values: number[] = []; 44 | unitCube.points.forEach(function (v) { 45 | values.push(density.get(x + v[0], y + v[1], z + v[2])); 46 | }); 47 | let p = [0, 0, 0]; 48 | let sum = 0; 49 | unitCube.edges.forEach(function (e) { 50 | // if the surface doesn't pass through this edge, skip it 51 | if (values[e[0]] < level && values[e[1]] < level) return; 52 | if (values[e[0]] >= level && values[e[1]] >= level) return; 53 | // Calculate the rate of change of the density along this edge. 54 | const dv = values[e[1]] - values[e[0]]; 55 | // Figure out how far along this edge the surface lies (linear approximation). 56 | const dr = (level - values[e[0]]) / dv; 57 | // Figure out the direction of this edge. 58 | const r = [ 59 | unitCube.points[e[1]][0] - unitCube.points[e[0]][0], 60 | unitCube.points[e[1]][1] - unitCube.points[e[0]][1], 61 | unitCube.points[e[1]][2] - unitCube.points[e[0]][2], 62 | ]; 63 | // Figure out the point that the surface intersects this edge. 64 | const interp = [ 65 | unitCube.points[e[0]][0] + r[0] * dr, 66 | unitCube.points[e[0]][1] + r[1] * dr, 67 | unitCube.points[e[0]][2] + r[2] * dr, 68 | ]; 69 | // Add this intersection to the sum of intersections. 70 | p = [p[0] + interp[0] + x, p[1] + interp[1] + y, p[2] + interp[2] + z]; 71 | // Increment the edge intersection count for later averaging. 72 | sum++; 73 | }); 74 | featurePoints.push([p[0] / sum, p[1] / sum, p[2] / sum]); 75 | featurePointIndex[[x, y, z].toString()] = featurePoints.length - 1; 76 | return featurePointIndex[[x, y, z].toString()]; 77 | } 78 | 79 | const cells = []; 80 | 81 | for (let x = 0; x < width - 1; x++) { 82 | for (let y = 0; y < height - 1; y++) { 83 | for (let z = 0; z < depth - 1; z++) { 84 | const p0 = density.get(x + 0, y + 0, z + 0) >= level ? 1 : 0; 85 | const px = density.get(x + 1, y + 0, z + 0) >= level ? 1 : 0; 86 | const py = density.get(x + 0, y + 1, z + 0) >= level ? 1 : 0; 87 | const pz = density.get(x + 0, y + 0, z + 1) >= level ? 1 : 0; 88 | if (p0 + px === 1 && y > 0 && z > 0) { 89 | const a = getFeaturePointIndex(x + 0, y - 1, z - 1); 90 | const b = getFeaturePointIndex(x + 0, y - 1, z + 0); 91 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 92 | const d = getFeaturePointIndex(x + 0, y + 0, z - 1); 93 | if (px < p0) { 94 | cells.push([a, b, c]); 95 | cells.push([a, c, d]); 96 | } else { 97 | cells.push([a, c, b]); 98 | cells.push([a, d, c]); 99 | } 100 | } 101 | if (p0 + py === 1 && x > 0 && z > 0) { 102 | const a = getFeaturePointIndex(x - 1, y + 0, z - 1); 103 | const b = getFeaturePointIndex(x + 0, y + 0, z - 1); 104 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 105 | const d = getFeaturePointIndex(x - 1, y + 0, z + 0); 106 | if (py < p0) { 107 | cells.push([a, b, c]); 108 | cells.push([a, c, d]); 109 | } else { 110 | cells.push([a, c, b]); 111 | cells.push([a, d, c]); 112 | } 113 | } 114 | if (p0 + pz === 1 && x > 0 && y > 0) { 115 | const a = getFeaturePointIndex(x - 1, y - 1, z + 0); 116 | const b = getFeaturePointIndex(x + 0, y - 1, z + 0); 117 | const c = getFeaturePointIndex(x + 0, y + 0, z + 0); 118 | const d = getFeaturePointIndex(x - 1, y + 0, z + 0); 119 | if (pz > p0) { 120 | cells.push([a, b, c]); 121 | cells.push([a, c, d]); 122 | } else { 123 | cells.push([a, c, b]); 124 | cells.push([a, d, c]); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | return { 131 | positions: featurePoints, 132 | cells: cells, 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /src/primitives.ts: -------------------------------------------------------------------------------- 1 | import { vec2, vec3 } from "gl-matrix"; 2 | import { SDF } from "./sdf"; 3 | import { clamp, vec2abs, vec3abs } from "./util"; 4 | 5 | export class Box extends SDF { 6 | private radii; 7 | 8 | constructor(width: number, height: number, depth: number) { 9 | super(); 10 | this.radii = vec3.fromValues(width, height, depth); 11 | vec3.negate(this.bounds.min, this.radii); 12 | vec3.copy(this.bounds.max, this.radii); 13 | } 14 | 15 | public density = (x: number, y: number, z: number) => { 16 | const p = vec3.fromValues(x, y, z); 17 | const absp = vec3abs(vec3.create(), p); 18 | const q = vec3.subtract(vec3.create(), absp, this.radii); 19 | const posq = vec3.max(vec3.create(), q, vec3.fromValues(0, 0, 0)); 20 | return vec3.length(posq) + Math.min(Math.max(q[0], Math.max(q[1], q[2])), 0); 21 | }; 22 | } 23 | 24 | export class Sphere extends SDF { 25 | constructor(private radius: number) { 26 | super(); 27 | vec3.negate(this.bounds.min, [this.radius, this.radius, this.radius]); 28 | vec3.set(this.bounds.max, this.radius, this.radius, this.radius); 29 | } 30 | 31 | public density = (x: number, y: number, z: number) => { 32 | const p = vec3.fromValues(x, y, z); 33 | return vec3.length(p) - this.radius; 34 | }; 35 | } 36 | 37 | export class BoxFrame extends SDF { 38 | private radii; 39 | constructor(width: number, height: number, depth: number, private edge: number) { 40 | super(); 41 | this.radii = vec3.fromValues(width, height, depth); 42 | vec3.negate(this.bounds.min, this.radii); 43 | vec3.copy(this.bounds.max, this.radii); 44 | } 45 | 46 | public density = (x: number, y: number, z: number) => { 47 | const p = vec3.subtract(vec3.create(), vec3abs(vec3.create(), vec3.fromValues(x, y, z)), this.radii); 48 | const e = vec3.fromValues(this.edge, this.edge, this.edge); 49 | const q = vec3.sub(vec3.create(), vec3abs(vec3.create(), vec3.add(vec3.create(), p, e)), e); 50 | return Math.min( 51 | Math.min( 52 | vec3.length(vec3.max(vec3.create(), [p[0], q[1], q[2]], [0, 0, 0])) + 53 | Math.min(Math.max(p[0], Math.max(q[1], q[2])), 0.0), 54 | vec3.length(vec3.max(vec3.create(), [q[0], p[1], q[2]], [0, 0, 0])) + 55 | Math.min(Math.max(q[0], Math.max(p[1], q[2])), 0.0) 56 | ), 57 | vec3.length(vec3.max(vec3.create(), [q[0], q[1], p[2]], [0, 0, 0])) + 58 | Math.min(Math.max(q[0], Math.max(q[1], p[2])), 0.0) 59 | ); 60 | }; 61 | } 62 | 63 | export class Torus extends SDF { 64 | constructor(private majorRadius: number, private minorRadius: number) { 65 | super(); 66 | vec3.set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius, -minorRadius); 67 | vec3.set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius, +minorRadius); 68 | } 69 | 70 | public density = (x: number, y: number, z: number) => { 71 | const q = vec2.fromValues(vec2.length(vec2.fromValues(x, y)) - this.majorRadius, z); 72 | return vec2.length(q) - this.minorRadius; 73 | }; 74 | } 75 | 76 | export class CappedTorus extends SDF { 77 | constructor(private majorRadius: number, private minorRadius: number, private angle: number) { 78 | super(); 79 | vec3.set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius, -minorRadius); 80 | vec3.set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius, +minorRadius); 81 | } 82 | 83 | public density = (x: number, y: number, z: number) => { 84 | const sc = vec2.fromValues(Math.sin(this.angle), Math.cos(this.angle)); 85 | x = Math.abs(x); 86 | const k = sc[1] * x > sc[0] * y ? vec2.dot([x, y], sc) : vec2.length([x, y]); 87 | const p = vec3.fromValues(x, y, z); 88 | return ( 89 | Math.sqrt(vec3.dot(p, p) + this.majorRadius * this.majorRadius - 2.0 * this.majorRadius * k) - this.minorRadius 90 | ); 91 | }; 92 | } 93 | 94 | export class Link extends SDF { 95 | constructor(private majorRadius: number, private minorRadius: number, private length: number) { 96 | super(); 97 | vec3.set(this.bounds.min, -majorRadius - minorRadius, -majorRadius - minorRadius - length, -minorRadius); 98 | vec3.set(this.bounds.max, majorRadius + minorRadius, majorRadius + minorRadius + length, +minorRadius); 99 | } 100 | 101 | public density = (x: number, y: number, z: number) => { 102 | const q = vec3.fromValues(x, Math.max(Math.abs(y) - this.length, 0.0), z); 103 | return vec2.length(vec2.fromValues(vec2.length([q[0], q[1]]) - this.majorRadius, q[2])) - this.minorRadius; 104 | }; 105 | } 106 | 107 | export class Cone extends SDF { 108 | constructor(private angle: number, private height: number) { 109 | super(); 110 | const sigma = height * Math.tan(angle); 111 | vec3.set(this.bounds.min, -sigma, -height, -sigma); 112 | vec3.set(this.bounds.max, sigma, 0, sigma); 113 | } 114 | 115 | public density = (x: number, y: number, z: number) => { 116 | const c = [Math.sin(this.angle), Math.cos(this.angle)]; 117 | const q = vec2.scale(vec2.create(), vec2.fromValues(c[0] / c[1], -1.0), this.height); 118 | 119 | const w = vec2.fromValues(vec2.length([x, z]), y); 120 | const a = vec2.sub( 121 | vec2.create(), 122 | w, 123 | vec2.scale(vec2.create(), q, clamp(vec2.dot(w, q) / vec2.dot(q, q), 0.0, 1.0)) 124 | ); 125 | const b = vec2.sub( 126 | vec2.create(), 127 | w, 128 | vec2.mul(vec2.create(), q, vec2.fromValues(clamp(w[0] / q[0], 0.0, 1.0), 1.0)) 129 | ); 130 | const k = Math.sign(q[1]); 131 | const d = Math.min(vec2.dot(a, a), vec2.dot(b, b)); 132 | const s = Math.max(k * (w[0] * q[1] - w[1] * q[0]), k * (w[1] - q[1])); 133 | return Math.sqrt(d) * Math.sign(s); 134 | }; 135 | } 136 | 137 | export class HexagonalPrism extends SDF { 138 | constructor(private radius: number, private length: number) { 139 | super(); 140 | const h = radius / Math.cos(Math.PI / 6); 141 | vec3.set(this.bounds.min, -h, -radius, -length); 142 | vec3.set(this.bounds.max, h, radius, length); 143 | } 144 | 145 | public density = (x: number, y: number, z: number) => { 146 | const k = vec3.fromValues(-0.8660254, 0.5, 0.57735); 147 | const p = vec3.fromValues(x, y, z); 148 | vec3abs(p, p); 149 | const q = vec2.scale(vec2.create(), [k[0], k[1]], 2.0 * Math.min(vec2.dot([k[0], k[1]], [p[0], p[1]]), 0.0)); 150 | p[0] -= q[0]; 151 | p[1] -= q[1]; 152 | const d = vec2.fromValues( 153 | vec2.length( 154 | vec2.sub( 155 | vec2.create(), 156 | [p[0], p[1]], 157 | vec2.fromValues(clamp(p[0], -k[2] * this.radius, k[2] * this.radius), this.radius) 158 | ) 159 | ) * Math.sign(p[1] - this.radius), 160 | p[2] - this.length 161 | ); 162 | 163 | return Math.min(Math.max(d[0], d[1]), 0.0) + vec2.length(vec2.max(vec2.create(), d, [0, 0])); 164 | }; 165 | } 166 | 167 | export class Capsule extends SDF { 168 | constructor(private pointA: number[], private pointB: number[], private radius: number) { 169 | super(); 170 | const min = vec3.min(vec3.create(), pointA as vec3, pointB as vec3); 171 | const max = vec3.max(vec3.create(), pointA as vec3, pointB as vec3); 172 | vec3.sub(this.bounds.min, min, [radius, radius, radius]); 173 | vec3.add(this.bounds.max, max, [radius, radius, radius]); 174 | } 175 | 176 | public density = (x: number, y: number, z: number) => { 177 | const p = vec3.fromValues(x, y, z); 178 | const pa = vec3.sub(vec3.create(), p, this.pointA as vec3); 179 | const ba = vec3.sub(vec3.create(), this.pointB as vec3, this.pointA as vec3); 180 | const h = clamp(vec3.dot(pa, ba) / vec3.dot(ba, ba), 0.0, 1.0); 181 | return vec3.length(vec3.sub(vec3.create(), pa, vec3.scale(vec3.create(), ba, h))) - this.radius; 182 | }; 183 | } 184 | 185 | export class CappedCylinder extends SDF { 186 | constructor(private length: number, private radius: number) { 187 | super(); 188 | vec3.set(this.bounds.min, -radius, -length, -radius); 189 | vec3.set(this.bounds.max, radius, length, radius); 190 | } 191 | 192 | public density = (x: number, y: number, z: number) => { 193 | const d = vec2.sub( 194 | vec2.create(), 195 | vec2abs(vec2.create(), vec2.fromValues(vec2.length([x, z]), y)), 196 | vec2.fromValues(this.radius, this.length) 197 | ); 198 | return Math.min(Math.max(d[0], d[1]), 0.0) + vec2.length(vec2.max(vec2.create(), d, [0, 0])); 199 | }; 200 | } 201 | 202 | export class CappedCone extends SDF { 203 | constructor(private length: number, private radius1: number, private radius2: number) { 204 | super(); 205 | const r = Math.max(radius1, radius2); 206 | vec3.set(this.bounds.min, -r, -length, -r); 207 | vec3.set(this.bounds.max, r, length, r); 208 | } 209 | 210 | public density = (x: number, y: number, z: number) => { 211 | const q = vec2.fromValues(vec2.length([x, z]), y); 212 | const k1 = vec2.fromValues(this.radius2, this.length); 213 | const k2 = vec2.fromValues(this.radius2 - this.radius1, 2.0 * this.length); 214 | const ca = vec2.fromValues( 215 | q[0] - Math.min(q[0], q[1] < 0.0 ? this.radius1 : this.radius2), 216 | Math.abs(q[1]) - this.length 217 | ); 218 | const qk1 = vec2.sub(vec2.create(), k1, q); 219 | const cb = vec2.add( 220 | vec2.create(), 221 | vec2.sub(vec2.create(), q, k1), 222 | vec2.scale(vec2.create(), k2, clamp(vec2.dot(qk1, k2) / vec2.dot(k2, k2), 0.0, 1.0)) 223 | ); 224 | const s = cb[0] < 0.0 && ca[1] < 0.0 ? -1.0 : 1.0; 225 | return s * Math.sqrt(Math.min(vec2.dot(ca, ca), vec2.dot(cb, cb))); 226 | }; 227 | } 228 | 229 | export class SolidAngle extends SDF { 230 | constructor(private angle: number, private radius: number) { 231 | super(); 232 | const h = radius * Math.sin(angle); 233 | vec3.set(this.bounds.min, -h, 0, -h); 234 | vec3.set(this.bounds.max, h, radius, h); 235 | } 236 | 237 | public density = (x: number, y: number, z: number) => { 238 | const c = vec2.fromValues(Math.sin(this.angle), Math.cos(this.angle)); 239 | const q = vec2.fromValues(vec2.length([x, z]), y); 240 | const l = vec2.length(q) - this.radius; 241 | const m = vec2.length( 242 | vec2.sub(vec2.create(), q, vec2.scale(vec2.create(), c, clamp(vec2.dot(q, c), 0.0, this.radius))) 243 | ); 244 | return Math.max(l, m * Math.sign(c[1] * q[0] - c[0] * q[1])); 245 | }; 246 | } 247 | 248 | export class TriangularPrism_ extends SDF { 249 | constructor(private radius: number, private length: number) { 250 | super(); 251 | const h0 = (0.5 * radius) / Math.cos(Math.PI / 3); 252 | const h1 = 0.5 * radius * Math.tan(Math.PI / 3); 253 | vec3.set(this.bounds.min, -h1, -0.5 * radius, -length); 254 | vec3.set(this.bounds.max, h1, h0, length); 255 | } 256 | 257 | public density = (x: number, y: number, z: number) => { 258 | const q = vec3abs(vec3.create(), [x, y, z]); 259 | return Math.max(q[2] - this.length, Math.max(q[0] * 0.866025 + y * 0.5, -y) - this.radius * 0.5); 260 | }; 261 | } 262 | -------------------------------------------------------------------------------- /src/sdf.ts: -------------------------------------------------------------------------------- 1 | import { mat4, quat, vec3 } from "gl-matrix"; 2 | import { clamp, mix } from "./util"; 3 | import { isosurfaceGenerator } from "./isosurface"; 4 | 5 | export interface Bounds { 6 | min: vec3; 7 | max: vec3; 8 | } 9 | 10 | interface Mesh { 11 | positions: number[][]; 12 | normals: number[][]; 13 | cells: number[][]; 14 | userdata: number[][] | null; 15 | } 16 | 17 | export abstract class SDF { 18 | protected _userData: number[] | null = null; 19 | 20 | public bounds: Bounds = { 21 | min: vec3.fromValues(0, 0, 0), 22 | max: vec3.fromValues(0, 0, 0), 23 | }; 24 | 25 | abstract density(x: number, y: number, z: number): number; 26 | 27 | public normal(x: number, y: number, z: number) { 28 | const dr = 0.001; 29 | const p0 = this.density(x, y, z); 30 | const px = this.density(x + dr, y, z); 31 | const py = this.density(x, y + dr, z); 32 | const pz = this.density(x, y, z + dr); 33 | const n0 = vec3.fromValues((px - p0) / dr, (py - p0) / dr, (pz - p0) / dr); 34 | return vec3.normalize(vec3.create(), n0); 35 | } 36 | 37 | public generateMesh(resolution: number[], padding: number): Mesh { 38 | let t0 = performance.now(); 39 | const grid = this.generateGrid(resolution, padding); 40 | console.log(`Grid: ${Math.round(performance.now() - t0)} ms`); 41 | t0 = performance.now(); 42 | const mesh = isosurfaceGenerator(grid, 0); 43 | console.log(`Isosurface extraction: ${Math.round(performance.now() - t0)} ms`); 44 | const min = vec3.sub(vec3.create(), this.bounds.min, [padding, padding, padding]); 45 | const max = vec3.add(vec3.create(), this.bounds.max, [padding, padding, padding]); 46 | const dm = vec3.sub(vec3.create(), max, min); 47 | for (const position of mesh.positions as vec3[]) { 48 | vec3.mul(position, position, [dm[0] / resolution[0], dm[1] / resolution[1], dm[2] / resolution[2]]); 49 | vec3.add(position, position, min); 50 | } 51 | const normals: number[][] = []; 52 | for (const p of mesh.positions as vec3[]) { 53 | normals.push(this.normal(p[0], p[1], p[2]) as number[]); 54 | } 55 | let userdata: number[][] | null = null; 56 | if (this.getUserData(0, 0, 0) !== null) { 57 | userdata = []; 58 | for (const p of mesh.positions) { 59 | userdata.push(this.getUserData(p[0], p[1], p[2])!); 60 | } 61 | } 62 | return { 63 | ...mesh, 64 | normals, 65 | userdata, 66 | }; 67 | } 68 | 69 | private generateGrid(resolution: number[], padding: number) { 70 | const grid = new Float32Array((resolution[0] + 1) * (resolution[1] + 1) * (resolution[2] + 1)); 71 | const min = vec3.sub(vec3.create(), this.bounds.min, [padding, padding, padding]); 72 | const max = vec3.add(vec3.create(), this.bounds.max, [padding, padding, padding]); 73 | const dx = (max[0] - min[0]) / resolution[0]; 74 | const dy = (max[1] - min[1]) / resolution[1]; 75 | const dz = (max[2] - min[2]) / resolution[2]; 76 | for (let i = 0; i < resolution[0] + 1; i++) { 77 | const x = i * dx + min[0]; 78 | for (let j = 0; j < resolution[1] + 1; j++) { 79 | const y = j * dy + min[1]; 80 | for (let k = 0; k < resolution[2] + 1; k++) { 81 | const z = k * dz + min[2]; 82 | const index = resolution[0] * resolution[2] * j + resolution[0] * k + i; 83 | grid[index] = this.density(x, y, z); 84 | } 85 | } 86 | } 87 | return { 88 | get: (i: number, j: number, k: number) => grid[resolution[0] * resolution[2] * j + resolution[0] * k + i], 89 | shape: [resolution[0] + 1, resolution[1] + 1, resolution[2] + 1], 90 | }; 91 | } 92 | 93 | public setUserData(data: number[]) { 94 | this._userData = data.slice(); 95 | return this; 96 | } 97 | 98 | public getUserData(x: number, y: number, z: number) { 99 | return this._userData; 100 | } 101 | 102 | public union(sdf: SDF) { 103 | return new Union(this, sdf); 104 | } 105 | 106 | public subtract(sdf: SDF) { 107 | return new Subtraction(sdf, this); 108 | } 109 | 110 | public intersect(sdf: SDF) { 111 | return new Intersection(sdf, this); 112 | } 113 | 114 | public smoothUnion(sdf: SDF, smoothness: number) { 115 | return new SmoothUnion(this, sdf, smoothness); 116 | } 117 | 118 | public smoothSubtract(sdf: SDF, smoothness: number) { 119 | return new SmoothSubtraction(sdf, this, smoothness); 120 | } 121 | 122 | public smoothIntersect(sdf: SDF, smoothness: number) { 123 | return new SmoothIntersection(this, sdf, smoothness); 124 | } 125 | 126 | public translate(x: number, y: number, z: number) { 127 | return new Transform(this, [x, y, z], quat.create() as number[], 1.0); 128 | } 129 | 130 | public rotate(quat: number[]) { 131 | return new Transform(this, [0, 0, 0], quat, 1.0); 132 | } 133 | 134 | public rotateX(radians: number) { 135 | return new Transform(this, [0, 0, 0], quat.rotateX(quat.create(), quat.create(), radians) as number[], 1.0); 136 | } 137 | 138 | public rotateY(radians: number) { 139 | return new Transform(this, [0, 0, 0], quat.rotateY(quat.create(), quat.create(), radians) as number[], 1.0); 140 | } 141 | 142 | public rotateZ(radians: number) { 143 | return new Transform(this, [0, 0, 0], quat.rotateZ(quat.create(), quat.create(), radians) as number[], 1.0); 144 | } 145 | 146 | public scale(amount: number) { 147 | return new Transform(this, [0, 0, 0], quat.create() as number[], amount); 148 | } 149 | 150 | public round(amount: number) { 151 | return new Round(this, amount); 152 | } 153 | } 154 | 155 | export class Subtraction extends SDF { 156 | constructor(private sdf1: SDF, private sdf2: SDF) { 157 | super(); 158 | vec3.copy(this.bounds.min, sdf2.bounds.min); 159 | vec3.copy(this.bounds.max, sdf2.bounds.max); 160 | } 161 | 162 | public density = (x: number, y: number, z: number) => { 163 | const d1 = this.sdf1.density(x, y, z); 164 | const d2 = this.sdf2.density(x, y, z); 165 | return Math.max(-d1, d2); 166 | }; 167 | 168 | public getUserData(x: number, y: number, z: number) { 169 | if (this._userData !== null) { 170 | return this._userData; 171 | } 172 | const ud1 = this.sdf1.getUserData(x, y, z); 173 | const ud2 = this.sdf2.getUserData(x, y, z); 174 | if (ud1 !== null && ud2 === null) { 175 | return ud1; 176 | } else if (ud1 === null && ud2 !== null) { 177 | return ud2; 178 | } else if (ud1 === null && ud2 === null) { 179 | return null; 180 | } 181 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 182 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 183 | const frac = d1 / (d1 + d2); 184 | const lerp: number[] = []; 185 | for (let i = 0; i < ud1!.length; i++) { 186 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 187 | } 188 | return lerp; 189 | } 190 | } 191 | 192 | export class Union extends SDF { 193 | constructor(private sdf1: SDF, private sdf2: SDF) { 194 | super(); 195 | vec3.min(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 196 | vec3.max(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 197 | } 198 | 199 | public density = (x: number, y: number, z: number) => { 200 | const d1 = this.sdf1.density(x, y, z); 201 | const d2 = this.sdf2.density(x, y, z); 202 | return Math.min(d1, d2); 203 | }; 204 | 205 | public getUserData(x: number, y: number, z: number) { 206 | if (this._userData !== null) { 207 | return this._userData; 208 | } 209 | const ud1 = this.sdf1.getUserData(x, y, z); 210 | const ud2 = this.sdf2.getUserData(x, y, z); 211 | if (ud1 !== null && ud2 === null) { 212 | return ud1; 213 | } else if (ud1 === null && ud2 !== null) { 214 | return ud2; 215 | } else if (ud1 === null && ud2 === null) { 216 | return null; 217 | } 218 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 219 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 220 | const frac = d1 / (d1 + d2); 221 | const lerp: number[] = []; 222 | for (let i = 0; i < ud1!.length; i++) { 223 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 224 | } 225 | return lerp; 226 | } 227 | } 228 | 229 | export class Intersection extends SDF { 230 | constructor(private sdf1: SDF, private sdf2: SDF) { 231 | super(); 232 | vec3.max(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 233 | vec3.min(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 234 | } 235 | 236 | public density = (x: number, y: number, z: number) => { 237 | const d1 = this.sdf1.density(x, y, z); 238 | const d2 = this.sdf2.density(x, y, z); 239 | return Math.max(d1, d2); 240 | }; 241 | 242 | public getUserData(x: number, y: number, z: number) { 243 | if (this._userData !== null) { 244 | return this._userData; 245 | } 246 | const ud1 = this.sdf1.getUserData(x, y, z); 247 | const ud2 = this.sdf2.getUserData(x, y, z); 248 | if (ud1 !== null && ud2 === null) { 249 | return ud1; 250 | } else if (ud1 === null && ud2 !== null) { 251 | return ud2; 252 | } else if (ud1 === null && ud2 === null) { 253 | return null; 254 | } 255 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 256 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 257 | const frac = d1 / (d1 + d2); 258 | const lerp: number[] = []; 259 | for (let i = 0; i < ud1!.length; i++) { 260 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 261 | } 262 | return lerp; 263 | } 264 | } 265 | 266 | export class SmoothSubtraction extends SDF { 267 | constructor(private sdf1: SDF, private sdf2: SDF, private smoothness: number) { 268 | super(); 269 | vec3.copy(this.bounds.min, sdf2.bounds.min); 270 | vec3.copy(this.bounds.max, sdf2.bounds.max); 271 | } 272 | 273 | public density = (x: number, y: number, z: number) => { 274 | const d1 = this.sdf1.density(x, y, z); 275 | const d2 = this.sdf2.density(x, y, z); 276 | const h = clamp(0.5 - (0.5 * (d2 + d1)) / this.smoothness, 0.0, 1.0); 277 | return mix(d2, -d1, h) + this.smoothness * h * (1.0 - h); 278 | }; 279 | 280 | public getUserData(x: number, y: number, z: number) { 281 | if (this._userData !== null) { 282 | return this._userData; 283 | } 284 | const ud1 = this.sdf1.getUserData(x, y, z); 285 | const ud2 = this.sdf2.getUserData(x, y, z); 286 | if (ud1 !== null && ud2 === null) { 287 | return ud1; 288 | } else if (ud1 === null && ud2 !== null) { 289 | return ud2; 290 | } else if (ud1 === null && ud2 === null) { 291 | return null; 292 | } 293 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 294 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 295 | const frac = d1 / (d1 + d2); 296 | const lerp: number[] = []; 297 | for (let i = 0; i < ud1!.length; i++) { 298 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 299 | } 300 | return lerp; 301 | } 302 | } 303 | 304 | export class SmoothUnion extends SDF { 305 | constructor(private sdf1: SDF, private sdf2: SDF, private smoothness: number) { 306 | super(); 307 | vec3.min(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 308 | vec3.max(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 309 | } 310 | 311 | public density = (x: number, y: number, z: number) => { 312 | const d1 = this.sdf1.density(x, y, z); 313 | const d2 = this.sdf2.density(x, y, z); 314 | const h = clamp(0.5 + (0.5 * (d2 - d1)) / this.smoothness, 0.0, 1.0); 315 | return mix(d2, d1, h) - this.smoothness * h * (1.0 - h); 316 | }; 317 | 318 | public getUserData(x: number, y: number, z: number) { 319 | if (this._userData !== null) { 320 | return this._userData; 321 | } 322 | const ud1 = this.sdf1.getUserData(x, y, z); 323 | const ud2 = this.sdf2.getUserData(x, y, z); 324 | if (ud1 !== null && ud2 === null) { 325 | return ud1; 326 | } else if (ud1 === null && ud2 !== null) { 327 | return ud2; 328 | } else if (ud1 === null && ud2 === null) { 329 | return null; 330 | } 331 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 332 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 333 | const frac = d1 / (d1 + d2); 334 | const lerp: number[] = []; 335 | for (let i = 0; i < ud1!.length; i++) { 336 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 337 | } 338 | return lerp; 339 | } 340 | } 341 | 342 | export class SmoothIntersection extends SDF { 343 | constructor(private sdf1: SDF, private sdf2: SDF, private smoothness: number) { 344 | super(); 345 | vec3.max(this.bounds.min, sdf1.bounds.min, sdf2.bounds.min); 346 | vec3.min(this.bounds.max, sdf1.bounds.max, sdf2.bounds.max); 347 | } 348 | 349 | public density = (x: number, y: number, z: number) => { 350 | const d1 = this.sdf1.density(x, y, z); 351 | const d2 = this.sdf2.density(x, y, z); 352 | const h = clamp(0.5 - (0.5 * (d2 - d1)) / this.smoothness, 0.0, 1.0); 353 | return mix(d2, d1, h) + this.smoothness * h * (1.0 - h); 354 | }; 355 | 356 | public getUserData(x: number, y: number, z: number) { 357 | if (this._userData !== null) { 358 | return this._userData; 359 | } 360 | const ud1 = this.sdf1.getUserData(x, y, z); 361 | const ud2 = this.sdf2.getUserData(x, y, z); 362 | if (ud1 !== null && ud2 === null) { 363 | return ud1; 364 | } else if (ud1 === null && ud2 !== null) { 365 | return ud2; 366 | } else if (ud1 === null && ud2 === null) { 367 | return null; 368 | } 369 | const d1 = Math.abs(this.sdf1.density(x, y, z)); 370 | const d2 = Math.abs(this.sdf2.density(x, y, z)); 371 | const frac = d1 / (d1 + d2); 372 | const lerp: number[] = []; 373 | for (let i = 0; i < ud1!.length; i++) { 374 | lerp.push(ud1![i] + frac * (ud2![i] - ud1![i])); 375 | } 376 | return lerp; 377 | } 378 | } 379 | 380 | export class Transform extends SDF { 381 | private matrix: mat4; 382 | 383 | constructor(private sdf: SDF, translation: number[], rotation: number[], scale: number) { 384 | super(); 385 | this.matrix = mat4.fromRotationTranslationScale( 386 | mat4.create(), 387 | rotation, 388 | translation as vec3, 389 | vec3.fromValues(scale, scale, scale) 390 | ); 391 | const bmin = vec3.clone(this.sdf.bounds.min); 392 | const bmax = vec3.clone(this.sdf.bounds.max); 393 | const db = vec3.subtract(vec3.create(), bmax, bmin); 394 | const t0 = vec3.add(vec3.create(), bmin, [db[0], 0, 0]); 395 | const t1 = vec3.add(vec3.create(), bmin, [db[0], db[1], 0]); 396 | const t2 = vec3.add(vec3.create(), bmin, [0, db[1], 0]); 397 | const t3 = vec3.sub(vec3.create(), bmax, [db[0], 0, 0]); 398 | const t4 = vec3.sub(vec3.create(), bmax, [db[0], db[1], 0]); 399 | const t5 = vec3.sub(vec3.create(), bmax, [0, db[1], 0]); 400 | vec3.transformMat4(bmin, bmin, this.matrix); 401 | vec3.transformMat4(bmax, bmax, this.matrix); 402 | vec3.transformMat4(t0, t0, this.matrix); 403 | vec3.transformMat4(t1, t1, this.matrix); 404 | vec3.transformMat4(t2, t2, this.matrix); 405 | vec3.transformMat4(t3, t3, this.matrix); 406 | vec3.transformMat4(t4, t4, this.matrix); 407 | vec3.transformMat4(t5, t5, this.matrix); 408 | vec3.min(this.bounds.min, bmin, bmax); 409 | vec3.min(this.bounds.min, this.bounds.min, t0); 410 | vec3.min(this.bounds.min, this.bounds.min, t1); 411 | vec3.min(this.bounds.min, this.bounds.min, t2); 412 | vec3.min(this.bounds.min, this.bounds.min, t3); 413 | vec3.min(this.bounds.min, this.bounds.min, t4); 414 | vec3.min(this.bounds.min, this.bounds.min, t5); 415 | vec3.max(this.bounds.max, bmin, bmax); 416 | vec3.max(this.bounds.max, this.bounds.max, t0); 417 | vec3.max(this.bounds.max, this.bounds.max, t1); 418 | vec3.max(this.bounds.max, this.bounds.max, t2); 419 | vec3.max(this.bounds.max, this.bounds.max, t3); 420 | vec3.max(this.bounds.max, this.bounds.max, t4); 421 | vec3.max(this.bounds.max, this.bounds.max, t5); 422 | mat4.invert(this.matrix, this.matrix); 423 | } 424 | 425 | public density = (x: number, y: number, z: number) => { 426 | const point = vec3.fromValues(x, y, z); 427 | vec3.transformMat4(point, point, this.matrix); 428 | return this.sdf.density(point[0], point[1], point[2]); 429 | }; 430 | 431 | public getUserData(x: number, y: number, z: number) { 432 | if (this._userData !== null) { 433 | return this._userData; 434 | } 435 | const p = vec3.transformMat4(vec3.create(), [x, y, z], this.matrix); 436 | return this.sdf.getUserData(p[0], p[1], p[2]); 437 | } 438 | } 439 | 440 | export class Round extends SDF { 441 | constructor(private sdf: SDF, private radius: number) { 442 | super(); 443 | vec3.sub(this.bounds.min, sdf.bounds.min, [radius, radius, radius]); 444 | vec3.add(this.bounds.max, sdf.bounds.max, [radius, radius, radius]); 445 | } 446 | 447 | public density = (x: number, y: number, z: number) => { 448 | return this.sdf.density(x, y, z) - this.radius; 449 | }; 450 | 451 | public getUserData(x: number, y: number, z: number) { 452 | if (this._userData !== null) { 453 | return this._userData; 454 | } 455 | return this.sdf.getUserData(x, y, z); 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { vec2, vec3 } from "gl-matrix"; 2 | 3 | export function vec3abs(out: vec3, p: vec3) { 4 | out[0] = Math.abs(p[0]); 5 | out[1] = Math.abs(p[1]); 6 | out[2] = Math.abs(p[2]); 7 | return out; 8 | } 9 | 10 | export function vec2abs(out: vec2, p: vec2) { 11 | out[0] = Math.abs(p[0]); 12 | out[1] = Math.abs(p[1]); 13 | return out; 14 | } 15 | 16 | export function clamp(n: number, min: number, max: number) { 17 | return Math.max(min, Math.min(max, n)); 18 | } 19 | 20 | export function mix(n0: number, n1: number, frac: number) { 21 | return n0 * (1 - frac) + n1 * frac; 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "emitDeclarationOnly": true, 16 | "declaration": true, 17 | "declarationDir": "./lib", 18 | "declarationMap": true 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /vite.example-config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | const path = require("path"); 3 | const { defineConfig } = require("vite"); 4 | 5 | module.exports = defineConfig({ 6 | root: "./example", 7 | base: "./", 8 | build: { 9 | outDir: "../docs", 10 | emptyOutDir: true, 11 | }, 12 | rollupOptions: { 13 | input: { 14 | main: path.resolve(__dirname, "example/index.html"), 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /vite.lib-config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | const path = require("path"); 3 | const { defineConfig } = require("vite"); 4 | 5 | module.exports = defineConfig({ 6 | build: { 7 | outDir: "lib", 8 | lib: { 9 | name: "sdf-csg", 10 | entry: path.resolve(__dirname, "src/index.ts"), 11 | fileName: (format) => `index.${format}.js`, 12 | }, 13 | }, 14 | }); 15 | --------------------------------------------------------------------------------