| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>Recursive Polygons in 3D with Continuous Snowflakes</title> |
| <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script> |
| <script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script> |
| <style> |
| #canvas { |
| height: 500px; |
| width: 800px; |
| } |
| </style> |
| </head> |
| <body> |
| <a-scene> |
| <a-entity environment="preset: forest"></a-entity> |
|
|
| |
| |
|
|
| |
| |
|
|
| <a-entity camera position="0 1.6 0" look-controls wasd-controls></a-entity> |
| <a-plane position="0 0 0" rotation="-90 0 0" width="100" height="100" color="#FFF"></a-plane> |
| </a-scene> |
|
|
| <script> |
| AFRAME.registerComponent('snowflake', { |
| schema: { |
| size: { type: 'number', default: 0.1 }, |
| meltTime: { type: 'number', default: 15000 } |
| }, |
| init: function() { |
| let size = this.data.size; |
| |
| this.el.setAttribute('geometry', { |
| primitive: 'sphere', |
| radius: size |
| }); |
| this.el.setAttribute('material', { color: '#FFF' }); |
| |
| this.resetPosition(); |
| this.startMeltingLoop(); |
| }, |
| startMeltingLoop: function() { |
| const resetAndStartAgain = () => { |
| this.resetPosition(); |
| setTimeout(resetAndStartAgain, Math.random() * this.data.meltTime + 15000); |
| }; |
| setTimeout(resetAndStartAgain, Math.random() * this.data.meltTime + 15000); |
| }, |
| resetPosition: function() { |
| this.el.object3D.position.set( |
| Math.random() * 20 - 10, |
| 5 + Math.random() * 5, |
| Math.random() * 20 - 10 |
| ); |
| this.velocity = new THREE.Vector3( |
| (Math.random() - 0.5) * 0.01, |
| -0.02, |
| (Math.random() - 0.5) * 0.01 |
| ); |
| }, |
| tick: function() { |
| this.el.object3D.position.add(this.velocity); |
| if (this.el.object3D.position.y <= -3.5) { |
| this.el.object3D.position.y = -3.5; |
| this.velocity.set(0, 0, 0); |
| } |
| } |
| }); |
| |
| AFRAME.registerComponent('custom-controls', { |
| init: function () { |
| this.velocityY = 0; |
| this.isMovingUp = false; |
| window.addEventListener('keydown', (e) => { |
| if (e.key === 'q' || e.key === 'Q') { |
| this.isMovingUp = true; |
| } |
| if (e.key === 'e' || e.key === 'E') { |
| this.velocityY = 0; |
| } |
| }); |
| window.addEventListener('keyup', (e) => { |
| if (e.key === 'q' || e.key === 'Q') { |
| this.isMovingUp = false; |
| } |
| }); |
| }, |
| tick: function () { |
| let position = this.el.getAttribute('position'); |
| if (this.isMovingUp) { |
| this.velocityY = 0.05; |
| } else { |
| this.velocityY -= 0.01; |
| } |
| position.y += this.velocityY; |
| if (position.y < 1.6) { |
| position.y = 1.6; |
| this.velocityY = 0; |
| } |
| this.el.setAttribute('position', position); |
| } |
| }); |
| |
| function createSnowflakeCloud(x, y, z, numParticles) { |
| let cloud = document.createElement('a-entity'); |
| cloud.object3D.position.set(x, y, z); |
| |
| for (let i = 0; i < numParticles; i++) { |
| let size = Math.random() * 0.1 + 0.05; |
| let meltTime = Math.random() * 20000 + 5000; |
| setTimeout(() => { |
| let snowflakeEl = document.createElement('a-entity'); |
| snowflakeEl.setAttribute('snowflake', {size: size, meltTime: meltTime}); |
| cloud.appendChild(snowflakeEl); |
| }, i * 100); |
| } |
| |
| document.querySelector('a-scene').appendChild(cloud); |
| } |
| |
| |
| for (let x = -10; x <= 10; x += 10) { |
| for (let z = -10; z <= 10; z += 10) { |
| createSnowflakeCloud(x, 5, z, 50); |
| } |
| } |
| |
| |
| createSnowflakeCloud(0, 8, 0, 100); |
| </script> |
| </body> |
| </html> |