diff options
Diffstat (limited to 'Build/source/utils/asymptote/webgl/gl.js')
-rw-r--r-- | Build/source/utils/asymptote/webgl/gl.js | 2649 |
1 files changed, 2027 insertions, 622 deletions
diff --git a/Build/source/utils/asymptote/webgl/gl.js b/Build/source/utils/asymptote/webgl/gl.js index 079b1bb70e4..4aab28909cc 100644 --- a/Build/source/utils/asymptote/webgl/gl.js +++ b/Build/source/utils/asymptote/webgl/gl.js @@ -1,738 +1,2143 @@ -// Contains code from http: //learningwebgl.com/blog/ ? p=28#triangle-vertex-positions -// modified to produce a subdivision algorithm for rendering Bezier -// patches with WebGL -var gl; - -function initGL(canvas) { - try { - gl=canvas.getContext("experimental-webgl"); - gl.viewportWidth=canvas.width; - gl.viewportHeight=canvas.height; - } catch(e) {} - if(!gl) { - alert("Could not initialise WebGL, sorry : -("); +/*@license + gl.js: Render Bezier patches via subdivision with WebGL. + Copyright 2019: John C. Bowman and Supakorn "Jamie" Rassameemasmuang + University of Alberta + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +let gl; + +let canvas; +let canvasWidth,canvasHeight; +let halfCanvasWidth,halfCanvasHeight; + +let pixel=0.75; // Adaptive rendering constant. +let BezierFactor=0.4; +let FillFactor=0.1; +let Zoom; +let Zoom0; + +let maxViewportWidth=window.innerWidth; +let maxViewportHeight=window.innerHeight; +let viewportmargin=0; + +const windowTrim=10; +let resizeStep=1.2; +let zoomFactor; +let zoomPinchFactor; +let zoomPinchCap; +let zoomStep; + +let shiftHoldDistance; +let shiftWaitTime; +let vibrateTime; + +let lastzoom; +let H; // maximum camera view half-height + +let Fuzz2=1000*Number.EPSILON; +let Fuzz4=Fuzz2*Fuzz2; +let third=1/3; + +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 + +// Don't account for device pixels when embedding in another html document +let absolute=false; + +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 rotMats=mat4.create(); +let cjMatInv=mat4.create(); +let translMat=mat4.create(); + +let zmin,zmax; +let center={x:0,y:0,z:0}; +let size2; +let ArcballFactor; +let b,B; // Scene min,max bounding box corners +let shift={ + x:0,y:0 +}; + +let viewParam = { + xmin:0,xmax:0, + ymin:0,ymax:0, + zmin:0,zmax:0 +}; + +let positionBuffer; +let materialBuffer; +let colorBuffer; +let indexBuffer; + +let redraw=true; +let remesh=true; +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,stringLoc,index=null) { + let getLoc; + if (index === null) + getLoc = + param => gl.getUniformLocation(program,stringLoc+"."+param); + else + getLoc = + param => gl.getUniformLocation(program,stringLoc+"["+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.uniform1f(getLoc("shininess"),this.shininess); + gl.uniform1f(getLoc("metallic"),this.metallic); + gl.uniform1f(getLoc("fresnel0"),this.fresnel0); + } +} + +let enumPointLight=1; +let enumDirectionalLight=2; + +class Light { + constructor(direction,color) { + this.direction=direction; + this.color=color; + } + + setUniform(program,stringLoc,index) { + let getLoc= + param => gl.getUniformLocation(program,stringLoc+"["+index+"]."+param); + + gl.uniform3fv(getLoc("direction"),new Float32Array(this.direction)); + gl.uniform3fv(getLoc("color"),new Float32Array(this.color)); + } +} + +function initGL() { + try { + gl=canvas.getContext("webgl",{alpha:Background[3] < 1}); + } catch(e) {} + if (!gl) + alert("Could not initialize WebGL"); +} + +function getShader(gl,id,options=[]) { + let shaderScript=document.getElementById(id); + if(!shaderScript) + return null; + + let str=`#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; +#else + precision mediump float; +#endif + const int nLights=${Lights.length}; + const int nMaterials=${Materials.length};\n` + + if(orthographic) + str += `#define ORTHOGRAPHIC\n`; + + options.forEach(s => str += `#define `+s+`\n`); + + let k=shaderScript.firstChild; + while(k) { + if(k.nodeType == 3) + str += k.textContent; + k=k.nextSibling; + } + let shader; + if(shaderScript.type == "x-shader/x-fragment") + shader = gl.createShader(gl.FRAGMENT_SHADER); + else if (shaderScript.type == "x-shader/x-vertex") + shader = gl.createShader(gl.VERTEX_SHADER); + else + return null; + + gl.shaderSource(shader,str); + gl.compileShader(shader); + if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)) { + alert(gl.getShaderInfoLog(shader)); + return null; + } + return shader; +} + + +function drawBuffer(data,shader,indices=data.indices) +{ + if(data.indices.length == 0) return; + let pixel=shader == pixelShader; + let normal=!pixel && (shader != noNormalShader); + + setUniforms(shader); + + gl.bindBuffer(gl.ARRAY_BUFFER,positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data.vertices), + gl.STATIC_DRAW); + gl.vertexAttribPointer(shader.vertexPositionAttribute, + 3,gl.FLOAT,false,normal ? 24 : (pixel ? 16 : 12),0); + if(normal) + gl.vertexAttribPointer(shader.vertexNormalAttribute, + 3,gl.FLOAT,false,24,12); + else if(pixel) + gl.vertexAttribPointer(shader.vertexWidthAttribute, + 1,gl.FLOAT,false,16,12); + + gl.bindBuffer(gl.ARRAY_BUFFER,materialBuffer); + gl.bufferData(gl.ARRAY_BUFFER,new Int16Array(data.materials), + gl.STATIC_DRAW); + gl.vertexAttribPointer(shader.vertexMaterialAttribute, + 1,gl.SHORT,false,2,0); + + if(shader == colorShader || shader == transparentShader) { + gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER,new Uint8Array(data.colors), + gl.STATIC_DRAW); + gl.vertexAttribPointer(shader.vertexColorAttribute, + 4,gl.UNSIGNED_BYTE,true,0,0); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, + indexExt ? new Uint32Array(indices) : + new Uint16Array(indices),gl.STATIC_DRAW); + + gl.drawElements(normal ? gl.TRIANGLES : (pixel ? gl.POINTS : gl.LINES), + indices.length, + indexExt ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT,0); +} + +class vertexBuffer { + constructor() { + this.clear(); + } + clear() { + this.vertices=[]; + this.materials=[]; + this.colors=[]; + this.indices=[]; + this.nvertices=0; + } + + // 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.materials.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.materials.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 without normal + vertex1(v) { + this.vertices.push(v[0]); + this.vertices.push(v[1]); + this.vertices.push(v[2]); + this.materials.push(materialIndex); + 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.materials.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.materials[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.materials,data.materials); + append(this.colors,data.colors); + appendOffset(this.indices,data.indices,this.nvertices); + this.nvertices += data.nvertices; + } +} + +let material0Data=new vertexBuffer(); // pixels +let material1Data=new vertexBuffer(); // material Bezier curves +let materialData=new vertexBuffer(); // material Bezier patches & triangles +let colorData=new vertexBuffer(); // colored Bezier patches & triangles +let transparentData=new vertexBuffer(); // transparent patches & triangles +let triangleData=new vertexBuffer(); // 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 +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)]; + + } + + render() { + // 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(); + 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]); } + + materialIndex=this.MaterialIndex; + + 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.Onscreen=true; + this.process(P); + } } -function getShader(gl, id) { - var shaderScript=document.getElementById(id); - if(!shaderScript) { - return null; +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; + if(this.transparent) { + this.MaterialIndex=color ? -1-MaterialIndex : 1+MaterialIndex; + this.vertex=this.data.Vertex.bind(this.data); + } else { + this.MaterialIndex=MaterialIndex; + this.vertex=this.data.vertex.bind(this.data); } - var str=""; - var k=shaderScript.firstChild; - while(k) { - if(k.nodeType == 3) { - str += k.textContent; - } - k=k.nextSibling; + this.L2norm(this.controlpoints); + } + +// 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 *= Fuzz4; + } + + 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])) { + if(this.color) { + this.data.indices.push(this.data.Vertex(p0,n,this.color[0])); + this.data.indices.push(this.data.Vertex(p1,n,this.color[1])); + this.data.indices.push(this.data.Vertex(p2,n,this.color[2])); + } else { + this.data.indices.push(this.vertex(p0,n)); + this.data.indices.push(this.vertex(p1,n)); + this.data.indices.push(this.vertex(p2,n)); + } + 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); + } + 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); + + this.append(); } - var shader; - if(shaderScript.type == "x-shader/x-fragment") { - shader=gl.createShader(gl.FRAGMENT_SHADER); - } else if(shaderScript.type == "x-shader/x-vertex") { - shader=gl.createShader(gl.VERTEX_SHADER); + } + + process(p) { + if(p.length == 10) return this.process3(p); + if(p.length == 3) return this.processTriangle(p); + if(p.length == 4) return this.processQuad(p); + + 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(iszero(n0)) { + n0=this.normal(p3,p[2],p[1],p0,p[13],p[14],p15); + if(iszero(n0)) 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(iszero(n1)) { + n1=this.normal(p0,p[4],p[8],p12,p[11],p[7],p3); + if(iszero(n1)) 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(iszero(n2)) { + n2=this.normal(p12,p[13],p[14],p15,p[2],p[1],p0); + if(iszero(n2)) 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(iszero(n3)) { + n3=this.normal(p15,p[11],p[7],p3,p[4],p[8],p12); + if(iszero(n3)) 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 { - return null; + 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); } - gl.shaderSource(shader,str); - gl.compileShader(shader); - if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)) { - alert(gl.getShaderInfoLog(shader)); - return null; + 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); + } + + Render(p,I0,I1,I2,I3,P0,P1,P2,P3,flat0,flat1,flat2,flat3,C0,C1,C2,C3) { + if(this.Distance(p) < this.res2) { // Bezier patch is flat + if(!this.offscreen([P0,P1,P2])) { + this.data.indices.push(I0); + this.data.indices.push(I1); + this.data.indices.push(I2); + } + if(!this.offscreen([P0,P2,P3])) { + this.data.indices.push(I0); + this.data.indices.push(I2); + this.data.indices.push(I3); + } + } 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 + + + 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 p0=p[0]; + let p3=p[3]; + let p12=p[12]; + let p15=p[15]; + + 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(iszero(n0)) { + n0=this.normal(s0[0],s0[4],s0[8],s0[12],s0[11],s0[7],s0[3]); + if(iszero(n0)) + 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(iszero(n1)) { + n1=this.normal(s1[12],s1[13],s1[14],s1[15],s1[2],s1[1],s1[0]); + if(iszero(n1)) + 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(iszero(n2)) { + n2=this.normal(s2[15],s2[11],s2[7],s2[3],s2[4],s2[8],s2[12]); + if(iszero(n2)) + 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(iszero(n3)) { + n3=this.normal(s3[3],s3[2],s3[1],s3[0],s3[13],s3[14],s3[15]); + if(iszero(n3)) + 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.derivative(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.derivative(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.derivative(s3[15],s2[14],s2[13],s1[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.derivative(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); + } } - return shader; -} + } + +// Render a Bezier triangle via subdivision. + process3(p) { + this.Res2=BezierFactor*BezierFactor*this.res2; + + let p0=p[0]; + let p6=p[6]; + let p9=p[9]; -var shaderProgram; + 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); -function initShaders() { - var fragmentShader=getShader(gl,"shader-fs"); - var vertexShader=getShader(gl,"shader-vs"); - shaderProgram=gl.createProgram(); - gl.attachShader(shaderProgram,vertexShader); - gl.attachShader(shaderProgram,fragmentShader); - gl.linkProgram(shaderProgram); - if(!gl.getProgramParameter(shaderProgram,gl.LINK_STATUS)) { - alert("Could not initialise shaders"); + this.Render3(p,i0,i1,i2,p0,p6,p9,false,false,false); } - gl.useProgram(shaderProgram); - shaderProgram.vertexPositionAttribute=gl.getAttribLocation(shaderProgram,"aVertexPosition"); - gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); - shaderProgram.vertexColorAttribute=gl.getAttribLocation(shaderProgram,"aVertexColor"); - gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); - shaderProgram.pMatrixUniform=gl.getUniformLocation(shaderProgram,"uPMatrix"); - shaderProgram.mvMatrixUniform=gl.getUniformLocation(shaderProgram,"uMVMatrix"); -} -var mvMatrix=mat4.create(); -var pMatrix=mat4.create(); + 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])) { + this.data.indices.push(I0); + this.data.indices.push(I1); + this.data.indices.push(I2); + } + } 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.sumderivative(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; + } -function setMatrixUniforms() { - gl.uniformMatrix4fv(shaderProgram.pMatrixUniform,false,pMatrix); - gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform,false,mvMatrix); -} -var VertexBuffer; -var ColorBuffer; + 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.sumderivative(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.sumderivative(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); + } + } + } -var vertices=new Array(); -var colors=new Array(); -var indices=new Array(); -var nvertices=0; + // 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]; -var mvMatrix=mat4.create(); -var translOffset=[0,0,0]; -var zoomFactor=1; + // Check the flatness of a patch. + let d=Distance2(p15,p0,this.normal(p3,p[2],p[1],p0,p[4],p[8],p12)); + + // Determine how straight the edges are. + d=Math.max(d,Straightness(p0,p[1],p[2],p3)); + d=Math.max(d,Straightness(p0,p[4],p[8],p12)); + d=Math.max(d,Straightness(p3,p[7],p[11],p15)); + d=Math.max(d,Straightness(p12,p[13],p[14],p15)); + + // Determine how straight the interior control curves are. + d=Math.max(d,Straightness(p[4],p[5],p[6],p[7])); + d=Math.max(d,Straightness(p[8],p[9],p[10],p[11])); + d=Math.max(d,Straightness(p[1],p[5],p[9],p[13])); + return Math.max(d,Straightness(p[2],p[6],p[10],p[14])); + } -var mvMatrixStack=[] ; -var pMatrix=mat4.create(); + // 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]; -var localRotation=mat4.create(); + // 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]]); -function mvPushMatrix() { - var copy=mat4.create(); - mat4.set(mvMatrix,copy); - mvMatrixStack.push(copy); + // 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)); + } + + derivative(p0,p1,p2,p3) { + let lp=[p1[0]-p0[0],p1[1]-p0[1],p1[2]-p0[2]]; + if(abs2(lp) > this.epsilon) + return lp; + + let lpp=bezierPP(p0,p1,p2); + if(abs2(lpp) > this.epsilon) + return lpp; + + return bezierPPP(p0,p1,p2,p3); + } + + sumderivative(p0,p1,p2,p3,p4,p5,p6) { + let d0=this.derivative(p0,p1,p2,p3); + let d1=this.derivative(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=right1[0]-middle[0]; + let uy=right1[1]-middle[1]; + let uz=right1[2]-middle[2]; + let vx=left1[0]-middle[0]; + let vy=left1[1]-middle[1]; + let vz=left1[2]-middle[2]; + let n=[uy*vz-uz*vy, + uz*vx-ux*vz, + ux*vy-uy*vx]; + if(abs2(n) > this.epsilon) + return unit(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 unit(n); + + let lppp=bezierPPP(middle,left1,left2,left3); + let rppp=bezierPPP(middle,right1,right2,right3); + a=cross(rpp,lpp); + b=cross(rp,lppp); + let c=cross(rppp,lp); + let d=cross(rppp,lpp); + let e=cross(rpp,lppp); + let f=cross(rppp,lppp); + return unit([9*a[0]+3*(b[0]+c[0]+d[0]+e[0])+f[0], + 9*a[1]+3*(b[1]+c[1]+d[1]+e[1])+f[1], + 9*a[2]+3*(b[2]+c[2]+d[2]+e[2])+f[2]]); + } } -function mvPopMatrix() { - if(mvMatrixStack.length == 0) { - throw "Invalid popMatrix!"; +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; + } + + processLine(p) { + let p0=p[0]; + let p1=p[1]; + if(!this.offscreen([p0,p1])) { + this.data.indices.push(this.data.vertex1(p0)); + this.data.indices.push(this.data.vertex1(p1)); + this.append(); + } + } + + process(p) { + if(p.length == 2) return this.processLine(p); + + let i0=this.data.vertex1(p[0]); + let i3=this.data.vertex1(p[3]); + + this.Render(p,i0,i3); + if(this.data.indices.length > 0) this.append(); + } + + append() { + material1Data.append(this.data); + } + + 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 i0=this.data.vertex1(m5); + + this.Render(s0,I0,i0); + this.Render(s1,i0,I1); } - mvMatrix=mvMatrixStack.pop(); + } } -function degToRad(degrees) { - return degrees*Math.PI/180; +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; + } + + process(p) { + this.data.indices.push(this.data.vertex0(this.controlpoint,this.width)); + this.append(); + } + + append() { + material0Data.append(this.data); + } } -var redraw=true; -var mouseDownOrTouchActive=false; -var lastMouseX=null; -var lastMouseY=null; -var touchID=null; +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; + } + + process(p) { + 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; + materialIndex=-1-this.MaterialIndex; + 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 { + materialIndex=1+this.MaterialIndex; + 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]]); + } + } + } + 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); + } +} +function home() +{ + mat4.identity(rotMat); + initProjection(); + setProjection(); + redraw=true; +} -var center=[0,0,1]; -var centerInv=[0,0,-1]; +function initShader(options=[]) +{ + let fragmentShader=getShader(gl,"fragment",options); + let vertexShader=getShader(gl,"vertex",options); + let shader=gl.createProgram(); + + gl.attachShader(shader,vertexShader); + gl.attachShader(shader,fragmentShader); + gl.linkProgram(shader); + if (!gl.getProgramParameter(shader,gl.LINK_STATUS)) { + alert("Could not initialize shaders"); + } + + return shader; +} -var rotationMatLocal=mat4.create(); -var rotationMatrix=mat4.create(); -mat4.identity(rotationMatrix); +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 handleMouseDown(event) { - mouseDownOrTouchActive=true; - lastMouseX=event.clientX; - lastMouseY=event.clientY; +function iszero(v) +{ + return v[0] == 0 && v[1] == 0 && v[2] == 0; } -function handleTouchStart(evt) { - evt.preventDefault(); - var touches=evt.targetTouches; +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]; +} - if(touches.length == 1 && !mouseDownOrTouchActive) { - touchId=touches[0].identifier; - lastMouseX=touches[0].pageX, - lastMouseY=touches[0].pageY; - } +function abs2(v) +{ + return v[0]*v[0]+v[1]*v[1]+v[2]*v[2]; } -function handleMouseUpOrTouchEnd(event) { - mouseDownOrTouchActive=false; +function dot(u,v) +{ + return u[0]*v[0]+u[1]*v[1]+u[2]*v[2]; } -function processDrag(newX, newY, pan=false) { - let lastX=(lastMouseX-400)/400; - let lastY=(lastMouseY-400)/400; +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]]; +} - let rawX=(newX-400)/400; - let rawY=(newY-400)/400; +// Return one-sixth of the second derivative of the Bezier curve defined +// by a,b,c,d at 0. +function bezierPP(a,b,c) +{ + return [a[0]+c[0]-2*b[0], + a[1]+c[1]-2*b[1], + a[2]+c[2]-2*b[2]]; +} - if(!pan) { - let [angle,axis]=arcballLib.arcball([lastX,-lastY],[rawX,-rawY]); - let tmpMatrix=mat4.create() - mat4.identity(tmpMatrix) - mat4.rotate(tmpMatrix,tmpMatrix,angle,[axis[0],axis[1],axis[2]]) - mat4.multiply(rotationMatrix,tmpMatrix,rotationMatrix); - } else { - translOffset[0] +=(rawX-lastX); - translOffset[1] -=(rawY-lastY); - } - lastMouseX=newX; - lastMouseY=newY; - redraw=true; +// Return one-third of the third derivative of the Bezier curve defined by +// a,b,c,d at 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])]; } -function handleKey(key) { - var keycode=key.key; - var rotate=true; - var axis=[0,0,1]; - switch(keycode) { - case "w": - axis=[-1,0,0]; - break; - case "d": - axis=[0,1,0]; - break; - case "a": - axis=[0,-1,0]; - break; - case "s": - axis=[1,0,0]; - break; - - default: - rotate=false; - break; - } +/** + * 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]])); +} - if(rotate) { - mat4.rotate(rotationMatrix,rotationMatrix,0.1,axis); - redraw=true; - } +/** + * Return the perpendicular distance squared of a point z from the plane + * through u with unit normal n. + */ +function Distance2(z,u,n) +{ + let d=dot([z[0]-u[0],z[1]-u[1],z[2]-u[2]],n); + return d*d; +} +// 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 handleMouseWheel(event) { - zoomFactor -= event.deltaY/100; +/** + * Perform a change of basis + * @param {*} out Out Matrix + * @param {*} mat Matrix + * + * Compute the matrix (translMatrix) * mat * (translMatrix)^{-1} + */ - if(zoomFactor < 0) { - zoomFactor=0; - } else if(zoomFactor > 100) { - zoomFactor=100; - } +function COBTarget(out,mat) +{ + mat4.fromTranslation(translMat,[center.x,center.y,center.z]) + mat4.invert(cjMatInv,translMat); + mat4.multiply(out,mat,cjMatInv); + mat4.multiply(out,translMat,out); +} - res=zoomFactor*0.001; - redraw=true; +function setUniforms(shader) +{ + let pixel=shader == pixelShader; + + gl.useProgram(shader); + + shader.vertexPositionAttribute= + gl.getAttribLocation(shader,"position"); + gl.enableVertexAttribArray(shader.vertexPositionAttribute); + + if(pixel) { + shader.vertexWidthAttribute= + gl.getAttribLocation(shader,"width"); + gl.enableVertexAttribArray(shader.vertexWidthAttribute); + } + + if(shader != noNormalShader && !pixel) { + shader.vertexNormalAttribute= + gl.getAttribLocation(shader,"normal"); + gl.enableVertexAttribArray(shader.vertexNormalAttribute); + } + + shader.vertexMaterialAttribute= + gl.getAttribLocation(shader,"materialIndex"); + gl.enableVertexAttribArray(shader.vertexMaterialAttribute); + + shader.projViewMatUniform=gl.getUniformLocation(shader,"projViewMat"); + shader.viewMatUniform=gl.getUniformLocation(shader,"viewMat"); + shader.normMatUniform=gl.getUniformLocation(shader,"normMat"); + + if(shader == colorShader || shader == transparentShader) { + shader.vertexColorAttribute= + gl.getAttribLocation(shader,"color"); + gl.enableVertexAttribArray(shader.vertexColorAttribute); + } + + for(let i=0; i < Materials.length; ++i) + Materials[i].setUniform(shader,"Materials",i); + + for(let i=0; i < Lights.length; ++i) + Lights[i].setUniform(shader,"Lights",i); + + gl.uniformMatrix4fv(shader.projViewMatUniform,false,projViewMat); + gl.uniformMatrix4fv(shader.viewMatUniform,false,viewMat); + gl.uniformMatrix3fv(shader.normMatUniform,false,normMat); } -function handleMouseMove(event) { - if(!mouseDownOrTouchActive) { - return; - } +function handleMouseDown(event) +{ + mouseDownOrTouchActive=true; + lastMouseX=event.clientX; + lastMouseY=event.clientY; +} - var newX=event.clientX; - var newY=event.clientY; +let pinch=false; +let pinchStart; - processDrag(newX,newY,event.getModifierState("Alt")); +function pinchDistance(touches) +{ + return Math.hypot( + touches[0].pageX-touches[1].pageX, + touches[0].pageY-touches[1].pageY); } -function handleTouchMove(evt) { - evt.preventDefault(); - var touches=evt.targetTouches; - if(touches.length == 1 && touchId == touches[0].identifier) { - var newX=touches[0].pageX; - var newY=touches[0].pageY; - processDrag(newX,newY); - } +let touchStartTime; + +function handleTouchStart(event) +{ + event.preventDefault(); + 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; + } } -// Prepare canvas for drawing -function sceneSetup() { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - mat4.perspective(pMatrix,45,gl.viewportWidth/gl.viewportHeight,0.1,100.0); - mat4.identity(mvMatrix); +function handleMouseUpOrTouchEnd(event) +{ + mouseDownOrTouchActive=false; +} - mat4.translate(mvMatrix,mvMatrix,centerInv); - mat4.multiply(mvMatrix,mvMatrix,rotationMatrix); - mat4.scale(mvMatrix,mvMatrix,[zoomFactor,zoomFactor,zoomFactor]); - mat4.translate(mvMatrix,mvMatrix,center); +function rotateScene(lastX,lastY,rawX,rawY,factor) +{ + if(lastX == rawX && lastY == rawY) return; + let [angle,axis]=arcball([lastX,-lastY],[rawX,-rawY]); - mat4.translate(mvMatrix,mvMatrix,translOffset) + mat4.fromRotation(rotMats,2*factor*ArcballFactor*angle/lastzoom,axis); + mat4.multiply(rotMat,rotMats,rotMat); } -var indexExt; +function shiftScene(lastX,lastY,rawX,rawY) +{ + let zoominv=1/lastzoom; + shift.x += (rawX-lastX)*zoominv*halfCanvasWidth; + shift.y -= (rawY-lastY)*zoominv*halfCanvasHeight; +} -// Create buffer data for the patch and its subdivisions to be pushed to the graphics card -//Takes as an argument the array of vertices that define the patch to be drawn -// Using the vertex position buffer of the above function,draw patch. -function setBuffer() { - VertexBuffer=gl.createBuffer(); - VertexBuffer.itemSize=3; +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); + } +} - ColorBuffer=gl.createBuffer(); - ColorBuffer.itemSize=4; +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); +} - indexBuffer=gl.createBuffer(); - indexBuffer.itemSize=1; +function capzoom() +{ + let maxzoom=Math.sqrt(Number.MAX_VALUE); + let minzoom=1/maxzoom; + if(Zoom <= minzoom) Zoom=minzoom; + if(Zoom >= maxzoom) Zoom=maxzoom; + + if(Zoom != lastzoom) remesh=true; + lastzoom=Zoom; } -function drawBuffer() { - gl.bindBuffer(gl.ARRAY_BUFFER,VertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(vertices),gl.STATIC_DRAW); - gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, - VertexBuffer.itemSize,gl.FLOAT,false,0,0); - VertexBuffer.numItems=nvertices; +function zoomImage(diff) +{ + let stepPower=zoomStep*halfCanvasHeight*diff; + const limit=Math.log(0.1*Number.MAX_VALUE)/Math.log(zoomFactor); - gl.bindBuffer(gl.ARRAY_BUFFER,ColorBuffer); - gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(colors),gl.STATIC_DRAW); - gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, - ColorBuffer.itemSize,gl.FLOAT,false,0,0); - ColorBuffer.numItems=nvertices; + if(Math.abs(stepPower) < limit) { + Zoom *= zoomFactor**stepPower; + capzoom(); + } +} - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, - indexExt ? new Uint32Array(indices) : new Uint16Array(indices), - gl.STATIC_DRAW); - indexBuffer.numItems=indices.length; - gl.drawElements(gl.TRIANGLES,indexBuffer.numItems, - indexExt ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT,0); - vertices=[]; - colors=[]; - indices=[]; - nvertices=0; -} - -var pixel=1.0; // Adaptive rendering constant. -var FillFactor=0.1; -var BezierFactor=0.4; -//var res=0.0005; // Temporary -var res=0.001; // Temporary -var res2=res*res; -var Epsilon=0.1*res; -var epsilon=0; -var Fuzz=1000*Number.EPSILON; -var Fuzz2=Fuzz*Fuzz; - -function Split3(z0, c0, c1, z1) { - this.m0=new Array(3); - this.m2=new Array(3); - this.m3=new Array(3); - this.m4=new Array(3); - this.m5=new Array(3); - for(var i=0; i < 3; ++i) { - this.m0[i]=0.5*(z0[i]+c0[i]); - var m1=0.5*(c0[i]+c1[i]); - this.m2[i]=0.5*(c1[i]+z1[i]); - this.m3[i]=0.5*(this.m0[i]+m1); - this.m4[i]=0.5*(m1+this.m2[i]); - this.m5[i]=0.5*(this.m3[i]+this.m4[i]); - } +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 unit(v) { - var norm=Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); - norm=(norm != 0) ? 1/norm : 1; - return [v[0]*norm,v[1]*norm,v[2]*norm]; -} - -// Store the vertex v and its color vector c in the buffer. -function vertex(v,c) { - vertices.push(v[0]); - vertices.push(v[1]); - vertices.push(v[2]); - - colors.push(c[0]); - colors.push(c[1]); - colors.push(c[2]); - colors.push(c[3]); - return nvertices++; -} - -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] - ]; -} - -function normal(left3, left2, left1, middle, right1, right2, right3) { - var u0=right1[0]-middle[0]; - var v0=left1[0]-middle[0]; - var u1=right1[1]-middle[1]; - var v1=left1[1]-middle[1]; - var u2=right1[2]-middle[2]; - var v2=left1[2]-middle[2]; - var n=[ - u1*v2-u2*v1, - u2*v0-u0*v2, - u0*v1-u1*v0 - ]; - if(abs2(n) > epsilon) - return unit(n); - - var lp=[v0,v1,v2]; - var rp=[u0,u1,u2]; - var lpp=[middle[0]+left2[0]-2*left1[0], - middle[1]+left2[1]-2*left1[1], - middle[2]+left2[2]-2*left1[2] - ]; - var rpp=[middle[0]+right2[0]-2*right1[0], - middle[1]+right2[1]-2*right1[1], - middle[2]+right2[2]-2*right1[2] - ]; - var a=cross(rpp,lp); - var b=cross(rp,lpp); - n=[a[0]+b[0], - a[1]+b[1], - a[2]+b[2] - ]; - if(abs2(n) > epsilon) - return unit(n); - - var lppp=[left3[0]-middle[0]+3*(left1[0]-left2[0]), - left3[1]-middle[1]+3*(left1[1]-left2[1]), - left3[2]-middle[2]+3*(left1[2]-left2[2]) - ]; - var rppp=[right3[0]-middle[0]+3*(right1[0]-right2[0]), - right3[1]-middle[1]+3*(right1[1]-right2[1]), - right3[2]-middle[2]+3*(right1[2]-right2[2]) - ]; - a=cross(rpp,lpp); - b=cross(rp,lppp); - var c=cross(rppp,lp); - var d=cross(rppp,lpp); - var e=cross(rpp,lppp); - var f=cross(rppp,lppp); - return unit([9*a[0]+3*(b[0]+c[0]+d[0]+e[0])+f[0], - 9*a[1]+3*(b[1]+c[1]+d[1]+e[1])+f[1], - 9*a[2]+3*(b[2]+c[2]+d[2]+e[2])+f[2] - ]); +function arcball(oldmouse,newmouse) +{ + let oldMouse=normMouse(oldmouse); + let newMouse=normMouse(newmouse); + let Dot=dot(oldMouse,newMouse); + if(Dot > 1) Dot=1; + else if(Dot < -1) Dot=-1; + return [Math.acos(Dot),unit(cross(oldMouse,newMouse))] } -// 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) +/** + * Mouse Drag Zoom + * @param {*} lastX unused + * @param {*} lastY + * @param {*} rawX unused + * @param {*} rawY + */ +function zoomScene(lastX,lastY,rawX,rawY) { - var third=1.0/3.0; - var 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]])); + zoomImage(lastY-rawY); } -// return the maximum perpendicular distance squared of points c0 and c1 -// from z0--z1. -function Distance1(z0, c0, c1, z1) { - var Z0=[c0[0]-z0[0],c0[1]-z0[1],c0[2]-z0[2]]; - var Q=unit([z1[0]-z0[0],z1[1]-z0[1],z1[2]-z0[2]]); - var Z1=[c1[0]-z0[0],c1[1]-z0[1],c1[2]-z0[2]]; - var p0=dot(Z0,Q); - var p1=dot(Z1,Q); - return Math.max(abs2([Z0[0]-p0*Q[0],Z0[1]-p0*Q[1],Z0[2]-p0*Q[2]]), - abs2([Z1[0]-p1*Q[0],Z1[1]-p1*Q[1],Z1[2]-p1*Q[2]])); +// 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(); + redraw=true; } -// return the perpendicular distance squared of a point z from the plane -// through u with unit normal n. -function Distance2(z, u, n) { - var d=dot([z[0]-u[0],z[1]-u[1],z[2]-u[2]],n); - return d*d; +function handleKey(event) +{ + 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 '+': + case '=': + case '>': + expand(); + break; + case '-': + case '_': + case '<': + shrink(); + break; + default: + break; + } + + if(axis.length > 0) { + mat4.rotate(rotMat,rotMat,0.1,axis); + updateViewMatrix(); + redraw=true; + } } -function Distance(p) { - var p0=p[0]; - var p3=p[3]; - var p12=p[12]; - var p15=p[15]; +function handleMouseWheel(event) +{ + event.preventDefault(); + + if (event.deltaY < 0) { + Zoom *= zoomFactor; + } else { + Zoom /= zoomFactor; + } + capzoom(); + setProjection(); + + redraw=true; +} - // Check the flatness of the quad. - var d=Distance2(p15,p0,normal(p3,p[2],p[1],p0,p[4],p[8],p12)); - - // Determine how straight the edges are. - d=Math.max(d,Straightness(p0,p[1],p[2],p3)); - d=Math.max(d,Straightness(p0,p[4],p[8],p12)); - d=Math.max(d,Straightness(p3,p[7],p[11],p15)); - d=Math.max(d,Straightness(p12,p[13],p[14],p15)); - - // Determine how straight the interior control curves are. - d=Math.max(d,Straightness(p[4],p[5],p[6],p[7])); - d=Math.max(d,Straightness(p[8],p[9],p[10],p[11])); - d=Math.max(d,Straightness(p[1],p[5],p[9],p[13])); - return Math.max(d,Straightness(p[2],p[6],p[10],p[14])); +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); } -var k=1; -// Return color associated with unit normal vector n. -function color(n) { - var Ldotn=L[0]*n[0]+L[1]*n[1]+L[2]*n[2]; - if(Ldotn < 0) Ldotn=0; - var p=[emissive[0]+ambient[0]*Ambient[0]+Ldotn*diffuse[0]*Diffuse[0], - emissive[1]+ambient[1]*Ambient[1]+Ldotn*diffuse[1]*Diffuse[1], - emissive[2]+ambient[2]*Ambient[2]+Ldotn*diffuse[2]*Diffuse[2] - ]; - var s=shininess*128; - var H=unit([L[0],L[1],L[2]+1]); - var f=Math.pow(H[0]*n[0]+H[1]*n[1]+H[2]*n[2],s); - - if(Ldotn > 0) // Phong-Blinn model of specular reflection - p=[p[0]+f*specular[0]*Specular[0],p[1]+f*specular[1]*Specular[1], - p[2]+f*specular[2]*Specular[2] - ]; - - return [p[0],p[1],p[2],1]; -} - -function render(p, I0, I1, I2, I3, P0, P1, P2, P3, flat0, flat1, flat2, flat3, - C0, C1, C2, C3) { - if(Distance(p) < res2) { // Patch is flat - indices.push(I0); - indices.push(I1); - indices.push(I2); - - indices.push(I0); - indices.push(I2); - indices.push(I3); - return; - } +let zooming=false; +let swipe=false; +let rotate=false; - var p0=p[0]; - var p3=p[3]; - var p12=p[12]; - var p15=p[15]; - - var c0=new Split3(p0,p[1],p[2],p3); - var c1=new Split3(p[4],p[5],p[6],p[7]); - var c2=new Split3(p[8],p[9],p[10],p[11]); - var c3=new Split3(p12,p[13],p[14],p15); - var c4=new Split3(p0,p[4],p[8],p12); - var c5=new Split3(c0.m0,c1.m0,c2.m0,c3.m0); - var c6=new Split3(c0.m3,c1.m3,c2.m3,c3.m3); - var c7=new Split3(c0.m5,c1.m5,c2.m5,c3.m5); - var c8=new Split3(c0.m4,c1.m4,c2.m4,c3.m4); - var c9=new Split3(c0.m2,c1.m2,c2.m2,c3.m2); - var c10=new Split3(p3,p[7],p[11],p15); - - var 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 - ]; - var 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 - ]; - var 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 - ]; - var 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 - ]; - - var m4=s0[15]; - - var n0=normal(s0[0],s0[4],s0[8],s0[12],s0[13],s0[14],s0[15]); - if(n0 == 0.0) { - n0=normal(s0[0],s0[4],s0[8],s0[12],s0[11],s0[7],s0[3]); - if(n0 == 0.0) n0=normal(s0[3],s0[2],s0[1],s0[0],s0[13],s0[14],s0[15]); +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; + } } - - var n1=normal(s1[12],s1[13],s1[14],s1[15],s1[11],s1[7],s1[3]); - if(n1 == 0.0) { - n1=normal(s1[12],s1[13],s1[14],s1[15],s1[2],s1[1],s1[0]); - if(n1 == 0.0) n1=normal(s1[0],s1[4],s1[8],s1[12],s1[11],s1[7],s1[3]); + 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(); + redraw=true; + } +} - var n2=normal(s2[15],s2[11],s2[7],s2[3],s2[2],s2[1],s2[0]); - if(n2 == 0.0) { - n2=normal(s2[15],s2[11],s2[7],s2[3],s2[4],s2[8],s2[12]); - if(n2 == 0.0) n2=normal(s2[12],s2[13],s2[14],s2[15],s2[2],s2[1],s2[0]); - } +let indexExt; - var n3=normal(s3[3],s3[2],s3[1],s3[0],s3[4],s3[8],s3[12]); - if(n3 == 0.0) { - n3=normal(s3[3],s3[2],s3[1],s3[0],s3[13],s3[14],s3[15]); - if(n3 == 0.0) n3=normal(s3[15],s3[11],s3[7],s3[3],s3[4],s3[8],s3[12]); - } +// Create buffers for the patch and its subdivisions. +function setBuffer() +{ + positionBuffer=gl.createBuffer(); + materialBuffer=gl.createBuffer(); + colorBuffer=gl.createBuffer(); + indexBuffer=gl.createBuffer(); + indexExt=gl.getExtension("OES_element_index_uint"); +} - var n4=normal(s2[3],s2[2],s2[1],m4,s2[4],s2[8],s2[12]); +let zbuffer=[]; - var m0,m1,m2,m3; +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]; + } +} - // 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. - if(flat0) - m0=[0.5*(P0[0]+P1[0]),0.5*(P0[1]+P1[1]),0.5*(P0[2]+P1[2])]; - else { - if((flat0=Distance1(p0,p[4],p[8],p12) < res2)) { - var u=s0[12]; - var v=s2[3]; - var e=unit([u[0]-v[0],u[1]-v[1],u[2]-v[2]]); - m0=[0.5*(P0[0]+P1[0])+Epsilon*e[0],0.5*(P0[1]+P1[1])+Epsilon*e[1], - 0.5*(P0[2]+P1[2])+Epsilon*e[2] - ]; - } else - m0=s0[12]; +function draw() +{ + gl.clearColor(Background[0],Background[1],Background[2],Background[3]); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + material0Data.clear(); + material1Data.clear(); + materialData.clear(); + colorData.clear(); + triangleData.clear(); + transparentData.clear(); + + P.forEach(function(p) { + p.render(); + }); + + drawBuffer(material0Data,pixelShader); + drawBuffer(material1Data,noNormalShader); + drawBuffer(materialData,materialShader); + drawBuffer(colorData,colorShader); + drawBuffer(triangleData,transparentShader); + + let indices=transparentData.indices; + 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]; } - if(flat1) - m1=[0.5*(P1[0]+P2[0]),0.5*(P1[1]+P2[1]),0.5*(P1[2]+P2[2])]; - else { - if((flat1=Distance1(p12,p[13],p[14],p15) < res2)) { - var u=s1[15]; - var v=s3[0]; - var e=unit([u[0]-v[0],u[1]-v[1],u[2]-v[2]]); - m1=[0.5*(P1[0]+P2[0])+Epsilon*e[0],0.5*(P1[1]+P2[1])+Epsilon*e[1], - 0.5*(P1[2]+P2[2])+Epsilon*e[2] - ]; - } else - m1=s1[15]; - } + gl.depthMask(false); // Enable transparency + drawBuffer(transparentData,transparentShader,Indices); + gl.depthMask(true); // Disable transparency + } - if(flat2) - m2=[0.5*(P2[0]+P3[0]),0.5*(P2[1]+P3[1]),0.5*(P2[2]+P3[2])]; - else { - if((flat2=Distance1(p15,p[11],p[7],p3) < res2)) { - var u=s2[3]; - var v=s0[12]; - var e=unit([u[0]-v[0],u[1]-v[1],u[2]-v[2]]); - m2=[0.5*(P2[0]+P3[0])+Epsilon*e[0],0.5*(P2[1]+P3[1])+Epsilon*e[1], - 0.5*(P2[2]+P3[2])+Epsilon*e[2] - ]; - } else - m2=s2[3]; - } + remesh=false; +} - if(flat3) - m3=[0.5*(P3[0]+P0[0]),0.5*(P3[1]+P0[1]),0.5*(P3[2]+P0[2])]; - else { - if((flat3=Distance1(p3,p[2],p[1],p0) < res2)) { - var u=s3[0]; - var v=s1[15]; - var e=unit([u[0]-v[0],u[1]-v[1],u[2]-v[2]]); - m3=[0.5*(P3[0]+P0[0])+Epsilon*e[0], - 0.5*(P3[1]+P0[1])+Epsilon*e[1], - 0.5*(P3[2]+P0[2])+Epsilon*e[2] - ]; - } else - m3=s3[0]; - } +function tick() +{ + requestAnimationFrame(tick); + if(redraw) { + draw(); + redraw=false; + } +} - // document.write(n0); - // document.write(" < br > "); - - { - /* - var c0=new Array(4); - var c1=new Array(4); - var c2=new Array(4); - var c3=new Array(4); - var c4=new Array(4); - - for(var 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]); - } - */ - - var c0=color(n0); - var c1=color(n1); - var c2=color(n2); - var c3=color(n3); - var c4=color(n4); - - - var i0=vertex(m0,c0); - var i1=vertex(m1,c1); - var i2=vertex(m2,c2); - var i3=vertex(m3,c3); - var i4=vertex(m4,c4); - - render(s0,I0,i0,i4,i3,P0,m0,m4,m3,flat0,false,false,flat3, - C0,c0,c4,c3); - render(s1,i0,I1,i1,i4,m0,P1,m1,m4,flat0,flat1,false,false, - c0,C1,c1,c4); - render(s2,i4,i1,I2,i2,m4,m1,P2,m2,false,flat1,flat2,false, - c4,c1,C2,c2); - render(s3,i3,i4,i2,I3,m3,m4,m2,P3,false,false,flat2,flat3, - c3,c4,c2,C3); +function setDimensions(width,height,X,Y) +{ + let Aspect=width/height; + let zoominv=1/lastzoom; + let xshift=X/width*lastzoom + let yshift=Y/height*lastzoom + + 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; -function draw(p) { - var p0=p[0]; - var p3=p[3]; - var p12=p[12]; - var p15=p[15]; + viewParam.zmin=b[2]; + viewParam.zmax=B[2]; + + shift.x=shift.y=0; +} - epsilon=0; - for(var i=1; i < 16; ++i) - epsilon=Math.max(epsilon, - abs2([p[i][0]-p0[0],p[i][1]-p0[1],p[i][2]-p0[2]])); - epsilon *= Fuzz2; +function setViewport() +{ + gl.viewportWidth=canvasWidth; + gl.viewportHeight=canvasHeight; + gl.viewport(0,0,gl.viewportWidth,gl.viewportHeight); + home(); +} - var n0=normal(p3,p[2],p[1],p0,p[4],p[8],p12); - var n1=normal(p0,p[4],p[8],p12,p[13],p[14],p15); - var n2=normal(p12,p[13],p[14],p15,p[11],p[7],p3); - var n3=normal(p15,p[11],p[7],p3,p[2],p[1],p0); +function setCanvas() +{ + canvas.width=canvasWidth; + canvas.height=canvasHeight; + size2=Math.hypot(canvasWidth,canvasHeight); + halfCanvasWidth=0.5*canvasWidth; + halfCanvasHeight=0.5*canvasHeight; +} - var c0=color(n0); - var c1=color(n1); - var c2=color(n2); - var c3=color(n3); +function setsize(w,h) +{ + if(w > maxViewportWidth) + w=maxViewportWidth; - var i0=vertex(p0,c0); - var i1=vertex(p12,c1); - var i2=vertex(p15,c2); - var i3=vertex(p3,c3); + if(h > maxViewportHeight) + h=maxViewportHeight; - render(p,i0,i1,i2,i3,p0,p12,p15,p3,false,false,false,false, - c0,c1,c2,c3); + shift.x *= w/canvasWidth; + shift.y *= h/canvasHeight; - drawBuffer(); + canvasWidth=w; + canvasHeight=h; + setCanvas(); + setViewport(); } -var p=[]; +function expand() +{ + setsize(canvasWidth*resizeStep+0.5,canvasHeight*resizeStep+0.5); +} -function tick() { - requestAnimationFrame(tick); - if(redraw) { - sceneSetup(); - mat4.translate(mvMatrix,mvMatrix,[-0.5,-0.5,-1.5]); - setMatrixUniforms(); - for(var i=0; i < p.length; ++i) - draw(p[i]); - redraw=false; - } +function shrink() +{ + setsize(Math.max((canvasWidth/resizeStep+0.5),1), + Math.max((canvasHeight/resizeStep+0.5),1)); } -function webGLStart() { - var canvas=document.getElementById("Asymptote"); - initGL(canvas); - initShaders(); - gl.clearColor(0.0,0.0,0.0,1.0); - gl.enable(gl.DEPTH_TEST); +let pixelShader,noNormalShader,materialShader,colorShader,transparentShader; + +function webGLStart() +{ + canvas=document.getElementById("Asymptote"); - indexExt=gl.getExtension("OES_element_index_uint"); + if(absolute) { + canvasWidth *= window.devicePixelRatio; + canvasHeight *= window.devicePixelRatio; + } else { + if(canvas.width == 0) + canvas.width=Math.max(window.innerWidth-windowTrim,windowTrim); - gl.viewport(0,0,gl.viewportWidth,gl.viewportHeight); - setBuffer(); + if(canvas.height == 0) + canvas.height=Math.max(window.innerHeight-windowTrim,windowTrim); - canvas.onmousedown=handleMouseDown; - document.onmouseup=handleMouseUpOrTouchEnd; - document.onmousemove=handleMouseMove; - canvas.onkeydown=handleKey; - document.onwheel=handleMouseWheel; + let Aspect=canvasWidth/canvasHeight; + if(canvas.width > canvas.height*Aspect) + canvas.width=Math.min(canvas.height*Aspect,canvas.width); + else + canvas.height=Math.min(canvas.width/Aspect,canvas.height); - 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); + if(canvas.width > 0) + canvasWidth=canvas.width; - tick(); -} + if(canvas.height > 0) + canvasHeight=canvas.height; -// Lighting parameters -var L = [0.447735768366173, 0.497260947684137, 0.743144825477394]; -var Ambient = [0.1, 0.1, 0.1]; -var Diffuse = [0.8, 0.8, 0.8, 1]; -var Specular = [0.7, 0.7, 0.7, 1]; -var specularfactor = 3; + } -// Material parameters -var emissive = [0, 0, 0, 1]; -var ambient = [1, 1, 1, 1]; -var diffuse = [0, 1, 0, 1]; -var specular = [0.75, 0.75, 0.75, 1]; -var shininess = 0.5; + setCanvas(); + ArcballFactor=1+8*Math.hypot(viewportmargin[0],viewportmargin[1])/size2; + + initGL(); + + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA); + gl.enable(gl.DEPTH_TEST); + setViewport(); + + noNormalShader=initShader(); + pixelShader=initShader(["WIDTH"]); + materialShader=initShader(["NORMAL"]); + colorShader=initShader(["NORMAL","COLOR"]); + transparentShader=initShader(["NORMAL","COLOR","TRANSPARENT"]); + + setBuffer(); + + canvas.onmousedown=handleMouseDown; + document.onmouseup=handleMouseUpOrTouchEnd; + document.onmousemove=handleMouseMove; + canvas.onkeydown=handleKey; + + canvas.addEventListener("wheel",handleMouseWheel,false); + 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); + + tick(); +} |