summaryrefslogtreecommitdiff
path: root/graphics/asymptote/webgl/gl.js
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/asymptote/webgl/gl.js')
-rw-r--r--graphics/asymptote/webgl/gl.js757
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;
+
+})();