PixAnvil/Three.js demo
Setup
Loop
P
V
UI
Save
Stats
// Create 2D texture where we'll run our simulation sim.nP=120*120; // number of particles (size of texture) sim.P=new gVector1D(sim.nP); // current particle positions sim.Pnext=new gVector1D(sim.nP); // positions in next frame sim.V=new gVector1D(sim.nP); // current particle velocities sim.Vnext=new gVector1D(sim.nP); // velocities in next frame // For debugging, show P and V onscreen sim.P.debugTo(scene,10.0,-6.0,10.0,6.0); // P left sim.V.debugTo(scene,10.0,+6.0,10.0,6.0); // V right // Extract simulated positions in a vertex shader. // Basically we look up each vertex's 3D position from P. // Using a mesh is totally wrong for this, of course! var Pgeom=new THREE.PlaneGeometry(1,1, /* vertices: */ sim.P.tex.width,sim.P.tex.height); var PgeomShader=new THREE.ShaderMaterial( { uniforms: { readP: {type: "t", value: sim.P.tex } }, vertexShader: "varying vec2 texcoords;\n"+ "varying vec3 normalVec;\n"+ "varying vec3 color;\n"+ "uniform sampler2D readP;\n"+ "void main(void) {\n"+ " texcoords=uv;\n"+ " float del=2.0/128.0; // one pixel, in texcoords\n"+ " vec3 P=vec3(texture2D(readP,uv));\n"+ " color=P; \n"+ " vec3 SP=P*3.0; // scale up! \n"+ " gl_Position=projectionMatrix * modelViewMatrix * vec4( SP, 1.0 );\n"+ " vec3 TR=vec3(texture2D(readP,uv+vec2(del,del)));\n"+ " vec3 BR=vec3(texture2D(readP,uv+vec2(del,-del)));\n"+ " normalVec=normalize(cross(BR-P,TR-P));\n"+ "}\n" , fragmentShader: "varying vec2 texcoords;\n"+ "varying vec3 normalVec;\n"+ "varying vec3 color;\n"+ "void main(void) {\n"+ " // gl_FragColor=vec4(normalVec,1.0); // debug normal \n"+ " /// vec3 T=fract(normalVec*2.0);\n"+ // shiny colors // diffuse lighting " vec3 L=normalize(vec3(-0.2,-0.3,1.0)); // light dir\n"+ " float T=dot(normalVec,L);\n"+ " if (T<0.0) T=-0.2*T;\n"+ " T+=0.1; // ambient\n"+ " vec2 tf=fract(16.0*texcoords);\n"+ // stripes " if (length(tf-vec2(0.5))<0.125) T*=0.7;\n"+ " gl_FragColor=vec4(vec3(T),1.0); \n"+ "}\n" } ); // Turn off view and backface culling Pgeom.boundingSphere = new THREE.Sphere( new THREE.Vector3(), 100.0 /* huge! */ ); PgeomShader.side = THREE.DoubleSide; var P3D=new THREE.Mesh(Pgeom, PgeomShader); P3D.castShadow=true; // why dis no work? P3D.receiveShadow=true; P3D.frustumCulled=false; scene.add(P3D); // Ground var groundTex=THREE.ImageUtils.loadTexture( "textures/checkerboard_noisy.jpg" ); groundTex.wrapS=groundTex.wrapT=THREE.RepeatWrapping; groundTex.repeat=new vec2(25.0,25.0); sim.ground = new THREE.Mesh( new THREE.CubeGeometry(50,50,0.0001), new THREE.MeshLambertMaterial( {color: 0xffccaa, opacity: 1, map:groundTex}) ); sim.ground.receiveShadow=true; scene.add(sim.ground); // Sun-like spotlight var l=new THREE.SpotLight(); sim.light = l; l.position.set( -50, -100, 100 ); l.castShadow=true; l.shadowCameraNear = 50; l.shadowCameraFar = 500; l.shadowCameraFov = 10; // degrees field of view scene.add(l);
// First time, compile shaders and initialize buffers. if (!sim.fnV) { sim.fnV=new gFunction1D(PixAnvil.loadTab('V')); sim.fnP=new gFunction1D(PixAnvil.loadTab('P')); // Initialize particle positions to a nice grid new gFunction1D( "vec4 run(float i) {\n"+ " vec4 P=fract( vec4(i/128.0,i/(128.0*128.0),2.0,1.0) );\n"+ " return P-0.5;\n"+ "}\n" ).run(sim.P); // Initialize velocities to semi-random values new gFunction1D( "vec4 run(float index) {\n"+ " vec4 P=Ptex(index);\n"+ " float f=10.0; // frequency\n"+ " float a=0.1; // amplitude\n"+ " vec4 V=vec4(a*sin(f*P.x),a*sin(f*P.y),1.0,0.0);\n"+ " V=normalize(V)*1.0;\n"+ " return V;\n"+ "}\n" ) .set("Ptex",sim.P) .run(sim.V); } var ground_z=0.01; // up, to prevent Z buffer fighting // Substepping loop: if (!sim.target_t) { sim.target_t=sim.current_t=0; } if (lib.dt<0.2) sim.target_t+=lib.dt; var dt=0.002; // simulation timestep size while (sim.current_t<sim.target_t) { // substep loop sim.current_t+=dt; // This GLSL code updates the V velocities sim.fnV // set uniforms and compute V .set("dt",dt) .set("gravity",new vec3(0,0,-9.8)) .set("ground_z",ground_z) .set("Ptex",sim.P) // read from old P .set("Vtex",sim.V) // read from old V .run(sim.Vnext); // write to Vnext sim.Vnext.swap(sim.V); // ping-pong buffers // This GLSL code updates the P positions sim.fnP // set uniforms and compute P .set("dt",dt) .set("ground_z",ground_z) .set("Ptex",sim.P) // read from (old) P .set("Vtex",sim.V) // read from (new) V .run(sim.Pnext); // write to Pnext sim.Pnext.swap(sim.P); // ping-pong buffers } // end substep loop
/* This code calculates the new P positions */ /* uniforms are set automatically (see Loop) */ vec4 run(float index) { vec4 P=Ptex(index); vec4 V=Vtex(index); if (index>127.0*128.0) { P.z=2.0; // lock top row in place V.x=0.0; V.y=0.0; } // Subtle: do bounce *first* (to match V) if (P.z<=ground_z) P.z=ground_z; P+=dt*V; return P; }
/* Return the net force on a spring connecting point P with T */ vec3 spring(vec3 P,vec3 T,float restScale,float k) { vec3 along=vec3(P)-vec3(T); float rest=1.0/128.0*restScale; float len=length(along); if (len<=0.0) return vec3(0.0); float x=len-rest; // F = k * x return -k*x*normalize(along); } const float sz=128.0; // pixels per side in texture /* 2D biased lookup in Ptex: shift by (dx,dy) pixels */ vec3 Ptex2(float dx,float dy) { dx/=sz; dy/=sz; return vec3(texture2D(readtex_Ptex,texcoords+vec2(dx,dy))); } /* This code calculates the new V velocities */ vec4 run(float index) { vec3 P=vec3(Ptex(index)); vec3 V=vec3(Vtex(index)); // Compute net force: vec3 F=vec3(0.0); // Direct neighbors float k=0.3; // newtons per meter of stretch F+= +spring(P,Ptex2(-1.0,0.0),1.0,k) +spring(P,Ptex2(+1.0,0.0),1.0,k) +spring(P,Ptex2(0.0,-1.0),1.0,k) +spring(P,Ptex2(0.0,+1.0),1.0,k) ; // Diagonal neighbors vec3 TL=Ptex2(-1.0,+1.0), TR=Ptex2(+1.0,+1.0); vec3 BL=Ptex2(-1.0,-1.0), BR=Ptex2(+1.0,-1.0); F+=spring(P,TL,1.414,k); F+=spring(P,TR,1.414,k); F+=spring(P,BL,1.414,k); F+=spring(P,BR,1.414,k); // Bend resistance: spring to midpoint of neighbors k*=0.5; // lower spring constant for bending vec3 TL2=Ptex2(-2.0,+2.0), TR2=Ptex2(+2.0,+2.0); vec3 BL2=Ptex2(-2.0,-2.0), BR2=Ptex2(+2.0,-2.0); F+=spring(P,0.5*(TL+BR),0.0,k); // direct bend F+=spring(P,0.5*(TR+BL),0.0,k); F-=0.5*( // reaction bend +spring(TL,0.5*(P+TL2),0.0,k) +spring(TR,0.5*(P+TR2),0.0,k) +spring(BL,0.5*(P+BL2),0.0,k) +spring(BR,0.5*(P+BR2),0.0,k) ); // Back to Newtonian physics float m=0.5; // kilograms per square meter of cloth m=m/(sz*sz); // discretize vec3 A=vec3(0,0,-0.8); // gravity A+=F/m; // springs A+=-0.3*V*length(V); // wind // Average in neighboring velocities (for smoothing/stability) V=0.2*V+0.2*vec3( Vtex(index-1.0) +Vtex(index+1.0) +Vtex(index+sz) +Vtex(index-sz) ); V+=dt*A; if (P.z<=ground_z) { // hit ground V.z=abs(V.z); V*=0.8; // energy lost } return vec4(V,1.0); }
// Camera control: // Update camera coordinate system var s=camera; if (!s.X) { // startup: create initial coordinates s.X=new vec3(1,0,0); s.Y=new vec3(0,0,1); s.Z=new vec3(0,-1,-0.2); // camera Z is world Y s.P=new vec3(0,-5,1.3); // initial location } // Move camera via keyboard var move=new vec3(0,0,0); // sums current frame motion // X control via A and D if (lib.key['a']) move.pe(new vec3(-1,0,0)); if (lib.key['d']) move.pe(new vec3(+1,0,0)); // Y control via W and S if (lib.key['w']) move.pe(new vec3(0,0,-1)); if (lib.key['s']) move.pe(new vec3(0,0,+1)); // Z control via Q and Z if (lib.key['q']) move.pe(new vec3(0,+1,0)); if (lib.key['z']) move.pe(new vec3(0,-1,0)); move.te(5.0*lib.dt); // meters/second motion rate s.P.pe(s.X.t(move.x).p(s.Y.t(move.y)).p(s.Z.t(move.z))); // Rotate camera via mouse var speed=0.01; // radians per mouse pixel if (lib.mouseleft) { // move Z with mouse s.Z.pe(s.X.t(-lib.mousedx*speed).p( s.Y.t( lib.mousedy*speed))); } // Keep level: make sure X is horizontal. s.X.z=0.0; s.Y.crossVectors(s.Z,s.X.normalize()); // Orthonormalize s.X.crossVectors(s.Y,s.Z).normalize(); s.Y.crossVectors(s.Z,s.X).normalize(); s.Z.normalize(); // Write coordinate system into matrix s.matrixAutoUpdate=false; // don't trash s.matrixWorldNeedsUpdate=true; // show var m=s.matrix; // the camera's matrix // Utility function: set a matrix column function setCol(m,col,vec) { m.elements[4*col+0]=vec.x; m.elements[4*col+1]=vec.y; m.elements[4*col+2]=vec.z; } setCol(m,0,s.X); setCol(m,1,s.Y); setCol(m,2,s.Z); setCol(m,3,s.P); // position from sim
To save, copy this text into a
plain text
editor like Notepad, and save as HTML.
Startup: 42 ms
Time:
10x
1x
0.1x
0.01x
0.001x
0.0001x