I have an A-Frame component that at some point creates a "base" entity, with a bunch of children elements (basically, boxes with colored faces) hanging from it. After all those children elements are done (meshes included), I want to merge their geometries, using the geometry-merger component. The problem is that when I add this component, all children component need to be completely initialized: otherwise, children geometries will not be properly merged (if they are not yet available in each of the children).
I thought it would be enough to wait for loaded
to be fired in the "base" entity, with a code such as:
base.addEventListener('loaded', (e) => {
base.setAttribute('geometry-merger', {preserveOriginal: true});
base.setAttribute('material', {vertexColors: 'face'});
});
(in the init
function of the component I'm writting).
The fact is that it works like a charm in Firefox, but not in Chrome.
I've debugged it, and it seems that in Chrome 'loaded' is fired on the "base" entity even when the geometry (property 3Dobject
) is still not set in the children (so, as I understand, they are not totally "loaded").
So, the question: is there any event or some hook in the lifecycle of an entity that is run when all children are initialized?
I've found a minimal example: cubes-loaded.html
and cubes-loaded.js
, below:
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
<script src="cubes-loaded.js"></script>
</head>
<body>
<a-scene>
<a-entity cubes position="0 0 -4"></a-entity>
</a-scene>
</body>
</html>
AFRAME.registerComponent('cubes', {
init: function () {
this.base = document.createElement('a-entity');
this.base.addEventListener('loaded', (e) => {
console.log("Base loaded, child object3DMap:", this.base.children[0].object3DMap);
});
this.el.appendChild(this.base);
let box = document.createElement('a-entity');
box.setAttribute('geometry', {primitive: 'box'});
this.base.appendChild(box);
},
play: function () {
console.log("Play run, child object3DMap:", this.base.children[0].object3DMap);
}
});
In Firefox, this example writes:
Base loaded, child object3DMap: Object { mesh: {…} }
Play run, child object3DMap: Object { mesh: {…} }
In Chrome, this example writes:
Base loaded, child object3DMap: {}
Play run, child object3DMap: {mesh: J}
By the way, running the code in play
seems to be a good way of being sure the meshes are in the children.
While playing with this minimal example, I think I found the answer to my question. The problem with the example is that the base is inserted in the DOM before the child is attached to it. That way, the 'loaded' event has the chance of being fired before initialization of the child is complete, and apparently Chrome does exactly that. In Firefox, it seems the event is not fired until the child is initialized, at least in this case.
So, the example can be fixed just by attaching the base to the DOM after the child is attached to it:
AFRAME.registerComponent('cubes', {
init: function () {
this.base = document.createElement('a-entity');
this.base.addEventListener('loaded', (e) => {
console.log("Base loaded, child object3DMap:", this.base.children[0].object3DMap);
});
let box = document.createElement('a-entity');
box.setAttribute('geometry', {primitive: 'box'});
this.base.appendChild(box);
this.el.appendChild(this.base);
}
});
Sorry for the noise, the answer is straightforward once you understand the order of attachment.