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