summaryrefslogtreecommitdiff
path: root/Build/source/utils/asymptote/webgl/gl.js
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/utils/asymptote/webgl/gl.js')
-rw-r--r--Build/source/utils/asymptote/webgl/gl.js3135
1 files changed, 3135 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/webgl/gl.js b/Build/source/utils/asymptote/webgl/gl.js
new file mode 100644
index 00000000000..f05312dec54
--- /dev/null
+++ b/Build/source/utils/asymptote/webgl/gl.js
@@ -0,0 +1,3135 @@
+let P=[]; // Array of Bezier patches, triangles, curves, and pixels
+let Materials=[]; // Array of materials
+let Lights=[]; // Array of lights
+let Centers=[]; // Array of billboard centers
+let Background=[1,1,1,1]; // Background color
+
+let canvasWidth,canvasHeight;
+let canvasWidth0,canvasHeight0; // Initial values
+
+let absolute=false;
+
+let b,B; // Scene min,max bounding box corners (3-tuples)
+let angle; // Field of view angle
+let Zoom0; // Initial zoom
+let zoom0; // Adjusted initial zoom
+let viewportmargin; // Margin around viewport (2-tuple)
+let viewportshift=[0,0]; // Viewport shift (for perspective projection)
+
+let zoomFactor;
+let zoomPinchFactor;
+let zoomPinchCap;
+let zoomStep;
+
+let shiftHoldDistance;
+let shiftWaitTime;
+let vibrateTime;
+
+let embedded; // Is image embedded within another window?
+
+let canvas; // Rendering canvas
+let gl; // WebGL rendering context
+let alpha; // Is background opaque?
+
+let offscreen; // Offscreen rendering canvas for embedded images
+let context; // 2D context for copying embedded offscreen images
+
+let nlights=0; // Number of lights compiled in shader
+let Nmaterials=2; // Maximum number of materials compiled in shader
+
+let materials=[]; // Subset of Materials passed as uniforms
+let maxMaterials; // Limit on number of materials allowed in shader
+
+let halfCanvasWidth,halfCanvasHeight;
+
+let pixel=0.75; // Adaptive rendering constant.
+let zoomRemeshFactor=1.5; // Zoom factor before remeshing
+let FillFactor=0.1;
+let Zoom;
+
+let maxViewportWidth;
+let maxViewportHeight;
+
+const windowTrim=10;
+
+let lastZoom;
+let H; // maximum camera view half-height
+
+let third=1/3;
+
+let rotMat=mat4.create();
+let projMat=mat4.create(); // projection matrix
+let viewMat=mat4.create(); // view matrix
+
+let projViewMat=mat4.create(); // projection view matrix
+let normMat=mat3.create();
+let viewMat3=mat3.create(); // 3x3 view matrix
+let cjMatInv=mat4.create();
+let T=mat4.create(); // Temporary matrix
+
+let zmin,zmax;
+let center={x:0,y:0,z:0};
+let size2;
+let ArcballFactor;
+let shift={
+ x:0,y:0
+};
+
+let viewParam = {
+ xmin:0,xmax:0,
+ ymin:0,ymax:0,
+ zmin:0,zmax:0
+};
+
+let remesh=true;
+let wireframe=0;
+let mouseDownOrTouchActive=false;
+let lastMouseX=null;
+let lastMouseY=null;
+let touchID=null;
+
+// Indexed triangles:
+let Positions=[];
+let Normals=[];
+let Colors=[];
+let Indices=[];
+
+class Material {
+ constructor(diffuse,emissive,specular,shininess,metallic,fresnel0) {
+ this.diffuse=diffuse;
+ this.emissive=emissive;
+ this.specular=specular;
+ this.shininess=shininess;
+ this.metallic=metallic;
+ this.fresnel0=fresnel0;
+ }
+
+ setUniform(program,index) {
+ let getLoc=
+ param => gl.getUniformLocation(program,"Materials["+index+"]."+param);
+
+ gl.uniform4fv(getLoc("diffuse"),new Float32Array(this.diffuse));
+ gl.uniform4fv(getLoc("emissive"),new Float32Array(this.emissive));
+ gl.uniform4fv(getLoc("specular"),new Float32Array(this.specular));
+
+ gl.uniform4f(getLoc("parameters"),this.shininess,this.metallic,
+ this.fresnel0,0);
+ }
+}
+
+let enumPointLight=1;
+let enumDirectionalLight=2;
+
+class Light {
+ constructor(direction,color) {
+ this.direction=direction;
+ this.color=color;
+ }
+
+ setUniform(program,index) {
+ let getLoc=
+ param => gl.getUniformLocation(program,"Lights["+index+"]."+param);
+
+ gl.uniform3fv(getLoc("direction"),new Float32Array(this.direction));
+ gl.uniform3fv(getLoc("color"),new Float32Array(this.color));
+ }
+}
+
+function initShaders()
+{
+ let maxUniforms=gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
+ maxMaterials=Math.floor((maxUniforms-14)/4);
+ Nmaterials=Math.min(Math.max(Nmaterials,Materials.length),maxMaterials);
+
+ pixelShader=initShader(["WIDTH"]);
+ materialShader=initShader(["NORMAL"]);
+ colorShader=initShader(["NORMAL","COLOR"]);
+ transparentShader=initShader(["NORMAL","COLOR","TRANSPARENT"]);
+}
+
+function deleteShaders()
+{
+ gl.deleteProgram(transparentShader);
+ gl.deleteProgram(colorShader);
+ gl.deleteProgram(materialShader);
+ gl.deleteProgram(pixelShader);
+}
+
+function noGL() {
+ if (!gl)
+ alert("Could not initialize WebGL");
+}
+
+function saveAttributes()
+{
+ let a=window.top.document.asygl[alpha];
+
+ a.gl=gl;
+ a.nlights=Lights.length;
+ a.Nmaterials=Nmaterials;
+ a.maxMaterials=maxMaterials;
+
+ a.pixelShader=pixelShader;
+ a.materialShader=materialShader;
+ a.colorShader=colorShader;
+ a.transparentShader=transparentShader;
+}
+
+function restoreAttributes()
+{
+ let a=window.top.document.asygl[alpha];
+
+ gl=a.gl;
+ nlights=a.nlights;
+ Nmaterials=a.Nmaterials;
+ maxMaterials=a.maxMaterials;
+
+ pixelShader=a.pixelShader;
+ materialShader=a.materialShader;
+ colorShader=a.colorShader;
+ transparentShader=a.transparentShader;
+}
+
+let indexExt;
+
+function initGL()
+{
+ alpha=Background[3] < 1;
+
+ if(embedded) {
+ let p=window.top.document;
+
+ if(p.asygl == null)
+ p.asygl=Array(2);
+
+ context=canvas.getContext("2d");
+ offscreen=p.offscreen;
+ if(!offscreen) {
+ offscreen=p.createElement("canvas");
+ p.offscreen=offscreen;
+ }
+
+ if(!p.asygl[alpha] || !p.asygl[alpha].gl) {
+ gl=offscreen.getContext("webgl",{alpha:alpha});
+ if(!gl) noGL();
+ initShaders();
+ p.asygl[alpha]={};
+ saveAttributes();
+ } else {
+ restoreAttributes();
+ if((Lights.length != nlights) ||
+ Math.min(Materials.length,maxMaterials) > Nmaterials) {
+ initShaders();
+ saveAttributes();
+ }
+ }
+ } else {
+ gl=canvas.getContext("webgl",{alpha:alpha});
+ if(!gl) noGL();
+ initShaders();
+ }
+
+ indexExt=gl.getExtension("OES_element_index_uint");
+
+ TRIANGLES=gl.TRIANGLES;
+ material0Data=new vertexBuffer(gl.POINTS);
+ material1Data=new vertexBuffer(gl.LINES);
+ materialData=new vertexBuffer();
+ colorData=new vertexBuffer();
+ transparentData=new vertexBuffer();
+ triangleData=new vertexBuffer();
+}
+
+function getShader(gl,shaderScript,type,options=[])
+{
+ let str=`#version 100
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+ precision highp float;
+#else
+ precision mediump float;
+#endif
+ #define nlights ${wireframe == 0 ? Lights.length : 0}\n
+ const int Nlights=${Math.max(Lights.length,1)};\n
+ #define Nmaterials ${Nmaterials}\n`;
+
+ if(orthographic)
+ str += `#define ORTHOGRAPHIC\n`;
+
+ options.forEach(s => str += `#define `+s+`\n`);
+
+ let shader=gl.createShader(type);
+ gl.shaderSource(shader,str+shaderScript);
+ gl.compileShader(shader);
+ if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)) {
+ alert(gl.getShaderInfoLog(shader));
+ return null;
+ }
+ return shader;
+}
+
+function registerBuffer(buffervector,bufferIndex,copy,type=gl.ARRAY_BUFFER)
+{
+ if(buffervector.length > 0) {
+ if(bufferIndex == 0) {
+ bufferIndex=gl.createBuffer();
+ copy=true;
+ }
+ gl.bindBuffer(type,bufferIndex);
+ if(copy)
+ gl.bufferData(type,buffervector,gl.STATIC_DRAW);
+ }
+ return bufferIndex;
+}
+
+function drawBuffer(data,shader,indices=data.indices)
+{
+ if(data.indices.length == 0) return;
+
+ let normal=shader != pixelShader;
+
+ setUniforms(data,shader);
+
+ let copy=remesh || data.partial || !data.rendered;
+ data.verticesBuffer=registerBuffer(new Float32Array(data.vertices),
+ data.verticesBuffer,copy);
+ gl.vertexAttribPointer(positionAttribute,3,gl.FLOAT,false,
+ normal ? 24 : 16,0);
+ if(normal && Lights.length > 0)
+ gl.vertexAttribPointer(normalAttribute,3,gl.FLOAT,false,24,12);
+ else if(pixel)
+ gl.vertexAttribPointer(widthAttribute,1,gl.FLOAT,false,16,12);
+
+ data.materialsBuffer=registerBuffer(new Int16Array(data.materialIndices),
+ data.materialsBuffer,copy);
+ gl.vertexAttribPointer(materialAttribute,1,gl.SHORT,false,2,0);
+
+
+ if(shader == colorShader || shader == transparentShader) {
+ data.colorsBuffer=registerBuffer(new Uint8Array(data.colors),
+ data.colorsBuffer,copy);
+ gl.vertexAttribPointer(colorAttribute,4,gl.UNSIGNED_BYTE,true,0,0);
+ }
+
+ data.indicesBuffer=registerBuffer(indexExt ? new Uint32Array(indices) :
+ new Uint16Array(indices),
+ data.indicesBuffer,copy,
+ gl.ELEMENT_ARRAY_BUFFER);
+ data.rendered=true;
+
+ gl.drawElements(normal ? (wireframe ? gl.LINES : data.type) : gl.POINTS,
+ indices.length,
+ indexExt ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT,0);
+}
+
+let TRIANGLES;
+
+class vertexBuffer {
+ constructor(type) {
+ this.type=type ? type : TRIANGLES;
+
+ this.verticesBuffer=0;
+ this.materialsBuffer=0;
+ this.colorsBuffer=0;
+ this.indicesBuffer=0;
+
+ this.rendered=false; // Are all patches in this buffer fully rendered?
+ this.partial=false; // Does buffer contain incomplete data?
+
+ this.clear();
+ }
+
+ clear() {
+ this.vertices=[];
+ this.materialIndices=[];
+ this.colors=[];
+ this.indices=[];
+ this.nvertices=0;
+ this.materials=[];
+ this.materialTable=[];
+ }
+
+ // material vertex
+ vertex(v,n) {
+ this.vertices.push(v[0]);
+ this.vertices.push(v[1]);
+ this.vertices.push(v[2]);
+ this.vertices.push(n[0]);
+ this.vertices.push(n[1]);
+ this.vertices.push(n[2]);
+ this.materialIndices.push(materialIndex);
+ return this.nvertices++;
+ }
+
+ // colored vertex
+ Vertex(v,n,c=[0,0,0,0]) {
+ this.vertices.push(v[0]);
+ this.vertices.push(v[1]);
+ this.vertices.push(v[2]);
+ this.vertices.push(n[0]);
+ this.vertices.push(n[1]);
+ this.vertices.push(n[2]);
+ this.materialIndices.push(materialIndex);
+ this.colors.push(c[0]);
+ this.colors.push(c[1]);
+ this.colors.push(c[2]);
+ this.colors.push(c[3]);
+ return this.nvertices++;
+ }
+
+ // material vertex with width and without normal
+ vertex0(v,width) {
+ this.vertices.push(v[0]);
+ this.vertices.push(v[1]);
+ this.vertices.push(v[2]);
+ this.vertices.push(width);
+ this.materialIndices.push(materialIndex);
+ return this.nvertices++;
+ }
+
+ // indexed colored vertex
+ iVertex(i,v,n,c=[0,0,0,0]) {
+ let i6=6*i;
+ this.vertices[i6]=v[0];
+ this.vertices[i6+1]=v[1];
+ this.vertices[i6+2]=v[2];
+ this.vertices[i6+3]=n[0];
+ this.vertices[i6+4]=n[1];
+ this.vertices[i6+5]=n[2];
+ this.materialIndices[i]=materialIndex;
+ let i4=4*i;
+ this.colors[i4]=c[0];
+ this.colors[i4+1]=c[1];
+ this.colors[i4+2]=c[2];
+ this.colors[i4+3]=c[3];
+ this.indices.push(i);
+ }
+
+ append(data) {
+ append(this.vertices,data.vertices);
+ append(this.materialIndices,data.materialIndices);
+ append(this.colors,data.colors);
+ appendOffset(this.indices,data.indices,this.nvertices);
+ this.nvertices += data.nvertices;
+ }
+}
+
+let material0Data; // pixels
+let material1Data; // material Bezier curves
+let materialData; // material Bezier patches & triangles
+let colorData; // colored Bezier patches & triangles
+let transparentData; // transparent patches & triangles
+let triangleData; // opaque indexed triangles
+
+let materialIndex;
+
+// efficiently append array b onto array a
+function append(a,b)
+{
+ let n=a.length;
+ let m=b.length;
+ a.length += m;
+ for(let i=0; i < m; ++i)
+ a[n+i]=b[i];
+}
+
+// efficiently append array b onto array a with offset
+function appendOffset(a,b,o)
+{
+ let n=a.length;
+ let m=b.length;
+ a.length += b.length;
+ for(let i=0; i < m; ++i)
+ a[n+i]=b[i]+o;
+}
+
+class Geometry {
+ constructor() {
+ this.data=new vertexBuffer();
+ this.Onscreen=false;
+ this.m=[];
+ }
+
+ // Is 2D bounding box formed by projecting 3d points in vector v offscreen?
+ offscreen(v) {
+ let m=projViewMat;
+ let v0=v[0];
+ let x=v0[0], y=v0[1], z=v0[2];
+ let f=1/(m[3]*x+m[7]*y+m[11]*z+m[15]);
+ this.x=this.X=(m[0]*x+m[4]*y+m[8]*z+m[12])*f;
+ this.y=this.Y=(m[1]*x+m[5]*y+m[9]*z+m[13])*f;
+ for(let i=1, n=v.length; i < n; ++i) {
+ let vi=v[i];
+ let x=vi[0], y=vi[1], z=vi[2];
+ let f=1/(m[3]*x+m[7]*y+m[11]*z+m[15]);
+ let X=(m[0]*x+m[4]*y+m[8]*z+m[12])*f;
+ let Y=(m[1]*x+m[5]*y+m[9]*z+m[13])*f;
+ if(X < this.x) this.x=X;
+ else if(X > this.X) this.X=X;
+ if(Y < this.y) this.y=Y;
+ else if(Y > this.Y) this.Y=Y;
+ }
+ let eps=1e-2;
+ let min=-1-eps;
+ let max=1+eps;
+ if(this.X < min || this.x > max || this.Y < min || this.y > max) {
+ this.Onscreen=false;
+ return true;
+ }
+ return false;
+ }
+
+ T(v) {
+ let c0=this.c[0];
+ let c1=this.c[1];
+ let c2=this.c[2];
+ let x=v[0]-c0;
+ let y=v[1]-c1;
+ let z=v[2]-c2;
+ return [x*normMat[0]+y*normMat[3]+z*normMat[6]+c0,
+ x*normMat[1]+y*normMat[4]+z*normMat[7]+c1,
+ x*normMat[2]+y*normMat[5]+z*normMat[8]+c2];
+ }
+
+ Tcorners(m,M) {
+ return [this.T(m),this.T([m[0],m[1],M[2]]),this.T([m[0],M[1],m[2]]),
+ this.T([m[0],M[1],M[2]]),this.T([M[0],m[1],m[2]]),
+ this.T([M[0],m[1],M[2]]),this.T([M[0],M[1],m[2]]),this.T(M)];
+ }
+
+ setMaterial(data,draw) {
+ if(data.materialTable[this.MaterialIndex] == null) {
+ if(data.materials.length >= Nmaterials) {
+ data.partial=true;
+ draw();
+ }
+ data.materialTable[this.MaterialIndex]=data.materials.length;
+ data.materials.push(Materials[this.MaterialIndex]);
+ }
+ materialIndex=data.materialTable[this.MaterialIndex];
+ }
+
+ render() {
+ this.setMaterialIndex();
+
+ // First check if re-rendering is required
+ let v;
+ if(this.CenterIndex == 0)
+ v=corners(this.Min,this.Max);
+ else {
+ this.c=Centers[this.CenterIndex-1];
+ v=this.Tcorners(this.Min,this.Max);
+ }
+
+ if(this.offscreen(v)) { // Fully offscreen
+ this.data.clear();
+ this.notRendered();
+ return;
+ }
+
+ let p=this.controlpoints;
+ let P;
+
+ if(this.CenterIndex == 0) {
+ if(!remesh && this.Onscreen) { // Fully onscreen; no need to re-render
+ this.append();
+ return;
+ }
+ P=p;
+ } else { // Transform billboard labels
+ let n=p.length;
+ P=Array(n);
+ for(let i=0; i < n; ++i)
+ P[i]=this.T(p[i]);
+ }
+
+ let s=orthographic ? 1 : this.Min[2]/B[2];
+ let res=pixel*Math.hypot(s*(viewParam.xmax-viewParam.xmin),
+ s*(viewParam.ymax-viewParam.ymin))/size2;
+ this.res2=res*res;
+ this.Epsilon=FillFactor*res;
+
+ this.data.clear();
+ this.notRendered();
+ this.Onscreen=true;
+ this.process(P);
+ }
+}
+
+class BezierPatch extends Geometry {
+ /**
+ * Constructor for Bezier Patch
+ * @param {*} controlpoints array of 16 control points
+ * @param {*} CenterIndex center index of billboard labels (or 0)
+ * @param {*} MaterialIndex material index (>= 0)
+ * @param {*} Min bounding box corner
+ * @param {*} Max bounding box corner
+ * @param {*} colors array of 4 RGBA color arrays
+ */
+ constructor(controlpoints,CenterIndex,MaterialIndex,Min,Max,color) {
+ super();
+ this.controlpoints=controlpoints;
+ this.Min=Min;
+ this.Max=Max;
+ this.color=color;
+ this.CenterIndex=CenterIndex;
+ let n=controlpoints.length;
+ if(color) {
+ let sum=color[0][3]+color[1][3]+color[2][3];
+ this.transparent=(n == 16 || n == 4) ?
+ sum+color[3][3] < 1020 : sum < 765;
+ } else
+ this.transparent=Materials[MaterialIndex].diffuse[3] < 1;
+ this.MaterialIndex=MaterialIndex;
+
+ this.vertex=this.transparent ? this.data.Vertex.bind(this.data) :
+ this.data.vertex.bind(this.data);
+ this.L2norm(this.controlpoints);
+ }
+
+ setMaterialIndex() {
+ if(this.transparent)
+ this.setMaterial(transparentData,drawTransparent);
+ else {
+ if(this.color)
+ this.setMaterial(colorData,drawColor);
+ else
+ this.setMaterial(materialData,drawMaterial);
+ }
+ }
+
+// Render a Bezier patch via subdivision.
+ L2norm(p) {
+ let p0=p[0];
+ this.epsilon=0;
+ let n=p.length;
+ for(let i=1; i < n; ++i)
+ this.epsilon=Math.max(this.epsilon,
+ abs2([p[i][0]-p0[0],p[i][1]-p0[1],p[i][2]-p0[2]]));
+ this.epsilon *= Number.EPSILON
+ }
+
+ processTriangle(p) {
+ let p0=p[0];
+ let p1=p[1];
+ let p2=p[2];
+ let n=unit(cross([p1[0]-p0[0],p1[1]-p0[1],p1[2]-p0[2]],
+ [p2[0]-p0[0],p2[1]-p0[1],p2[2]-p0[2]]));
+ if(!this.offscreen([p0,p1,p2])) {
+ let i0,i1,i2;
+ if(this.color) {
+ i0=this.data.Vertex(p0,n,this.color[0]);
+ i1=this.data.Vertex(p1,n,this.color[1]);
+ i2=this.data.Vertex(p2,n,this.color[2]);
+ } else {
+ i0=this.vertex(p0,n);
+ i1=this.vertex(p1,n);
+ i2=this.vertex(p2,n);
+ }
+
+ if(wireframe == 0) {
+ this.data.indices.push(i0);
+ this.data.indices.push(i1);
+ this.data.indices.push(i2);
+ } else {
+ this.data.indices.push(i0);
+ this.data.indices.push(i1);
+ this.data.indices.push(i1);
+ this.data.indices.push(i2);
+ this.data.indices.push(i2);
+ this.data.indices.push(i0);
+ }
+
+ this.append();
+ }
+ }
+
+ processQuad(p) {
+ let p0=p[0];
+ let p1=p[1];
+ let p2=p[2];
+ let p3=p[3];
+ let n1=cross([p1[0]-p0[0],p1[1]-p0[1],p1[2]-p0[2]],
+ [p2[0]-p1[0],p2[1]-p1[1],p2[2]-p1[2]]);
+ let n2=cross([p2[0]-p3[0],p2[1]-p3[1],p2[2]-p3[2]],
+ [p3[0]-p0[0],p3[1]-p0[1],p3[2]-p0[2]]);
+ let n=unit([n1[0]+n2[0],n1[1]+n2[1],n1[2]+n2[2]]);
+ if(!this.offscreen([p0,p1,p2,p3])) {
+ let i0,i1,i2,i3;
+ if(this.color) {
+ i0=this.data.Vertex(p0,n,this.color[0]);
+ i1=this.data.Vertex(p1,n,this.color[1]);
+ i2=this.data.Vertex(p2,n,this.color[2]);
+ i3=this.data.Vertex(p3,n,this.color[3]);
+ } else {
+ i0=this.vertex(p0,n);
+ i1=this.vertex(p1,n);
+ i2=this.vertex(p2,n);
+ i3=this.vertex(p3,n);
+ }
+
+ if(wireframe == 0) {
+ this.data.indices.push(i0);
+ this.data.indices.push(i1);
+ this.data.indices.push(i2);
+
+ this.data.indices.push(i0);
+ this.data.indices.push(i2);
+ this.data.indices.push(i3);
+ } else {
+ this.data.indices.push(i0);
+ this.data.indices.push(i1);
+ this.data.indices.push(i1);
+ this.data.indices.push(i2);
+ this.data.indices.push(i2);
+ this.data.indices.push(i3);
+ this.data.indices.push(i3);
+ this.data.indices.push(i0);
+ }
+
+ this.append();
+ }
+ }
+
+ curve(p,a,b,c,d) {
+ new BezierCurve([p[a],p[b],p[c],p[d]],0,materialIndex,
+ this.Min,this.Max).render();
+ }
+
+ process(p) {
+ if(this.transparent && wireframe != 1)
+ // Override materialIndex to encode color vs material
+ materialIndex=this.color ? -1-materialIndex : 1+materialIndex;
+
+ if(p.length == 10) return this.process3(p);
+ if(p.length == 3) return this.processTriangle(p);
+ if(p.length == 4) return this.processQuad(p);
+
+ if(wireframe == 1) {
+ this.curve(p,0,4,8,12);
+ this.curve(p,12,13,14,15);
+ this.curve(p,15,11,7,3);
+ this.curve(p,3,2,1,0);
+ return;
+ }
+
+ let p0=p[0];
+ let p3=p[3];
+ let p12=p[12];
+ let p15=p[15];
+
+ let n0=this.normal(p3,p[2],p[1],p0,p[4],p[8],p12);
+ if(abs2(n0) < this.epsilon) {
+ n0=this.normal(p3,p[2],p[1],p0,p[13],p[14],p15);
+ if(abs2(n0) < this.epsilon)
+ n0=this.normal(p15,p[11],p[7],p3,p[4],p[8],p12);
+ }
+
+ let n1=this.normal(p0,p[4],p[8],p12,p[13],p[14],p15);
+ if(abs2(n1) < this.epsilon) {
+ n1=this.normal(p0,p[4],p[8],p12,p[11],p[7],p3);
+ if(abs2(n1) < this.epsilon)
+ n1=this.normal(p3,p[2],p[1],p0,p[13],p[14],p15);
+ }
+
+ let n2=this.normal(p12,p[13],p[14],p15,p[11],p[7],p3);
+ if(abs2(n2) < this.epsilon) {
+ n2=this.normal(p12,p[13],p[14],p15,p[2],p[1],p0);
+ if(abs2(n2) < this.epsilon)
+ n2=this.normal(p0,p[4],p[8],p12,p[11],p[7],p3);
+ }
+
+ let n3=this.normal(p15,p[11],p[7],p3,p[2],p[1],p0);
+ if(abs2(n3) < this.epsilon) {
+ n3=this.normal(p15,p[11],p[7],p3,p[4],p[8],p12);
+ if(abs2(n3) < this.epsilon)
+ n3=this.normal(p12,p[13],p[14],p15,p[2],p[1],p0);
+ }
+
+ if(this.color) {
+ let c0=this.color[0];
+ let c1=this.color[1];
+ let c2=this.color[2];
+ let c3=this.color[3];
+
+ let i0=this.data.Vertex(p0,n0,c0);
+ let i1=this.data.Vertex(p12,n1,c1);
+ let i2=this.data.Vertex(p15,n2,c2);
+ let i3=this.data.Vertex(p3,n3,c3);
+
+ this.Render(p,i0,i1,i2,i3,p0,p12,p15,p3,false,false,false,false,
+ c0,c1,c2,c3);
+ } else {
+ let i0=this.vertex(p0,n0);
+ let i1=this.vertex(p12,n1);
+ let i2=this.vertex(p15,n2);
+ let i3=this.vertex(p3,n3);
+
+ this.Render(p,i0,i1,i2,i3,p0,p12,p15,p3,false,false,false,false);
+ }
+ if(this.data.indices.length > 0) this.append();
+ }
+
+ append() {
+ if(this.transparent)
+ transparentData.append(this.data);
+ else if(this.color)
+ colorData.append(this.data);
+ else
+ materialData.append(this.data);
+ }
+
+ notRendered() {
+ if(this.transparent)
+ transparentData.rendered=false;
+ else if(this.color)
+ colorData.rendered=false;
+ else
+ materialData.rendered=false;
+ }
+
+ Render(p,I0,I1,I2,I3,P0,P1,P2,P3,flat0,flat1,flat2,flat3,C0,C1,C2,C3) {
+ let d=this.Distance(p);
+ if(d[0] < this.res2 && d[1] < this.res2) { // Bezier patch is flat
+ if(!this.offscreen([P0,P1,P2])) {
+ if(wireframe == 0) {
+ this.data.indices.push(I0);
+ this.data.indices.push(I1);
+ this.data.indices.push(I2);
+ } else {
+ this.data.indices.push(I0);
+ this.data.indices.push(I1);
+ this.data.indices.push(I1);
+ this.data.indices.push(I2);
+ }
+ }
+ if(!this.offscreen([P0,P2,P3])) {
+ if(wireframe == 0) {
+ this.data.indices.push(I0);
+ this.data.indices.push(I2);
+ this.data.indices.push(I3);
+ } else {
+ this.data.indices.push(I2);
+ this.data.indices.push(I3);
+ this.data.indices.push(I3);
+ this.data.indices.push(I0);
+ }
+ }
+ } else {
+ // Approximate bounds by bounding box of control polyhedron.
+ if(this.offscreen(p)) return;
+
+ /* Control points are indexed as follows:
+
+ Coordinate
+ +-----
+ Index
+
+ 03 13 23 33
+ +-----+-----+-----+
+ |3 |7 |11 |15
+ | | | |
+ |02 |12 |22 |32
+ +-----+-----+-----+
+ |2 |6 |10 |14
+ | | | |
+ |01 |11 |21 |31
+ +-----+-----+-----+
+ |1 |5 |9 |13
+ | | | |
+ |00 |10 |20 |30
+ +-----+-----+-----+
+ 0 4 8 12
+
+ */
+
+ let p0=p[0];
+ let p3=p[3];
+ let p12=p[12];
+ let p15=p[15];
+
+ if(d[0] < this.res2) { // flat in horizontal direction; split vertically
+ /*
+ P refers to a corner
+ m refers to a midpoint
+ s refers to a subpatch
+
+ +--------+--------+
+ |P3 P2|
+ | |
+ | s1 |
+ | |
+ | |
+ m1 +-----------------+ m0
+ | |
+ | |
+ | s0 |
+ | |
+ |P0 P1|
+ +-----------------+
+
+ */
+
+ let c0=new Split3(p0,p[1],p[2],p3);
+ let c1=new Split3(p[4],p[5],p[6],p[7]);
+ let c2=new Split3(p[8],p[9],p[10],p[11]);
+ let c3=new Split3(p12,p[13],p[14],p15);
+
+ let s0=[p0 ,c0.m0,c0.m3,c0.m5,
+ p[4],c1.m0,c1.m3,c1.m5,
+ p[8],c2.m0,c2.m3,c2.m5,
+ p12 ,c3.m0,c3.m3,c3.m5];
+
+ let s1=[c0.m5,c0.m4,c0.m2,p3,
+ c1.m5,c1.m4,c1.m2,p[7],
+ c2.m5,c2.m4,c2.m2,p[11],
+ c3.m5,c3.m4,c3.m2,p15];
+
+ let n0=this.normal(s0[12],s0[13],s0[14],s0[15],s0[11],s0[7],s0[3]);
+ if(abs2(n0) <= this.epsilon) {
+ n0=this.normal(s0[12],s0[13],s0[14],s0[15],s0[2],s0[1],s0[0]);
+ if(abs2(n0) <= this.epsilon)
+ n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[11],s0[7],s0[3]);
+ }
+
+ let n1=this.normal(s1[3],s1[2],s1[1],s1[0],s1[4],s1[8],s1[12]);
+ if(abs2(n1) <= this.epsilon) {
+ n1=this.normal(s1[3],s1[2],s1[1],s1[0],s1[13],s1[14],s1[15]);
+ if(abs2(n1) <= this.epsilon)
+ n1=this.normal(s1[15],s1[11],s1[7],s1[3],s1[4],s1[8],s1[12]);
+ }
+
+ let e=this.Epsilon;
+
+ // A kludge to remove subdivision cracks, only applied the first time
+ // an edge is found to be flat before the rest of the subpatch is.
+
+ let m0=[0.5*(P1[0]+P2[0]),
+ 0.5*(P1[1]+P2[1]),
+ 0.5*(P1[2]+P2[2])];
+ if(!flat1) {
+ if((flat1=Straightness(p12,p[13],p[14],p15) < this.res2)) {
+ let r=unit(this.differential(s1[12],s1[8],s1[4],s1[0]));
+ m0=[m0[0]-e*r[0],m0[1]-e*r[1],m0[2]-e*r[2]];
+ }
+ else m0=s0[15];
+ }
+
+ let m1=[0.5*(P3[0]+P0[0]),
+ 0.5*(P3[1]+P0[1]),
+ 0.5*(P3[2]+P0[2])];
+ if(!flat3) {
+ if((flat3=Straightness(p0,p[1],p[2],p3) < this.res2)) {
+ let r=unit(this.differential(s0[3],s0[7],s0[11],s0[15]));
+ m1=[m1[0]-e*r[0],m1[1]-e*r[1],m1[2]-e*r[2]];
+ }
+ else m1=s1[0];
+ }
+
+ if(C0) {
+ let c0=Array(4);
+ let c1=Array(4);
+ for(let i=0; i < 4; ++i) {
+ c0[i]=0.5*(C1[i]+C2[i]);
+ c1[i]=0.5*(C3[i]+C0[i]);
+ }
+
+ let i0=this.data.Vertex(m0,n0,c0);
+ let i1=this.data.Vertex(m1,n1,c1);
+
+ this.Render(s0,I0,I1,i0,i1,P0,P1,m0,m1,flat0,flat1,false,flat3,
+ C0,C1,c0,c1);
+ this.Render(s1,i1,i0,I2,I3,m1,m0,P2,P3,false,flat1,flat2,flat3,
+ c1,c0,C2,C3);
+ } else {
+ let i0=this.vertex(m0,n0);
+ let i1=this.vertex(m1,n1);
+
+ this.Render(s0,I0,I1,i0,i1,P0,P1,m0,m1,flat0,flat1,false,flat3);
+ this.Render(s1,i1,i0,I2,I3,m1,m0,P2,P3,false,flat1,flat2,flat3);
+ }
+ return;
+ }
+
+ if(d[1] < this.res2) { // flat in vertical direction; split horizontally
+ /*
+ P refers to a corner
+ m refers to a midpoint
+ s refers to a subpatch
+
+ m1
+ +--------+--------+
+ |P3 | P2|
+ | | |
+ | | |
+ | | |
+ | | |
+ | s0 | s1 |
+ | | |
+ | | |
+ | | |
+ | | |
+ |P0 | P1|
+ +--------+--------+
+ m0
+
+ */
+
+ let c0=new Split3(p0,p[4],p[8],p12);
+ let c1=new Split3(p[1],p[5],p[9],p[13]);
+ let c2=new Split3(p[2],p[6],p[10],p[14]);
+ let c3=new Split3(p3,p[7],p[11],p15);
+
+ let s0=[p0,p[1],p[2],p3,
+ c0.m0,c1.m0,c2.m0,c3.m0,
+ c0.m3,c1.m3,c2.m3,c3.m3,
+ c0.m5,c1.m5,c2.m5,c3.m5];
+
+ let s1=[c0.m5,c1.m5,c2.m5,c3.m5,
+ c0.m4,c1.m4,c2.m4,c3.m4,
+ c0.m2,c1.m2,c2.m2,c3.m2,
+ p12,p[13],p[14],p15];
+
+ let n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[13],s0[14],s0[15]);
+ if(abs2(n0) <= this.epsilon) {
+ n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[11],s0[7],s0[3]);
+ if(abs2(n0) <= this.epsilon)
+ n0=this.normal(s0[3],s0[2],s0[1],s0[0],s0[13],s0[14],s0[15]);
+ }
+
+ let n1=this.normal(s1[15],s1[11],s1[7],s1[3],s1[2],s1[1],s1[0]);
+ if(abs2(n1) <= this.epsilon) {
+ n1=this.normal(s1[15],s1[11],s1[7],s1[3],s1[4],s1[8],s1[12]);
+ if(abs2(n1) <= this.epsilon)
+ n1=this.normal(s1[12],s1[13],s1[14],s1[15],s1[2],s1[1],s1[0]);
+ }
+
+ let e=this.Epsilon;
+
+ // A kludge to remove subdivision cracks, only applied the first time
+ // an edge is found to be flat before the rest of the subpatch is.
+
+ let m0=[0.5*(P0[0]+P1[0]),
+ 0.5*(P0[1]+P1[1]),
+ 0.5*(P0[2]+P1[2])];
+ if(!flat0) {
+ if((flat0=Straightness(p0,p[4],p[8],p12) < this.res2)) {
+ let r=unit(this.differential(s1[0],s1[1],s1[2],s1[3]));
+ m0=[m0[0]-e*r[0],m0[1]-e*r[1],m0[2]-e*r[2]];
+ }
+ else m0=s0[12];
+ }
+
+ let m1=[0.5*(P2[0]+P3[0]),
+ 0.5*(P2[1]+P3[1]),
+ 0.5*(P2[2]+P3[2])];
+ if(!flat2) {
+ if((flat2=Straightness(p15,p[11],p[7],p3) < this.res2)) {
+ let r=unit(this.differential(s0[15],s0[14],s0[13],s0[12]));
+ m1=[m1[0]-e*r[0],m1[1]-e*r[1],m1[2]-e*r[2]];
+ }
+ else m1=s1[3];
+ }
+
+ if(C0) {
+ let c0=Array(4);
+ let c1=Array(4);
+ for(let i=0; i < 4; ++i) {
+ c0[i]=0.5*(C0[i]+C1[i]);
+ c1[i]=0.5*(C2[i]+C3[i]);
+ }
+
+ let i0=this.data.Vertex(m0,n0,c0);
+ let i1=this.data.Vertex(m1,n1,c1);
+
+ this.Render(s0,I0,i0,i1,I3,P0,m0,m1,P3,flat0,false,flat2,flat3,
+ C0,c0,c1,C3);
+ this.Render(s1,i0,I1,I2,i1,m0,P1,P2,m1,flat0,flat1,flat2,false,
+ c0,C1,C2,c1);
+ } else {
+ let i0=this.vertex(m0,n0);
+ let i1=this.vertex(m1,n1);
+
+ this.Render(s0,I0,i0,i1,I3,P0,m0,m1,P3,flat0,false,flat2,flat3);
+ this.Render(s1,i0,I1,I2,i1,m0,P1,P2,m1,flat0,flat1,flat2,false);
+ }
+ return;
+ }
+
+ /*
+ Horizontal and vertical subdivision:
+ P refers to a corner
+ m refers to a midpoint
+ s refers to a subpatch
+
+ m2
+ +--------+--------+
+ |P3 | P2|
+ | | |
+ | s3 | s2 |
+ | | |
+ | | m4 |
+ m3 +--------+--------+ m1
+ | | |
+ | | |
+ | s0 | s1 |
+ | | |
+ |P0 | P1|
+ +--------+--------+
+ m0
+ */
+
+ // Subdivide patch:
+
+ let c0=new Split3(p0,p[1],p[2],p3);
+ let c1=new Split3(p[4],p[5],p[6],p[7]);
+ let c2=new Split3(p[8],p[9],p[10],p[11]);
+ let c3=new Split3(p12,p[13],p[14],p15);
+
+ let c4=new Split3(p0,p[4],p[8],p12);
+ let c5=new Split3(c0.m0,c1.m0,c2.m0,c3.m0);
+ let c6=new Split3(c0.m3,c1.m3,c2.m3,c3.m3);
+ let c7=new Split3(c0.m5,c1.m5,c2.m5,c3.m5);
+ let c8=new Split3(c0.m4,c1.m4,c2.m4,c3.m4);
+ let c9=new Split3(c0.m2,c1.m2,c2.m2,c3.m2);
+ let c10=new Split3(p3,p[7],p[11],p15);
+
+ let s0=[p0,c0.m0,c0.m3,c0.m5,c4.m0,c5.m0,c6.m0,c7.m0,
+ c4.m3,c5.m3,c6.m3,c7.m3,c4.m5,c5.m5,c6.m5,c7.m5];
+ let s1=[c4.m5,c5.m5,c6.m5,c7.m5,c4.m4,c5.m4,c6.m4,c7.m4,
+ c4.m2,c5.m2,c6.m2,c7.m2,p12,c3.m0,c3.m3,c3.m5];
+ let s2=[c7.m5,c8.m5,c9.m5,c10.m5,c7.m4,c8.m4,c9.m4,c10.m4,
+ c7.m2,c8.m2,c9.m2,c10.m2,c3.m5,c3.m4,c3.m2,p15];
+ let s3=[c0.m5,c0.m4,c0.m2,p3,c7.m0,c8.m0,c9.m0,c10.m0,
+ c7.m3,c8.m3,c9.m3,c10.m3,c7.m5,c8.m5,c9.m5,c10.m5];
+
+ let m4=s0[15];
+
+ let n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[13],s0[14],s0[15]);
+ if(abs2(n0) < this.epsilon) {
+ n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[11],s0[7],s0[3]);
+ if(abs2(n0) < this.epsilon)
+ n0=this.normal(s0[3],s0[2],s0[1],s0[0],s0[13],s0[14],s0[15]);
+ }
+
+ let n1=this.normal(s1[12],s1[13],s1[14],s1[15],s1[11],s1[7],s1[3]);
+ if(abs2(n1) < this.epsilon) {
+ n1=this.normal(s1[12],s1[13],s1[14],s1[15],s1[2],s1[1],s1[0]);
+ if(abs2(n1) < this.epsilon)
+ n1=this.normal(s1[0],s1[4],s1[8],s1[12],s1[11],s1[7],s1[3]);
+ }
+
+ let n2=this.normal(s2[15],s2[11],s2[7],s2[3],s2[2],s2[1],s2[0]);
+ if(abs2(n2) < this.epsilon) {
+ n2=this.normal(s2[15],s2[11],s2[7],s2[3],s2[4],s2[8],s2[12]);
+ if(abs2(n2) < this.epsilon)
+ n2=this.normal(s2[12],s2[13],s2[14],s2[15],s2[2],s2[1],s2[0]);
+ }
+
+ let n3=this.normal(s3[3],s3[2],s3[1],s3[0],s3[4],s3[8],s3[12]);
+ if(abs2(n3) < this.epsilon) {
+ n3=this.normal(s3[3],s3[2],s3[1],s3[0],s3[13],s3[14],s3[15]);
+ if(abs2(n3) < this.epsilon)
+ n3=this.normal(s3[15],s3[11],s3[7],s3[3],s3[4],s3[8],s3[12]);
+ }
+
+ let n4=this.normal(s2[3],s2[2],s2[1],m4,s2[4],s2[8],s2[12]);
+
+ let e=this.Epsilon;
+
+ // A kludge to remove subdivision cracks, only applied the first time
+ // an edge is found to be flat before the rest of the subpatch is.
+ let m0=[0.5*(P0[0]+P1[0]),
+ 0.5*(P0[1]+P1[1]),
+ 0.5*(P0[2]+P1[2])];
+ if(!flat0) {
+ if((flat0=Straightness(p0,p[4],p[8],p12) < this.res2)) {
+ let r=unit(this.differential(s1[0],s1[1],s1[2],s1[3]));
+ m0=[m0[0]-e*r[0],m0[1]-e*r[1],m0[2]-e*r[2]];
+ }
+ else m0=s0[12];
+ }
+
+ let m1=[0.5*(P1[0]+P2[0]),
+ 0.5*(P1[1]+P2[1]),
+ 0.5*(P1[2]+P2[2])];
+ if(!flat1) {
+ if((flat1=Straightness(p12,p[13],p[14],p15) < this.res2)) {
+ let r=unit(this.differential(s2[12],s2[8],s2[4],s2[0]));
+ m1=[m1[0]-e*r[0],m1[1]-e*r[1],m1[2]-e*r[2]];
+ }
+ else m1=s1[15];
+ }
+
+ let m2=[0.5*(P2[0]+P3[0]),
+ 0.5*(P2[1]+P3[1]),
+ 0.5*(P2[2]+P3[2])];
+ if(!flat2) {
+ if((flat2=Straightness(p15,p[11],p[7],p3) < this.res2)) {
+ let r=unit(this.differential(s3[15],s3[14],s3[13],s3[12]));
+ m2=[m2[0]-e*r[0],m2[1]-e*r[1],m2[2]-e*r[2]];
+ }
+ else m2=s2[3];
+ }
+
+ let m3=[0.5*(P3[0]+P0[0]),
+ 0.5*(P3[1]+P0[1]),
+ 0.5*(P3[2]+P0[2])];
+ if(!flat3) {
+ if((flat3=Straightness(p0,p[1],p[2],p3) < this.res2)) {
+ let r=unit(this.differential(s0[3],s0[7],s0[11],s0[15]));
+ m3=[m3[0]-e*r[0],m3[1]-e*r[1],m3[2]-e*r[2]];
+ }
+ else m3=s3[0];
+ }
+
+ if(C0) {
+ let c0=Array(4);
+ let c1=Array(4);
+ let c2=Array(4);
+ let c3=Array(4);
+ let c4=Array(4);
+ for(let i=0; i < 4; ++i) {
+ c0[i]=0.5*(C0[i]+C1[i]);
+ c1[i]=0.5*(C1[i]+C2[i]);
+ c2[i]=0.5*(C2[i]+C3[i]);
+ c3[i]=0.5*(C3[i]+C0[i]);
+ c4[i]=0.5*(c0[i]+c2[i]);
+ }
+
+ let i0=this.data.Vertex(m0,n0,c0);
+ let i1=this.data.Vertex(m1,n1,c1);
+ let i2=this.data.Vertex(m2,n2,c2);
+ let i3=this.data.Vertex(m3,n3,c3);
+ let i4=this.data.Vertex(m4,n4,c4);
+
+ this.Render(s0,I0,i0,i4,i3,P0,m0,m4,m3,flat0,false,false,flat3,
+ C0,c0,c4,c3);
+ this.Render(s1,i0,I1,i1,i4,m0,P1,m1,m4,flat0,flat1,false,false,
+ c0,C1,c1,c4);
+ this.Render(s2,i4,i1,I2,i2,m4,m1,P2,m2,false,flat1,flat2,false,
+ c4,c1,C2,c2);
+ this.Render(s3,i3,i4,i2,I3,m3,m4,m2,P3,false,false,flat2,flat3,
+ c3,c4,c2,C3);
+ } else {
+ let i0=this.vertex(m0,n0);
+ let i1=this.vertex(m1,n1);
+ let i2=this.vertex(m2,n2);
+ let i3=this.vertex(m3,n3);
+ let i4=this.vertex(m4,n4);
+
+ this.Render(s0,I0,i0,i4,i3,P0,m0,m4,m3,flat0,false,false,flat3);
+ this.Render(s1,i0,I1,i1,i4,m0,P1,m1,m4,flat0,flat1,false,false);
+ this.Render(s2,i4,i1,I2,i2,m4,m1,P2,m2,false,flat1,flat2,false);
+ this.Render(s3,i3,i4,i2,I3,m3,m4,m2,P3,false,false,flat2,flat3);
+ }
+ }
+ }
+
+// Render a Bezier triangle via subdivision.
+ process3(p) {
+ if(wireframe == 1) {
+ this.curve(p,0,1,3,6);
+ this.curve(p,6,7,8,9);
+ this.curve(p,9,5,2,0);
+ return;
+ }
+
+ let p0=p[0];
+ let p6=p[6];
+ let p9=p[9];
+
+ let n0=this.normal(p9,p[5],p[2],p0,p[1],p[3],p6);
+ let n1=this.normal(p0,p[1],p[3],p6,p[7],p[8],p9);
+ let n2=this.normal(p6,p[7],p[8],p9,p[5],p[2],p0);
+
+ if(this.color) {
+ let c0=this.color[0];
+ let c1=this.color[1];
+ let c2=this.color[2];
+
+ let i0=this.data.Vertex(p0,n0,c0);
+ let i1=this.data.Vertex(p6,n1,c1);
+ let i2=this.data.Vertex(p9,n2,c2);
+
+ this.Render3(p,i0,i1,i2,p0,p6,p9,false,false,false,c0,c1,c2);
+
+ } else {
+ let i0=this.vertex(p0,n0);
+ let i1=this.vertex(p6,n1);
+ let i2=this.vertex(p9,n2);
+
+ this.Render3(p,i0,i1,i2,p0,p6,p9,false,false,false);
+ }
+ if(this.data.indices.length > 0) this.append();
+ }
+
+ Render3(p,I0,I1,I2,P0,P1,P2,flat0,flat1,flat2,C0,C1,C2) {
+ if(this.Distance3(p) < this.res2) { // Bezier triangle is flat
+ if(!this.offscreen([P0,P1,P2])) {
+ if(wireframe == 0) {
+ this.data.indices.push(I0);
+ this.data.indices.push(I1);
+ this.data.indices.push(I2);
+ } else {
+ this.data.indices.push(I0);
+ this.data.indices.push(I1);
+ this.data.indices.push(I1);
+ this.data.indices.push(I2);
+ this.data.indices.push(I2);
+ this.data.indices.push(I0);
+ }
+ }
+ } else {
+ // Approximate bounds by bounding box of control polyhedron.
+ if(this.offscreen(p)) return;
+
+ /* Control points are indexed as follows:
+
+ Coordinate
+ Index
+
+ 030
+ 9
+ /\
+ / \
+ / \
+ / \
+ / \
+ 021 + + 120
+ 5 / \ 8
+ / \
+ / \
+ / \
+ / \
+ 012 + + + 210
+ 2 / 111 \ 7
+ / 4 \
+ / \
+ / \
+ / \
+ /__________________________________\
+ 003 102 201 300
+ 0 1 3 6
+
+
+ Subdivision:
+ P2
+ 030
+ /\
+ / \
+ / \
+ / \
+ / \
+ / up \
+ / \
+ / \
+ p1 /________________\ p0
+ /\ / \
+ / \ / \
+ / \ / \
+ / \ center / \
+ / \ / \
+ / \ / \
+ / left \ / right \
+ / \ / \
+ /________________V_________________\
+ 003 p2 300
+ P0 P1
+ */
+
+ // Subdivide triangle:
+
+ let l003=p[0];
+ let p102=p[1];
+ let p012=p[2];
+ let p201=p[3];
+ let p111=p[4];
+ let p021=p[5];
+ let r300=p[6];
+ let p210=p[7];
+ let p120=p[8];
+ let u030=p[9];
+
+ let u021=[0.5*(u030[0]+p021[0]),
+ 0.5*(u030[1]+p021[1]),
+ 0.5*(u030[2]+p021[2])];
+ let u120=[0.5*(u030[0]+p120[0]),
+ 0.5*(u030[1]+p120[1]),
+ 0.5*(u030[2]+p120[2])];
+
+ let p033=[0.5*(p021[0]+p012[0]),
+ 0.5*(p021[1]+p012[1]),
+ 0.5*(p021[2]+p012[2])];
+ let p231=[0.5*(p120[0]+p111[0]),
+ 0.5*(p120[1]+p111[1]),
+ 0.5*(p120[2]+p111[2])];
+ let p330=[0.5*(p120[0]+p210[0]),
+ 0.5*(p120[1]+p210[1]),
+ 0.5*(p120[2]+p210[2])];
+
+ let p123=[0.5*(p012[0]+p111[0]),
+ 0.5*(p012[1]+p111[1]),
+ 0.5*(p012[2]+p111[2])];
+
+ let l012=[0.5*(p012[0]+l003[0]),
+ 0.5*(p012[1]+l003[1]),
+ 0.5*(p012[2]+l003[2])];
+ let p312=[0.5*(p111[0]+p201[0]),
+ 0.5*(p111[1]+p201[1]),
+ 0.5*(p111[2]+p201[2])];
+ let r210=[0.5*(p210[0]+r300[0]),
+ 0.5*(p210[1]+r300[1]),
+ 0.5*(p210[2]+r300[2])];
+
+ let l102=[0.5*(l003[0]+p102[0]),
+ 0.5*(l003[1]+p102[1]),
+ 0.5*(l003[2]+p102[2])];
+ let p303=[0.5*(p102[0]+p201[0]),
+ 0.5*(p102[1]+p201[1]),
+ 0.5*(p102[2]+p201[2])];
+ let r201=[0.5*(p201[0]+r300[0]),
+ 0.5*(p201[1]+r300[1]),
+ 0.5*(p201[2]+r300[2])];
+
+ let u012=[0.5*(u021[0]+p033[0]),
+ 0.5*(u021[1]+p033[1]),
+ 0.5*(u021[2]+p033[2])];
+ let u210=[0.5*(u120[0]+p330[0]),
+ 0.5*(u120[1]+p330[1]),
+ 0.5*(u120[2]+p330[2])];
+ let l021=[0.5*(p033[0]+l012[0]),
+ 0.5*(p033[1]+l012[1]),
+ 0.5*(p033[2]+l012[2])];
+ let p4xx=[0.5*p231[0]+0.25*(p111[0]+p102[0]),
+ 0.5*p231[1]+0.25*(p111[1]+p102[1]),
+ 0.5*p231[2]+0.25*(p111[2]+p102[2])];
+ let r120=[0.5*(p330[0]+r210[0]),
+ 0.5*(p330[1]+r210[1]),
+ 0.5*(p330[2]+r210[2])];
+ let px4x=[0.5*p123[0]+0.25*(p111[0]+p210[0]),
+ 0.5*p123[1]+0.25*(p111[1]+p210[1]),
+ 0.5*p123[2]+0.25*(p111[2]+p210[2])];
+ let pxx4=[0.25*(p021[0]+p111[0])+0.5*p312[0],
+ 0.25*(p021[1]+p111[1])+0.5*p312[1],
+ 0.25*(p021[2]+p111[2])+0.5*p312[2]];
+ let l201=[0.5*(l102[0]+p303[0]),
+ 0.5*(l102[1]+p303[1]),
+ 0.5*(l102[2]+p303[2])];
+ let r102=[0.5*(p303[0]+r201[0]),
+ 0.5*(p303[1]+r201[1]),
+ 0.5*(p303[2]+r201[2])];
+
+ let l210=[0.5*(px4x[0]+l201[0]),
+ 0.5*(px4x[1]+l201[1]),
+ 0.5*(px4x[2]+l201[2])]; // =c120
+ let r012=[0.5*(px4x[0]+r102[0]),
+ 0.5*(px4x[1]+r102[1]),
+ 0.5*(px4x[2]+r102[2])]; // =c021
+ let l300=[0.5*(l201[0]+r102[0]),
+ 0.5*(l201[1]+r102[1]),
+ 0.5*(l201[2]+r102[2])]; // =r003=c030
+
+ let r021=[0.5*(pxx4[0]+r120[0]),
+ 0.5*(pxx4[1]+r120[1]),
+ 0.5*(pxx4[2]+r120[2])]; // =c012
+ let u201=[0.5*(u210[0]+pxx4[0]),
+ 0.5*(u210[1]+pxx4[1]),
+ 0.5*(u210[2]+pxx4[2])]; // =c102
+ let r030=[0.5*(u210[0]+r120[0]),
+ 0.5*(u210[1]+r120[1]),
+ 0.5*(u210[2]+r120[2])]; // =u300=c003
+
+ let u102=[0.5*(u012[0]+p4xx[0]),
+ 0.5*(u012[1]+p4xx[1]),
+ 0.5*(u012[2]+p4xx[2])]; // =c201
+ let l120=[0.5*(l021[0]+p4xx[0]),
+ 0.5*(l021[1]+p4xx[1]),
+ 0.5*(l021[2]+p4xx[2])]; // =c210
+ let l030=[0.5*(u012[0]+l021[0]),
+ 0.5*(u012[1]+l021[1]),
+ 0.5*(u012[2]+l021[2])]; // =u003=c300
+
+ let l111=[0.5*(p123[0]+l102[0]),
+ 0.5*(p123[1]+l102[1]),
+ 0.5*(p123[2]+l102[2])];
+ let r111=[0.5*(p312[0]+r210[0]),
+ 0.5*(p312[1]+r210[1]),
+ 0.5*(p312[2]+r210[2])];
+ let u111=[0.5*(u021[0]+p231[0]),
+ 0.5*(u021[1]+p231[1]),
+ 0.5*(u021[2]+p231[2])];
+ let c111=[0.25*(p033[0]+p330[0]+p303[0]+p111[0]),
+ 0.25*(p033[1]+p330[1]+p303[1]+p111[1]),
+ 0.25*(p033[2]+p330[2]+p303[2]+p111[2])];
+
+ let l=[l003,l102,l012,l201,l111,l021,l300,l210,l120,l030]; // left
+ let r=[l300,r102,r012,r201,r111,r021,r300,r210,r120,r030]; // right
+ let u=[l030,u102,u012,u201,u111,u021,r030,u210,u120,u030]; // up
+ let c=[r030,u201,r021,u102,c111,r012,l030,l120,l210,l300]; // center
+
+ let n0=this.normal(l300,r012,r021,r030,u201,u102,l030);
+ let n1=this.normal(r030,u201,u102,l030,l120,l210,l300);
+ let n2=this.normal(l030,l120,l210,l300,r012,r021,r030);
+
+ let e=this.Epsilon;
+
+ // A kludge to remove subdivision cracks, only applied the first time
+ // an edge is found to be flat before the rest of the subpatch is.
+
+ let m0=[0.5*(P1[0]+P2[0]),
+ 0.5*(P1[1]+P2[1]),
+ 0.5*(P1[2]+P2[2])];
+ if(!flat0) {
+ if((flat0=Straightness(r300,p210,p120,u030) < this.res2)) {
+ let r=unit(this.sumdifferential(c[0],c[2],c[5],c[9],c[1],c[3],c[6]));
+ m0=[m0[0]-e*r[0],m0[1]-e*r[1],m0[2]-e*r[2]];
+ }
+ else m0=r030;
+ }
+
+
+ let m1=[0.5*(P2[0]+P0[0]),
+ 0.5*(P2[1]+P0[1]),
+ 0.5*(P2[2]+P0[2])];
+ if(!flat1) {
+ if((flat1=Straightness(l003,p012,p021,u030) < this.res2)) {
+ let r=unit(this.sumdifferential(c[6],c[3],c[1],c[0],c[7],c[8],c[9]));
+ m1=[m1[0]-e*r[0],m1[1]-e*r[1],m1[2]-e*r[2]];
+ }
+ else m1=l030;
+ }
+
+ let m2=[0.5*(P0[0]+P1[0]),
+ 0.5*(P0[1]+P1[1]),
+ 0.5*(P0[2]+P1[2])];
+ if(!flat2) {
+ if((flat2=Straightness(l003,p102,p201,r300) < this.res2)) {
+ let r=unit(this.sumdifferential(c[9],c[8],c[7],c[6],c[5],c[2],c[0]));
+ m2=[m2[0]-e*r[0],m2[1]-e*r[1],m2[2]-e*r[2]];
+ }
+ else m2=l300;
+ }
+
+ if(C0) {
+ let c0=Array(4);
+ let c1=Array(4);
+ let c2=Array(4);
+ for(let i=0; i < 4; ++i) {
+ c0[i]=0.5*(C1[i]+C2[i]);
+ c1[i]=0.5*(C2[i]+C0[i]);
+ c2[i]=0.5*(C0[i]+C1[i]);
+ }
+
+ let i0=this.data.Vertex(m0,n0,c0);
+ let i1=this.data.Vertex(m1,n1,c1);
+ let i2=this.data.Vertex(m2,n2,c2);
+
+ this.Render3(l,I0,i2,i1,P0,m2,m1,false,flat1,flat2,C0,c2,c1);
+ this.Render3(r,i2,I1,i0,m2,P1,m0,flat0,false,flat2,c2,C1,c0);
+ this.Render3(u,i1,i0,I2,m1,m0,P2,flat0,flat1,false,c1,c0,C2);
+ this.Render3(c,i0,i1,i2,m0,m1,m2,false,false,false,c0,c1,c2);
+ } else {
+ let i0=this.vertex(m0,n0);
+ let i1=this.vertex(m1,n1);
+ let i2=this.vertex(m2,n2);
+
+ this.Render3(l,I0,i2,i1,P0,m2,m1,false,flat1,flat2);
+ this.Render3(r,i2,I1,i0,m2,P1,m0,flat0,false,flat2);
+ this.Render3(u,i1,i0,I2,m1,m0,P2,flat0,flat1,false);
+ this.Render3(c,i0,i1,i2,m0,m1,m2,false,false,false);
+ }
+ }
+ }
+
+ // Check the flatness of a Bezier patch
+ Distance(p) {
+ let p0=p[0];
+ let p3=p[3];
+ let p12=p[12];
+ let p15=p[15];
+
+ // Check the horizontal flatness.
+ let h=Flatness(p0,p12,p3,p15);
+ // Check straightness of the horizontal edges and interior control curves.
+ h=Math.max(Straightness(p0,p[4],p[8],p12));
+ h=Math.max(h,Straightness(p[1],p[5],p[9],p[13]));
+ h=Math.max(h,Straightness(p3,p[7],p[11],p15));
+ h=Math.max(h,Straightness(p[2],p[6],p[10],p[14]));
+
+ // Check the vertical flatness.
+ let v=Flatness(p0,p3,p12,p15);
+ // Check straightness of the vertical edges and interior control curves.
+ v=Math.max(v,Straightness(p0,p[1],p[2],p3));
+ v=Math.max(v,Straightness(p[4],p[5],p[6],p[7]));
+ v=Math.max(v,Straightness(p[8],p[9],p[10],p[11]));
+ v=Math.max(v,Straightness(p12,p[13],p[14],p15));
+
+ return [h,v];
+ }
+
+ // Check the flatness of a Bezier triangle
+ Distance3(p) {
+ let p0=p[0];
+ let p4=p[4];
+ let p6=p[6];
+ let p9=p[9];
+
+ // Check how far the internal point is from the centroid of the vertices.
+ let d=abs2([(p0[0]+p6[0]+p9[0])*third-p4[0],
+ (p0[1]+p6[1]+p9[1])*third-p4[1],
+ (p0[2]+p6[2]+p9[2])*third-p4[2]]);
+
+ // Determine how straight the edges are.
+ d=Math.max(d,Straightness(p0,p[1],p[3],p6));
+ d=Math.max(d,Straightness(p0,p[2],p[5],p9));
+ return Math.max(d,Straightness(p6,p[7],p[8],p9));
+ }
+
+ // Return the differential of the Bezier curve p0,p1,p2,p3 at 0.
+ differential(p0,p1,p2,p3) {
+ let p=[3*(p1[0]-p0[0]),3*(p1[1]-p0[1]),3*(p1[2]-p0[2])];
+ if(abs2(p) > this.epsilon)
+ return p;
+
+ p=bezierPP(p0,p1,p2);
+ if(abs2(p) > this.epsilon)
+ return p;
+
+ return bezierPPP(p0,p1,p2,p3);
+ }
+
+ sumdifferential(p0,p1,p2,p3,p4,p5,p6) {
+ let d0=this.differential(p0,p1,p2,p3);
+ let d1=this.differential(p0,p4,p5,p6);
+ return [d0[0]+d1[0],d0[1]+d1[1],d0[2]+d1[2]];
+ }
+
+ normal(left3,left2,left1,middle,right1,right2,right3) {
+ let ux=3*(right1[0]-middle[0]);
+ let uy=3*(right1[1]-middle[1]);
+ let uz=3*(right1[2]-middle[2]);
+ let vx=3*(left1[0]-middle[0]);
+ let vy=3*(left1[1]-middle[1]);
+ let vz=3*(left1[2]-middle[2]);
+
+ let n=[uy*vz-uz*vy,
+ uz*vx-ux*vz,
+ ux*vy-uy*vx];
+ if(abs2(n) > this.epsilon)
+ return n;
+
+ let lp=[vx,vy,vz];
+ let rp=[ux,uy,uz];
+
+ let lpp=bezierPP(middle,left1,left2);
+ let rpp=bezierPP(middle,right1,right2);
+
+ let a=cross(rpp,lp);
+ let b=cross(rp,lpp);
+ n=[a[0]+b[0],
+ a[1]+b[1],
+ a[2]+b[2]];
+ if(abs2(n) > this.epsilon)
+ return n;
+
+ let lppp=bezierPPP(middle,left1,left2,left3);
+ let rppp=bezierPPP(middle,right1,right2,right3);
+
+ a=cross(rp,lppp);
+ b=cross(rppp,lp);
+ let c=cross(rpp,lpp);
+
+ n=[a[0]+b[0]+c[0],
+ a[1]+b[1]+c[1],
+ a[2]+b[2]+c[2]];
+ if(abs2(n) > this.epsilon)
+ return n;
+
+ a=cross(rppp,lpp);
+ b=cross(rpp,lppp);
+
+ n=[a[0]+b[0],
+ a[1]+b[1],
+ a[2]+b[2]];
+ if(abs2(n) > this.epsilon)
+ return n;
+
+ return cross(rppp,lppp);
+ }
+}
+
+class BezierCurve extends Geometry {
+ constructor(controlpoints,CenterIndex,MaterialIndex,Min,Max) {
+ super();
+ this.controlpoints=controlpoints;
+ this.Min=Min;
+ this.Max=Max;
+ this.CenterIndex=CenterIndex;
+ this.MaterialIndex=MaterialIndex;
+ }
+
+ setMaterialIndex() {
+ this.setMaterial(material1Data,drawMaterial1);
+ }
+
+ processLine(p) {
+ let p0=p[0];
+ let p1=p[1];
+ if(!this.offscreen([p0,p1])) {
+ let n=[0,0,1];
+ this.data.indices.push(this.data.vertex(p0,n));
+ this.data.indices.push(this.data.vertex(p1,n));
+ this.append();
+ }
+ }
+
+ process(p) {
+ if(p.length == 2) return this.processLine(p);
+
+ let p0=p[0];
+ let p1=p[1];
+ let p2=p[2];
+ let p3=p[3];
+
+ let n0=this.normal(bezierP(p0,p1),bezierPP(p0,p1,p2));
+ let n1=this.normal(bezierP(p2,p3),bezierPP(p3,p2,p1));
+
+ let i0=this.data.vertex(p0,n0);
+ let i3=this.data.vertex(p3,n1);
+
+ this.Render(p,i0,i3);
+ if(this.data.indices.length > 0) this.append();
+ }
+
+ append() {
+ material1Data.append(this.data);
+ }
+
+ notRendered() {
+ material1Data.rendered=false;
+ }
+
+ Render(p,I0,I1) {
+ let p0=p[0];
+ let p1=p[1];
+ let p2=p[2];
+ let p3=p[3];
+
+ if(Straightness(p0,p1,p2,p3) < this.res2) { // Segment is flat
+ if(!this.offscreen([p0,p3])) {
+ this.data.indices.push(I0);
+ this.data.indices.push(I1);
+ }
+ } else { // Segment is not flat
+ if(this.offscreen(p)) return;
+
+ let m0=[0.5*(p0[0]+p1[0]),0.5*(p0[1]+p1[1]),0.5*(p0[2]+p1[2])];
+ let m1=[0.5*(p1[0]+p2[0]),0.5*(p1[1]+p2[1]),0.5*(p1[2]+p2[2])];
+ let m2=[0.5*(p2[0]+p3[0]),0.5*(p2[1]+p3[1]),0.5*(p2[2]+p3[2])];
+ let m3=[0.5*(m0[0]+m1[0]),0.5*(m0[1]+m1[1]),0.5*(m0[2]+m1[2])];
+ let m4=[0.5*(m1[0]+m2[0]),0.5*(m1[1]+m2[1]),0.5*(m1[2]+m2[2])];
+ let m5=[0.5*(m3[0]+m4[0]),0.5*(m3[1]+m4[1]),0.5*(m3[2]+m4[2])];
+
+ let s0=[p0,m0,m3,m5];
+ let s1=[m5,m4,m2,p3];
+
+ let n0=this.normal(bezierPh(p0,p1,p2,p3),bezierPPh(p0,p1,p2,p3));
+ let i0=this.data.vertex(m5,n0);
+
+ this.Render(s0,I0,i0);
+ this.Render(s1,i0,I1);
+ }
+ }
+
+ normal(bP,bPP) {
+ let bPbP=dot(bP,bP);
+ let bPbPP=dot(bP,bPP);
+ return [bPbP*bPP[0]-bPbPP*bP[0],
+ bPbP*bPP[1]-bPbPP*bP[1],
+ bPbP*bPP[2]-bPbPP*bP[2]];
+ }
+}
+
+class Pixel extends Geometry {
+ constructor(controlpoint,width,MaterialIndex,Min,Max) {
+ super();
+ this.controlpoint=controlpoint;
+ this.width=width;
+ this.CenterIndex=0;
+ this.MaterialIndex=MaterialIndex;
+ this.Min=Min;
+ this.Max=Max;
+ }
+
+ setMaterialIndex() {
+ this.setMaterial(material0Data,drawMaterial0);
+ }
+
+ process(p) {
+ this.data.indices.push(this.data.vertex0(this.controlpoint,this.width));
+ this.append();
+ }
+
+ append() {
+ material0Data.append(this.data);
+ }
+
+ notRendered() {
+ material0Data.rendered=false;
+ }
+}
+
+class Triangles extends Geometry {
+ constructor(MaterialIndex,Min,Max) {
+ super();
+ this.CenterIndex=0;
+ this.MaterialIndex=MaterialIndex;
+ this.Min=Min;
+ this.Max=Max;
+ this.Positions=Positions;
+ this.Normals=Normals;
+ this.Colors=Colors;
+ this.Indices=Indices;
+ Positions=[];
+ Normals=[];
+ Colors=[];
+ Indices=[];
+ this.transparent=Materials[MaterialIndex].diffuse[3] < 1;
+ }
+
+ setMaterialIndex() {
+ if(this.transparent)
+ this.setMaterial(transparentData,drawTransparent);
+ else
+ this.setMaterial(triangleData,drawTriangle);
+ }
+
+ process(p) {
+ // Override materialIndex to encode color vs material
+ materialIndex=this.Colors.length > 0 ?
+ -1-materialIndex : 1+materialIndex;
+
+ for(let i=0, n=this.Indices.length; i < n; ++i) {
+ let index=this.Indices[i];
+ let PI=index[0];
+ let P0=this.Positions[PI[0]];
+ let P1=this.Positions[PI[1]];
+ let P2=this.Positions[PI[2]];
+ if(!this.offscreen([P0,P1,P2])) {
+ let NI=index.length > 1 ? index[1] : PI;
+ if(!NI || NI.length == 0) NI=PI;
+ if(this.Colors.length > 0) {
+ let CI=index.length > 2 ? index[2] : PI;
+ if(!CI || CI.length == 0) CI=PI;
+ let C0=this.Colors[CI[0]];
+ let C1=this.Colors[CI[1]];
+ let C2=this.Colors[CI[2]];
+ this.transparent |= C0[3]+C1[3]+C2[3] < 765;
+ if(wireframe == 0) {
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]],C0);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]],C1);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]],C2);
+ } else {
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]],C0);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]],C1);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]],C1);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]],C2);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]],C2);
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]],C0);
+ }
+ } else {
+ if(wireframe == 0) {
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]]);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]]);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]]);
+ } else {
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]]);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]]);
+ this.data.iVertex(PI[1],P1,this.Normals[NI[1]]);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]]);
+ this.data.iVertex(PI[2],P2,this.Normals[NI[2]]);
+ this.data.iVertex(PI[0],P0,this.Normals[NI[0]]);
+ }
+ }
+ }
+ }
+ this.data.nvertices=this.Positions.length;
+ if(this.data.indices.length > 0) this.append();
+ }
+
+ append() {
+ if(this.transparent)
+ transparentData.append(this.data);
+ else
+ triangleData.append(this.data);
+ }
+
+ notRendered() {
+ if(this.transparent)
+ transparentData.rendered=false;
+ else
+ triangleData.rendered=false;
+ }
+
+}
+
+function redraw()
+{
+ initProjection();
+ setProjection();
+ remesh=true;
+ draw();
+}
+
+function home()
+{
+ mat4.identity(rotMat);
+ redraw();
+}
+
+let positionAttribute=0;
+let normalAttribute=1;
+let materialAttribute=2;
+let colorAttribute=3;
+let widthAttribute=4;
+
+function initShader(options=[])
+{
+ let vertexShader=getShader(gl,vertex,gl.VERTEX_SHADER,options);
+ let fragmentShader=getShader(gl,fragment,gl.FRAGMENT_SHADER,options);
+ let shader=gl.createProgram();
+
+ gl.attachShader(shader,vertexShader);
+ gl.attachShader(shader,fragmentShader);
+ gl.bindAttribLocation(shader,positionAttribute,"position");
+ gl.bindAttribLocation(shader,normalAttribute,"normal");
+ gl.bindAttribLocation(shader,materialAttribute,"materialIndex");
+ gl.bindAttribLocation(shader,colorAttribute,"color");
+ gl.bindAttribLocation(shader,widthAttribute,"width");
+ gl.linkProgram(shader);
+ if (!gl.getProgramParameter(shader,gl.LINK_STATUS)) {
+ alert("Could not initialize shaders");
+ }
+
+ return shader;
+}
+
+class Split3 {
+ constructor(z0,c0,c1,z1) {
+ this.m0=[0.5*(z0[0]+c0[0]),0.5*(z0[1]+c0[1]),0.5*(z0[2]+c0[2])];
+ let m1_0=0.5*(c0[0]+c1[0]);
+ let m1_1=0.5*(c0[1]+c1[1]);
+ let m1_2=0.5*(c0[2]+c1[2]);
+ this.m2=[0.5*(c1[0]+z1[0]),0.5*(c1[1]+z1[1]),0.5*(c1[2]+z1[2])];
+ this.m3=[0.5*(this.m0[0]+m1_0),0.5*(this.m0[1]+m1_1),
+ 0.5*(this.m0[2]+m1_2)];
+ this.m4=[0.5*(m1_0+this.m2[0]),0.5*(m1_1+this.m2[1]),
+ 0.5*(m1_2+this.m2[2])];
+ this.m5=[0.5*(this.m3[0]+this.m4[0]),0.5*(this.m3[1]+this.m4[1]),
+ 0.5*(this.m3[2]+this.m4[2])];
+ }
+}
+
+function unit(v)
+{
+ let norm=1/(Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) || 1);
+ return [v[0]*norm,v[1]*norm,v[2]*norm];
+}
+
+function abs2(v)
+{
+ return v[0]*v[0]+v[1]*v[1]+v[2]*v[2];
+}
+
+function dot(u,v)
+{
+ return u[0]*v[0]+u[1]*v[1]+u[2]*v[2];
+}
+
+function cross(u,v)
+{
+ return [u[1]*v[2]-u[2]*v[1],
+ u[2]*v[0]-u[0]*v[2],
+ u[0]*v[1]-u[1]*v[0]];
+}
+
+// Return one-third of the first derivative of the Bezier curve defined
+// by a,b,c,d at t=0.
+function bezierP(a,b)
+{
+ return [b[0]-a[0],
+ b[1]-a[1],
+ b[2]-a[2]];
+}
+
+// Return one-half of the second derivative of the Bezier curve defined
+// by a,b,c,d at t=0.
+function bezierPP(a,b,c)
+{
+ return [3*(a[0]+c[0])-6*b[0],
+ 3*(a[1]+c[1])-6*b[1],
+ 3*(a[2]+c[2])-6*b[2]];
+}
+
+// Return one-sixth of the third derivative of the Bezier curve defined by
+// a,b,c,d at t=0.
+function bezierPPP(a,b,c,d)
+{
+ return [d[0]-a[0]+3*(b[0]-c[0]),
+ d[1]-a[1]+3*(b[1]-c[1]),
+ d[2]-a[2]+3*(b[2]-c[2])];
+}
+
+// Return four-thirds of the first derivative of the Bezier curve defined by
+// a,b,c,d at t=1/2.
+function bezierPh(a,b,c,d)
+{
+ return [c[0]+d[0]-a[0]-b[0],
+ c[1]+d[1]-a[1]-b[1],
+ c[2]+d[2]-a[2]-b[2]];
+}
+
+// Return two-thirds of the second derivative of the Bezier curve defined by
+// a,b,c,d at t=1/2.
+function bezierPPh(a,b,c,d)
+{
+ return [3*a[0]-5*b[0]+c[0]+d[0],
+ 3*a[1]-5*b[1]+c[1]+d[1],
+ 3*a[2]-5*b[2]+c[2]+d[2]];
+}
+
+/**
+ * Return the maximum distance squared of points c0 and c1 from
+ * the respective internal control points of z0--z1.
+*/
+function Straightness(z0,c0,c1,z1)
+{
+ let v=[third*(z1[0]-z0[0]),third*(z1[1]-z0[1]),third*(z1[2]-z0[2])];
+ return Math.max(abs2([c0[0]-v[0]-z0[0],c0[1]-v[1]-z0[1],c0[2]-v[2]-z0[2]]),
+ abs2([z1[0]-v[0]-c1[0],z1[1]-v[1]-c1[1],z1[2]-v[2]-c1[2]]));
+}
+
+// Return one ninth of the relative flatness squared of a--b and c--d.
+function Flatness(a,b,c,d)
+{
+ let u=[b[0]-a[0],b[1]-a[1],b[2]-a[2]];
+ let v=[d[0]-c[0],d[1]-c[1],d[2]-c[2]];
+ return Math.max(abs2(cross(u,unit(v))),abs2(cross(v,unit(u))))/9;
+}
+
+// Return the vertices of the box containing 3d points m and M.
+function corners(m,M)
+{
+ return [m,[m[0],m[1],M[2]],[m[0],M[1],m[2]],[m[0],M[1],M[2]],
+ [M[0],m[1],m[2]],[M[0],m[1],M[2]],[M[0],M[1],m[2]],M];
+}
+
+function minbound(v) {
+ return [
+ Math.min(v[0][0],v[1][0],v[2][0],v[3][0],v[4][0],v[5][0],v[6][0],v[7][0]),
+ Math.min(v[0][1],v[1][1],v[2][1],v[3][1],v[4][1],v[5][1],v[6][1],v[7][1]),
+ Math.min(v[0][2],v[1][2],v[2][2],v[3][2],v[4][2],v[5][2],v[6][2],v[7][2])
+ ];
+}
+
+function maxbound(v) {
+ return [
+ Math.max(v[0][0],v[1][0],v[2][0],v[3][0],v[4][0],v[5][0],v[6][0],v[7][0]),
+ Math.max(v[0][1],v[1][1],v[2][1],v[3][1],v[4][1],v[5][1],v[6][1],v[7][1]),
+ Math.max(v[0][2],v[1][2],v[2][2],v[3][2],v[4][2],v[5][2],v[6][2],v[7][2])
+ ];
+}
+
+/**
+ * Perform a change of basis
+ * @param {*} out Out Matrix
+ * @param {*} mat Matrix
+ *
+ * Compute the matrix (translMatrix) * mat * (translMatrix)^{-1}
+ */
+
+function COBTarget(out,mat)
+{
+ mat4.fromTranslation(T,[center.x,center.y,center.z])
+ mat4.invert(cjMatInv,T);
+ mat4.multiply(out,mat,cjMatInv);
+ mat4.multiply(out,T,out);
+}
+
+function setUniforms(data,shader)
+{
+ let pixel=shader == pixelShader;
+
+ gl.useProgram(shader);
+
+ gl.enableVertexAttribArray(positionAttribute);
+
+ if(pixel)
+ gl.enableVertexAttribArray(widthAttribute);
+
+ let normals=!pixel && Lights.length > 0;
+ if(normals)
+ gl.enableVertexAttribArray(normalAttribute);
+
+ gl.enableVertexAttribArray(materialAttribute);
+
+ shader.projViewMatUniform=gl.getUniformLocation(shader,"projViewMat");
+ shader.viewMatUniform=gl.getUniformLocation(shader,"viewMat");
+ shader.normMatUniform=gl.getUniformLocation(shader,"normMat");
+
+ if(shader == colorShader || shader == transparentShader)
+ gl.enableVertexAttribArray(colorAttribute);
+
+ if(normals) {
+ for(let i=0; i < Lights.length; ++i)
+ Lights[i].setUniform(shader,i);
+ }
+
+ for(let i=0; i < data.materials.length; ++i)
+ data.materials[i].setUniform(shader,i);
+
+ gl.uniformMatrix4fv(shader.projViewMatUniform,false,projViewMat);
+ gl.uniformMatrix4fv(shader.viewMatUniform,false,viewMat);
+ gl.uniformMatrix3fv(shader.normMatUniform,false,normMat);
+}
+
+function handleMouseDown(event)
+{
+ if(!zoomEnabled)
+ enableZoom();
+ mouseDownOrTouchActive=true;
+ lastMouseX=event.clientX;
+ lastMouseY=event.clientY;
+}
+
+let pinch=false;
+let pinchStart;
+
+function pinchDistance(touches)
+{
+ return Math.hypot(
+ touches[0].pageX-touches[1].pageX,
+ touches[0].pageY-touches[1].pageY);
+}
+
+
+let touchStartTime;
+
+function handleTouchStart(event)
+{
+ event.preventDefault();
+ if(!zoomEnabled)
+ enableZoom();
+ let touches=event.targetTouches;
+ swipe=rotate=pinch=false;
+ if(zooming) return;
+
+ if(touches.length == 1 && !mouseDownOrTouchActive) {
+ touchStartTime=new Date().getTime();
+ touchId=touches[0].identifier;
+ lastMouseX=touches[0].pageX,
+ lastMouseY=touches[0].pageY;
+ }
+
+ if(touches.length == 2 && !mouseDownOrTouchActive) {
+ touchId=touches[0].identifier;
+ pinchStart=pinchDistance(touches);
+ pinch=true;
+ }
+}
+
+function handleMouseUpOrTouchEnd(event)
+{
+ mouseDownOrTouchActive=false;
+}
+
+function rotateScene(lastX,lastY,rawX,rawY,factor)
+{
+ if(lastX == rawX && lastY == rawY) return;
+ let [angle,axis]=arcball([lastX,-lastY],[rawX,-rawY]);
+
+ mat4.fromRotation(T,2*factor*ArcballFactor*angle/Zoom,axis);
+ mat4.multiply(rotMat,T,rotMat);
+}
+
+function shiftScene(lastX,lastY,rawX,rawY)
+{
+ let zoominv=1/Zoom;
+ shift.x += (rawX-lastX)*zoominv*halfCanvasWidth;
+ shift.y -= (rawY-lastY)*zoominv*halfCanvasHeight;
+}
+
+function panScene(lastX,lastY,rawX,rawY)
+{
+ if (orthographic) {
+ shiftScene(lastX,lastY,rawX,rawY);
+ } else {
+ center.x += (rawX-lastX)*(viewParam.xmax-viewParam.xmin);
+ center.y -= (rawY-lastY)*(viewParam.ymax-viewParam.ymin);
+ }
+}
+
+function updateViewMatrix()
+{
+ COBTarget(viewMat,rotMat);
+ mat4.translate(viewMat,viewMat,[center.x,center.y,0]);
+ mat3.fromMat4(viewMat3,viewMat);
+ mat3.invert(normMat,viewMat3);
+ mat4.multiply(projViewMat,projMat,viewMat);
+}
+
+function capzoom()
+{
+ let maxzoom=Math.sqrt(Number.MAX_VALUE);
+ let minzoom=1/maxzoom;
+ if(Zoom <= minzoom) Zoom=minzoom;
+ if(Zoom >= maxzoom) Zoom=maxzoom;
+
+ if(zoomRemeshFactor*Zoom < lastZoom || Zoom > zoomRemeshFactor*lastZoom) {
+ remesh=true;
+ lastZoom=Zoom;
+ }
+}
+
+function zoomImage(diff)
+{
+ let stepPower=zoomStep*halfCanvasHeight*diff;
+ const limit=Math.log(0.1*Number.MAX_VALUE)/Math.log(zoomFactor);
+
+ if(Math.abs(stepPower) < limit) {
+ Zoom *= zoomFactor**stepPower;
+ capzoom();
+ }
+}
+
+function normMouse(v)
+{
+ let v0=v[0];
+ let v1=v[1];
+ let norm=Math.hypot(v0,v1);
+ if(norm > 1) {
+ denom=1/norm;
+ v0 *= denom;
+ v1 *= denom;
+ }
+ return [v0,v1,Math.sqrt(Math.max(1-v1*v1-v0*v0,0))];
+}
+
+function arcball(oldmouse,newmouse)
+{
+ let oldMouse=normMouse(oldmouse);
+ let newMouse=normMouse(newmouse);
+ let Dot=dot(oldMouse,newMouse);
+ let angle=Dot > 1 ? 0 : Dot < -1 ? pi : Math.acos(Dot);
+ return [angle,unit(cross(oldMouse,newMouse))]
+}
+
+/**
+ * Mouse Drag Zoom
+ * @param {*} lastX unused
+ * @param {*} lastY
+ * @param {*} rawX unused
+ * @param {*} rawY
+ */
+function zoomScene(lastX,lastY,rawX,rawY)
+{
+ zoomImage(lastY-rawY);
+}
+
+// mode:
+const DRAGMODE_ROTATE=1;
+const DRAGMODE_SHIFT=2;
+const DRAGMODE_ZOOM=3;
+const DRAGMODE_PAN=4
+function processDrag(newX,newY,mode,factor=1)
+{
+ let dragFunc;
+ switch (mode) {
+ case DRAGMODE_ROTATE:
+ dragFunc=rotateScene;
+ break;
+ case DRAGMODE_SHIFT:
+ dragFunc=shiftScene;
+ break;
+ case DRAGMODE_ZOOM:
+ dragFunc=zoomScene;
+ break;
+ case DRAGMODE_PAN:
+ dragFunc=panScene;
+ break;
+ default:
+ dragFunc=(_a,_b,_c,_d) => {};
+ break;
+ }
+
+ let lastX=(lastMouseX-halfCanvasWidth)/halfCanvasWidth;
+ let lastY=(lastMouseY-halfCanvasHeight)/halfCanvasHeight;
+ let rawX=(newX-halfCanvasWidth)/halfCanvasWidth;
+ let rawY=(newY-halfCanvasHeight)/halfCanvasHeight;
+
+ dragFunc(lastX,lastY,rawX,rawY,factor);
+
+ lastMouseX=newX;
+ lastMouseY=newY;
+
+ setProjection();
+ draw();
+}
+
+let zoomEnabled=0;
+
+function enableZoom()
+{
+ zoomEnabled=1;
+ canvas.addEventListener("wheel",handleMouseWheel,false);
+}
+
+function disableZoom()
+{
+ zoomEnabled=0;
+ canvas.removeEventListener("wheel",handleMouseWheel,false);
+}
+
+function handleKey(event)
+{
+ let ESC=27;
+
+ if(!zoomEnabled)
+ enableZoom();
+
+ if(embedded && zoomEnabled && event.keyCode == ESC) {
+ disableZoom();
+ return;
+ }
+
+ let keycode=event.key;
+ let axis=[];
+
+ switch(keycode) {
+ case 'x':
+ axis=[1,0,0];
+ break;
+ case 'y':
+ axis=[0,1,0];
+ break;
+ case 'z':
+ axis=[0,0,1];
+ break;
+ case 'h':
+ home();
+ break;
+ case 'm':
+ ++wireframe;
+ if(wireframe == 3) wireframe=0;
+ if(wireframe != 2) {
+ if(!embedded)
+ deleteShaders();
+ initShaders();
+ }
+ remesh=true;
+ draw();
+ break;
+ case '+':
+ case '=':
+ case '>':
+ expand();
+ break;
+ case '-':
+ case '_':
+ case '<':
+ shrink();
+ break;
+ default:
+ break;
+ }
+
+ if(axis.length > 0) {
+ mat4.rotate(rotMat,rotMat,0.1,axis);
+ updateViewMatrix();
+ draw();
+ }
+}
+
+function setZoom()
+{
+ capzoom();
+ setProjection();
+ draw();
+}
+
+function handleMouseWheel(event)
+{
+ event.preventDefault();
+
+ if (event.deltaY < 0) {
+ Zoom *= zoomFactor;
+ } else {
+ Zoom /= zoomFactor;
+ }
+
+ setZoom();
+}
+
+function handleMouseMove(event)
+{
+ if(!mouseDownOrTouchActive) {
+ return;
+ }
+
+ let newX=event.clientX;
+ let newY=event.clientY;
+
+ let mode;
+ if(event.getModifierState("Control")) {
+ mode=DRAGMODE_SHIFT;
+ } else if(event.getModifierState("Shift")) {
+ mode=DRAGMODE_ZOOM;
+ } else if(event.getModifierState("Alt")) {
+ mode=DRAGMODE_PAN;
+ } else {
+ mode=DRAGMODE_ROTATE;
+ }
+
+ processDrag(newX,newY,mode);
+}
+
+let zooming=false;
+let swipe=false;
+let rotate=false;
+
+function handleTouchMove(event)
+{
+ event.preventDefault();
+ if(zooming) return;
+ let touches=event.targetTouches;
+
+ if(!pinch && touches.length == 1 && touchId == touches[0].identifier) {
+ let newX=touches[0].pageX;
+ let newY=touches[0].pageY;
+ let dx=newX-lastMouseX;
+ let dy=newY-lastMouseY;
+ let stationary=dx*dx+dy*dy <= shiftHoldDistance*shiftHoldDistance;
+ if(stationary) {
+ if(!swipe && !rotate &&
+ new Date().getTime()-touchStartTime > shiftWaitTime) {
+ if(navigator.vibrate)
+ window.navigator.vibrate(vibrateTime);
+ swipe=true;
+ }
+ }
+ if(swipe)
+ processDrag(newX,newY,DRAGMODE_SHIFT);
+ else if(!stationary) {
+ rotate=true;
+ let newX=touches[0].pageX;
+ let newY=touches[0].pageY;
+ processDrag(newX,newY,DRAGMODE_ROTATE,0.5);
+ }
+ }
+
+ if(pinch && !swipe &&
+ touches.length == 2 && touchId == touches[0].identifier) {
+ let distance=pinchDistance(touches);
+ let diff=distance-pinchStart;
+ zooming=true;
+ diff *= zoomPinchFactor;
+ if(diff > zoomPinchCap) diff=zoomPinchCap;
+ if(diff < -zoomPinchCap) diff=-zoomPinchCap;
+ zoomImage(diff/size2);
+ pinchStart=distance;
+ swipe=rotate=zooming=false;
+ setProjection();
+ draw();
+ }
+}
+
+let zbuffer=[];
+
+function transformVertices(vertices)
+{
+ let Tz0=viewMat[2];
+ let Tz1=viewMat[6];
+ let Tz2=viewMat[10];
+ zbuffer.length=vertices.length;
+ for(let i=0; i < vertices.length; ++i) {
+ let i6=6*i;
+ zbuffer[i]=Tz0*vertices[i6]+Tz1*vertices[i6+1]+Tz2*vertices[i6+2];
+ }
+}
+
+function drawMaterial0()
+{
+ drawBuffer(material0Data,pixelShader);
+ material0Data.clear();
+}
+
+function drawMaterial1()
+{
+ drawBuffer(material1Data,materialShader);
+ material1Data.clear();
+}
+
+function drawMaterial()
+{
+ drawBuffer(materialData,materialShader);
+ materialData.clear();
+}
+
+function drawColor()
+{
+ drawBuffer(colorData,colorShader);
+ colorData.clear();
+}
+
+function drawTriangle()
+{
+ drawBuffer(triangleData,transparentShader);
+ triangleData.rendered=false; // Force copying of sorted triangles to GPU.
+ triangleData.clear();
+}
+
+function drawTransparent()
+{
+ let indices=transparentData.indices;
+ if(wireframe > 0) {
+ drawBuffer(transparentData,transparentShader,indices);
+ transparentData.clear();
+ return;
+ }
+ if(indices.length > 0) {
+ transformVertices(transparentData.vertices);
+
+ let n=indices.length/3;
+ let triangles=Array(n).fill().map((_,i)=>i);
+
+ triangles.sort(function(a,b) {
+ let a3=3*a;
+ Ia=indices[a3];
+ Ib=indices[a3+1];
+ Ic=indices[a3+2];
+
+ let b3=3*b;
+ IA=indices[b3];
+ IB=indices[b3+1];
+ IC=indices[b3+2];
+
+ return zbuffer[Ia]+zbuffer[Ib]+zbuffer[Ic] <
+ zbuffer[IA]+zbuffer[IB]+zbuffer[IC] ? -1 : 1;
+ });
+
+ let Indices=Array(indices.length);
+
+ for(let i=0; i < n; ++i) {
+ let i3=3*i;
+ let t=3*triangles[i];
+ Indices[3*i]=indices[t];
+ Indices[3*i+1]=indices[t+1];
+ Indices[3*i+2]=indices[t+2];
+ }
+
+ gl.depthMask(false); // Enable transparency
+ drawBuffer(transparentData,transparentShader,Indices);
+ // Force copying of sorted triangles to GPU.
+ transparentData.rendered=false;
+ gl.depthMask(true); // Disable transparency
+ }
+ transparentData.clear();
+}
+
+function drawBuffers()
+{
+ drawMaterial0();
+ drawMaterial1();
+ drawMaterial();
+ drawColor();
+ drawTriangle();
+ drawTransparent();
+}
+
+function draw()
+{
+ if(embedded) {
+ offscreen.width=canvasWidth;
+ offscreen.height=canvasHeight;
+ setViewport();
+ }
+
+ gl.clearColor(Background[0],Background[1],Background[2],Background[3]);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ for(let i=0; i < P.length; ++i)
+ P[i].render();
+
+ drawBuffers();
+
+ if(embedded) {
+ context.clearRect(0,0,canvasWidth,canvasHeight);
+ context.drawImage(offscreen,0,0);
+ }
+
+ if(wireframe == 0) remesh=false;
+}
+
+function setDimensions(width,height,X,Y)
+{
+ let Aspect=width/height;
+ let zoominv=1/Zoom;
+ let xshift=(X/width+viewportshift[0])*Zoom;
+ let yshift=(Y/height+viewportshift[1])*Zoom;
+
+ if (orthographic) {
+ let xsize=B[0]-b[0];
+ let ysize=B[1]-b[1];
+ if (xsize < ysize*Aspect) {
+ let r=0.5*ysize*Aspect*zoominv;
+ let X0=2*r*xshift;
+ let Y0=ysize*zoominv*yshift;
+ viewParam.xmin=-r-X0;
+ viewParam.xmax=r-X0;
+ viewParam.ymin=b[1]*zoominv-Y0;
+ viewParam.ymax=B[1]*zoominv-Y0;
+ } else {
+ let r=0.5*xsize/(Aspect*Zoom);
+ let X0=xsize*zoominv*xshift;
+ let Y0=2*r*yshift;
+ viewParam.xmin=b[0]*zoominv-X0;
+ viewParam.xmax=B[0]*zoominv-X0;
+ viewParam.ymin=-r-Y0;
+ viewParam.ymax=r-Y0;
+ }
+ } else {
+ let r=H*zoominv;
+ let rAspect=r*Aspect;
+ let X0=2*rAspect*xshift;
+ let Y0=2*r*yshift;
+ viewParam.xmin=-rAspect-X0;
+ viewParam.xmax=rAspect-X0;
+ viewParam.ymin=-r-Y0;
+ viewParam.ymax=r-Y0;
+ }
+}
+
+function setProjection()
+{
+ setDimensions(canvasWidth,canvasHeight,shift.x,shift.y);
+ let f=orthographic ? mat4.ortho : mat4.frustum;
+ f(projMat,viewParam.xmin,viewParam.xmax,
+ viewParam.ymin,viewParam.ymax,
+ -viewParam.zmax,-viewParam.zmin);
+ updateViewMatrix();
+}
+
+function initProjection()
+{
+ H=-Math.tan(0.5*angle)*B[2];
+
+ center.x=center.y=0;
+ center.z=0.5*(b[2]+B[2]);
+ lastZoom=Zoom=zoom0;
+
+ viewParam.zmin=b[2];
+ viewParam.zmax=B[2];
+
+ shift.x=shift.y=0;
+}
+
+function setViewport()
+{
+ gl.viewportWidth=canvasWidth;
+ gl.viewportHeight=canvasHeight;
+ gl.viewport(0.5*(canvas.width-canvasWidth),0.5*(canvas.height-canvasHeight),
+ canvasWidth,canvasHeight);
+ gl.scissor(0,0,canvas.width,canvas.height);
+}
+
+function setCanvas()
+{
+ if(embedded) {
+ canvas.width=offscreen.width=canvasWidth;
+ canvas.height=offscreen.height=canvasHeight;
+ }
+ size2=Math.hypot(canvasWidth,canvasHeight);
+ halfCanvasWidth=0.5*canvas.width;
+ halfCanvasHeight=0.5*canvas.height;
+ ArcballFactor=1+8*Math.hypot(viewportmargin[0],viewportmargin[1])/size2;
+}
+
+function setsize(w,h)
+{
+ if(w > maxViewportWidth)
+ w=maxViewportWidth;
+
+ if(h > maxViewportHeight)
+ h=maxViewportHeight;
+
+ shift.x *= w/canvasWidth;
+ shift.y *= h/canvasHeight;
+
+ canvasWidth=w;
+ canvasHeight=h;
+ setCanvas();
+ setViewport();
+
+ setProjection();
+ remesh=true;
+}
+
+function resize()
+{
+ zoom0=Zoom0;
+
+ if(absolute && !embedded) {
+ canvasWidth=canvasWidth0*window.devicePixelRatio;
+ canvasHeight=canvasHeight0*window.devicePixelRatio;
+ } else {
+ let Aspect=canvasWidth0/canvasHeight0;
+ canvasWidth=Math.max(window.innerWidth-windowTrim,windowTrim);
+ canvasHeight=Math.max(window.innerHeight-windowTrim,windowTrim);
+
+ if(!orthographic && canvasWidth < canvasHeight*Aspect)
+ zoom0 *= canvasWidth/(canvasHeight*Aspect);
+ }
+
+ canvas.width=canvasWidth;
+ canvas.height=canvasHeight;
+
+ let maxViewportWidth=window.innerWidth;
+ let maxViewportHeight=window.innerHeight;
+
+ viewportshift[0] /= zoom0;
+ viewportshift[1] /= zoom0;
+
+ setsize(canvasWidth,canvasHeight);
+ redraw();
+}
+
+function expand()
+{
+ Zoom *= zoomFactor;
+ setZoom();
+}
+
+function shrink()
+{
+ Zoom /= zoomFactor;
+ setZoom();
+}
+
+let pixelShader,materialShader,colorShader,transparentShader;
+
+class Align {
+ constructor(center,dir) {
+ this.center=center;
+ if(dir) {
+ let theta=dir[0];
+ let phi=dir[1];
+
+ this.ct=Math.cos(theta);
+ this.st=Math.sin(theta);
+ this.cp=Math.cos(phi);
+ this.sp=Math.sin(phi);
+ }
+ }
+
+ T0(v) {
+ return [v[0]+this.center[0],v[1]+this.center[1],v[2]+this.center[2]];
+ }
+
+ T(v) {
+ let x=v[0];
+ let Y=v[1];
+ let z=v[2];
+ let X=x*this.ct+z*this.st;
+ return [X*this.cp-Y*this.sp+this.center[0],
+ X*this.sp+Y*this.cp+this.center[1],
+ -x*this.st+z*this.ct+this.center[2]];
+ };
+}
+
+function Tcorners(T,m,M) {
+ let v=[T(m),T([m[0],m[1],M[2]]),T([m[0],M[1],m[2]]),
+ T([m[0],M[1],M[2]]),T([M[0],m[1],m[2]]),
+ T([M[0],m[1],M[2]]),T([M[0],M[1],m[2]]),T(M)];
+ return [minbound(v),maxbound(v)];
+}
+
+// draw a sphere of radius r about center
+// (or optionally a hemisphere symmetric about direction dir)
+function sphere(center,r,CenterIndex,MaterialIndex,dir)
+{
+ let b=0.524670512339254;
+ let c=0.595936986722291;
+ let d=0.954967051233925;
+ let e=0.0820155480083437;
+ let f=0.996685028842544;
+ let g=0.0549670512339254;
+ let h=0.998880711874577;
+ let i=0.0405017186586849;
+
+ let octant=[[
+ [1,0,0],
+ [1,0,b],
+ [c,0,d],
+ [e,0,f],
+
+ [1,a,0],
+ [1,a,b],
+ [c,a*c,d],
+ [e,a*e,f],
+
+ [a,1,0],
+ [a,1,b],
+ [a*c,c,d],
+ [a*e,e,f],
+
+ [0,1,0],
+ [0,1,b],
+ [0,c,d],
+ [0,e,f]
+ ],[
+ [e,0,f],
+ [e,a*e,f],
+ [g,0,h],
+ [a*e,e,f],
+ [i,i,1],
+ [0.05*a,0,1],
+ [0,e,f],
+ [0,g,h],
+ [0,0.05*a,1],
+ [0,0,1]
+ ]];
+
+ let rx,ry,rz;
+ let A=new Align(center,dir);
+ let s,t,z;
+
+ if(dir) {
+ s=1;
+ z=0;
+ t=A.T.bind(A);
+ } else {
+ s=-1;
+ z=-r;
+ t=A.T0.bind(A);
+ }
+
+ function T(V) {
+ let p=Array(V.length);
+ for(let i=0; i < V.length; ++i) {
+ let v=V[i];
+ p[i]=t([rx*v[0],ry*v[1],rz*v[2]]);
+ }
+ return p;
+ }
+
+ let v=Tcorners(t,[-r,-r,z],[r,r,r]);
+ let Min=v[0], Max=v[1];
+ for(let i=-1; i <= 1; i += 2) {
+ rx=i*r;
+ for(let j=-1; j <= 1; j += 2) {
+ ry=j*r;
+ for(let k=s; k <= 1; k += 2) {
+ rz=k*r;
+ for(let m=0; m < 2; ++m)
+ P.push(new BezierPatch(T(octant[m]),CenterIndex,MaterialIndex,
+ Min,Max));
+ }
+ }
+ }
+}
+
+let a=4/3*(Math.sqrt(2)-1);
+
+// draw a disk of radius r aligned in direction dir
+function disk(center,r,CenterIndex,MaterialIndex,dir)
+{
+ let b=1-2*a/3;
+
+ let unitdisk=[
+ [1,0,0],
+ [1,-a,0],
+ [a,-1,0],
+ [0,-1,0],
+
+ [1,a,0],
+ [b,0,0],
+ [0,-b,0],
+ [-a,-1,0],
+
+ [a,1,0],
+ [0,b,0],
+ [-b,0,0],
+ [-1,-a,0],
+
+ [0,1,0],
+ [-a,1,0],
+ [-1,a,0],
+ [-1,0,0]
+ ];
+
+ let A=new Align(center,dir);
+
+ function T(V) {
+ let p=Array(V.length);
+ for(let i=0; i < V.length; ++i) {
+ let v=V[i];
+ p[i]=A.T([r*v[0],r*v[1],0]);
+ }
+ return p;
+ }
+
+ let v=Tcorners(A.T.bind(A),[-r,-r,0],[r,r,0]);
+ P.push(new BezierPatch(T(unitdisk),CenterIndex,MaterialIndex,v[0],v[1]));
+}
+
+// draw a cylinder with circular base of radius r about center and height h
+// aligned in direction dir
+function cylinder(center,r,h,CenterIndex,MaterialIndex,dir,core)
+{
+ let unitcylinder=[
+ [1,0,0],
+ [1,0,1/3],
+ [1,0,2/3],
+ [1,0,1],
+
+ [1,a,0],
+ [1,a,1/3],
+ [1,a,2/3],
+ [1,a,1],
+
+ [a,1,0],
+ [a,1,1/3],
+ [a,1,2/3],
+ [a,1,1],
+
+ [0,1,0],
+ [0,1,1/3],
+ [0,1,2/3],
+ [0,1,1]
+ ];
+
+ let rx,ry,rz;
+ let A=new Align(center,dir);
+
+ function T(V) {
+ let p=Array(V.length);
+ for(let i=0; i < V.length; ++i) {
+ let v=V[i];
+ p[i]=A.T([rx*v[0],ry*v[1],h*v[2]]);
+ }
+ return p;
+ }
+
+ let v=Tcorners(A.T.bind(A),[-r,-r,0],[r,r,h]);
+ let Min=v[0], Max=v[1];
+
+ for(let i=-1; i <= 1; i += 2) {
+ rx=i*r;
+ for(let j=-1; j <= 1; j += 2) {
+ ry=j*r;
+ P.push(new BezierPatch(T(unitcylinder),CenterIndex,MaterialIndex,
+ Min,Max));
+ }
+ }
+
+ if(core) {
+ let Center=A.T([0,0,h]);
+ P.push(new BezierCurve([center,Center],CenterIndex,MaterialIndex,
+ center,Center));
+ }
+}
+
+function rmf(z0,c0,c1,z1,t)
+{
+ class Rmf {
+ constructor(p,r,t) {
+ this.p=p;
+ this.r=r;
+ this.t=t;
+ this.s=cross(t,r);
+ }
+ }
+
+ // Return a unit vector perpendicular to a given unit vector v.
+ function perp(v)
+ {
+ let u=cross(v,[0,1,0]);
+ let norm=Number.EPSILON*abs2(v);
+ if(abs2(u) > norm) return unit(u);
+ u=cross(v,[0,0,1]);
+ return (abs2(u) > norm) ? unit(u) : [1,0,0];
+ }
+
+ let norm=Number.EPSILON*Math.max(abs2(z0),abs2(c0),abs2(c1),
+ abs2(z1));
+
+// Special case of dir for t in (0,1].
+ function dir(t) {
+ if(t == 1) {
+ let dir=[z1[0]-c1[0],
+ z1[1]-c1[1],
+ z1[2]-c1[2]];
+ if(abs2(dir) > norm) return unit(dir);
+ dir=[2*c1[0]-c0[0]-z1[0],
+ 2*c1[1]-c0[1]-z1[1],
+ 2*c1[2]-c0[2]-z1[2]];
+ if(abs2(dir) > norm) return unit(dir);
+ return [z1[0]-z0[0]+3*(c0[0]-c1[0]),
+ z1[1]-z0[1]+3*(c0[1]-c1[1]),
+ z1[2]-z0[2]+3*(c0[2]-c1[2])];
+ }
+ let a=[z1[0]-z0[0]+3*(c0[0]-c1[0]),
+ z1[1]-z0[1]+3*(c0[1]-c1[1]),
+ z1[2]-z0[2]+3*(c0[2]-c1[2])];
+ let b=[2*(z0[0]+c1[0])-4*c0[0],
+ 2*(z0[1]+c1[1])-4*c0[1],
+ 2*(z0[2]+c1[2])-4*c0[2]];
+ let c=[c0[0]-z0[0],c0[1]-z0[1],c0[2]-z0[2]];
+ let t2=t*t;
+ let dir=[a[0]*t2+b[0]*t+c[0],
+ a[1]*t2+b[1]*t+c[1],
+ a[2]*t2+b[2]*t+c[2]];
+ if(abs2(dir) > norm) return unit(dir);
+ t2=2*t;
+ dir=[a[0]*t2+b[0],
+ a[1]*t2+b[1],
+ a[2]*t2+b[2]];
+ if(abs2(dir) > norm) return unit(dir);
+ return unit(a);
+ }
+
+ let R=Array(t.length);
+ let T=[c0[0]-z0[0],
+ c0[1]-z0[1],
+ c0[2]-z0[2]];
+ if(abs2(T) < norm) {
+ T=[z0[0]-2*c0[0]+c1[0],
+ z0[1]-2*c0[1]+c1[1],
+ z0[2]-2*c0[2]+c1[2]];
+ if(abs2(T) < norm)
+ T=[z1[0]-z0[0]+3*(c0[0]-c1[0]),
+ z1[1]-z0[1]+3*(c0[1]-c1[1]),
+ z1[2]-z0[2]+3*(c0[2]-c1[2])];
+ }
+ T=unit(T);
+ let Tp=perp(T);
+ R[0]=new Rmf(z0,Tp,T);
+ for(let i=1; i < t.length; ++i) {
+ let Ri=R[i-1];
+ let s=t[i];
+ let onemt=1-s;
+ let onemt2=onemt*onemt;
+ let onemt3=onemt2*onemt;
+ let s3=3*s;
+ onemt2 *= s3;
+ onemt *= s3*s;
+ let t3=s*s*s;
+ let p=[
+ onemt3*z0[0]+onemt2*c0[0]+onemt*c1[0]+t3*z1[0],
+ onemt3*z0[1]+onemt2*c0[1]+onemt*c1[1]+t3*z1[1],
+ onemt3*z0[2]+onemt2*c0[2]+onemt*c1[2]+t3*z1[2]];
+ let v1=[p[0]-Ri.p[0],p[1]-Ri.p[1],p[2]-Ri.p[2]];
+ if(v1[0] != 0 || v1[1] != 0 || v1[2] != 0) {
+ let r=Ri.r;
+ let u1=unit(v1);
+ let ti=Ri.t;
+ let dotu1ti=dot(u1,ti)
+ let tp=[ti[0]-2*dotu1ti*u1[0],
+ ti[1]-2*dotu1ti*u1[1],
+ ti[2]-2*dotu1ti*u1[2]];
+ ti=dir(s);
+ let dotu1r2=2*dot(u1,r);
+ let rp=[r[0]-dotu1r2*u1[0],r[1]-dotu1r2*u1[1],r[2]-dotu1r2*u1[2]];
+ let u2=unit([ti[0]-tp[0],ti[1]-tp[1],ti[2]-tp[2]]);
+ let dotu2rp2=2*dot(u2,rp);
+ rp=[rp[0]-dotu2rp2*u2[0],rp[1]-dotu2rp2*u2[1],rp[2]-dotu2rp2*u2[2]];
+ R[i]=new Rmf(p,unit(rp),unit(ti));
+ } else
+ R[i]=R[i-1];
+ }
+ return R;
+}
+
+// draw a tube of width w using control points v
+function tube(v,w,CenterIndex,MaterialIndex,Min,Max,core)
+{
+ let Rmf=rmf(v[0],v[1],v[2],v[3],[0,1/3,2/3,1]);
+
+ let aw=a*w;
+ let arc=[[w,0],[w,aw],[aw,w],[0,w]];
+
+ function f(a,b,c,d) {
+ let s=Array(16);
+ for(let i=0; i < 4; ++i) {
+ let R=Rmf[i];
+
+ let R0=R.r[0], R1=R.s[0];
+ let T0=R0*a+R1*b;
+ let T1=R0*c+R1*d;
+
+ R0=R.r[1]; R1=R.s[1];
+ let T4=R0*a+R1*b;
+ let T5=R0*c+R1*d;
+
+ R0=R.r[2]; R1=R.s[2];
+ let T8=R0*a+R1*b;
+ let T9=R0*c+R1*d;
+
+ let w=v[i];
+ let w0=w[0]; w1=w[1]; w2=w[2];
+ for(let j=0; j < 4; ++j) {
+ let u=arc[j];
+ let x=u[0], y=u[1];
+ s[4*i+j]=[T0*x+T1*y+w0,
+ T4*x+T5*y+w1,
+ T8*x+T9*y+w2];
+ }
+ }
+ P.push(new BezierPatch(s,CenterIndex,MaterialIndex,Min,Max));
+ }
+
+ f(1,0,0,1);
+ f(0,-1,1,0);
+ f(-1,0,0,-1);
+ f(0,1,-1,0);
+
+ if(core)
+ P.push(new BezierCurve(v,CenterIndex,MaterialIndex,Min,Max));
+}
+
+function webGLStart()
+{
+ canvas=document.getElementById("Asymptote");
+ embedded=window.top.document != document;
+
+ initGL();
+
+ gl.enable(gl.BLEND);
+ gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.SCISSOR_TEST);
+
+ canvas.onmousedown=handleMouseDown;
+ document.onmouseup=handleMouseUpOrTouchEnd;
+ document.onmousemove=handleMouseMove;
+ canvas.onkeydown=handleKey;
+
+ if(!embedded)
+ enableZoom();
+ canvas.addEventListener("touchstart",handleTouchStart,false);
+ canvas.addEventListener("touchend",handleMouseUpOrTouchEnd,false);
+ canvas.addEventListener("touchcancel",handleMouseUpOrTouchEnd,false);
+ canvas.addEventListener("touchleave",handleMouseUpOrTouchEnd,false);
+ canvas.addEventListener("touchmove",handleTouchMove,false);
+ document.addEventListener("keydown",handleKey,false);
+
+ canvasWidth0=canvasWidth;
+ canvasHeight0=canvasHeight;
+
+ mat4.identity(rotMat);
+
+ if(window.innerWidth != 0 && window.innerHeight != 0)
+ resize();
+
+ window.addEventListener("resize",resize,false);
+}