0
votes

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?

1

1 Answers

1
votes

According to the docs, and this anwser:

When using a buffer geometry (and the model is loaded as such), then you need to set the needsUpdate flag on the changed attribute object:

// Vertices change
var vertices = node.geometry.attributes.position.array;
// .... updates ...

// nofitication
node.geometry.attributes.position.needsUpdate = true

Glitch here.