diff options
author | Karl Berry <karl@freefriends.org> | 2021-02-25 19:22:25 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-02-25 19:22:25 +0000 |
commit | ad547a6b5986815fda458221149728d9d9ab1d87 (patch) | |
tree | 16296910eb3eca724371474ea9aea3994dc69614 /Build/source/utils/asymptote/webgl | |
parent | 947b43de3dd21d58ccc2ffadefc4441ea1c2a813 (diff) |
restore Build,TODO from r57911
git-svn-id: svn://tug.org/texlive/trunk@57915 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/utils/asymptote/webgl')
-rw-r--r-- | Build/source/utils/asymptote/webgl/fragment.glsl | 95 | ||||
-rw-r--r-- | Build/source/utils/asymptote/webgl/gl.js | 3135 | ||||
-rw-r--r-- | Build/source/utils/asymptote/webgl/license | 18 | ||||
-rw-r--r-- | Build/source/utils/asymptote/webgl/vertex.glsl | 80 |
4 files changed, 3328 insertions, 0 deletions
diff --git a/Build/source/utils/asymptote/webgl/fragment.glsl b/Build/source/utils/asymptote/webgl/fragment.glsl new file mode 100644 index 00000000000..de9ab75dac5 --- /dev/null +++ b/Build/source/utils/asymptote/webgl/fragment.glsl @@ -0,0 +1,95 @@ +#ifdef NORMAL +#ifndef ORTHOGRAPHIC +varying vec3 ViewPosition; +#endif +varying vec3 Normal; +varying vec4 diffuse; +varying vec3 specular; +varying float roughness,metallic,fresnel0; + +float Roughness2; +vec3 normal; + +struct Light { + vec3 direction; + vec3 color; +}; + +uniform Light Lights[Nlights]; + +float NDF_TRG(vec3 h) +{ + float ndoth=max(dot(normal,h),0.0); + float alpha2=Roughness2*Roughness2; + float denom=ndoth*ndoth*(alpha2-1.0)+1.0; + return denom != 0.0 ? alpha2/(denom*denom) : 0.0; +} + +float GGX_Geom(vec3 v) +{ + float ndotv=max(dot(v,normal),0.0); + float ap=1.0+Roughness2; + float k=0.125*ap*ap; + return ndotv/((ndotv*(1.0-k))+k); +} + +float Geom(vec3 v, vec3 l) +{ + return GGX_Geom(v)*GGX_Geom(l); +} + +float Fresnel(vec3 h, vec3 v, float fresnel0) +{ + float a=1.0-max(dot(h,v),0.0); + float b=a*a; + return fresnel0+(1.0-fresnel0)*b*b*a; +} + +// physical based shading using UE4 model. +vec3 BRDF(vec3 viewDirection, vec3 lightDirection) +{ + vec3 lambertian=diffuse.rgb; + vec3 h=normalize(lightDirection+viewDirection); + + float omegain=max(dot(viewDirection,normal),0.0); + float omegali=max(dot(lightDirection,normal),0.0); + + float D=NDF_TRG(h); + float G=Geom(viewDirection,lightDirection); + float F=Fresnel(h,viewDirection,fresnel0); + + float denom=4.0*omegain*omegali; + float rawReflectance=denom > 0.0 ? (D*G)/denom : 0.0; + + vec3 dielectric=mix(lambertian,rawReflectance*specular,F); + vec3 metal=rawReflectance*diffuse.rgb; + + return mix(dielectric,metal,metallic); +} +#endif +varying vec4 emissive; + +void main(void) +{ +#if defined(NORMAL) && nlights > 0 + normal=normalize(Normal); + normal=gl_FrontFacing ? normal : -normal; +#ifdef ORTHOGRAPHIC + vec3 viewDir=vec3(0.0,0.0,1.0); +#else + vec3 viewDir=-normalize(ViewPosition); +#endif + Roughness2=roughness*roughness; + vec3 color=emissive.rgb; + for(int i=0; i < nlights; ++i) { + Light Li=Lights[i]; + vec3 L=Li.direction; + float cosTheta=max(dot(normal,L),0.0); + vec3 radiance=cosTheta*Li.color; + color += BRDF(viewDir,L)*radiance; + } + gl_FragColor=vec4(color,diffuse.a); +#else + gl_FragColor=emissive; +#endif +} 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); +} diff --git a/Build/source/utils/asymptote/webgl/license b/Build/source/utils/asymptote/webgl/license new file mode 100644 index 00000000000..f1169e9d3fe --- /dev/null +++ b/Build/source/utils/asymptote/webgl/license @@ -0,0 +1,18 @@ +/*@license + AsyGL: Render Bezier patches and triangles via subdivision with WebGL. + Copyright 2019-2020: 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/>. +*/ diff --git a/Build/source/utils/asymptote/webgl/vertex.glsl b/Build/source/utils/asymptote/webgl/vertex.glsl new file mode 100644 index 00000000000..bf55fc0042e --- /dev/null +++ b/Build/source/utils/asymptote/webgl/vertex.glsl @@ -0,0 +1,80 @@ +attribute vec3 position; +#ifdef WIDTH +attribute float width; +#endif +#ifdef NORMAL +attribute vec3 normal; +#endif +attribute float materialIndex; +#ifdef COLOR +attribute vec4 color; +#endif + +uniform mat3 normMat; +uniform mat4 viewMat; +uniform mat4 projViewMat; + +#ifdef NORMAL +#ifndef ORTHOGRAPHIC +varying vec3 ViewPosition; +#endif +varying vec3 Normal; +#endif +varying vec4 diffuse; +varying vec3 specular; +varying float roughness,metallic,fresnel0; +varying vec4 emissive; + +struct Material { + vec4 diffuse,emissive,specular; + vec4 parameters; +}; + +uniform Material Materials[Nmaterials]; + +void main(void) +{ + vec4 v=vec4(position,1.0); + gl_Position=projViewMat*v; +#ifdef NORMAL +#ifndef ORTHOGRAPHIC + ViewPosition=(viewMat*v).xyz; +#endif + Normal=normalize(normal*normMat); + + Material m; +#ifdef TRANSPARENT + m=Materials[int(abs(materialIndex))-1]; + emissive=m.emissive; + if(materialIndex >= 0.0) { + diffuse=m.diffuse; + } else { + diffuse=color; +#if nlights == 0 + emissive += color; +#endif + } +#else + m=Materials[int(materialIndex)]; + emissive=m.emissive; +#ifdef COLOR + diffuse=color; +#if nlights == 0 + emissive += color; +#endif +#else + diffuse=m.diffuse; +#endif +#endif + specular=m.specular.rgb; + vec4 parameters=m.parameters; + roughness=1.0-parameters[0]; + metallic=parameters[1]; + fresnel0=parameters[2]; +#else + emissive=Materials[int(materialIndex)].emissive; +#endif +#ifdef WIDTH + gl_PointSize=width; +#endif +} |