Search code examples
javascriptmathrandomthree.jsspiral

Generate particles for spiral galaxy in THREE.js


I would like to create a simple spiral galaxy using THREE.Points in THEE.js.
I cannot figure out how to generate spiral arms. Is there any (not too difficult) way to do this?

Here I created this fiddle. There is a flattened sphere but without spiral arms.

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight)
camera.position.z = 1500
scene.add(camera)
const renderer = new THREE.WebGLRenderer({ clearColor: 0x000000 })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

// Random function with normal dustribution.
const normalRandom = (mean, std) => {
    let n = 0
    
    for (let i = 1; i <= 12; i++) {
      n += Math.random()
    }

    return (n - 6) * std + mean
}

const geometry = new THREE.Geometry()
const galaxySize = 1000

// Generate particles for spiral galaxy:
for (let i = 0; i < 10000; i++) {
  var theta = THREE.Math.randFloatSpread(360) 
  var phi = THREE.Math.randFloatSpread(360)
  const distance = THREE.Math.randFloatSpread(galaxySize)

  // Here I need to generate spiral arms instead of a sphere.
  geometry.vertices.push(new THREE.Vector3(
  	 distance * Math.sin(theta) * Math.cos(phi),
     distance * Math.sin(theta) * Math.sin(phi),
     distance * Math.cos(theta) / 10
  ))
}

const spiralGalaxy = new THREE.Points(geometry, new THREE.PointsMaterial({ color: 0xffffff }))
scene.add(spiralGalaxy)

function render() {
    requestAnimationFrame(render)
    renderer.render(scene, camera)
    spiralGalaxy.rotation.x += 0.01;
    spiralGalaxy.rotation.y += 0.01;
}

render()
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.min.js"></script>


Solution

  • Looks like you've already done a lot of the heavy lifting yourself, with the polar coordinate system. Keep in mind, however, that the angles are in radians, not in degrees, so instead of using 360, you should be using PI * 2. Now all you have to do is increment both distance and theta simultaneously to create a spiral, while phi remains close to 0 to create the galaxy's ecliptic plane.

    • distance will grow from 0 to galaxySize
    • theta will grow from 0 to Math.PI (or PI * 2, if you want tighter spirals)
    • phi stays close to 0
    // Temp variables to assign new values inside loop
    var norm, theta, phi, thetaVar, distance;
    
    // Generate particles for spiral galaxy:
    for (let i = 0; i < 1000; i++) {
        // Norm increments from 0 to 1
        norm = i / 1000;
    
        // Random variation to theta [-0.5, 0.5]
        thetaVar = THREE.Math.randFloatSpread(0.5);
    
        // Theta grows from 0 to Math.PI (+ random variation)
        theta = norm * Math.PI + thetaVar;
    
        // Phi stays close to 0 to create galaxy ecliptic plane
        phi = THREE.Math.randFloatSpread(0.1);
    
        // Distance grows from 0 to galaxySize
        distance = norm * galaxySize;
    
        // Here I need generate spiral arms instead of sphere.
        geometry.vertices.push(new THREE.Vector3(
            distance * Math.sin(theta) * Math.cos(phi),
            distance * Math.sin(theta) * Math.sin(phi),
            distance * Math.cos(theta)
        ));
    }
    

    You could create one loop for each arm. Notice that instead of dividing by 10 at the end, as you did in your example, I simply limited the range of phi to [-0.1, 0.1]. You can see it in action in this fiddle