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).
Any more info needed, please ask. Any help is much appreciated as ever.
material.needsUpdate
flag, but it didn't help ( You can see it in the fiddle though) – Piotr Adam Milewskiwindow.onload
callback. The<a-scene test>
node loads earlier, so thetest
is nonexistent. When You move thetest
to one of Your nodes it works. If I think of any other solution i'll let you know – Piotr Adam Milewski