Jake Dluhy
Jake Dluhy

Reputation: 627

Morph Target Animations in THREE.js for a custom mesh

I am working on create an animation that displays a revolution of a 2D function about an axis to create a 3D surface. I've been able to render the surface successfully, and now I'm trying to create the animation section using MorphTargets.

Unfortunately, although I have successfully pushed all of the morph frames into my geometry, I'm having trouble getting the animation to play. All of the animation examples I have found use blender to import models, and so aren't completely helpful (many are outdated as well).

I was looking at the morphing horse as an example ( http://threejs.org/examples/#webgl_morphtargets_horse ), and tried to replicate the code as much as possible, but I haven't been successful.

Here's my code initializing the morphVerticeHolder, which is a 2D array to hold the vertices of each animation frame

 //First indices of multiarray corresponds to morph frame, second to the vertice
 var morphVerticeHolder = [];
 for(var i = 0; i < 20; i++) {
   morphVerticeHolder[i] = [];
 }

And here is my code pushing the vertices along the axis. Note that I simultaneously push my morph vertices as well. mI stands for mesh interval.

for(var i = xstart; i <= xend; i+= mI) {
  geo.vertices.push(new THREE.Vector3(i,0,0));
  for(var j = 0; j < 20; j++) { //20 Frames of the morph animation
    morphVerticeHolder[j].push(new THREE.Vector3(i,0,0));
  }
}

This code pushes all of the vertices. i ranges along the x axis, while j ranges along theta, filling in the vertices for the mesh.

//Push the vertices
  var tmesh = 2*Math.PI/meshPoints; //Theta mesh interval
  verticeCounter = 0;
  for(var i = xstart; i <= xend; i+=mI) {
    var rMain = solveEquation(i);
    var rSecond = solveEquation(i+mI);
    var counter = 0;
    for(var j = 0; j < 2*Math.PI; j += tmesh) {
      geo.vertices.push(new THREE.Vector3(i, rMain*Math.cos(j/1000),rMain*Math.sin(j/1000)));
      for(var k = 0; k < 20; k++) {
        morphVerticeHolder[k].push(new THREE.Vector3(i, rMain*Math.cos(j*(k+1)/20),rMain*Math.sin(j*(k+1)/20)));
      }
    }
  }

The dividing by 1000 is for the purpose of starting the original mesh out close to 0, and then animating it rotating using the morph vertices (which range from 1/20 of the desired value to 20/20.

Then I push the faces. You can try to interpret my algorithm, but essentially I figured out how to keep track of which vertice is where.

for(var i = 0; i < meshPoints; i++) {
    var sI = meshPoints*(i+1);
    var sI2 = meshPoints*(i+2);
    for(var j = 0; j < meshPoints-1; j ++) {
      if(i === 0) {
        //First Point, Fill end
        geo.faces.push(new THREE.Face3(sI+j, sI+j+1, 0));
        geo.faces.push(new THREE.Face3(sI+j+1, sI+j, 0));
        //Filll Body
        geo.faces.push(new THREE.Face3(sI+j, sI+j+1, sI2+j));
        geo.faces.push(new THREE.Face3(sI+j+1, sI+j, sI2+j));
        geo.faces.push(new THREE.Face3(sI+j+1, sI2+j, sI2+j+1));
        geo.faces.push(new THREE.Face3(sI2+j, sI+j+1, sI2+j+1));
      } else if(i === meshPoints-1) {
        //Last Point, Fill end
        geo.faces.push(new THREE.Face3(sI+j, sI+j+1, meshPoints-1));
        geo.faces.push(new THREE.Face3(sI+j+1, sI+j, meshPoints-1));
      } else {
        geo.faces.push(new THREE.Face3(sI+j, sI+j+1, sI2+j));
        geo.faces.push(new THREE.Face3(sI+j+1, sI+j, sI2+j));
        geo.faces.push(new THREE.Face3(sI+j+1, sI2+j, sI2+j+1));
        geo.faces.push(new THREE.Face3(sI2+j, sI+j+1, sI2+j+1));
      }
    }
  }

And here's the rest, initializing and such.

for(var k = 0; k < 20; k++) {
    geo.morphTargets.push( { name: "target" + k, vertices: morphVerticeHolder[k]} );
  }

  var uniforms = {
    resolution: { type: "v2", value: new THREE.Vector2 },
    zmax: { type: "f", value: maxz},
    zmin: { type: "f", value: minz}
  };
  var mat = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: document.getElementById('cubeVertexShader').innerHTML,
    fragmentShader: document.getElementById('cubeFragmentShader').innerHTML
  });
  functionObject = new THREE.Mesh( geo, mat);
  this.scene.add(functionObject);
  functionObject.name = 'current';

  this.animation = new THREE.MorphAnimation( functionObject, 'Revolution' );
  this.animation.play();

  this.setWithinRender(function() {
    this.animation.update(.1);
  });

this.setWitinRender puts the anonymous function into the render loop of THREE.js (this is a function called separately from the setup for THREE.

As I mentioned above, the mesh renders, and if I get rid of the /1000 when pushing the original vertices I get the whole surface. However, the animation, set up as above, doesn't play. Does anyone have any insight into this?

Thanks!

Upvotes: 0

Views: 2535

Answers (1)

Jake Dluhy
Jake Dluhy

Reputation: 627

After a little bit more research, and delving into the source code, I discovered that the material used must have the morphTargets attribute set to true. I had tried this with the shader material that I was using, but with that you have to manually apply the morphTargetInfluences in the vertex shader.

Therefore, I switched the material over to

var mat = new THREE.MeshNormalMaterial( { color: 0x990000, morphTargets: true } );

and everything worked out fine!

Upvotes: 4

Related Questions