Created
March 5, 2026 18:53
-
-
Save RichardPotthoff/2eb4c69891f614f293594ee1e2a110ee to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> | |
| <title>WebGL - 3D Torus</title> | |
| </head> | |
| <body></body> | |
| <script>(function(global){function cube(dx=1,dy,dz){const numVertices=24;const stride=6*4;const X=0.5*dx;const Y=dy|X;const Z=dz|X;const vertices=new Float32Array([X,-Y,-Z,1,0,0,X,Y,-Z,1,0,0,X,Y,Z,1,0,0,X,-Y,Z,1,0,0,-X,Y,Z,-1,0,0,-X,Y,-Z,-1,0,0,-X,-Y,-Z,-1,0,0,-X,-Y,Z,-1,0,0,-X,Y,-Z,0,1,0,-X,Y,Z,0,1,0,X,Y,Z,0,1,0,X,Y,-Z,0,1,0,X,-Y,Z,0,-1,0,-X,-Y,Z,0,-1,0,-X,-Y,-Z,0,-1,0,X,-Y,-Z,0,-1,0,-X,-Y,Z,0,0,1,X,-Y,Z,0,0,1,X,Y,Z,0,0,1,-X,Y,Z,0,0,1,X,Y,-Z,0,0,-1,X,-Y,-Z,0,0,-1,-X,-Y,-Z,0,0,-1,-X,Y,-Z,0,0,-1,]);const indices=new Int16Array([0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]);return{indices,vertices,stride,numVertices};} | |
| function circle(r,n){const epath=[];for(let i=0;i<n;i++){const theta=i*2*Math.PI/n;const s=Math.sin(theta);const c=Math.cos(theta);epath[i]=[[r*c,r*s],[-s,c]];} | |
| return epath;} | |
| function extrude(epath,shape){const m=epath.length;const n=shape.length;const numVertices=m*n;const vertices=new Float32Array(numVertices*6);const stride=6*4;const indices=new Int16Array(numVertices*6);for(let j=0;j<m;j++){let[[x_p,y_p],[ms_p,c_p]]=epath[j];for(let i=0;i<n;i++){const[[x_s,y_s],[ms_s,c_s]]=shape[i];const k=j*n+i;vertices[k*6+0]=x_p+x_s*c_p;vertices[k*6+1]=y_p-x_s*ms_p;vertices[k*6+2]=y_s;vertices[k*6+3]=c_s*c_p;vertices[k*6+4]=-c_s*ms_p;vertices[k*6+5]=-ms_s;indices[k*6+0]=j*n+i;indices[k*6+1]=((j+1)%m)*n+((i+1)%n);indices[k*6+2]=j*n+((i+1)%n);indices[k*6+3]=j*n+i;indices[k*6+4]=((j+1)%m)*n+i;indices[k*6+5]=((j+1)%m)*n+((i+1)%n);}} | |
| return{indices,vertices,stride,numVertices};} | |
| if(!("modules" in global)){global["modules"]={}} | |
| global.modules["geometry.js"]={cube,circle,extrude};})(window);(function(global){const cookiecutters={"outlines":{"Star":{"turtlePath":[[2,-58.0],[8,0.0],[3.2,130.0],[8,0.0],[2,-58.0],[8,0.0],[3.2,130.0],[8,0.0],[2,-58.0],[8,0.0],[3.2,130.0],[8,0.0],[2,-58.0],[8,0.0],[3.2,130.0],[8,0.0],[2,-58.0],[8,0.0],[3.2,130.0],[8,0.0]]},"Plain":{"startPoint":[10,0.0],"startAngle":90.0,"turtlePath":[[62.83185,360.0]]},"Scalloped":{"turtlePath":[[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0],[1.0,-110.0],[2,150.0]]},"Heart":{"startAngle":180,"turtlePath":[[0.45,-45.0],[10.0,180.0],[6.91,-10.0],[1.1,110.0],[6.91,-10.0],[10.0,180.0],[0.45,-45.0]]},"Duck":{"startAngle":180,"turtlePath":[[0.4,-10.0],[13.297,25.0],[3,-80.0],[4,160.0],[22.913,90.0],[15,90.0],[5,-90.0],[5,20.0],[3,170.0],[2,-20.0],[3,-90.0],[15,220.0],[5,-125.0]]},"Tree":{"startAngle":180,"turtlePath":[[0.75,40.0],[3,0.0],[1.5,140.0],[0.6,0.0],[1.0,-140.0],[3,0.0],[1.5,140.0],[0.6,0.0],[1.0,-140.0],[3,0.0],[1.5,140.0],[0.6,0.0],[1.0,-140.0],[3,0.0],[1.5,140.0],[11.431,0.0],[1.5,140.0],[3,0.0],[1.0,-140.0],[0.6,0.0],[1.5,140.0],[3,0.0],[1.0,-140.0],[0.6,0.0],[1.5,140.0],[3,0.0],[1.0,-140.0],[0.6,0.0],[1.5,140.0],[3,0.0],[0.75,40.0]]},"Blade":{"startPoint":[-1.8,0.0],"startAngle":0.0,"turtlePath":[[3.6,0],[0,45],[0.661522368915,0],[3,90],[2.5,0],[0,-43.5679],[10,0],[0,88.5679],[0.5,0],[0,88.5679],[10,0],[0,-43.5679],[2.5,0],[3,90],[0.661522368915,0],[0,45]]},"L":{"turtlePath":[[8,0],[0,90],[4,0],[0,90],[8,0],[0,-180],[8,0],[0,90],[4,0],[0,90],[8,0],[0,90],[8,0],[0,-90],[16,0],[0,90],[8,0],[0,90],[4,0],[0,90],[8,0],[0,-180],[8,0],[0,90],[4,0],[0,90],[8,0],[0,-180],[8,0],[0,90],[4,0],[0,90],[8,0],[0,-180],[8,0],[0,90],[4,0],[0,90],[8,0],[0,-180],[8,0],[0,90],[4,0],[0,90],[16,0],[0,-180],[16,0],[0,90],[4,0],[0,90],[4,0],[0,90],[24,0],[0,-180],[24,0],[0,90],[4,0],[0,90],[8,0],[0,-180]]},"A":{"turtlePath":[[0,70],[0.4,0],[0,-70],[0.41,0],[0,180],[0.41,0],[0,-110],[0.6,0],[0,-140],[1,0],[0,70]]},"B":{"turtlePath":[[0,90],[1,0],[0,-90],[0.25,0],[0.684,-180],[0.25,0],[0,180],[0.25,0],[0.896,-180],[0.25,0],[0,180]]}},"brickworks":{"centered":[[[-0.45,0.9],[-1.35,0.9],[0.45,0.9],[1.35,0.9]],[[-0.8,0.8],[-1.6,0.8],[0.8,0.8],[1.6,0.8],[0.0,0.8]],[[-1.1,0.733],[-1.833,0.733],[1.1,0.733],[1.833,0.733],[-0.367,0.733],[0.367,0.733]],[[-0.92,0.92],[-1.84,0.92],[0.92,0.92],[1.84,0.92],[0.0,0.92]],[[0.0,1.0],[-1.9,1.0],[1.9,1.0]],[[0.0,0.8],[-2.0,1.0],[2.0,1.0]],[[0.0,0.6],[-2.05,0.9],[2.05,0.9]],[[0.0,0.5],[-2.05,0.9],[2.05,0.9]],[[0.0,0.5],[-2.05,0.9],[2.05,0.9]],[[0.0,0.5],[-1.95,0.9],[1.95,0.9]],[[0.0,0.5],[-1.85,0.9],[1.85,0.9]],[[0.0,0.5],[-1.7,1.0],[1.7,1.0]],[[0.0,0.5],[-1.5,1.0],[1.5,1.0]],[[0.0,0.5],[-1.3,1.0],[1.3,1.0]],[[0.0,0.5],[-1.1,1.0],[1.1,1.0]],[[0.0,0.5],[-0.9,1.0],[0.9,1.0]],[[0.0,0.6],[-0.75,0.9],[0.75,0.9]],[[-0.5,1.0],[0.5,1.0]],[[-0.533,0.533],[-0.0,0.533],[0.533,0.533]],[[-0.3,0.6],[0.3,0.6]],[[0,1.0]]]}};if(!("modules" in global)){global["modules"]={}} | |
| global.modules["cookiecutters.js"]={cookiecutters};})(window);(function(global){function TurtlePathLengthArea(TurtlePath,arcStartAngle=0){let totalLength=0;let totalArea=0;let firstMoment=[0,0];let arcStartPoint=[0,0];let arcEndAngle=0;for(let[arcLength,arcAngle] of TurtlePath){const halfArcAngle=arcAngle/2;const chordAngle=arcStartAngle+halfArcAngle;const cosChordAngle=Math.cos(chordAngle);const sinChordAngle=Math.sin(chordAngle);totalLength+=arcLength;let chordLength;if(arcAngle !==0){if(arcLength !==0){const radius=arcLength/arcAngle;const sinHalfArcAngle=Math.sin(halfArcAngle);chordLength=radius*sinHalfArcAngle*2;const arcSegmentArea=0.5*radius**2*(arcAngle-Math.sin(arcAngle));const y_a=(2/3)*(radius*sinHalfArcAngle)**3;totalArea+=arcSegmentArea;firstMoment[0]+=arcSegmentArea*(arcStartPoint[0]-radius*Math.sin(arcStartAngle))+y_a*sinChordAngle;firstMoment[1]+=arcSegmentArea*(arcStartPoint[1]+radius*Math.cos(arcStartAngle))-y_a*cosChordAngle;} else{chordLength=0;}} else{chordLength=arcLength;} | |
| const arcEndPoint=[arcStartPoint[0]+chordLength*cosChordAngle,arcStartPoint[1]+chordLength*sinChordAngle];arcEndAngle=arcStartAngle+arcAngle;const triangleArea=(arcStartPoint[0]*arcEndPoint[1]-arcEndPoint[0]*arcStartPoint[1])/2;totalArea+=triangleArea;firstMoment[0]+=triangleArea*(arcStartPoint[0]+arcEndPoint[0])/3;firstMoment[1]+=triangleArea*(arcStartPoint[1]+arcEndPoint[1])/3;arcStartPoint=arcEndPoint;arcStartAngle=arcEndAngle;} | |
| const centroid=[firstMoment[0]/totalArea,firstMoment[1]/totalArea];return[totalLength,totalArea,arcStartPoint,arcEndAngle,centroid];} | |
| function*Segments2Complex({p0_a0_segs=[[[0,0],[1,0]],[]],scale=1.0,tol=0.05,offs=0,loops=1,return_start=false}){const[p0,a0]=p0_a0_segs[0];const Segs=p0_a0_segs[1];let a=a0.slice();let p=p0.slice();p[0]=p[0]-a[0]*offs;p[1]=p[1]-a[1]*offs;let L=0;if(return_start){yield{point:p,angle:a,length:L,segmentIndex:-1};} | |
| let loopcount=0;while(loops===null||loops===Infinity||loopcount<loops){loopcount++;for(let X=0;X<Segs.length;X++){let[l,da,..._]=Segs[X];l*=scale;let n;let v;let dda;if(da !==0){let r=l/da;r+=offs;if(r !==0){l=r*da;let dl=2*Math.sqrt(2*Math.abs(r)*tol);n=Math.max(Math.ceil(6*Math.abs(da/(2*Math.PI))),Math.floor(l/dl)+1);let dda2=[Math.cos(0.5*da/n),Math.sin(0.5*da/n)];v=[2*r*dda2[1]*dda2[0],2*r*dda2[1]*dda2[1]];v=[v[0]*a[0]-v[1]*a[1],v[0]*a[1]+v[1]*a[0]];} else{n=1;v=[0,0];} | |
| dda=[Math.cos(da/n),Math.sin(da/n)];for(let i=0;i<n;i++){L+=l/n;p[0]+=v[0];p[1]+=v[1];a=[a[0]*dda[0]-a[1]*dda[1],a[0]*dda[1]+a[1]*dda[0]];yield{point:p.slice(),angle:a,length:L,segmentIndex:X};v=[v[0]*dda[0]-v[1]*dda[1],v[0]*dda[1]+v[1]*dda[0]];}} else{n=1;L+=l;p[0]+=l*a[0];p[1]+=l*a[1];yield{point:p.slice(),angle:a,length:L,segmentIndex:X};}}}} | |
| function plot_segments(ctx,{p0=[0,0],a0=[1,0],segs=[],scale=1.0,tol=0.05,offs=0,loops=1,return_start=true}={}){let gen=Segments2Complex({p0_a0_segs:[[p0,a0],segs],scale:scale,tol:tol,offs:0,loops:loops,return_start:return_start});ctx.beginPath();let{value:{point,angle:[cos_ang,sin_ang]}}=gen.next();ctx.moveTo(point[0]-offs*sin_ang,point[1]+offs*cos_ang);for(let{point,angle:[cos_ang,sin_ang]} of gen){ctx.lineTo(point[0]-offs*sin_ang,point[1]+offs*cos_ang);} | |
| ctx.stroke()} | |
| if(!("modules" in global)){global["modules"]={}} | |
| global.modules["turtle-graphics.js"]={TurtlePathLengthArea,Segments2Complex,plot_segments};})(window);(function(global){const{sin,cos,sqrt,tan,PI}=Math,pi=PI;function xRot(a){const s=sin(a);const c=cos(a);return[1,0,0,0,0,c,s,0,0,-s,c,0,0,0,0,1];} | |
| function yRot(a){const s=sin(a);const c=cos(a);return[c,0,-s,0,0,1,0,0,s,0,c,0,0,0,0,1];} | |
| function zRot(a){const s=sin(a);const c=cos(a);return[c,s,0,0,-s,c,0,0,0,0,1,0,0,0,0,1];} | |
| function vRot([x,y,z],theta){x??=0;y??=0;z??=0;const length=sqrt(x*x+y*y+z*z);if(length==0){if(theta===undefined){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];} | |
| else{throw new Error("Rotation axis vector cannot be zero if a rotation angle is specified!");}} | |
| if(theta===undefined) theta=length;const c=cos(theta);const c1=1-c;const s=sin(theta);x/=length;y/=length;z/=length;return[c+c1*x**2,c1*x*y+s*z,c1*x*z-s*y,0,c1*x*y-s*z,c+c1*y**2,c1*y*z+s*x,0,c1*x*z+s*y,c1*y*z-s*x,c+c1*z**2,0,0,0,0,1];} | |
| function tLat([tx,ty,tz]){tx??=0;ty??=0;tz??=0;return[1,0,0,0,0,1,0,0,0,0,1,0,tx,ty,tz,1];} | |
| function scal([sx,sy,sz]){sx??=1;sy??=1;sz??=1;return[sx,0,0,0,0,sy,0,0,0,0,sz,0,0,0,0,1];} | |
| function T(A){return new Float32Array([A[0],A[4],A[8],A[12],A[1],A[5],A[9],A[13],A[2],A[6],A[10],A[14],A[3],A[7],A[11],A[15]]);} | |
| function mMul(B,A){const C=new Array(16);let sum;for(let i=0;i<4;++i) | |
| for(let j=0;j<4;++j){sum=0;for(let k=0;k<4;++k) | |
| sum+=B[i+4*k]*A[4*j+k];C[i+4*j]=sum;} | |
| return C;} | |
| function vMul(A,[x0,x1,x2,x3]){x0??=0;x1??=0;x2??=0;x3??=0;return new Float32Array([A[0]*x0+A[4]*x1+A[8]*x2+A[12]*x3,A[1]*x0+A[5]*x1+A[9]*x2+A[13]*x3,A[2]*x0+A[6]*x1+A[10]*x2+A[14]*x3,A[3]*x0+A[7]*x1+A[11]*x2+A[15]*x3]);} | |
| function persp(fov,aspR,near,far){const f=tan(pi*0.5-0.5*fov);const nfInv=1.0/(near-far);return[f/aspR,0,0,0,0,f,0,0,0,0,nfInv*(far+near),-1,0,0,2*far*near*nfInv,0];} | |
| function cMaj(A){return A;} | |
| function rMaj(A){return T(A);} | |
| function camMat([tx,ty,tz],azim,elev,d){tx??=0;ty??=0;tz??=0;d??=0;const s=sin(azim),c=cos(azim),se=sin(elev),ce=cos(elev);return new Float32Array([-s,-c*se,c*ce,0,c,-s*se,ce*s,0,0,ce,se,0,-c*ty+s*tx,c*se*tx-ce*tz+s*se*ty,-c*ce*tx-ce*s*ty-d-se*tz,1])};function icamMat(t,C,d){d??=0;return new Float32Array([C[0],C[4],C[8],0,C[1],C[5],C[9],0,C[2],C[6],C[10],0,C[2]*d+t[0]??0,C[6]*d+t[1]??0,C[10]*d+t[2]??0,1])};function camPos(targ,camMat,d){const[tx,ty,tz]=targ;const ex=camMat[2],ey=camMat[6],ez=camMat[10];return[tx+ex*d,ty+ey*d,tz+ez*d,1];};if(!("modules" in global)){global["modules"]={}} | |
| global.modules["m4_cMaj.js"]={xRot,yRot,zRot,vRot,tLat,scal,T,mMul,vMul,persp,cMaj,rMaj,camMat,icamMat,camPos};})(window);(function(global){let elementIsDefined | |
| if(typeof element==='undefined'){elementIsDefined=false;window.element=document.body;} | |
| else{elementIsDefined=true;} | |
| const deg=Math.PI/180;const iDeg=1/deg;function radToDeg(r){return r*iDeg;} | |
| function degToRad(d){return d*deg;} | |
| let vertexShaderSource=` | |
| attribute vec4 a_position; | |
| attribute vec4 a_normal; | |
| uniform mat4 u_matrix; | |
| varying vec4 v_color; | |
| void main() { | |
| // Multiply the position by the matrix. | |
| gl_Position = u_matrix * a_position; | |
| // Pass the color to the fragment shader. | |
| v_color = vec4(0.0,0.0,0.0,1.0); | |
| float wxp =max(a_normal.x,0.0); | |
| float wxn =max(-a_normal.x,0.0); | |
| float wyp= max(a_normal.y,0.0); | |
| float wyn= max(-a_normal.y,0.0); | |
| float wzp= max(a_normal.z,0.0); | |
| float wzn= max(-a_normal.z,0.0); | |
| v_color.xyz += wxp*wxp*wxp *vec3(1.0,0.0,0.0); | |
| v_color.xyz += wxn*wxn*wxn *vec3(0.1725,0.8157,0.7843); | |
| v_color.xyz += wyp*wyp*wyp *vec3(0.0,0.8706,0.0); | |
| v_color.xyz += wyn*wyn*wyn *vec3(0.9412,0.06275,1.0); | |
| v_color.xyz += wzp*wzp*wzp *vec3(0.102,0.3451,1.0); | |
| v_color.xyz += wzn*wzn*wzn *vec3(0.9137,0.9098,0.07451); | |
| } | |
| `;let fragmentShaderSource=` | |
| precision mediump float; | |
| // Passed in from the vertex shader. | |
| varying vec4 v_color; | |
| void main() { | |
| gl_FragColor = v_color; | |
| } | |
| `;const state={animate:true};const wrapper=document.createElement("div");wrapper.style.position="relative";wrapper.style.display="inline-block";const hint=document.createElement("div");hint.innerText="Colors indicate surface normal directions: +xyz=rgb, -xyz=cmy \nDrag to rotate the view.";if(typeof logdiv==='undefined'){hint.innerText+="\nelementIsDefined="+elementIsDefined;} | |
| else{logdiv.innerText+="\nelementIsDefined="+elementIsDefined;} | |
| wrapper.appendChild(hint);if(!(typeof element.logdiv==='undefined')){element.logdiv.innerText+="initializing";} | |
| const canvas=document.createElement("canvas");canvas.style.display="block";canvas.style.width="400px";canvas.style.height="400px";wrapper.appendChild(canvas);element.appendChild(wrapper);const gl=canvas.getContext("webgl");let isDragging=false;let previousTouchX=0;let previousTouchY=0;let scale=1;let translateX=0;let translateY=0;canvas.addEventListener('touchstart',(e)=>{e.preventDefault();isDragging=true;if(e.touches.length===1){previousTouchX=e.touches[0].clientX;previousTouchY=e.touches[0].clientY;}});canvas.addEventListener('touchmove',(e)=>{e.preventDefault();if(!isDragging) return;if(e.touches.length===1){const currentX=e.touches[0].clientX;const currentY=e.touches[0].clientY;const deltaX=currentX-previousTouchX;const deltaY=currentY-previousTouchY;camera.azim=(camera.azim-deltaX*0.01)%(Math.PI*2);camera.elev=Math.max(Math.min(camera.elev+deltaY*0.01,Math.PI/2),-Math.PI/2);previousTouchX=currentX;previousTouchY=currentY;} else if(e.touches.length===2){const touch1=e.touches[0];const touch2=e.touches[1];const currentDistance=Math.hypot(touch1.clientX-touch2.clientX,touch1.clientY-touch2.clientY);if(!canvas.dataset.previousDistance){canvas.dataset.previousDistance=currentDistance;} | |
| const previousDistance=parseFloat(canvas.dataset.previousDistance);const distanceDelta=currentDistance-previousDistance;scale+=distanceDelta*0.005;scale=Math.max(0.1,Math.min(scale,10));const midX=(touch1.clientX+touch2.clientX)/2;const midY=(touch1.clientY+touch2.clientY)/2;if(canvas.dataset.previousMidX){translateX+=midX-parseFloat(canvas.dataset.previousMidX);translateY+=midY-parseFloat(canvas.dataset.previousMidY);} | |
| canvas.dataset.previousDistance=currentDistance;canvas.dataset.previousMidX=midX;canvas.dataset.previousMidY=midY;} | |
| if(!state.animate) | |
| drawScene();});canvas.addEventListener('touchend',(e)=>{e.preventDefault();isDragging=false;delete canvas.dataset.previousDistance;delete canvas.dataset.previousMidX;delete canvas.dataset.previousMidY;});canvas.addEventListener('touchcancel',()=>{isDragging=false;delete canvas.dataset.previousDistance;delete canvas.dataset.previousMidX;delete canvas.dataset.previousMidY;});document.addEventListener('touchmove',(e)=>{if(e.target===canvas){e.preventDefault();}},{passive:false});let program;let normalLocation;let positionLocation;let matrixLocation;const camera={fov:30*deg,target:[0,0,0],azim:30*deg,elev:40*deg,dist:1000};const style={hideButton:false};let{cube,circle,extrude}=modules["geometry.js"];let{cookiecutters}=modules["cookiecutters.js"];let{Segments2Complex}=modules["turtle-graphics.js"];const ShapeData={};function getOutlinePath(name,{scale=1.0}){const{turtlePath,startPoint,startAngle}=cookiecutters.outlines[name];const p0=startPoint||[0,0];const a0=[Math.cos(startAngle*deg),Math.sin(startAngle*deg)];const segs=turtlePath.map(([l,a])=>[l,a*deg]);const S2C=Segments2Complex({segs:segs,p0_a0_segs:[[[p0[0]*scale,p0[1]*scale],a0],segs],scale:scale,tol:0.05,loops:1,return_start:false});const pathPoints=Array.from(S2C);const epath=pathPoints.map(({point,angle})=>[point,angle]);if(name==='Circle'){return circle(scale*10,Math.round(13*Math.sqrt(scale)));} | |
| return epath;} | |
| function initialize(){let scale,epath,spath;epath=getOutlinePath("Duck",{scale:11});spath=getOutlinePath("Blade",{scale:5});Object.assign(ShapeData,extrude(epath,spath));if(!gl){return;} | |
| const vertexShader=gl.createShader(gl.VERTEX_SHADER);gl.shaderSource(vertexShader,vertexShaderSource);gl.compileShader(vertexShader);const fragmentShader=gl.createShader(gl.FRAGMENT_SHADER);gl.shaderSource(fragmentShader,fragmentShaderSource);gl.compileShader(fragmentShader);program=gl.createProgram();[vertexShader,fragmentShader].forEach(function(shader){gl.attachShader(program,shader);});gl.linkProgram(program);gl.useProgram(program);positionLocation=gl.getAttribLocation(program,"a_position");normalLocation=gl.getAttribLocation(program,"a_normal");matrixLocation=gl.getUniformLocation(program,"u_matrix");let vertexBuffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER,ShapeData.vertices,gl.STATIC_DRAW);gl.enableVertexAttribArray(positionLocation);gl.vertexAttribPointer(positionLocation,3,gl.FLOAT,false,ShapeData.stride,0);gl.enableVertexAttribArray(normalLocation);gl.vertexAttribPointer(normalLocation,3,gl.FLOAT,true,ShapeData.stride,12);let indexBuffer=gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,ShapeData.indices,gl.STATIC_DRAW);requestAnimationFrame(drawScene);} | |
| function drawScene(){let targ=[0,0,0];let cM=M4.camMat(targ,camera.azim,camera.elev,camera.dist);let icM=M4.icamMat(targ,cM,camera.dist);let cP=M4.camPos(targ,cM,camera.dist);gl.canvas.width=gl.canvas.clientWidth;gl.canvas.height=gl.canvas.clientHeight;gl.viewport(0,0,gl.canvas.width,gl.canvas.height);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);gl.enable(gl.CULL_FACE);gl.enable(gl.DEPTH_TEST);gl.useProgram(program);let aspR=gl.canvas.clientWidth/gl.canvas.clientHeight;let zNear=1;let zFar=camera.dist+1000;let projectionMatrix=M4.persp(camera.fov,aspR,zNear,zFar);let viewProjectionMatrix=M4.mMul(projectionMatrix,cM);drawTorus([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],viewProjectionMatrix,matrixLocation);if(state.animate){camera.azim=(camera.azim+0.02)%(Math.PI*2);requestAnimationFrame(drawScene);}} | |
| function drawTorus(matrix,viewProjectionMatrix,matrixLocation){matrix=M4.mMul(viewProjectionMatrix,matrix);gl.uniformMatrix4fv(matrixLocation,false,M4.cMaj(matrix));gl.drawElements(gl.TRIANGLES,ShapeData.indices.length,gl.UNSIGNED_SHORT,0);} | |
| function setAnimationState(animate){if(animate&&!state.animate){requestAnimationFrame(drawScene);} | |
| state.animate=animate;animBtn.style.display=state.animate||style.hideButton?'none':'block';} | |
| const animBtn=document.createElement('button');animBtn.id='animBtn';animBtn.className='';animBtn.className='';animBtn.style.padding='5px 10px';animBtn.style.backgroundColor='var(--slate-1, #f0f0f0)';animBtn.style.border='1px solid var(--slate-6, #ccc)';animBtn.style.color='var(--slate-11, #333)';animBtn.style.borderRadius='4px';animBtn.style.cursor='pointer';animBtn.textContent='Start Animation';animBtn.style.position='absolute';animBtn.style.bottom='10px';animBtn.style.left='10px';animBtn.style.zIndex='10';animBtn.addEventListener('click',()=>setAnimationState(true));wrapper.appendChild(animBtn);setAnimationState(state.animate);canvas.addEventListener('touchstart',(e)=>{e.preventDefault();if(state.animate){setAnimationState(false);}});canvas.addEventListener('mousedown',(e)=>{if(state.animate){setAnimationState(false);}});let M4=modules["m4_cMaj.js"];initialize();if(!("modules" in global)){global["modules"]={}} | |
| global.modules["webgl-torus.js"]={radToDeg,degToRad,vertexShaderSource,fragmentShaderSource,state,hint,canvas,program,normalLocation,positionLocation,matrixLocation,camera,style,ShapeData,drawScene,render:drawScene,setAnimationState,animBtn};})(window);(function(global){})(window);</script> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment