Zainab Hussain
Zainab Hussain

Reputation: 333

Three.js TimeLineLite animation not working

I'm trying to animate the TextGeometries to form a shape of box {delay} sphere {delay} and then a cone. Also, change the colors while the transition. I tried the below code for trying to animate the initial state to a box:

var pointsInsideShape = shapes[0].points.attributes.position.array;
for (i = 0; i < MAX_PARTICLES; i++) {
  var tl = new TimelineLite();
  tl.from(particleGroup.children[i].position, 2, {
    x: pointsInsideShape[ index ++ ],
    y: pointsInsideShape[ index ++ ],
    z: pointsInsideShape[ index ++ ]
  })
}

The console throws the error below:

particleGroup.children[i] is undefined

I checked the particleGroup has objects, however I'm getting this error. I'm not sure why this isn't working.

Currently all the particles are in the center, I'm trying to animate them to form a shape of a sphere, then a delay before the next shape animation and so on. Repeat the loop at the end. If someone could point me in the right direction, it'd be much appreciated. Thank you!

Below is the code:

var renderer, camera, scene, light, shapes, triggers, particleCount, particleGroup,
  defaultAnimationSpeed, morphAnimationSpeed, colorToStartWith, textGeometries, loader, typeface,
  animationVars;

const MAX_PARTICLES = 100,
  PARTICLE_SIZE = .65;

var sts = {
  config: function() {
    shapes = [{
        "geoCode": new THREE.BoxBufferGeometry(13, 13, 13),
        "textMat": new THREE.MeshPhongMaterial({
          color: 0x029894
        }),
        "color": 0x029894,
        "size": [50, 50, 50]
      },
      {
        "geoCode": new THREE.SphereBufferGeometry(25, 33, 33),
        "textMat": new THREE.MeshPhongMaterial({
          color: 0x8F3985
        }),
        "color": 0x8F3985,
        "size": [25, 33, 33]
      },
      {
        "geoCode": new THREE.ConeBufferGeometry(25, 50, 30),
        "textMat": new THREE.MeshPhongMaterial({
          color: 0x11659C
        }),
        "color": 0x11659C,
        "size": [25, 50, 30]
      }
    ];
    triggers = document.getElementsByTagName('span');
    particleGroup = new THREE.Group();
    defaultAnimationSpeed = 1;
    morphAnimationSpeed = 18;
    normalSpeed = (defaultAnimationSpeed / 100)
    fullSpeed = (morphAnimationSpeed / 100)
    colorToStartWith = '#8F3985';
    textGeometries = new Array();
  },
  initScene: function() {

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

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

    // Camera and position
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.y = -25;
    camera.position.z = 45;
    camera.rotation.x = -.45;

    // Lighting
    light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);
    var light2 = new THREE.DirectionalLight(0xffffff, 1.5);
    light2.position.set(0, -1, 0);
    scene.add(light2);

    // Texts
    loader = new THREE.FontLoader();
    typeface = 'https://raw.githubusercontent.com/7dir/json-fonts/master/fonts/cyrillic/roboto/Roboto_Regular.json';

    //particleGroup
    particleGroup = new THREE.Group();
    particleGroup.position.x = 0;
    particleGroup.position.y = -45;
    particleGroup.position.z = 0;
    scene.add(particleGroup);
  },
  fullScreen: function() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  },
  attachEvents: function() {
    window.addEventListener('resize', this.fullScreen, false);
  },
  addShapes: function() {
    for (idx = 0; idx < shapes.length; idx++) {
      shapes[idx].geometry = shapes[idx].geoCode.toNonIndexed();
      m = new THREE.MeshLambertMaterial({
        color: shapes[idx].color,
        opacity: 0,
        transparent: true,
        wireframe: true
      });
      shapes[idx].geometry.center();
      shape = new THREE.Mesh(shapes[idx].geometry, m);
      scene.add(shape);
    }
  },
  computePointsInside: function(idx) {
    shapes[idx].points = this.fillWithPoints(shapes[idx].geometry, MAX_PARTICLES);
  },
  addText: function() {
    loader.load(typeface, (font) => {
      //var x, y, z, index1, index2;
      //x = y = z = index1 = index2 = 0;
      var m = new THREE.MeshPhongMaterial({
        color: 0x8F3985,
        opacity: .8,
        specular: 0xffffff,
        shininess: 100
      });
      for (i = 0; i < MAX_PARTICLES; i++) {
        g = new THREE.TextGeometry(Math.random() < .5 ? '6' : '9', {
          font: font,
          size: PARTICLE_SIZE,
          height: 0.1
        });

        var text = new THREE.Mesh(g, m);
        text.position.x = 0;
        text.position.y = 0;
        text.position.z = 0;

        text.rotation.x = Math.random() * 2 * Math.PI;
        text.rotation.y = Math.random() * 2 * Math.PI;
        text.rotation.z = Math.random() * 2 * Math.PI;

        text.scale.x = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);
        text.scale.y = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);
        text.scale.z = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);

        //var pointsInsideShape = shapes[0].points.attributes.position.array;

        //text.position.x = pointsInsideShape[ index2 ++ ];
        //text.position.y = pointsInsideShape[ index2 ++ ];
        //text.position.z = pointsInsideShape[ index2 ++ ];

        particleGroup.add(text);
      }
    });
  },
  animationSequence: function() {
    var x, y, z, index;
    x = y = z = index = 0;
    var pointsInsideShape = shapes[0].points.attributes.position.array;
    for (i = 0; i < MAX_PARTICLES; i++) {
      var tl = new TimelineLite();
      tl.from(particleGroup.children[i].position, 2, {
        x: pointsInsideShape[index++],
        y: pointsInsideShape[index++],
        z: pointsInsideShape[index++]
      })
    }
  },
  animate: function() {
    window.requestAnimationFrame(sts.animate);
    particleGroup.rotation.y += 0.005;
    renderer.render(scene, camera)
  },
  fillWithPoints: function(geometry, count) {

    var ray = new THREE.Ray()

    var size = new THREE.Vector3();
    geometry.computeBoundingBox();
    var bbox = geometry.boundingBox;

    var points = [];

    var dir = new THREE.Vector3(1, 1, 1).normalize();
    for (var i = 0; i < count; i++) {
      var p = setRandomVector(bbox.min, bbox.max);
      points.push(p);
    }

    function setRandomVector(min, max) {
      var v = new THREE.Vector3(
        THREE.Math.randFloat(min.x, max.x),
        THREE.Math.randFloat(min.y, max.y),
        THREE.Math.randFloat(min.z, max.z)
      );
      if (!isInside(v)) {
        return setRandomVector(min, max);
      }
      return v;
    }

    function isInside(v) {

      ray.set(v, dir);
      var counter = 0;

      var pos = geometry.attributes.position;
      var faces = pos.count / 3;
      var vA = new THREE.Vector3(),
        vB = new THREE.Vector3(),
        vC = new THREE.Vector3();

      for (var i = 0; i < faces; i++) {
        vA.fromBufferAttribute(pos, i * 3 + 0);
        vB.fromBufferAttribute(pos, i * 3 + 1);
        vC.fromBufferAttribute(pos, i * 3 + 2);
        if (ray.intersectTriangle(vA, vB, vC)) counter++;
      }

      return counter % 2 == 1;
    }

    return new THREE.BufferGeometry().setFromPoints(points);
  },
  init: function() {
    this.config();
    this.initScene();
    this.attachEvents();
    this.addShapes();
    this.computePointsInside(0);
    this.computePointsInside(1);
    this.computePointsInside(2);
    this.addText();
    this.animate();
    this.animationSequence();
  }
}

sts.init();
body {
  margin: 0;
  overflow: hidden;
}
<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>

Upvotes: 2

Views: 133

Answers (1)

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11293

I added a slight delay to animationSequence

setTimeout(() => this.animationSequence(), 1000);

This is not the best approach and ideally you'd wait until particleGroup.children is loaded with data

After bit of investigation I found the culprit.

addText: function() {
    loader.load(typeface, (font) => {

Wrap that in Promise

addText: function() {
    return new Promise((resolve, reject) => {
    loader.load(typeface, (font) => {

Use put rest of logic in then callback

this.addText().then(_ => {
      this.animate();
      this.animationSequence();
    })

Working example

var renderer, camera, scene, light, shapes, triggers, particleCount, particleGroup,
defaultAnimationSpeed, morphAnimationSpeed, colorToStartWith, textGeometries, loader, typeface,
animationVars;

const MAX_PARTICLES = 100, PARTICLE_SIZE = .65;

var sts = {
  config: function() {
    shapes = [
      {
        "geoCode": new THREE.BoxBufferGeometry(13, 13, 13),
        "textMat": new THREE.MeshPhongMaterial({ color: 0x029894 }),
        "color": 0x029894,
        "size": [50, 50, 50]
      },
      {
        "geoCode": new THREE.SphereBufferGeometry(25, 33, 33),
        "textMat": new THREE.MeshPhongMaterial({ color: 0x8F3985 }),
        "color": 0x8F3985,
        "size": [25, 33, 33]
      },
      {
        "geoCode": new THREE.ConeBufferGeometry(25, 50, 30),
        "textMat": new THREE.MeshPhongMaterial({ color: 0x11659C }),
        "color": 0x11659C,
        "size": [25, 50, 30]
      }
    ];
    triggers = document.getElementsByTagName('span');
    particleGroup = new THREE.Group();
    defaultAnimationSpeed = 1;
    morphAnimationSpeed = 18;
    normalSpeed = (defaultAnimationSpeed / 100),
    fullSpeed = (morphAnimationSpeed / 100)
    colorToStartWith = '#8F3985';
    textGeometries = new Array();
  },
  initScene: function() {

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

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

    // Camera and position
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.y = -25;
    camera.position.z = 45;
    camera.rotation.x = -.45;

    // Lighting
    light = new THREE.DirectionalLight( 0xffffff, 1 );
		light.position.set( 1, 1, 1 ).normalize();
    scene.add(light);
		var light2 = new THREE.DirectionalLight( 0xffffff, 1.5 );
		light2.position.set( 0, - 1, 0 );
		scene.add( light2 );

    // Texts
    loader = new THREE.FontLoader();
    typeface = 'https://raw.githubusercontent.com/7dir/json-fonts/master/fonts/cyrillic/roboto/Roboto_Regular.json';

    //particleGroup
    particleGroup = new THREE.Group();
    particleGroup.position.x = 0;
    particleGroup.position.y = -45;
    particleGroup.position.z = 0;
    scene.add(particleGroup);
  },
  fullScreen: function() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  },
  attachEvents: function() {
    window.addEventListener('resize', this.fullScreen, false);
  },
  addShapes: function() {
    for (idx = 0; idx < shapes.length; idx++) {
      shapes[idx].geometry = shapes[idx].geoCode.toNonIndexed();
      m = new THREE.MeshLambertMaterial({
        color: shapes[idx].color,
        opacity: 0,
        transparent: true,
        wireframe: true
      });
      shapes[idx].geometry.center();
      shape = new THREE.Mesh(shapes[idx].geometry, m);
      scene.add(shape);
    }
  },
  computePointsInside: function(idx) {
    shapes[idx].points = this.fillWithPoints(shapes[idx].geometry, MAX_PARTICLES);
  },
  addText: function() {
    return new Promise((resolve, reject) => {
      loader.load(typeface, (font) => {
        //var x, y, z, index1, index2;
        //x = y = z = index1 = index2 = 0;
        var m = new THREE.MeshPhongMaterial({color: 0x8F3985, opacity: .8, specular: 0xffffff, shininess: 100});
        for (i = 0; i < MAX_PARTICLES; i++) {
          g = new THREE.TextGeometry( Math.random() < .5 ? '6' : '9', {
            font: font,
            size: PARTICLE_SIZE,
            height: 0.1
          });

          var text = new THREE.Mesh(g, m);
          text.position.x = 0;
          text.position.y = 0;
          text.position.z = 0;

          text.rotation.x = Math.random() * 2 * Math.PI;
          text.rotation.y = Math.random() * 2 * Math.PI;
          text.rotation.z = Math.random() * 2 * Math.PI;

          text.scale.x = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);
          text.scale.y = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);
          text.scale.z = (Math.random() * (0.95 - 0.1) + 0.95).toFixed(4);

          //var pointsInsideShape = shapes[0].points.attributes.position.array;

          //text.position.x = pointsInsideShape[ index2 ++ ];
          //text.position.y = pointsInsideShape[ index2 ++ ];
          //text.position.z = pointsInsideShape[ index2 ++ ];

          particleGroup.add(text);
        }
        resolve();
      });
    })
  },
  animationSequence: function() {
    var x, y, z, index;
    x = y = z = index = 0;
    
    const children = particleGroup.children;
    
    var pointsInsideShape = shapes[0].points.attributes.position.array;
    for (i = 0; i < MAX_PARTICLES; i++) {
      var tl = new TimelineLite();
      const child = children[i];
      
      tl.from(child.position, 2, {
        x: pointsInsideShape[ index ++ ],
        y: pointsInsideShape[ index ++ ],
        z: pointsInsideShape[ index ++ ]
      })
    }
  },
  animate: function() {
    window.requestAnimationFrame(sts.animate);
    particleGroup.rotation.y +=0.005;
    renderer.render(scene, camera)
  },
  fillWithPoints: function(geometry, count) {
    
    var ray = new THREE.Ray()
    
    var size = new THREE.Vector3();
    geometry.computeBoundingBox();
    var bbox = geometry.boundingBox;
    
    var points = [];
    
    var dir = new THREE.Vector3(1, 1, 1).normalize();
    for (var i = 0; i < count; i++) {
      var p = setRandomVector(bbox.min, bbox.max);
      points.push(p);
    }
    
    function setRandomVector(min, max){
      var v = new THREE.Vector3(
        THREE.Math.randFloat(min.x, max.x),
        THREE.Math.randFloat(min.y, max.y),
        THREE.Math.randFloat(min.z, max.z)
      );
      if (!isInside(v)){return setRandomVector(min, max);}
      return v;
    }
    
    function isInside(v){
      
      ray.set(v, dir);
      var counter = 0;

      var pos = geometry.attributes.position;
      var faces = pos.count / 3;
      var vA = new THREE.Vector3(), vB = new THREE.Vector3(), vC = new THREE.Vector3();
  
      for(var i = 0; i < faces; i++){
        vA.fromBufferAttribute(pos, i * 3 + 0);
        vB.fromBufferAttribute(pos, i * 3 + 1);
        vC.fromBufferAttribute(pos, i * 3 + 2);
        if (ray.intersectTriangle(vA, vB, vC)) counter++;
      }
      
      return counter % 2 == 1;
    }
    
    return new THREE.BufferGeometry().setFromPoints(points);
  },
  init: function() {
    this.config();
    this.initScene();
    this.attachEvents();
    this.addShapes();
    this.computePointsInside(0);
    this.computePointsInside(1);
    this.computePointsInside(2);
    this.addText().then(_ => {
      this.animate();
      this.animationSequence();
    })
  }
}

sts.init();
body {
  margin: 0;
  overflow: hidden;
}
<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>

Upvotes: 3

Related Questions