diff options
Diffstat (limited to 'graphics/asymptote/webgl/gl.js')
-rw-r--r-- | graphics/asymptote/webgl/gl.js | 757 |
1 files changed, 560 insertions, 197 deletions
diff --git a/graphics/asymptote/webgl/gl.js b/graphics/asymptote/webgl/gl.js index a3b990c385..4f7ee9530e 100644 --- a/graphics/asymptote/webgl/gl.js +++ b/graphics/asymptote/webgl/gl.js @@ -1,53 +1,70 @@ -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 Transform; // Transformation matrix T[4][4] that maps back to user -// coordinates, with T[i][j] stored as element 4*i+j. - -let canvasWidth,canvasHeight; // Canvas width, height - -let absolute=false; // true: absolute size; false: scale to canvas -let ibl=false; -let imageURL; -let image; - -let minBound,maxBound; // Scene min,max bounding box corners (3-tuples) -let orthographic; // true: orthographic; false: perspective -let angleOfView; // Field of view angle -let initialZoom; // Initial zoom -let viewportShift=[0,0]; // Viewport shift (for perspective projection) -let viewportMargin; // Margin around viewport (2-tuple) -let webgl2=false; - -let zoomFactor; // Zoom base factor -let zoomPinchFactor; // Zoom pinch factor -let zoomPinchCap; // Zoom pinch limit -let zoomStep; // Zoom power step - -let shiftHoldDistance; // Shift-mode maximum hold distance (pixels) -let shiftWaitTime; // Shift-mode hold time (milliseconds) -let vibrateTime; // Shift-mode vibrate time (milliseconds) - -let canvasWidth0,canvasHeight0; // Initial values -let zoom0; // Adjusted initial zoom +// AsyGL library core -let embedded; // Is image embedded within another window? +(function() { + +document.asy={ + canvasWidth:0, + canvasHeight:0, + absolute:false, // true: absolute size; false: scale to canvas + + minBound:[0,0,0], // Component-wise minimum bounding box corner + maxBound:[0,0,0], // Component-wise maximum bounding box corner + + orthographic:false, // true: orthographic; false: perspective + angleOfView:0, // Field of view angle + initialZoom:0, // Initial zoom + + viewportShift:[0,0], // Viewport shift (for perspective projection) + viewportMargin:[0,0], // Margin around viewport + + background:[], // Background color + + zoomFactor:0, // Zoom base factor + zoomPinchFactor:0, // Zoom pinch factor + zoomPinchCap:0, // Zoom pinch limit + zoomStep:0, // Zoom power step + + shiftHoldDistance:0, // Shift-mode maximum hold distance (pixels) + shiftWaitTime:0, // Shift-mode hold time (milliseconds) + vibrateTime:0, // Shift-mode vibrate time (milliseconds) + + ibl:false, + webgl2:false, + + imageURL:"", + image:"", + + // Transformation matrix T[4][4] that maps back to user + // coordinates, with T[i][j] stored as element 4*i+j. + Transform:[], + + Centers:[] // Array of billboard centers +} + +let W=document.asy; -let canvas; // Rendering canvas let gl; // WebGL rendering context let alpha; // Is background opaque? - +let embedded; // Is image embedded within another window? +let canvas; // Rendering canvas let offscreen; // Offscreen rendering canvas for embedded images let context; // 2D context for copying embedded offscreen images +let P=[]; // Array of Bezier patches, triangles, curves, and pixels +let Lights=[]; // Array of lights +let Materials=[]; // Array of materials + 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 +// Initial values: +let canvasWidth0; +let canvasHeight0; +let zoom0; + let halfCanvasWidth,halfCanvasHeight; const pixelResolution=0.75; // Adaptive rendering constant. @@ -57,6 +74,7 @@ const windowTrim=10; const third=1/3; const pi=Math.acos(-1.0); const radians=pi/180.0; +const maxDepth=Math.ceil(1-Math.log2(Number.EPSILON)); let Zoom; let lastZoom; @@ -116,9 +134,9 @@ function IBLReady() function SetIBL() { - if(!embedded) + if(!W.embedded) deleteShaders(); - initShaders(ibl); + initShaders(W.ibl); } let roughnessStepCount=8; @@ -196,7 +214,7 @@ function deleteShaders() function saveAttributes() { - let a=webgl2 ? + let a=W.webgl2 ? window.top.document.asygl2[alpha] : window.top.document.asygl[alpha]; @@ -213,7 +231,7 @@ function saveAttributes() function restoreAttributes() { - let a=webgl2 ? + let a=W.webgl2 ? window.top.document.asygl2[alpha] : window.top.document.asygl[alpha]; @@ -232,18 +250,18 @@ let indexExt; function webGL(canvas,alpha) { let gl; - if(webgl2) { + if(W.webgl2) { gl=canvas.getContext("webgl2",{alpha: alpha}); - if(embedded && !gl) { - webgl2=false; - ibl=false; + if(W.embedded && !gl) { + W.webgl2=false; + W.ibl=false; initGL(false); // Look for an existing webgl context return null; // Skip remainder of parent call } } if(!gl) { - webgl2=false; - ibl=false; + W.webgl2=false; + W.ibl=false; gl=canvas.getContext("webgl",{alpha: alpha}); } if(!gl) @@ -253,24 +271,24 @@ function webGL(canvas,alpha) { function initGL(outer=true) { - if(ibl) webgl2=true; + if(W.ibl) W.webgl2=true; - alpha=Background[3] < 1; + alpha=W.background[3] < 1; - if(embedded) { + if(W.embedded) { let p=window.top.document; - if(outer) context=canvas.getContext("2d"); - offscreen=webgl2 ? p.offscreen2 : p.offscreen; + if(outer) context=W.canvas.getContext("2d"); + offscreen=W.webgl2 ? p.offscreen2 : p.offscreen; if(!offscreen) { offscreen=p.createElement("canvas"); - if(webgl2) + if(W.webgl2) p.offscreen2=offscreen; else p.offscreen=offscreen; } - if(webgl2) { + if(W.webgl2) { if(!p.asygl2) p.asygl2=Array(2); } else { @@ -278,14 +296,14 @@ function initGL(outer=true) p.asygl=Array(2); } - asygl=webgl2 ? p.asygl2 : p.asygl; + asygl=W.webgl2 ? p.asygl2 : p.asygl; if(!asygl[alpha] || !asygl[alpha].gl) { rc=webGL(offscreen,alpha); if(rc) gl=rc; else return; initShaders(); - if(webgl2) + if(W.webgl2) p.asygl2[alpha]={}; else p.asygl[alpha]={}; @@ -299,7 +317,7 @@ function initGL(outer=true) } } } else { - gl=webGL(canvas,alpha); + gl=webGL(W.canvas,alpha); initShaders(); } @@ -316,7 +334,7 @@ function initGL(outer=true) function getShader(gl,shaderScript,type,options=[]) { - let version=webgl2 ? '300 es' : '100'; + let version=W.webgl2 ? '300 es' : '100'; let defines=Array(...options) let macros=[ ['nlights',wireframe == 0 ? Lights.length : 0], @@ -337,13 +355,13 @@ precision mediump float; let extensions=[]; - if(webgl2) + if(W.webgl2) defines.push('WEBGL2'); - if(ibl) + if(W.ibl) macros.push(['ROUGHNESS_STEP_COUNT',roughnessStepCount.toFixed(2)]); - if(orthographic) + if(W.orthographic) defines.push('ORTHOGRAPHIC'); macros_str=macros.map(macro => `#define ${macro[0]} ${macro[1]}`).join('\n') @@ -422,9 +440,9 @@ function drawBuffer(data,shader,indices=data.indices) gl.vertexAttribPointer(materialAttribute,1,gl.SHORT,false,2,0); if(shader == colorShader || shader == transparentShader) { - data.colorsBuffer=registerBuffer(new Uint8Array(data.colors), + data.colorsBuffer=registerBuffer(new Float32Array(data.colors), data.colorsBuffer,copy); - gl.vertexAttribPointer(colorAttribute,4,gl.UNSIGNED_BYTE,true,0,0); + gl.vertexAttribPointer(colorAttribute,4,gl.FLOAT,true,0,0); } data.indicesBuffer=registerBuffer(indexExt ? new Uint32Array(indices) : @@ -633,7 +651,7 @@ class Geometry { if(this.CenterIndex == 0) v=corners(this.Min,this.Max); else { - this.c=Centers[this.CenterIndex-1]; + this.c=W.Centers[this.CenterIndex-1]; v=this.Tcorners(this.Min,this.Max); } @@ -659,7 +677,7 @@ class Geometry { P[i]=this.T(p[i]); } - let s=orthographic ? 1 : this.Min[2]/maxBound[2]; + let s=W.orthographic ? 1 : this.Min[2]/W.maxBound[2]; let res=pixelResolution* Math.hypot(s*(viewParam.xmax-viewParam.xmin), s*(viewParam.ymax-viewParam.ymin))/size2; @@ -673,23 +691,28 @@ class Geometry { } } +function boundPoints(p,m) +{ + let b=p[0]; + let n=p.length; + for(let i=1; i < n; ++i) + b=m(b,p[i]); + return b; +} + 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) { + constructor(controlpoints,CenterIndex,MaterialIndex,color,Min,Max) { super(); this.controlpoints=controlpoints; this.CenterIndex=CenterIndex; this.MaterialIndex=MaterialIndex; - this.Min=Min; - this.Max=Max; this.color=color; let n=controlpoints.length; if(color) { @@ -701,7 +724,13 @@ class BezierPatch extends Geometry { this.vertex=this.transparent ? this.data.Vertex.bind(this.data) : this.data.vertex.bind(this.data); - this.L2norm(this.controlpoints); + + let norm2=this.L2norm2(this.controlpoints); + let fuzz=Math.sqrt(1000*Number.EPSILON*norm2); + this.epsilon=norm2*Number.EPSILON; + + this.Min=Min ? Min : this.Bounds(this.controlpoints,Math.min,fuzz); + this.Max=Max ? Max : this.Bounds(this.controlpoints,Math.max,fuzz); } setMaterialIndex() { @@ -715,15 +744,128 @@ class BezierPatch extends Geometry { } } + cornerbound(p,m) { + let b=m(p[0],p[3]); + b=m(b,p[12]); + return m(b,p[15]); + } + + controlbound(p,m) { + let b=m(p[1],p[2]); + b=m(b,p[4]); + b=m(b,p[5]); + b=m(b,p[6]); + b=m(b,p[7]); + b=m(b,p[8]); + b=m(b,p[9]); + b=m(b,p[10]); + b=m(b,p[11]); + b=m(b,p[13]); + return m(b,p[14]); + } + + bound(p,m,b,fuzz,depth) { + b=m(b,this.cornerbound(p,m)); + if(m(-1.0,1.0)*(b-this.controlbound(p,m)) >= -fuzz || depth == 0) + return b; + + --depth; + fuzz *= 2; + + let c0=new Split(p[0],p[1],p[2],p[3]); + let c1=new Split(p[4],p[5],p[6],p[7]); + let c2=new Split(p[8],p[9],p[10],p[11]); + let c3=new Split(p[12],p[13],p[14],p[15]); + + let c4=new Split(p[0],p[4],p[8],p[12]); + let c5=new Split(c0.m0,c1.m0,c2.m0,c3.m0); + let c6=new Split(c0.m3,c1.m3,c2.m3,c3.m3); + let c7=new Split(c0.m5,c1.m5,c2.m5,c3.m5); + let c8=new Split(c0.m4,c1.m4,c2.m4,c3.m4); + let c9=new Split(c0.m2,c1.m2,c2.m2,c3.m2); + let c10=new Split(p[3],p[7],p[11],p[15]); + + // Check all 4 Bezier subpatches. + let s0=[p[0],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]; + b=this.bound(s0,m,b,fuzz,depth); + 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,p[12],c3.m0,c3.m3,c3.m5]; + b=this.bound(s1,m,b,fuzz,depth); + 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,p[15]]; + b=this.bound(s2,m,b,fuzz,depth); + let s3=[c0.m5,c0.m4,c0.m2,p[3],c7.m0,c8.m0,c9.m0,c10.m0, + c7.m3,c8.m3,c9.m3,c10.m3,c7.m5,c8.m5,c9.m5,c10.m5]; + return this.bound(s3,m,b,fuzz,depth); + } + + cornerboundtri(p,m) { + let b=m(p[0],p[6]); + return m(b,p[9]); + } + + controlboundtri(p,m) { + let b=m(p[1],p[2]); + b=m(b,p[3]); + b=m(b,p[4]); + b=m(b,p[5]); + b=m(b,p[7]); + return m(b,p[8]); + } + + boundtri(p,m,b,fuzz,depth) { + b=m(b,this.cornerboundtri(p,m)); + if(m(-1.0,1.0)*(b-this.controlboundtri(p,m)) >= -fuzz || depth == 0) + return b; + + --depth; + fuzz *= 2; + + let s=new Splittri(p); + + let l=[s.l003,s.l102,s.l012,s.l201,s.l111, + s.l021,s.l300,s.l210,s.l120,s.l030]; // left + b=this.boundtri(l,m,b,fuzz,depth); + + let r=[s.l300,s.r102,s.r012,s.r201,s.r111, + s.r021,s.r300,s.r210,s.r120,s.r030]; // right + b=this.boundtri(r,m,b,fuzz,depth); + + let u=[s.l030,s.u102,s.u012,s.u201,s.u111, + s.u021,s.r030,s.u210,s.u120,s.u030]; // up + b=this.boundtri(u,m,b,fuzz,depth); + + let c=[s.r030,s.u201,s.r021,s.u102,s.c111, + s.r012,s.l030,s.l120,s.l210,s.l300]; // center + return this.boundtri(c,m,b,fuzz,depth); + } + + Bounds(p,m,fuzz) { + let b=Array(3); + let n=p.length; + let x=Array(n); + for(let i=0; i < 3; ++i) { + for(let j=0; j < n; ++j) + x[j]=p[j][i]; + if(n == 16) + b[i]=this.bound(x,m,x[0],fuzz,maxDepth) + else if(n == 10) + b[i]=this.boundtri(x,m,x[0],fuzz,maxDepth); + else + b[i]=boundPoints(x,m); + } + return [b[0],b[1],b[2]]; + } + // Render a Bezier patch via subdivision. - L2norm(p) { + L2norm2(p) { let p0=p[0]; - this.epsilon=0; + let norm2=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 + norm2=Math.max(norm2,abs2([p[i][0]-p0[0],p[i][1]-p0[1],p[i][2]-p0[2]])); + return norm2; } processTriangle(p) { @@ -1776,14 +1918,128 @@ class BezierPatch extends Geometry { } } +// Calculate the coefficients of a Bezier derivative divided by 3. +function derivative(z0,c0,c1,z1) +{ + let a=z1-z0+3.0*(c0-c1); + let b=2.0*(z0+c1)-4.0*c0; + let c=c0-z0; + return [a,b,c]; +} + +function goodroot(t) +{ + return 0.0 <= t && t <= 1.0; +} + +// Accurate computation of sqrt(1+x)-1. +function sqrt1pxm1(x) +{ + return x/(Math.sqrt(1.0+x)+1.0); +} + +// Solve for the real roots of the quadratic equation ax^2+bx+c=0. +class quadraticroots { + constructor(a,b,c) { + const Fuzz2=1000*Number.EPSILON; + const Fuzz4=Fuzz2*Fuzz2; + + // Remove roots at numerical infinity. + if(Math.abs(a) <= Fuzz2*Math.abs(b)+Fuzz4*Math.abs(c)) { + if(Math.abs(b) > Fuzz2*Math.abs(c)) { + this.roots=1; + this.t1=-c/b; + } else if(c == 0.0) { + this.roots=1; + this.t1=0.0; + } else { + this.roots=0; + } + } else { + let factor=0.5*b/a; + let denom=b*factor; + if(Math.abs(denom) <= Fuzz2*Math.abs(c)) { + let x=-c/a; + if(x >= 0.0) { + this.roots=2; + this.t2=Math.sqrt(x); + this.t1=-this.t2; + } else + this.roots=0; + } else { + let x=-2.0*c/denom; + if(x > -1.0) { + this.roots=2; + let r2=factor*sqrt1pxm1(x); + let r1=-r2-2.0*factor; + if(r1 <= r2) { + this.t1=r1; + this.t2=r2; + } else { + this.t1=r2; + this.t2=r1; + } + } else if(x == -1.0) { + this.roots=1; + this.t1=this.t2=-factor; + } else + this.roots=0; + } + } + } +} + class BezierCurve extends Geometry { constructor(controlpoints,CenterIndex,MaterialIndex,Min,Max) { super(); this.controlpoints=controlpoints; this.CenterIndex=CenterIndex; this.MaterialIndex=MaterialIndex; - this.Min=Min; - this.Max=Max; + + if(Min && Max) { + this.Min=Min; + this.Max=Max; + } else { + let b=this.Bounds(this.controlpoints); + this.Min=b[0]; + this.Max=b[1]; + } + } + + Bounds(p) { + let b=Array(3); + let B=Array(3); + let n=p.length; + let x=Array(n); + for(let i=0; i < 3; ++i) { + for(let j=0; j < n; ++j) + x[j]=p[j][i]; + let m,M; + m=M=x[0]; + if(n == 4) { + m=Math.min(m,x[3]); + M=Math.max(M,x[3]); + let a=derivative(x[0],x[1],x[2],x[3]); + let q=new quadraticroots(a[0],a[1],a[2]); + if(q.roots != 0 && goodroot(q.t1)) { + let v=bezier(x[0],x[1],x[2],x[3],q.t1); + m=Math.min(m,v); + M=Math.max(M,v); + } + if(q.roots == 2 && goodroot(q.t2)) { + let v=bezier(x[0],x[1],x[2],x[3],q.t2); + m=Math.min(m,v); + M=Math.max(M,v); + } + } else { + let v=x[1]; + m=Math.min(m,v); + M=Math.max(M,v); + } + b[i]=m; + B[i]=M; + } + return [[b[0],b[1],b[2]],[B[0],B[1],B[2]]]; } setMaterialIndex() { @@ -1869,14 +2125,14 @@ class BezierCurve extends Geometry { } class Pixel extends Geometry { - constructor(controlpoint,width,MaterialIndex,Min,Max) { + constructor(controlpoint,width,MaterialIndex) { super(); this.controlpoint=controlpoint; this.width=width; this.CenterIndex=0; this.MaterialIndex=MaterialIndex; - this.Min=Min; - this.Max=Max; + this.Min=controlpoint; + this.Max=controlpoint; } setMaterialIndex() { @@ -1898,12 +2154,12 @@ class Pixel extends Geometry { } class Triangles extends Geometry { - constructor(CenterIndex,MaterialIndex,Min,Max) { + constructor(CenterIndex,MaterialIndex) { super(); this.CenterIndex=CenterIndex; this.MaterialIndex=MaterialIndex; - this.Min=Min; - this.Max=Max; + this.Min=this.Bounds(Positions,Math.min); + this.Max=this.Bounds(Positions,Math.max); this.controlpoints=Positions; this.Normals=Normals; @@ -1916,6 +2172,18 @@ class Triangles extends Geometry { this.transparent=Materials[this.MaterialIndex].diffuse[3] < 1; } + Bounds(p,m) { + let b=Array(3); + let n=p.length; + let x=Array(n); + for(let i=0; i < 3; ++i) { + for(let j=0; j < n; ++j) + x[j]=p[j][i]; + b[i]=boundPoints(x,m); + } + return [b[0],b[1],b[2]]; + } + setMaterialIndex() { if(this.transparent) this.setMaterial(transparentData,drawTransparent); @@ -2036,6 +2304,17 @@ function initShader(options=[]) return shader; } +class Split { + constructor(z0,c0,c1,z1) { + this.m0=0.5*(z0+c0); + let m1=0.5*(c0+c1); + this.m2=0.5*(c1+z1); + this.m3=0.5*(this.m0+m1); + this.m4=0.5*(m1+this.m2); + this.m5=0.5*(this.m3+this.m4); + } +} + 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])]; @@ -2052,6 +2331,65 @@ class Split3 { } } +class Splittri { + constructor(p) { + this.l003=p[0]; + let p102=p[1]; + let p012=p[2]; + let p201=p[3]; + let p111=p[4]; + let p021=p[5]; + this.r300=p[6]; + let p210=p[7]; + let p120=p[8]; + this.u030=p[9]; + + this.u021=0.5*(this.u030+p021); + this.u120=0.5*(this.u030+p120); + + let p033=0.5*(p021+p012); + let p231=0.5*(p120+p111); + let p330=0.5*(p120+p210); + + let p123=0.5*(p012+p111); + + this.l012=0.5*(p012+this.l003); + let p312=0.5*(p111+p201); + this.r210=0.5*(p210+this.r300); + + this.l102=0.5*(this.l003+p102); + let p303=0.5*(p102+p201); + this.r201=0.5*(p201+this.r300); + + this.u012=0.5*(this.u021+p033); + this.u210=0.5*(this.u120+p330); + this.l021=0.5*(p033+this.l012); + let p4xx=0.5*p231+0.25*(p111+p102); + this.r120=0.5*(p330+this.r210); + let px4x=0.5*p123+0.25*(p111+p210); + let pxx4=0.25*(p021+p111)+0.5*p312; + this.l201=0.5*(this.l102+p303); + this.r102=0.5*(p303+this.r201); + + this.l210=0.5*(px4x+this.l201); // = m120 + this.r012=0.5*(px4x+this.r102); // = m021 + this.l300=0.5*(this.l201+this.r102); // = r003 = m030 + + this.r021=0.5*(pxx4+this.r120); // = m012 + this.u201=0.5*(this.u210+pxx4); // = m102 + this.r030=0.5*(this.u210+this.r120); // = u300 = m003 + + this.u102=0.5*(this.u012+p4xx); // = m201 + this.l120=0.5*(this.l021+p4xx); // = m210 + this.l030=0.5*(this.u012+this.l021); // = u003 = m300 + + this.l111=0.5*(p123+this.l102); + this.r111=0.5*(p312+this.r210); + this.u111=0.5*(this.u021+p231); + this.c111=0.25*(p033+p330+p303+p111); + } +} + function unit(v) { let norm=1/(Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) || 1); @@ -2075,6 +2413,14 @@ function cross(u,v) u[0]*v[1]-u[1]*v[0]]; } +// Evaluate the Bezier curve defined by a,b,c,d at t. +function bezier(a,b,c,d,t) +{ + let onemt=1-t; + let onemt2=onemt*onemt; + return onemt2*onemt*a+t*(3.0*(onemt2*b+t*onemt*c)+t*t*d); +} + // Return one-third of the first derivative of the Bezier curve defined // by a,b,c,d at t=0. function bezierP(a,b) @@ -2283,7 +2629,7 @@ function shiftScene(lastX,lastY,rawX,rawY) function panScene(lastX,lastY,rawX,rawY) { - if(orthographic) { + if(W.orthographic) { shiftScene(lastX,lastY,rawX,rawY); } else { center.x += (rawX-lastX)*(viewParam.xmax-viewParam.xmin); @@ -2315,11 +2661,11 @@ function capzoom() function zoomImage(diff) { - let stepPower=zoomStep*halfCanvasHeight*diff; - const limit=Math.log(0.1*Number.MAX_VALUE)/Math.log(zoomFactor); + let stepPower=W.zoomStep*halfCanvasHeight*diff; + const limit=Math.log(0.1*Number.MAX_VALUE)/Math.log(W.zoomFactor); if(Math.abs(stepPower) < limit) { - Zoom *= zoomFactor**stepPower; + Zoom *= W.zoomFactor**stepPower; capzoom(); } } @@ -2403,13 +2749,13 @@ let zoomEnabled=0; function enableZoom() { zoomEnabled=1; - canvas.addEventListener("wheel",handleMouseWheel,false); + W.canvas.addEventListener("wheel",handleMouseWheel,false); } function disableZoom() { zoomEnabled=0; - canvas.removeEventListener("wheel",handleMouseWheel,false); + W.canvas.removeEventListener("wheel",handleMouseWheel,false); } function Camera() @@ -2431,7 +2777,7 @@ function Camera() let R1=rotMat[j4+1]; let R2=rotMat[j4+2]; let R3=rotMat[j4+3]; - let T4ij=Transform[i4+j]; + let T4ij=W.Transform[i4+j]; sumCamera += T4ij*(R3-cx*R0-cy*R1-cz*R2); sumUp += T4ij*R1; sumTarget += T4ij*(R3-cx*R0-cy*R1); @@ -2445,30 +2791,28 @@ function Camera() function projection() { - if(Transform == null) return ""; - let camera,up,target; [camera,up,target]=Camera(); - let projection=orthographic ? " orthographic(" : " perspective("; + let projection=W.orthographic ? " orthographic(" : " perspective("; let indent="".padStart(projection.length); let currentprojection="currentprojection="+"\n"+ projection+"camera=("+camera+"),\n"+ indent+"up=("+up+"),"+"\n"+ indent+"target=("+target+"),"+"\n"+ - indent+"zoom="+Zoom*initialZoom/zoom0; + indent+"zoom="+Zoom*W.initialZoom/W.zoom0; - if(!orthographic) + if(!W.orthographic) currentprojection += ","+"\n" +indent+"angle="+ - 2.0*Math.atan(Math.tan(0.5*angleOfView)/Zoom)/radians; + 2.0*Math.atan(Math.tan(0.5*W.angleOfView)/Zoom)/radians; if(xshift != 0 || yshift != 0) currentprojection += ","+"\n"+ indent+"viewportshift=("+xshift+","+yshift+")"; - if(!orthographic) + if(!W.orthographic) currentprojection += ","+"\n"+ indent+"autoadjust=false"; @@ -2485,7 +2829,7 @@ function handleKey(event) if(!zoomEnabled) enableZoom(); - if(embedded && zoomEnabled && event.keyCode == ESC) { + if(W.embedded && zoomEnabled && event.keyCode == ESC) { disableZoom(); return; } @@ -2510,9 +2854,9 @@ function handleKey(event) ++wireframe; if(wireframe == 3) wireframe=0; if(wireframe != 2) { - if(!embedded) + if(!W.embedded) deleteShaders(); - initShaders(ibl); + initShaders(W.ibl); } remesh=true; drawScene(); @@ -2553,9 +2897,9 @@ function handleMouseWheel(event) event.preventDefault(); if(event.deltaY < 0) { - Zoom *= zoomFactor; + Zoom *= W.zoomFactor; } else { - Zoom /= zoomFactor; + Zoom /= W.zoomFactor; } setZoom(); @@ -2599,12 +2943,12 @@ function handleTouchMove(event) let newY=touches[0].pageY; let dx=newX-lastMouseX; let dy=newY-lastMouseY; - let stationary=dx*dx+dy*dy <= shiftHoldDistance*shiftHoldDistance; + let stationary=dx*dx+dy*dy <= W.shiftHoldDistance*W.shiftHoldDistance; if(stationary) { if(!swipe && !rotate && - new Date().getTime()-touchStartTime > shiftWaitTime) { + new Date().getTime()-touchStartTime > W.shiftWaitTime) { if(navigator.vibrate) - window.navigator.vibrate(vibrateTime); + window.navigator.vibrate(W.vibrateTime); swipe=true; } } @@ -2623,9 +2967,9 @@ function handleTouchMove(event) let distance=pinchDistance(touches); let diff=distance-pinchStart; zooming=true; - diff *= zoomPinchFactor; - if(diff > zoomPinchCap) diff=zoomPinchCap; - if(diff < -zoomPinchCap) diff=-zoomPinchCap; + diff *= W.zoomPinchFactor; + if(diff > W.zoomPinchCap) diff=W.zoomPinchCap; + if(diff < -W.zoomPinchCap) diff=-W.zoomPinchCap; zoomImage(diff/size2); pinchStart=distance; swipe=rotate=zooming=false; @@ -2740,22 +3084,22 @@ function drawBuffers() function drawScene() { - if(embedded) { - offscreen.width=canvasWidth; - offscreen.height=canvasHeight; + if(W.embedded) { + offscreen.width=W.canvasWidth; + offscreen.height=W.canvasHeight; setViewport(); } - gl.clearColor(Background[0],Background[1],Background[2],Background[3]); + gl.clearColor(W.background[0],W.background[1],W.background[2],W.background[3]); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - for(let i=0; i < P.length; ++i) - P[i].render(); + for(const p of P) + p.render(); drawBuffers(); - if(embedded) { - context.clearRect(0,0,canvasWidth,canvasHeight); + if(W.embedded) { + context.clearRect(0,0,W.canvasWidth,W.canvasHeight); context.drawImage(offscreen,0,0); } @@ -2765,26 +3109,26 @@ function drawScene() function setDimensions(width,height,X,Y) { let Aspect=width/height; - xshift=(X/width+viewportShift[0])*Zoom; - yshift=(Y/height+viewportShift[1])*Zoom; + xshift=(X/width+W.viewportShift[0])*Zoom; + yshift=(Y/height+W.viewportShift[1])*Zoom; let Zoominv=1/Zoom; - if(orthographic) { - let xsize=maxBound[0]-minBound[0]; - let ysize=maxBound[1]-minBound[1]; + if(W.orthographic) { + let xsize=W.maxBound[0]-W.minBound[0]; + let ysize=W.maxBound[1]-W.minBound[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=minBound[1]*Zoominv-Y0; - viewParam.ymax=maxBound[1]*Zoominv-Y0; + viewParam.ymin=W.minBound[1]*Zoominv-Y0; + viewParam.ymax=W.maxBound[1]*Zoominv-Y0; } else { let r=0.5*xsize*Zoominv/Aspect; let X0=xsize*Zoominv*xshift; let Y0=2*r*yshift; - viewParam.xmin=minBound[0]*Zoominv-X0; - viewParam.xmax=maxBound[0]*Zoominv-X0; + viewParam.xmin=W.minBound[0]*Zoominv-X0; + viewParam.xmax=W.maxBound[0]*Zoominv-X0; viewParam.ymin=-r-Y0; viewParam.ymax=r-Y0; } @@ -2802,8 +3146,8 @@ function setDimensions(width,height,X,Y) function setProjection() { - setDimensions(canvasWidth,canvasHeight,shift.x,shift.y); - let f=orthographic ? mat4.ortho : mat4.frustum; + setDimensions(W.canvasWidth,W.canvasHeight,shift.x,shift.y); + let f=W.orthographic ? mat4.ortho : mat4.frustum; f(projMat,viewParam.xmin,viewParam.xmax, viewParam.ymin,viewParam.ymax, -viewParam.zmax,-viewParam.zmin); @@ -2822,37 +3166,37 @@ function showCamera() function initProjection() { - H=-Math.tan(0.5*angleOfView)*maxBound[2]; + H=-Math.tan(0.5*W.angleOfView)*W.maxBound[2]; center.x=center.y=0; - center.z=0.5*(minBound[2]+maxBound[2]); - lastZoom=Zoom=zoom0; + center.z=0.5*(W.minBound[2]+W.maxBound[2]); + lastZoom=Zoom=W.zoom0; - viewParam.zmin=minBound[2]; - viewParam.zmax=maxBound[2]; + viewParam.zmin=W.minBound[2]; + viewParam.zmax=W.maxBound[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); + gl.viewportWidth=W.canvasWidth; + gl.viewportHeight=W.canvasHeight; + gl.viewport(0.5*(W.canvas.width-W.canvasWidth),0.5*(W.canvas.height-W.canvasHeight), + W.canvasWidth,W.canvasHeight); + gl.scissor(0,0,W.canvas.width,W.canvas.height); } function setCanvas() { - if(embedded) { - canvas.width=offscreen.width=canvasWidth; - canvas.height=offscreen.height=canvasHeight; + if(W.embedded) { + W.canvas.width=offscreen.width=W.canvasWidth; + W.canvas.height=offscreen.height=W.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; + size2=Math.hypot(W.canvasWidth,W.canvasHeight); + halfCanvasWidth=0.5*W.canvas.width; + halfCanvasHeight=0.5*W.canvas.height; + ArcballFactor=1+8*Math.hypot(W.viewportMargin[0],W.viewportMargin[1])/size2; } function setsize(w,h) @@ -2863,11 +3207,11 @@ function setsize(w,h) if(h > maxViewportHeight) h=maxViewportHeight; - shift.x *= w/canvasWidth; - shift.y *= h/canvasHeight; + shift.x *= w/W.canvasWidth; + shift.y *= h/W.canvasHeight; - canvasWidth=w; - canvasHeight=h; + W.canvasWidth=w; + W.canvasHeight=h; setCanvas(); setViewport(); @@ -2877,48 +3221,48 @@ function setsize(w,h) function resize() { - zoom0=initialZoom; + W.zoom0=W.initialZoom; if(window.top.asyWebApplication && window.top.asyWebApplication.getProjection() == "") window.parent.asyProjection=false; - if(absolute && !embedded) { - canvasWidth=canvasWidth0*window.devicePixelRatio; - canvasHeight=canvasHeight0*window.devicePixelRatio; + if(W.absolute && !W.embedded) { + W.canvasWidth=W.canvasWith0*window.devicePixelRatio; + W.canvasHeight=W.canvasHeight0*window.devicePixelRatio; } else { - let Aspect=canvasWidth0/canvasHeight0; - canvasWidth=Math.max(window.innerWidth-windowTrim,windowTrim); - canvasHeight=Math.max(window.innerHeight-windowTrim,windowTrim); + let Aspect=W.canvasWith0/W.canvasHeight0; + W.canvasWidth=Math.max(window.innerWidth-windowTrim,windowTrim); + W.canvasHeight=Math.max(window.innerHeight-windowTrim,windowTrim); - if(!orthographic && !window.parent.asyProjection && - canvasWidth < canvasHeight*Aspect) - zoom0 *= canvasWidth/(canvasHeight*Aspect); + if(!W.orthographic && !window.parent.asyProjection && + W.canvasWidth < W.canvasHeight*Aspect) + W.zoom0 *= W.canvasWidth/(W.canvasHeight*Aspect); } - canvas.width=canvasWidth; - canvas.height=canvasHeight; + W.canvas.width=W.canvasWidth; + W.canvas.height=W.canvasHeight; let maxViewportWidth=window.innerWidth; let maxViewportHeight=window.innerHeight; - let Zoominv=1/zoom0; - viewportShift[0] *= Zoominv; - viewportShift[1] *= Zoominv; + let Zoominv=1/W.zoom0; + W.viewportShift[0] *= Zoominv; + W.viewportShift[1] *= Zoominv; - setsize(canvasWidth,canvasHeight); + setsize(W.canvasWidth,W.canvasHeight); redrawScene(); } function expand() { - Zoom *= zoomFactor; + Zoom *= W.zoomFactor; setZoom(); } function shrink() { - Zoom /= zoomFactor; + Zoom /= W.zoomFactor; setZoom(); } @@ -2961,31 +3305,35 @@ function Tcorners(T,m,M) return [minbound(v),maxbound(v)]; } +function light(direction,color) +{ + Lights.push(new Light(direction,color)); +} + function material(diffuse,emissive,specular,shininess,metallic,fresnel0) { Materials.push(new Material(diffuse,emissive,specular,shininess,metallic, fresnel0)); } -function patch(controlpoints,CenterIndex,MaterialIndex,Min,Max,color) +function patch(controlpoints,CenterIndex,MaterialIndex,color) { - P.push(new BezierPatch(controlpoints,CenterIndex,MaterialIndex,Min,Max, - color)); + P.push(new BezierPatch(controlpoints,CenterIndex,MaterialIndex,color)); } -function curve(controlpoints,CenterIndex,MaterialIndex,Min,Max) +function curve(controlpoints,CenterIndex,MaterialIndex) { - P.push(new BezierCurve(controlpoints,CenterIndex,MaterialIndex,Min,Max)); + P.push(new BezierCurve(controlpoints,CenterIndex,MaterialIndex)); } -function pixel(controlpoint,width,MaterialIndex,Min,Max) +function pixel(controlpoint,width,MaterialIndex) { - P.push(new Pixel(controlpoint,width,MaterialIndex,Min,Max)); + P.push(new Pixel(controlpoint,width,MaterialIndex)); } -function triangles(CenterIndex,MaterialIndex,Min,Max) +function triangles(CenterIndex,MaterialIndex) { - P.push(new Triangles(CenterIndex,MaterialIndex,Min,Max)); + P.push(new Triangles(CenterIndex,MaterialIndex)); } // draw a sphere of radius r about center @@ -3066,7 +3414,7 @@ function sphere(center,r,CenterIndex,MaterialIndex,dir) 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, + P.push(new BezierPatch(T(octant[m]),CenterIndex,MaterialIndex,null, Min,Max)); } } @@ -3114,7 +3462,8 @@ function disk(center,r,CenterIndex,MaterialIndex,dir) } 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])); + P.push(new BezierPatch(T(unitdisk),CenterIndex,MaterialIndex,null, + v[0],v[1])); } // draw a cylinder with circular base of radius r about center and height h @@ -3162,7 +3511,7 @@ function cylinder(center,r,h,CenterIndex,MaterialIndex,dir,core) rx=i*r; for(let j=-1; j <= 1; j += 2) { ry=j*r; - P.push(new BezierPatch(T(unitcylinder),CenterIndex,MaterialIndex, + P.push(new BezierPatch(T(unitcylinder),CenterIndex,MaterialIndex,null, Min,Max)); } } @@ -3286,7 +3635,7 @@ function rmf(z0,c0,c1,z1,t) } // draw a tube of width w using control points v -function tube(v,w,CenterIndex,MaterialIndex,Min,Max,core) +function tube(v,w,CenterIndex,MaterialIndex,core) { let Rmf=rmf(v[0],v[1],v[2],v[3],[0,1/3,2/3,1]); @@ -3320,7 +3669,7 @@ function tube(v,w,CenterIndex,MaterialIndex,Min,Max,core) T8*x+T9*y+w2]; } } - P.push(new BezierPatch(s,CenterIndex,MaterialIndex,Min,Max)); + P.push(new BezierPatch(s,CenterIndex,MaterialIndex)); } f(1,0,0,1); @@ -3329,7 +3678,7 @@ function tube(v,w,CenterIndex,MaterialIndex,Min,Max,core) f(0,1,-1,0); if(core) - P.push(new BezierCurve(v,CenterIndex,MaterialIndex,Min,Max)); + P.push(new BezierCurve(v,CenterIndex,MaterialIndex)); } async function getReq(req) @@ -3358,7 +3707,7 @@ function createTexture(image, textureNumber, fmt=gl.RGB16F) async function initIBL() { - let imagePath=imageURL+image+'/'; + let imagePath=W.imageURL+W.image+'/'; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -3371,7 +3720,7 @@ async function initIBL() } promises=[ - getReq(imageURL+'refl.exr').then(obj => { + getReq(W.imageURL+'refl.exr').then(obj => { let img=new Module.EXRLoader(obj); IBLbdrfMap=createTexture(img,0); }), @@ -3415,8 +3764,8 @@ async function initIBL() function webGLStart() { - canvas=document.getElementById("Asymptote"); - embedded=window.top.document != document; + W.canvas=document.getElementById("Asymptote"); + W.embedded=window.top.document != document; initGL(); @@ -3425,22 +3774,22 @@ function webGLStart() gl.enable(gl.DEPTH_TEST); gl.enable(gl.SCISSOR_TEST); - canvas.onmousedown=handleMouseDown; + W.canvas.onmousedown=handleMouseDown; document.onmouseup=handleMouseUpOrTouchEnd; document.onmousemove=handleMouseMove; - canvas.onkeydown=handleKey; + W.canvas.onkeydown=handleKey; - if(!embedded) + if(!W.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); + W.canvas.addEventListener("touchstart",handleTouchStart,false); + W.canvas.addEventListener("touchend",handleMouseUpOrTouchEnd,false); + W.canvas.addEventListener("touchcancel",handleMouseUpOrTouchEnd,false); + W.canvas.addEventListener("touchleave",handleMouseUpOrTouchEnd,false); + W.canvas.addEventListener("touchmove",handleTouchMove,false); document.addEventListener("keydown",handleKey,false); - canvasWidth0=canvasWidth; - canvasHeight0=canvasHeight; + W.canvasWith0=W.canvasWidth; + W.canvasHeight0=W.canvasHeight; mat4.identity(rotMat); @@ -3449,6 +3798,20 @@ function webGLStart() window.addEventListener("resize",resize,false); - if(ibl) + if(W.ibl) initIBL().then(SetIBL).then(redrawScene); } + + window['webGLStart']=webGLStart; + window['light']=light; + window['material']=material; + window['patch']=patch; + window['curve']=curve; + window['pixel']=pixel; + window['triangles']=triangles; + window['sphere']=sphere; + window['disk']=disk; + window['cylinder']=cylinder; + window['tube']=tube; + +})(); |