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 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 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 Temp=mat4.create();
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=[];
let IBLReflMap=null;
let IBLDiffuseMap=null;
let IBLbdrfMap=null;
function initShaders(ibl=false)
{
let maxUniforms=gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
maxMaterials=Math.floor((maxUniforms-14)/4);
Nmaterials=Math.min(Math.max(Nmaterials,Materials.length),maxMaterials);
data.indicesBuffer=registerBuffer(indexExt ? new Uint32Array(indices) :
new Uint16Array(indices),
data.indicesBuffer,copy,
gl.ELEMENT_ARRAY_BUFFER);
data.rendered=true;
// 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++;
}
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];
}
// First check if re-rendering is required
let v;
if(this.CenterIndex == 0)
v=corners(this.Min,this.Max);
else {
this.c=W.Centers[this.CenterIndex-1];
v=this.Tcorners(this.Min,this.Max);
}
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]);
}
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.
L2norm2(p) {
let p0=p[0];
let norm2=0;
let n=p.length;
for(let i=1; i < n; ++i)
norm2=Math.max(norm2,abs2([p[i][0]-p0[0],p[i][1]-p0[1],p[i][2]-p0[2]]));
return norm2;
}
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);
}
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);
}
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(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);
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 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 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 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);
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);
// 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(h,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;
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);
// 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;
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();
}
}
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]];
}
}
this.data.vertices=new Array(6*p.length);
// 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=p[PI[0]];
let P1=p[PI[1]];
let P2=p[PI[2]];
let onscreen=!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] < 3;
if(wireframe == 0) {
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen,C0);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen,C1);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen,C2);
} else {
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen,C0);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen,C1);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen,C1);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen,C2);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen,C2);
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen,C0);
}
} else {
if(wireframe == 0) {
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen);
} else {
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen);
this.data.iVertex(PI[1],P1,this.Normals[NI[1]],onscreen);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen);
this.data.iVertex(PI[2],P2,this.Normals[NI[2]],onscreen);
this.data.iVertex(PI[0],P0,this.Normals[NI[0]],onscreen);
}
}
}
this.data.nvertices=p.length;
if(this.data.indices.length > 0) this.append();
}
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();
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])];
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])];
}
}
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];
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);
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]];
}
// 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)
{
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(Temp,[center.x,center.y,center.z])
mat4.invert(cjMatInv,Temp);
mat4.multiply(out,mat,cjMatInv);
mat4.multiply(out,Temp,out);
}
function setUniforms(data,shader)
{
let pixel=shader == pixelShader;
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;
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))]
}
// 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();
drawScene();
}
let zoomEnabled=0;
function enableZoom()
{
zoomEnabled=1;
W.canvas.addEventListener("wheel",handleMouseWheel,false);
}
function disableZoom()
{
zoomEnabled=0;
W.canvas.removeEventListener("wheel",handleMouseWheel,false);
}
function Camera()
{
let vCamera=Array(3);
let vUp=Array(3);
let vTarget=Array(3);
let cx=center.x;
let cy=center.y;
let cz=0.5*(viewParam.zmin+viewParam.zmax);
for(let i=0; i < 3; ++i) {
let sumCamera=0.0, sumTarget=0.0, sumUp=0.0;
let i4=4*i;
for(let j=0; j < 4; ++j) {
let j4=4*j;
let R0=rotMat[j4];
let R1=rotMat[j4+1];
let R2=rotMat[j4+2];
let R3=rotMat[j4+3];
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);
}
vCamera[i]=sumCamera;
vUp[i]=sumUp;
vTarget[i]=sumTarget;
}
return [vCamera,vUp,vTarget];
}
function projection()
{
let camera,up,target;
[camera,up,target]=Camera();
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*W.initialZoom/W.zoom0;
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(!W.embedded)
deleteShaders();
initShaders(W.ibl);
}
remesh=true;
drawScene();
break;
case '+':
case '=':
case '>':
expand();
break;
case '-':
case '_':
case '<':
shrink();
break;
case 'c':
showCamera();
break;
default:
break;
}
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];
function setDimensions(width,height,X,Y)
{
let Aspect=width/height;
xshift=(X/width+W.viewportShift[0])*Zoom;
yshift=(Y/height+W.viewportShift[1])*Zoom;
let Zoominv=1/Zoom;
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=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=W.minBound[0]*Zoominv-X0;
viewParam.xmax=W.maxBound[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(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);
updateViewMatrix();
function showCamera()
{
if(!window.top.asyWebApplication)
prompt("Ctrl+c Enter to copy currentprojection to clipboard; then append to asy file:",
projection());
}
function initProjection()
{
H=-Math.tan(0.5*W.angleOfView)*W.maxBound[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)];
}
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,color)
{
P.push(new BezierPatch(controlpoints,CenterIndex,MaterialIndex,color));
}
function curve(controlpoints,CenterIndex,MaterialIndex)
{
P.push(new BezierCurve(controlpoints,CenterIndex,MaterialIndex));
}
function pixel(controlpoint,width,MaterialIndex)
{
P.push(new Pixel(controlpoint,width,MaterialIndex));
}
function triangles(CenterIndex,MaterialIndex)
{
P.push(new Triangles(CenterIndex,MaterialIndex));
window.Positions=Positions=[];
window.Normals=Normals=[];
window.Colors=Colors=[];
window.Indices=Indices=[];
}
// 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;
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,null,
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,null,
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];
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,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));
}