简体   繁体   中英

How to update shadow when updating vertices of custom geometry in a-frame / three.js

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.

初始状态,折叠,阴影正确

更新后展开,阴影从初始状态持续存在

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM