Fountain Emitter
Upward particle fountain with gravity and per-particle lifetime.
What this code does
- Particles spawn at origin with random angle and upward power.
- Gravity pulls them down; they respawn when life ends or hit ground.
- Additive blending and radial sprite yield a soft water look.
JavaScript (plain)
// Ultra realistic water fountain with droplet physics
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x0a0a12)
const camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 1000)
camera.position.set(0, 3, 12)
camera.lookAt(0, 2, 0)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
document.querySelector('#app').appendChild(renderer.domElement)
// Enhanced lighting for realistic water reflections
const ambient = new THREE.AmbientLight(0x404080, 0.4)
scene.add(ambient)
const key = new THREE.DirectionalLight(0xffffff, 1.2)
key.position.set(10, 20, 10)
scene.add(key)
// Create realistic water droplet geometry and materials
const dropletGeometry = new THREE.SphereGeometry(0.05, 8, 6)
const waterMaterial = new THREE.MeshPhysicalMaterial({
color: 0x006694,
metalness: 0.0,
roughness: 0.1,
transmission: 0.9,
thickness: 0.01,
ior: 1.33, // Water's index of refraction
transparent: true,
opacity: 0.8
})
// Create instanced mesh for water droplets
const count = 800
const instancedDroplets = new THREE.InstancedMesh(dropletGeometry, waterMaterial, count)
scene.add(instancedDroplets)
// Initialize droplet physics arrays
const positions = new Float32Array(count * 3)
const velocities = new Float32Array(count * 3)
const sizes = new Float32Array(count)
const life = new Float32Array(count)
// Initialize particles with realistic fountain physics
for (let i = 0; i < count; i++) {
const i3 = i * 3
const angle = Math.random() * Math.PI * 2
const elevation = Math.random() * 0.3 + 0.7
const speed = 12 + Math.random() * 8
positions[i3] = (Math.random() - 0.5) * 0.3
positions[i3 + 1] = 0.1 + Math.random() * 3
positions[i3 + 2] = (Math.random() - 0.5) * 0.3
velocities[i3] = Math.cos(angle) * Math.sin(elevation) * speed * 0.3
velocities[i3 + 1] = Math.cos(elevation) * speed
velocities[i3 + 2] = Math.sin(angle) * Math.sin(elevation) * speed * 0.3
sizes[i] = 0.8 + Math.random() * 1.5
life[i] = 2 + Math.random() * 2
}
// Create water pool at base
const poolGeometry = new THREE.CylinderGeometry(3.8, 3.8, 0.1, 32)
const poolMaterial = new THREE.MeshPhysicalMaterial({
color: 0x006694,
transmission: 0.95,
ior: 1.33,
transparent: true,
opacity: 0.8
})
const waterPool = new THREE.Mesh(poolGeometry, poolMaterial)
waterPool.position.y = 0.05
scene.add(waterPool)
// Animation loop with realistic water physics
function animate() {
requestAnimationFrame(animate)
const matrix = new THREE.Matrix4()
const gravity = 18
const dt = 0.016
for (let i = 0; i < count; i++) {
const i3 = i * 3
// Apply gravity and physics
velocities[i3 + 1] -= gravity * dt
positions[i3] += velocities[i3] * dt
positions[i3 + 1] += velocities[i3 + 1] * dt
positions[i3 + 2] += velocities[i3 + 2] * dt
// Droplet deformation based on velocity
const speed = Math.sqrt(velocities[i3] ** 2 + velocities[i3 + 1] ** 2 + velocities[i3 + 2] ** 2)
const deformation = Math.min(speed * 0.1, 0.3)
const scaleX = sizes[i] * (1 - deformation)
const scaleY = sizes[i] * (1 + deformation * 1.5)
const scaleZ = sizes[i] * (1 - deformation)
life[i] -= dt
// Respawn when droplet hits ground or expires
if (positions[i3 + 1] <= 0.15 || life[i] <= 0) {
positions[i3] = (Math.random() - 0.5) * 0.3
positions[i3 + 1] = 0.1
positions[i3 + 2] = (Math.random() - 0.5) * 0.3
const angle = Math.random() * Math.PI * 2
const elevation = Math.random() * 0.3 + 0.7
const speed = 12 + Math.random() * 8
velocities[i3] = Math.cos(angle) * Math.sin(elevation) * speed * 0.3
velocities[i3 + 1] = Math.cos(elevation) * speed
velocities[i3 + 2] = Math.sin(angle) * Math.sin(elevation) * speed * 0.3
life[i] = 2 + Math.random() * 2
}
// Update instance matrix
matrix.makeScale(scaleX, scaleY, scaleZ)
matrix.setPosition(positions[i3], positions[i3 + 1], positions[i3 + 2])
instancedDroplets.setMatrixAt(i, matrix)
}
instancedDroplets.instanceMatrix.needsUpdate = true
renderer.render(scene, camera)
}
animate()