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()