| | <!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> |