1
votes

I have created a custom component in a-frame that creates a bespoke geometry. I am using the tick function to animate the update of the geometry's vertices. That is all working fine, however the shadow on the geometry does not update.

In my below example I toggle a flat shape between 2 states, folded and unfolded. When, for example it animates from folded to unfolded, the faces retain the shadow as though it were still folded.

Here is the HTML, please see <a-fold> in this example starting with the folded state (this can be changed to 'unfolded' by changing that attribute).

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script> 
  </head>
  <body>
    <a-scene test>
        <a-fold
            state="folded"
            color="blue"
            position="-7.5 1 -20">
        </a-fold>
        <a-plane 
            position="0 0 -4" 
            rotation="-90 0 0" 
            width="20" 
            height="20" 
            color="#7BC8A4">
        </a-plane>
        <a-sky 
            color="#ECECEC">
        </a-sky>
    </a-scene>
  </body>
</html>

and the javascript

//a trigger to test state change
AFRAME.registerComponent('test', {
  init: function () {
    setTimeout(function(){
          var target  = document.querySelector('a-fold');
          target.setAttribute('changestate', true);
    }, 2000);
  }
});

//component
AFRAME.registerComponent('folder', {

    schema: {         
        state: {type: 'string', default: 'unfolded'},
        color: {type: 'color', default: '#000'},
        changestate: {type: 'boolean', default: false},
        changedur: {type: 'number', default: 400},
    },

    store: {          
          unfolded: [{"x":15,"y":0,"z":0},{"x":15,"y":15,"z":0},{"x":0,"y":15,"z":0},{"x":0,"y":0,"z":0},{"x":0,"y":15,"z":0},{"x":15,"y":0,"z":0},],
          folded: [{"x":15,"y":0,"z":0},{"x":12,"y":12,"z":5},{"x":0,"y":15,"z":0},{"x":3,"y":3,"z":5},{"x":0,"y":15,"z":0},{"x":15,"y":0,"z":0},],       
    },

    update: function () {       
        this.geometry = new THREE.Geometry();
        var geometry = this.geometry
        var verts = this.store[this.data.state]

        $.each( verts, function( i, v3 ) {
            geometry.vertices.push ( new THREE.Vector3(v3.x, v3.y, v3.z) );
        });     
        geometry.faces.push(new THREE.Face3(0, 1, 2))
        geometry.faces.push(new THREE.Face3(3, 4, 5))
        geometry.computeBoundingBox();
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        this.material = new THREE.MeshStandardMaterial({color: this.data.color});
        this.material.side = THREE.DoubleSide;
        this.material.needsUpdate = true
        this.mesh = new THREE.Mesh(this.geometry, this.material);
        this.mesh.castShadow = true;
        this.mesh.receiveShadow = true;
        this.el.setObject3D('mesh', this.mesh);

    },

    tick: function (t, td) {
        if (this.data.changestate === true){
            var dur = this.data.changedur
            var geom = this.el.getObject3D('mesh').geometry
            var currVerts = geom.vertices
            var toVerts = this.store[this.gotoState()]
            var somethingChanged = false;
            var allAxis = ["x", "y", "z"];
            var thisParent = this

            $.each( currVerts, function( i, vert) {
                var curr = currVerts[i]
                var to = toVerts[i]
                $.each( allAxis, function( i, axis ) {  
                    if (thisParent.approxEqual(curr[axis], to[axis])) {
                    somethingChanged = somethingChanged || false;
                    } else if (curr[axis] < to[axis]) {
                        var step = thisParent.stepCalc(curr[axis], to[axis], dur, td)
                        curr[axis] += step;
                        somethingChanged = true;
                    } else {
                        var step = thisParent.stepCalc(curr[axis], to[axis], dur, td)
                        curr[axis] -= step;
                        somethingChanged = true;
                    }
                });
            });
            geom.verticesNeedUpdate = somethingChanged;
        }
    },

    gotoState: function (){
        var to = ""
        var current = this.data.state
        var states = Object.keys(this.store)
        $.each( states, function( i, state) {
            if ( state != current ){
                to = state
            }
        }); 
        return to;
    },

    approxEqual: function (x, y) {
        return Math.abs(x - y) < 0.00001;
    },

    stepCalc: function (curr, to, dur, delta){
        var distance = Math.abs(curr - to)
        var speed = distance/dur
        var step = speed*delta

        return step;
    },

    remove: function () {
        this.el.removeObject3D('mesh');
    }

});

//primitive
AFRAME.registerPrimitive('a-fold', {

  defaultComponents: {
    folder: {}
  },

  mappings: {
    state: 'folder.state',
    color: 'folder.color',
    changestate: 'folder.changestate',
    changedur: 'folder.changedur'

  }

});

Sorry, I know its a lot but I don't want to simplify it in case I lose the problem. As you can see in the component I have tried to add this.material.needsUpdate = true as well as adding this.mesh.castShadow = true and this.mesh.receiveShadow = true but it does not make a difference.

On a side note, if I do an animation of the whole entity (ie a rotation) I can see the material reflecting the light so the material does respond to lighting dynamically, just not when I change the shape by updating the vertices.

Here are 2 images that demonstrate what I mean.

initial state, folded, shadows are correct

after update, unfolded, shadows persist from initial state

You can see that in 2nd image, although the plane is flat, its shadows suggest it has a fold. And it does the same the other way around (ie if it starts unfolded, the shadow is correct and that persists when it folds).

Here is a js fiddle

Any more info needed, please ask. Any help is much appreciated as ever.

1
Here's a fiddle with the animation. I thought You lack a material.needsUpdate flag, but it didn't help ( You can see it in the fiddle though)Piotr Adam Milewski
@PiotrAdamMilewski thanks for trying.. and for getting the animation to work. Out of interest, in my original fiddle, why did that bit of js not work when it was in the js pane?Nick
I think the bottom part (js) executes within an window.onload callback. The <a-scene test> node loads earlier, so the test is nonexistent. When You move the test to one of Your nodes it works. If I think of any other solution i'll let you knowPiotr Adam Milewski
ok, great, thank youNick

1 Answers

2
votes

Lighting is calculated using normal vectors, in your case you're moving the mesh vertices but leaving the normal vectors, which is why the lighting doesn't change. You need to add geometry.computeVertexNormals(); in your tick function after you've altered the vertices.

In 3D shadows are different to shading or lighting, the problem you're having is related to lighting but not shadows. Which is why adjusting the mesh.castShadow properties didn't behave like you expected.