I have a GLTF model with a mesh that represents low poly water. After loading the GLTF model in A-Frame I am trying to animate the water by modifying the y-coordinate of each vertex in a tick() function.
The problem is that the modified vertices are not drawn on screen, only the initial modification is drawn. What am I missing?
To reproduce the problem I made a simple GLTF model with a single mesh which consists of 32 triangles in a 4x4 grid:
{
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 96,
"max": [
24
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 25,
"max": [
1.0,
0.0,
1.0
],
"min": [
-1.0,
0.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 25,
"max": [
0.0,
1.0,
0.0
],
"min": [
0.0,
1.0,
0.0
],
"type": "VEC3"
}
],
"asset": {
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 192,
"byteOffset": 0,
"target": 34963
},
{
"buffer": 0,
"byteLength": 300,
"byteOffset": 192,
"target": 34962
},
{
"buffer": 0,
"byteLength": 300,
"byteOffset": 492,
"target": 34962
}
],
"buffers": [
{
"byteLength": 792,
"uri": "data:application/octet-stream;base64,AAABAAIAAAADAAEAAwAEAAEAAwAFAAQABQAGAAQABQAHAAYABwAIAAYABwAJAAgABgAIAAoABgAKAAsABAAGAAsABAALAAwAAQAEAAwAAQAMAA0AAgABAA0AAgANAA4ADgANAA8ADgAPABAAEAAPABEAEAARABIADwATABEADwAUABMADQAUAA8ADQAMABQADAAVABQADAALABUACwAWABUACwAKABYAFQAWABcAFQAXABgAFAAVABgAFAAYABMAAACAvwAAAAAAAIC/AAAAvwAAAAAAAAC/AAAAvwAAAAAAAIC/AACAvwAAAAAAAAC/AAAAvwAAAAAAAACAAACAvwAAAAAAAAAAAAAAvwAAAAAAAAA/AACAvwAAAAAAAAA/AAAAvwAAAAAAAIA/AACAvwAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAA/AAAAAAAAAAAAAACAAAAAAAAAAAAAAAC/AAAAAAAAAAAAAIC/AAAAPwAAAAAAAAC/AAAAPwAAAAAAAIC/AACAPwAAAAAAAAC/AACAPwAAAAAAAIC/AACAPwAAAAAAAAAAAAAAPwAAAAAAAACAAAAAPwAAAAAAAAA/AAAAPwAAAAAAAIA/AACAPwAAAAAAAIA/AACAPwAAAAAAAAA/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA"
}
],
"materials": [
{
"name": "Water",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.0,
0.46700000762939453,
0.7450000047683716,
1.0
],
"metallicFactor": 0.0,
"roughnessFactor": 1.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1
},
"indices": 0,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"mesh": 0,
"name": "water"
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}
To load and animate the GLTF mode in A-Frame I am using the following HTML + JavaScript:
<html>
<head>
<script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('aniwater', {
init: function() {
const data = this.data;
this.setupWater();
this.el.addEventListener('object3dset', this.setupWater.bind(this));
},
tick: function() {
const mesh = this.el.getObject3D('mesh');
if (!mesh) {
return;
}
mesh.traverse(function (node) {
// water is child node
if (node.name === "water") {
var vertexCount = node.geometry.attributes.position.count;
var vertices = node.geometry.attributes.position.array;
// update y of vertex
for (var i = 0; i < vertexCount; i++) {
var vy = vertices[i * 3 + 1];
// get the data associated to it
var vprops = this.waves[i];
// update the position of the vertex
vy = vprops.y + Math.sin(vprops.ang) * vprops.amp;
// increment the angle for the next frame
vprops.ang += vprops.speed;
vertices[i * 3 + 1] = vy;
}
// compute normals
node.geometry.computeFaceNormals();
node.geometry.computeVertexNormals();
// this does not work?
node.geometry.verticesNeedUpdate=true;
}
});
},
setupWater: function() {
const mesh = this.el.getObject3D('mesh');
if (!mesh) {
return;
}
mesh.traverse(function (node) {
// water is child node
if (node.name === "water") {
// array with wave information
this.waves = [];
var vertexCount = node.geometry.attributes.position.count;
var vertices = node.geometry.attributes.position.array;
// create an array to store new data associated to each vertex
for (var i = 0; i < vertexCount; i++) {
// get y position of each vertex
var vy = vertices[i * 3 + 1];
// store some data associated to it
this.waves.push({
y: vy,
// random start angle
ang: Math.random() * Math.PI * 2,
// random amplitude
amp: 0.5 + Math.random() * 0.15,
// random speed
speed:0.016 + Math.random() * 0.032
});
}
}
});
}
});
</script>
<a-scene>
<a-assets>
<a-asset-item id="water_asset" src="water.gltf">
</a-assets>
<a-gltf-model id="water" src="#water_asset" position="0 0 -4" aniwater></a-gltf-model>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
The GLTF model is loaded into an a-gltf-model using assets and a custom component named aniwater is attached.
The custom component is based on the cube-env-map.js in the aframes-extra repository (https://github.com/donmccurdy/aframe-extras/blob/master/src/misc/cube-env-map.js) and defines 3 functions.
The setupWater() function creates an array with information about each vertex so that the water can be animated. This is for now based on the code found here: https://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/
The init() function attaches the setupWater() function to the model when it is loaded. This seems to work fine.
The actual modification of the vertices is done in the function tick(). For each vertex the y coordinate is updated and a new value for y is calculated. After this the normals are recomputed and the verticesNeedUpdate flag is set to true to indicate a change. This does not seems to work.
I think the problem comes from the fact that the vertices of a GLTF model are not stored in geometry.vertices but rather in geometry.attributes.position.array since the triangles are indexes to vertices. This makes me suspect that the verticesNeedUpdate flag only works on geometry.vertices, but I am not sure.
The vertices in the position array do seem to be updated, because I can see the shading change on the mesh which is caused by the updated normals.
How can I make A-Frame use the updated vertices when drawing the water mesh?