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