Search code examples
javascripthtmlcssthree.jsparticle-system

THREE.js particles not showing up inside a shape


I've been trying to modify this demo. Currently the particles are placed in a text geometry, my end goal here is opposite. I'd like to:

  1. Replace the text geometries with a box, cone and sphere.
  2. Replace the particles with numbers.

So basically, have the numbers placed inside one of the shapes and animate the particles to form the next shape with a delay.

First, I tried changing the shapes, leaving the particles and everything else as is. However, for some reason the particles aren't showing up. I'm not sure why the particles aren't rendered? Console logs no errors.

Also, what would be the fastest way to have numbers as particles? It'd be great if someone could point me in the right direction. Thank you!

Below is the modified code:

// Options
const shapes = [{
      "geoCode": new THREE.ConeGeometry(25, 50, 30),
      "color": 0x11659C,
    },
    {
      "geoCode": new THREE.SphereGeometry(25, 33, 33),
      "color": 0x8F3985,
    },
    {
      "geoCode": new THREE.BoxGeometry(50, 50, 50),
      "color": 0x029894,
    }
  ],
  triggers = document.getElementsByTagName('span'),
  particleCount = 1000,
  particleSize = .3,
  defaultAnimationSpeed = 1,
  morphAnimationSpeed = 18,
  color = '#FFFFFF';

// Renderer
const renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Ensure Full Screen on Resize
function fullScreen() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('resize', fullScreen, false)

// Scene
const scene = new THREE.Scene();

// Camera and position
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);

camera.position.y = -45;
camera.position.z = 45;

// Lighting
const light = new THREE.AmbientLight(0xFFFFFF, 1);
scene.add(light);

// Particle Vars
const particles = new THREE.Geometry();

const pMaterial = new THREE.PointsMaterial({
  size: particleSize,
});

// Texts
const loader = new THREE.FontLoader();
const typeface = 'https://dl.dropboxusercontent.com/s/bkqic142ik0zjed/swiss_black_cond.json?';

//CONTINUE

loader.load(typeface, (font) => {
  Array.from(shapes).forEach((shape, idx) => {
    shapes[idx].geometry = shapes[idx].geoCode;
    const material = new THREE.MeshLambertMaterial({
      color: shapes[idx].color,
      opacity: .5,
      transparent: true,
      wireframe: true
    });
    const mesh = new THREE.Mesh(shapes[idx].geometry, material);
    //THREE.GeometryUtils.center(shapes[idx].geometry)
    scene.add(mesh);
    shapes[idx].particles = new THREE.Geometry();
    shapes[idx].points = THREE.GeometryUtils.randomPointsInGeometry(shapes[idx].geometry, particleCount);
    createVertices(shapes[idx].particles, shapes[idx].points)
    enableTrigger(shape, idx, triggers[idx]);
  });
});

// Particles
for (var i = 0; i < shapes; i++) {

  const vertex = new THREE.Vector3();
  vertex.x = 0;
  vertex.y = 0;
  vertex.z = 0;
  particles.vertices.push(vertex);
}

function createVertices(emptyArray, points) {
  for (var p = 0; p < particleCount; p++) {
    const vertex = new THREE.Vector3();
    vertex.x = points[p]['x'];
    vertex.y = points[p]['y'];
    vertex.z = points[p]['z'];

    emptyArray.vertices.push(vertex);
  }
}

function enableTrigger(trigger, idx, el) {
  el.setAttribute('data-disabled', false);

  el.addEventListener('click', () => {
    morphTo(shapes[idx].particles, el.dataset.color);
  })

  if (idx == 0) {
    morphTo(shapes[idx].particles, el.dataset.color);
  }
}

const particleSystem = new THREE.Points(
  particles,
  pMaterial
);

particleSystem.sortParticles = true;

// Add the particles to the scene
scene.add(particleSystem);

// Animate
const normalSpeed = (defaultAnimationSpeed / 100),
  fullSpeed = (morphAnimationSpeed / 100)

let animationVars = {
  speed: normalSpeed,
  color: color,
  rotation: 100
}

function animate() {
  particleSystem.rotation.y += animationVars.speed;
  particles.verticesNeedUpdate = true;

  camera.position.z = animationVars.rotation;
  camera.position.y = animationVars.rotation;
  camera.lookAt(scene.position);

  particleSystem.material.color = new THREE.Color(animationVars.color);

  window.requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

function morphTo(newParticles, color = '#FFFFFF') {

  TweenMax.to(animationVars, 2, {
    ease: Linear.easeNone,
    color: color
  });

  particleSystem.material.color.setHex(color);

  for (const i = 0; i < particles.vertices.length; i++) {
    TweenMax.to(particles.vertices[i], 2, {
      ease: Elastic.easeOut.config(0.1, .3),
      x: newParticles.vertices[i].x,
      y: newParticles.vertices[i].y,
      z: newParticles.vertices[i].z
    })
  }
}
body {
  font-family: 'Titillium Web', sans-serif;
  margin: 0;
  overflow: hidden;
}

.triggers {
  bottom: 20px;
  color: white;
  left: 50%;
  position: absolute;
  text-align: center;
  transform: translateX(-50%);
  width: 100%;
  z-index: 10;
}

.triggers span {
  cursor: pointer;
  display: inline-block;
  font-size: 14px;
  margin: 0 20px;
  padding: 2px 4px;
  transition: opacity 0.5s, color 0.5s;
}

.triggers span[data-disabled="true"] {
  opacity: 0.3;
  pointer-events: none;
}

.triggers span:hover {
  color: red;
}
<div class="triggers">
  <span data-disabled="true" data-color="#3D8CD0">CLICK</span>
  <span data-disabled="true" data-color="#D32A7B">TO</span>
  <span data-disabled="true" data-color="#2AD37A">SWITCH</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js" type="text/javascript"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/GeometryUtils.js" type="text/javascript"></script>

EDIT: Particles are now rendered, the updated code is here --> Fiddle


Solution

  • Apologies, I was actually able to fix the particles not rendering. I was iterating over the shapes instead of the particleCount.

    // Particles
    for (var i = 0; i < shapes; i++) {
                        ^^^^^^
      let vertex = new THREE.Vector3();
      vertex.x = 0;
      vertex.y = 0;
      vertex.z = 0;
      particles.vertices.push(vertex);
    }
    

    Below is the updated code that loads the particles.

    // Options
    const shapes = [{
          "geoCode": new THREE.ConeGeometry(25, 50, 30),
          "color": 0x11659C,
        },
        {
          "geoCode": new THREE.SphereGeometry(25, 33, 33),
          "color": 0x8F3985,
        },
        {
          "geoCode": new THREE.BoxGeometry(50, 50, 50),
          "color": 0x029894,
        }
      ],
      triggers = document.getElementsByTagName('span'),
      particleCount = 1000,
      particleSize = 3,
      defaultAnimationSpeed = 1,
      morphAnimationSpeed = 18,
      color = '#FFFFFF';
    
    // Renderer
    const renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    
    // Ensure Full Screen on Resize
    function fullScreen() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
    
    window.addEventListener('resize', fullScreen, false)
    
    // Scene
    const scene = new THREE.Scene();
    
    // Camera and position
    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
    
    camera.position.y = -45;
    camera.position.z = 45;
    
    // Lighting
    const light = new THREE.AmbientLight(0xFFFFFF, 1);
    scene.add(light);
    
    // Particle Vars
    const particles = new THREE.Geometry();
    
    const pMaterial = new THREE.PointsMaterial({
      size: particleSize,
    });
    
    // Texts
    const loader = new THREE.FontLoader();
    const typeface = 'https://dl.dropboxusercontent.com/s/bkqic142ik0zjed/swiss_black_cond.json?';
    
    //CONTINUE
    
    loader.load(typeface, (font) => {
      Array.from(shapes).forEach((shape, idx) => {
        shapes[idx].geometry = shapes[idx].geoCode;
        const material = new THREE.MeshLambertMaterial({
          color: shapes[idx].color,
          opacity: .5,
          transparent: true,
          wireframe: true
        });
        const mesh = new THREE.Mesh(shapes[idx].geometry, material);
        //THREE.GeometryUtils.center(shapes[idx].geometry)
        scene.add(mesh);
        shapes[idx].particles = new THREE.Geometry();
        shapes[idx].points = THREE.GeometryUtils.randomPointsInGeometry(shapes[idx].geometry, particleCount);
        createVertices(shapes[idx].particles, shapes[idx].points)
        enableTrigger(shape, idx, triggers[idx]);
      });
    });
    
    // Particles
    for (var i = 0; i < particleCount; i++) {
    
      const vertex = new THREE.Vector3();
      vertex.x = 0;
      vertex.y = 0;
      vertex.z = 0;
      particles.vertices.push(vertex);
    }
    
    function createVertices(emptyArray, points) {
      for (var p = 0; p < particleCount; p++) {
        const vertex = new THREE.Vector3();
        vertex.x = points[p]['x'];
        vertex.y = points[p]['y'];
        vertex.z = points[p]['z'];
    
        emptyArray.vertices.push(vertex);
      }
    }
    
    function enableTrigger(trigger, idx, el) {
      el.setAttribute('data-disabled', false);
    
      el.addEventListener('click', () => {
        morphTo(shapes[idx].particles, el.dataset.color);
      })
    
      if (idx == 0) {
        morphTo(shapes[idx].particles, el.dataset.color);
      }
    }
    
    let particleSystem = new THREE.Points(
      particles,
      pMaterial
    );
    
    particleSystem.sortParticles = true;
    
    // Add the particles to the scene
    scene.add(particleSystem);
    
    // Animate
    const normalSpeed = (defaultAnimationSpeed / 100),
      fullSpeed = (morphAnimationSpeed / 100)
    
    let animationVars = {
      speed: normalSpeed,
      color: color,
      rotation: 100
    }
    
    function animate() {
      particleSystem.rotation.y += animationVars.speed;
      particles.verticesNeedUpdate = true;
    
      camera.position.z = animationVars.rotation;
      camera.position.y = animationVars.rotation;
      camera.lookAt(scene.position);
    
      particleSystem.material.color = new THREE.Color(animationVars.color);
    
      window.requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }
    
    animate();
    
    function morphTo(newParticles, color = '#FFFFFF') {
    
      TweenMax.to(animationVars, 2, {
        ease: Linear.easeNone,
        color: color
      });
    
      particleSystem.material.color.setHex(color);
    
      for (let i = 0; i < particles.vertices.length; i++) {
        TweenMax.to(particles.vertices[i], 2, {
          ease: Elastic.easeOut.config(0.1, .3),
          x: newParticles.vertices[i].x,
          y: newParticles.vertices[i].y,
          z: newParticles.vertices[i].z
        })
      }
    }
    body {
      font-family: 'Titillium Web', sans-serif;
      margin: 0;
      overflow: hidden;
    }
    
    .triggers {
      bottom: 20px;
      color: white;
      left: 50%;
      position: absolute;
      text-align: center;
      transform: translateX(-50%);
      width: 100%;
      z-index: 10;
    }
    
    .triggers span {
      cursor: pointer;
      display: inline-block;
      font-size: 14px;
      margin: 0 20px;
      padding: 2px 4px;
      transition: opacity 0.5s, color 0.5s;
    }
    
    .triggers span[data-disabled="true"] {
      opacity: 0.3;
      pointer-events: none;
    }
    
    .triggers span:hover {
      color: red;
    }
    <div class="triggers">
      <span data-disabled="true" data-color="#3D8CD0">CLICK</span>
      <span data-disabled="true" data-color="#D32A7B">TO</span>
      <span data-disabled="true" data-color="#2AD37A">SWITCH</span>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js" type="text/javascript"></script>
    <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/GeometryUtils.js" type="text/javascript"></script>

    Note: This post only has a solution for particles not rendering. I will accept the post that answers how I should go about replacing the particles with a number.