Download canvas_three_scene.html from developerjeremylive/qwopus36-35b-a3b-eval-etheroi: direct link, hf CLI and curl.
- Browser
- Download file 16.3 kB
-
https://huggingface.co/spaces/developerjeremylive/qwopus36-35b-a3b-eval-etheroi/resolve/main/canvas_three_scene.html
- Command line
-
hf download hf://spaces/developerjeremylive/qwopus36-35b-a3b-eval-etheroi/canvas_three_scene.html
-
curl -L -o canvas_three_scene.html https://huggingface.co/spaces/developerjeremylive/qwopus36-35b-a3b-eval-etheroi/resolve/main/canvas_three_scene.html
16.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Crystal Formation</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{overflow:hidden;background:#000} | |
| canvas{display:block} | |
| </style> | |
| </head> | |
| <body> | |
| <script type="importmap"> | |
| { | |
| "imports":{ | |
| "three":"https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| <script type="module"> | |
| import*as THREE from'three'; | |
| import{EffectComposer}from'three/addons/postprocessing/EffectComposer.js'; | |
| import{RenderPass}from'three/addons/postprocessing/RenderPass.js'; | |
| import{UnrealBloomPass}from'three/addons/postprocessing/UnrealBloomPass.js'; | |
| import{ShaderPass}from'three/addons/postprocessing/ShaderPass.js'; | |
| import{OrbitControls}from'three/addons/controls/OrbitControls.js'; | |
| import{OutputPass}from'three/addons/postprocessing/OutputPass.js'; | |
| /* βββ Renderer βββ */ | |
| const renderer=new THREE.WebGLRenderer({antialias:true,powerPreference:'high-performance'}); | |
| renderer.setSize(innerWidth,innerHeight); | |
| renderer.setPixelRatio(Math.min(devicePixelRatio,2)); | |
| renderer.shadowMap.enabled=true; | |
| renderer.shadowMap.type=THREE.PCFSoftShadowMap; | |
| renderer.toneMapping=THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure=1.2; | |
| document.body.appendChild(renderer.domElement); | |
| /* βββ Scene βββ */ | |
| const scene=new THREE.Scene(); | |
| /* βββ Camera βββ */ | |
| const camera=new THREE.PerspectiveCamera(38,innerWidth/innerHeight,0.1,120); | |
| camera.position.set(8,5,10); | |
| /* βββ Controls βββ */ | |
| const controls=new OrbitControls(camera,renderer.domElement); | |
| controls.enableDamping=true; | |
| controls.dampingFactor=0.05; | |
| controls.autoRotate=true; | |
| controls.autoRotateSpeed=0.3; | |
| controls.enablePan=false; | |
| controls.maxPolarAngle=Math.PI*0.55; | |
| controls.minDistance=6; | |
| controls.maxDistance=22; | |
| controls.target.set(0,2,0); | |
| /* βββ Mouse parallax βββ */ | |
| const mouse={x:0,y:0,tx:0,ty:0}; | |
| addEventListener('mousemove',e=>{ | |
| mouse.tx=(e.clientX/innerWidth-0.5)*2; | |
| mouse.ty=(e.clientY/innerHeight-0.5)*2; | |
| }); | |
| /* βββ Fog βββ */ | |
| scene.fog=new THREE.FogExp2(0x080818,0.028); | |
| /* βββ Procedural Sky βββ */ | |
| const skyGeo=new THREE.SphereGeometry(60,32,32); | |
| const skyMat=new THREE.ShaderMaterial({ | |
| side:THREE.BackSide, | |
| depthWrite:false, | |
| uniforms:{ | |
| topColor:{value:new THREE.Color(0x060618)}, | |
| midColor:{value:new THREE.Color(0x1a0830)}, | |
| bottomColor:{value:new THREE.Color(0x080818)}, | |
| offset:{value:15}, | |
| exponent:{value:0.5} | |
| }, | |
| vertexShader:`varying vec3 vWorldPos; | |
| void main(){ | |
| vWorldPos=position; | |
| gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); | |
| }`, | |
| fragmentShader:`uniform vec3 topColor; | |
| uniform vec3 midColor; | |
| uniform vec3 bottomColor; | |
| uniform float offset; | |
| uniform float exponent; | |
| varying vec3 vWorldPos; | |
| void main(){ | |
| float h=normalize(vWorldPos+offset).y; | |
| vec3 c=mix(midColor,bottomColor,smoothstep(0.0,0.15,h)); | |
| c=mix(midColor,topColor,smoothstep(-0.1,0.55,h)); | |
| gl_FragColor=vec4(c,1.0); | |
| }` | |
| }); | |
| scene.add(new THREE.Mesh(skyGeo,skyMat)); | |
| /* βββ Grid βββ */ | |
| const gridGroup=new THREE.Group(); | |
| const gridMat=new THREE.MeshBasicMaterial({ | |
| color:0x1a1a3a, | |
| transparent:true, | |
| opacity:0.35 | |
| }); | |
| const halfGrid=12; | |
| const step=0.7; | |
| for(let i=-halfGrid;i<=halfGrid;i+=step){ | |
| const x=i; | |
| const y1=-0.3; | |
| const y2=-0.3; | |
| const z1=-halfGrid; | |
| const z2=halfGrid; | |
| const x1=-halfGrid; | |
| const x2=halfGrid; | |
| const z1_=-0.3; | |
| const z2_=-0.3; | |
| const y1_=halfGrid; | |
| const y2_=-halfGrid; | |
| // X lines | |
| const line1=new THREE.BufferGeometry().setFromPoints([ | |
| new THREE.Vector3(x,-0.3,z1),new THREE.Vector3(x,-0.3,z2) | |
| ]); | |
| gridGroup.add(new THREE.Line(line1,gridMat)); | |
| // Z lines | |
| const line2=new THREE.BufferGeometry().setFromPoints([ | |
| new THREE.Vector3(z1_,y1_,x),new THREE.Vector3(z2_,y2_,x) | |
| ]); | |
| gridGroup.add(new THREE.Line(line2,gridMat)); | |
| } | |
| // Subtle ground plane | |
| const groundGeo=new THREE.PlaneGeometry(120,120); | |
| const groundMat=new THREE.MeshStandardMaterial({ | |
| color:0x080818, | |
| roughness:0.95, | |
| metalness:0.05, | |
| transparent:true, | |
| opacity:0.5 | |
| }); | |
| const ground=new THREE.Mesh(groundGeo,groundMat); | |
| ground.rotation.x=-Math.PI/2; | |
| ground.position.y=-0.35; | |
| ground.receiveShadow=true; | |
| gridGroup.add(ground); | |
| scene.add(gridGroup); | |
| /* βββ Crystal Material βββ */ | |
| const crystalMat=new THREE.MeshPhysicalMaterial({ | |
| color:0xd0d0ff, | |
| metalness:0.05, | |
| roughness:0.05, | |
| transmission:0.92, | |
| thickness:1.5, | |
| ior:1.8, | |
| clearcoat:0.3, | |
| clearcoatRoughness:0.1, | |
| attenuationColor:new THREE.Color(0.5,0.5,1.0), | |
| attenuationDistance:3, | |
| envMapIntensity:1.0, | |
| transparent:true, | |
| opacity:0.85, | |
| side:THREE.DoubleSide | |
| }); | |
| /* βββ Icosahedron Geometry βββ */ | |
| const icoGeo=new THREE.IcosahedronGeometry(1,0); | |
| /* βββ Crystal Clusters βββ */ | |
| const clusters=[]; | |
| // Central cluster | |
| const centralCluster=new THREE.Group(); | |
| const centralSizes=[2.2,1.6,1.3,1.0,0.8]; | |
| const centralPositions=[ | |
| [0,0,0],[0.7,0.5,-0.3],[-0.8,0.3,0.5],[0.4,0.8,0.6],[-0.3,0.6,-0.8] | |
| ]; | |
| const centralRotations=[ | |
| [0.2,0.3,0.1],[0.5,-0.2,0.4],[-0.3,0.6,0.2],[0.1,0.4,-0.5],[0.7,-0.1,0.3] | |
| ]; | |
| for(let i=0;i<centralSizes.length;i++){ | |
| const m=new THREE.Mesh(icoGeo,crystalMat); | |
| m.scale.setScalar(centralSizes[i]); | |
| m.position.set(...centralPositions[i]); | |
| m.rotation.set(...centralRotations[i]); | |
| m.castShadow=true; | |
| m.receiveShadow=true; | |
| centralCluster.add(m); | |
| } | |
| centralCluster.position.y=2.5; | |
| scene.add(centralCluster); | |
| clusters.push(centralCluster); | |
| // Left cluster | |
| const leftCluster=new THREE.Group(); | |
| const leftSizes=[1.4,1.1,0.9]; | |
| const leftPositions=[[0,0,0],[-0.8,0.4,0.5],[0.6,0.3,-0.4]]; | |
| const leftRotations=[[0.3,0.1,0.2],[-0.4,0.5,0.1],[0.6,-0.3,0.4]]; | |
| for(let i=0;i<leftSizes.length;i++){ | |
| const m=new THREE.Mesh(icoGeo,crystalMat); | |
| m.scale.setScalar(leftSizes[i]); | |
| m.position.set(...leftPositions[i]); | |
| m.rotation.set(...leftRotations[i]); | |
| m.castShadow=true; | |
| m.receiveShadow=true; | |
| leftCluster.add(m); | |
| } | |
| leftCluster.position.set(-4.5,1.2,2.5); | |
| scene.add(leftCluster); | |
| clusters.push(leftCluster); | |
| // Right cluster | |
| const rightCluster=new THREE.Group(); | |
| const rightSizes=[1.6,1.2,0.7]; | |
| const rightPositions=[[0,0,0],[0.5,0.6,-0.3],[-0.6,0.3,0.6]]; | |
| const rightRotations=[[0.1,-0.4,0.3],[0.5,0.2,-0.6],[-0.2,0.7,0.1]]; | |
| for(let i=0;i<rightSizes.length;i++){ | |
| const m=new THREE.Mesh(icoGeo,crystalMat); | |
| m.scale.setScalar(rightSizes[i]); | |
| m.position.set(...rightPositions[i]); | |
| m.rotation.set(...rightRotations[i]); | |
| m.castShadow=true; | |
| m.receiveShadow=true; | |
| rightCluster.add(m); | |
| } | |
| rightCluster.position.set(3.5,0.8,-3); | |
| scene.add(rightCluster); | |
| clusters.push(rightCluster); | |
| // Small foreground cluster | |
| const frontCluster=new THREE.Group(); | |
| const frontSizes=[0.7,0.5]; | |
| const frontPositions=[[0,0,0],[0.3,0.3,0.2]]; | |
| const frontRotations=[[0.4,0.2,0.1],[-0.3,0.5,0.3]]; | |
| for(let i=0;i<frontSizes.length;i++){ | |
| const m=new THREE.Mesh(icoGeo,crystalMat); | |
| m.scale.setScalar(frontSizes[i]); | |
| m.position.set(...frontPositions[i]); | |
| m.rotation.set(...frontRotations[i]); | |
| m.castShadow=true; | |
| m.receiveShadow=true; | |
| frontCluster.add(m); | |
| } | |
| frontCluster.position.set(2,0.3,5); | |
| scene.add(frontCluster); | |
| clusters.push(frontCluster); | |
| /* βββ Satellite Crystal βββ */ | |
| const satGroup=new THREE.Group(); | |
| const satCrystal=new THREE.Mesh(icoGeo,crystalMat); | |
| satCrystal.scale.setScalar(0.5); | |
| satCrystal.castShadow=true; | |
| satGroup.add(satCrystal); | |
| const satGlowMat=new THREE.MeshBasicMaterial({ | |
| color:0x00ffff, | |
| transparent:true, | |
| opacity:0.15, | |
| blending:THREE.AdditiveBlending, | |
| depthWrite:false | |
| }); | |
| const satGlow=new THREE.Mesh(new THREE.SphereGeometry(0.8,16,16),satGlowMat); | |
| satGroup.add(satGlow); | |
| const satLight=new THREE.PointLight(0x00ffff,3,8,2); | |
| satGroup.add(satLight); | |
| // Orbital path indicator | |
| const orbitPath=new THREE.Line( | |
| new THREE.BufferGeometry().setFromPoints( | |
| Array.from({length:64},(_,i)=>{ | |
| const a=i/64*Math.PI*2; | |
| return new THREE.Vector3(Math.cos(a)*5,2+Math.sin(a*3)*0.8,Math.sin(a)*5); | |
| }) | |
| ), | |
| new THREE.LineBasicMaterial({ | |
| color:0x00ffff, | |
| transparent:true, | |
| opacity:0.08, | |
| blending:THREE.AdditiveBlending | |
| }) | |
| ); | |
| scene.add(orbitPath); | |
| /* βββ Particle Trail βββ */ | |
| const MAX_PARTICLES=400; | |
| const particles=new Float32Array(MAX_PARTICLES*3); | |
| const particleLifetimes=new Float32Array(MAX_PARTICLES); | |
| const particleSizes=new Float32Array(MAX_PARTICLES); | |
| const particleAlphas=new Float32Array(MAX_PARTICLES); | |
| const trailGeo=new THREE.BufferGeometry(); | |
| trailGeo.setAttribute('position',new THREE.BufferAttribute(particles,3)); | |
| trailGeo.setAttribute('aLife',new THREE.BufferAttribute(particleLifetimes,1)); | |
| trailGeo.setAttribute('aSize',new THREE.BufferAttribute(particleSizes,1)); | |
| trailGeo.setAttribute('aAlpha',new THREE.BufferAttribute(particleAlphas,1)); | |
| const trailMat=new THREE.ShaderMaterial({ | |
| transparent:true, | |
| depthWrite:false, | |
| blending:THREE.AdditiveBlending, | |
| uniforms:{ | |
| uColor:{value:new THREE.Color(0x00ffff)}, | |
| uSize:{value:3.0} | |
| }, | |
| vertexShader:` | |
| attribute float aLife; | |
| attribute float aSize; | |
| attribute float aAlpha; | |
| varying float vAlpha; | |
| void main(){ | |
| vAlpha=aAlpha; | |
| vec4 mv=modelViewMatrix*vec4(position,1.0); | |
| gl_PointSize=aSize*(200.0/(-mv.z)); | |
| gl_Position=projectionMatrix*mv; | |
| } | |
| `, | |
| fragmentShader:` | |
| uniform vec3 uColor; | |
| varying float vAlpha; | |
| void main(){ | |
| float d=length(gl_PointCoord-0.5); | |
| if(d>0.5)discard; | |
| float a=smoothstep(0.5,0.0,d)*vAlpha; | |
| gl_FragColor=vec4(uColor,a); | |
| } | |
| ` | |
| }); | |
| const trailPoints=new THREE.Points(trailGeo,trailMat); | |
| scene.add(trailPoints); | |
| let particleIdx=0; | |
| function emitParticle(x,y,z){ | |
| const i=particleIdx%MAX_PARTICLES; | |
| particles[i*3]=x; | |
| particles[i*3+1]=y; | |
| particles[i*3+2]=z; | |
| particleLifetimes[i]=1.0; | |
| particleSizes[i]=0.5+Math.random()*0.5; | |
| particleAlphas[i]=0.6+Math.random()*0.4; | |
| particleIdx++; | |
| } | |
| /* βββ Floating Dust Particles βββ */ | |
| const dustCount=600; | |
| const dustGeo=new THREE.BufferGeometry(); | |
| const dustPos=new Float32Array(dustCount*3); | |
| for(let i=0;i<dustCount;i++){ | |
| dustPos[i*3]=(Math.random()-0.5)*30; | |
| dustPos[i*3+1]=Math.random()*8; | |
| dustPos[i*3+2]=(Math.random()-0.5)*30; | |
| } | |
| dustGeo.setAttribute('position',new THREE.BufferAttribute(dustPos,3)); | |
| const dustMat=new THREE.PointsMaterial({ | |
| color:0x4466aa, | |
| size:0.04, | |
| transparent:true, | |
| opacity:0.5, | |
| blending:THREE.AdditiveBlending, | |
| depthWrite:false, | |
| sizeAttenuation:true | |
| }); | |
| const dust=new THREE.Points(dustGeo,dustMat); | |
| scene.add(dust); | |
| /* βββ Lighting βββ */ | |
| // Key directional light | |
| const keyLight=new THREE.DirectionalLight(0xfff8ee,2.5); | |
| keyLight.position.set(8,10,6); | |
| keyLight.castShadow=true; | |
| keyLight.shadow.mapSize.set(2048,2048); | |
| keyLight.shadow.camera.near=0.5; | |
| keyLight.shadow.camera.far=30; | |
| keyLight.shadow.camera.left=-8; | |
| keyLight.shadow.camera.right=8; | |
| keyLight.shadow.camera.top=8; | |
| keyLight.shadow.camera.bottom=-8; | |
| keyLight.shadow.bias=-0.001; | |
| keyLight.shadow.radius=4; | |
| scene.add(keyLight); | |
| // Cyan rim light | |
| const rimLight=new THREE.DirectionalLight(0x00ccff,1.8); | |
| rimLight.position.set(-6,3,-5); | |
| scene.add(rimLight); | |
| // Magenta fill | |
| const fillLight=new THREE.DirectionalLight(0xff44cc,0.9); | |
| fillLight.position.set(-4,2,8); | |
| scene.add(fillLight); | |
| // Ambient | |
| const ambient=new THREE.AmbientLight(0x1a1a3a,0.5); | |
| scene.add(ambient); | |
| // Subtle point light near crystals for refraction | |
| const crystalLight=new THREE.PointLight(0xaaccff,1.5,15); | |
| crystalLight.position.set(0,4,0); | |
| scene.add(crystalLight); | |
| /* βββ Environment Map (procedural) βββ */ | |
| const pmrem=new THREE.PMREMGenerator(renderer); | |
| const envScene=new THREE.Scene(); | |
| envScene.background=new THREE.Color(0x0a0a20); | |
| const envLight1=new THREE.PointLight(0x00ccff,5,30); | |
| envLight1.position.set(5,5,5); | |
| envScene.add(envLight1); | |
| const envLight2=new THREE.PointLight(0xff44cc,3,30); | |
| envLight2.position.set(-5,3,-5); | |
| envScene.add(envLight2); | |
| const envLight3=new THREE.PointLight(0xffffff,2,30); | |
| envLight3.position.set(0,8,-3); | |
| envScene.add(envLight3); | |
| const envRT=pmrem.fromScene(envScene,0.04); | |
| crystalMat.envMap=envRT.texture; | |
| crystalMat.envMapIntensity=1.2; | |
| /* βββ Post-Processing βββ */ | |
| const composer=new EffectComposer(renderer); | |
| composer.addPass(new RenderPass(scene,camera)); | |
| // Bloom | |
| const bloomPass=new UnrealBloomPass( | |
| new THREE.Vector2(innerWidth,innerHeight), | |
| 0.8, // strength | |
| 0.4, // radius | |
| 0.85 // threshold | |
| ); | |
| composer.addPass(bloomPass); | |
| // Chromatic Aberration | |
| const chromaShader={ | |
| uniforms:{ | |
| tDiffuse:{value:null}, | |
| intensity:{value:0.002} | |
| }, | |
| vertexShader:`varying vec2 vUv; | |
| void main(){ | |
| vUv=uv; | |
| gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); | |
| }`, | |
| fragmentShader:`uniform sampler2D tDiffuse; | |
| uniform float intensity; | |
| varying vec2 vUv; | |
| void main(){ | |
| vec2 d=vUv-0.5; | |
| float r=length(d); | |
| float a=atan(d.y,d.x); | |
| float ca=intensity*(r*r+0.001); | |
| float off=ca*0.5; | |
| float dx=cos(a)*off; | |
| float dy=sin(a)*off; | |
| float r2=texture2D(tDiffuse,vUv+vec2(dx,dy)).r; | |
| float g=texture2D(tDiffuse,vUv).g; | |
| float b2=texture2D(tDiffuse,vUv-vec2(dx,dy)).b; | |
| gl_FragColor=vec4(r2,g,b2,1.0); | |
| }` | |
| }; | |
| const chromaPass=new ShaderPass(chromaShader); | |
| composer.addPass(chromaPass); | |
| // Output pass for tone mapping | |
| composer.addPass(new OutputPass()); | |
| /* βββ Resize βββ */ | |
| addEventListener('resize',()=>{ | |
| const w=innerWidth,h=innerHeight; | |
| camera.aspect=w/h; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(w,h); | |
| composer.setSize(w,h); | |
| }); | |
| /* βββ Animation βββ */ | |
| const clock=new THREE.Clock(); | |
| let satTime=0; | |
| function animate(){ | |
| requestAnimationFrame(animate); | |
| const t=clock.getElapsedTime(); | |
| const dt=clock.getDelta(); | |
| // Parallax | |
| mouse.x+=(mouse.tx-mouse.x)*0.05; | |
| mouse.y+=(mouse.ty-mouse.y)*0.05; | |
| const parallaxX=mouse.x*0.4; | |
| const parallaxY=-mouse.y*0.25; | |
| controls.target.x+=(parallaxX-controls.target.x)*0.03; | |
| controls.target.y+=(2.5+parallaxY-controls.target.y)*0.03; | |
| // Rotate clusters slowly | |
| centralCluster.rotation.y=t*0.1; | |
| centralCluster.position.y=2.5+Math.sin(t*0.3)*0.15; | |
| leftCluster.rotation.y=t*0.08+0.5; | |
| leftCluster.position.y=1.2+Math.sin(t*0.25+1)*0.1; | |
| rightCluster.rotation.y=t*0.07-0.5; | |
| rightCluster.position.y=0.8+Math.sin(t*0.35+2)*0.12; | |
| frontCluster.rotation.y=t*0.12; | |
| frontCluster.position.y=0.3+Math.sin(t*0.4+3)*0.08; | |
| // Satellite orbit | |
| satTime+=0.008; | |
| const satX=Math.cos(satTime*1.2)*5; | |
| const satY=2.5+Math.sin(satTime*3.6)*1.2; | |
| const satZ=Math.sin(satTime*1.2)*5; | |
| satGroup.position.set(satX,satY,satZ); | |
| satCrystal.rotation.y=t*2; | |
| satCrystal.rotation.x=Math.sin(t*1.5)*0.3; | |
| // Sat glow pulse | |
| satGlowMat.opacity=0.1+0.05*Math.sin(t*4); | |
| satGlow.scale.setScalar(1+0.15*Math.sin(t*3)); | |
| // Emit particles | |
| if(Math.random()<0.6){ | |
| emitParticle( | |
| satX+(Math.random()-0.5)*0.1, | |
| satY+(Math.random()-0.5)*0.1, | |
| satZ+(Math.random()-0.5)*0.1 | |
| ); | |
| } | |
| // Update particles | |
| for(let i=0;i<MAX_PARTICLES;i++){ | |
| if(particleLifetimes[i]>0){ | |
| particleLifetimes[i]-=0.012; | |
| particles[i*3+1]+=0.003; // slight upward drift | |
| particles[i*3]+=Math.sin(t*2+i)*0.001; | |
| particleAlphas[i]=particleLifetimes[i]*0.8; | |
| particleSizes[i]*=0.998; | |
| }else{ | |
| particleAlphas[i]=0; | |
| particleLifetimes[i]=0; | |
| } | |
| } | |
| trailGeo.attributes.position.needsUpdate=true; | |
| trailGeo.attributes.aLife.needsUpdate=true; | |
| trailGeo.attributes.aSize.needsUpdate=true; | |
| trailGeo.attributes.aAlpha.needsUpdate=true; | |
| // Animate dust | |
| const dp=dust.geometry.attributes.position.array; | |
| for(let i=0;i<dustCount;i++){ | |
| dp[i*3+1]+=0.003; | |
| if(dp[i*3+1]>8)dp[i*3+1]=0; | |
| dp[i*3]+=Math.sin(t*0.5+i*0.1)*0.002; | |
| } | |
| dust.geometry.attributes.position.needsUpdate=true; | |
| // Crystal light pulse | |
| crystalLight.intensity=1.5+0.5*Math.sin(t*1.5); | |
| // Chromatic aberration subtle variation | |
| chromaPass.uniforms.intensity.value=0.0015+0.001*Math.sin(t*2); | |
| controls.update(); | |
| composer.render(); | |
| } | |
| animate(); | |
| </script> | |
| </body> | |
| </html> |