Aframe: getting vertices of any object - three.js

Is there a way we can get the vertices of an object in a scene. This is either for a primitive or for a loaded model. For example
<a-entity geometry='primitive:box' rotation='0 30 0'></a-entity>
or
<a-entity gltf-model='#model'></a-entity>

For geometry, defaults to buffer attributes (vertices in array) contained in:
el.getObject3D('mesh').geometry.attributes
To get easier to manage, set to non-buffer (geometry="primitive: box; buffer: false") and get from:
el.getObject3D('mesh').geometry.vertices
For gltf-model, it also has a geometr(ies) (buffer geometry):
el.getObject3D('mesh').traverse(node => {
if (node.geometry) {
// node.geometry.attributes...
}
});

Related

A-Frame Frustum Culling

How do I turn off frustum culling on a gltf model in A-Frame? I know in Three.js you can just traverse the object and add node.frustumCulled = false. I've tried
AFRAME.registerComponent('disable-culling', {
init: function(){
var object3D = this.el.sceneEl.object3D;
object3D.traverse((node) => {
node.frustumCulled = false
})
}
})
but that doesn't work. Does anyone have any idea? The entity is
<a-entity
id="ball"
scale="0.3 0.3 0.3"
position="0 0 -7"
gltf-model="#ballModel"
disable-culling
animation-mixer="clip: *; loop: once; clampWhenFinished: true;"
shadow>
</a-entity>
I had a similar problem which was solved with frustum culling -
el.addEventListener('model-loaded', () => {
const model = el.getObject3D('mesh');
model.traverse((node) => {
if (node.isMesh) {
node.frustumCulled = false;
}
});
});
I wonder if your solution didn't work simply because the model hadn't finished loading.
Go back with your 3D model to Blender, select your model on object mode and then press "ctrl A" - apply all transformations.
I just had this problem where animated models were getting culled before fully exiting the scene. In my case, the cause seemed to be that the object scale was too small. Once I scaled up the object up in Blender and re-exported the gltf file, the models were culled correctly.

Is it possible to append to an animated glb object in threejs/aframe?

I have an object moving about a scene, which I exported as a .glb (model A).
Within Aframe I'm playing the animation on model A using the animation-mixer tag and can see the object moving around. Now what I'm trying to do is append another model (model B), so that model B is also moving around the scene. However, model B just stays in place - at the specified model A position.
Is there a way to accomplish this in Threejs/Aframe?
I have revisited this technique and have the below code, so far, but the position of the model flickers between it's static position and where it's being set to in the tick.
<a-entity id="mantaray-path" gltf-model="#mantaray-path-glb" animation-mixer="timeScale: 1; loop: true;" pathanim></a-entity>
<a-entity id="mantaray" gltf-model="#mantaray-glb" animation-mixer="timeScale: 1;"></a-entity>
AFRAME.registerComponent("pathanim", {
init: function() {
this.el.addEventListener('model-loaded', this.update.bind(this));
},
tick: function() {
this.el.object3D.traverse(function(child) {
if (child.name == "mantaraypath") {
mantaray.object3D.position = child.position;
}
});
}
});

How to declare a "mask" material using A-Frame.js

I'm trying to make a scene where there's a "hole in the wall".
This requires a plane with square removed, and then a material applied to the plane with the following properties:
Invisible to the camera
Hides any other objects from being rendered that are behind it
There's an example of this with three.js here, but how can I do it with the a-frame material syntax?
The "Mask".
Looking at the box-hole example, to create the illusion, Lee creates two boxes.
1) The box which is "in the hole"
2) A slightly bigger invisible box without a top - to cloak the first one. The top is removed to work as a "hole" through which you can see the first box
How it can be done in THREE.js
The cloaking is done by preventing the second box from rendering any color. From Lee's example:
let material = new THREE.MeshBasicMaterial({
colorWrite: false;
})
The docs state, that the flag can be used to create invisible objects hiding others.
How it can be done in a-frame
I'm afraid you can't simply make the "cloak" material in a-frame. The colorWrite property is not exposed in the material component.
What i think the simplest way would be - is creating a cloak component, which will create the second box in THREE.js:
AFRAME.registerComponent('cloak', {
init: function() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
geometry.faces.splice(4, 2) // cut out the top faces
let material = new THREE.MeshBasicMaterial({
colorWrite: false
})
let mesh = new THREE.Mesh(geometry, material)
mesh.scale.set(1.1, 1.1, 1.1)
this.el.object3D.add(mesh)
}
})
and use it like this:
<a-box material="src: myPic.png; side: back;" cloak>
Check it out in this codepen. With a HIRO marker, you should get a hole like this:
Using models, or other objects as "cloaks"
Here we need to apply the colorWrite=false magic to each node/child of the model.
init: function() {
// make sure the model is loaded first
this.el.addEventListener('model-loaded', e=>{
let mesh = this.el.getObject3D('mesh') // grab the mesh
if (mesh === undefined) return; // return if no mesh :(
mesh.traverse(function(node) { // traverse through and apply settings
if (node.isMesh && node.material) { // make sure the element can be a cloak
node.material.colorWrite = false
node.material.needsUpdate = true;
}
});
})
}
Also make sure the cloak is rendered before the elements that needs cloaking:
<a-marker>
<a-entity gltf-model="#wall-with-a-hole" cloak-component></a-entity>
<!-- the other stuff that needs to be cloaked-->
</a-marker

how to implement complex models in aframe

I'm pretty new to aframe and the ECS-modeling technique, so I probably didn't fully grasp how the architecture should be used.
I want to model something like a robotic arm: in a simplified version that is a base, on top of that a rotator and the arm itself. The model is loaded from a single json-file and consist of several nested objects for the different parts.
How would something like this be implemented in aframe if I want to be able to control the different degrees of freedom independently (which means setting object.rotation-values on the different childs of the object itself)?
One thing I thought of was to implement the loading of the model-file as one component and each degree-of-freedom as a seperate component. So basically something like this:
<a-entity robot-model="..." base-rotation="123" arm-pitch="10" />
Or would it be a better way to use registerPrimitive for something like this?
My first take on it looks like this:
registerComponent('robot', {
schema: {type: 'asset'},
update() {
// - load and parse model using THREE.ObjectLoader
// - once ready, assign property this.parts with the various
// parts of the robot-arm
}
});
registerComponent('dof-1', {
schema: {type: 'number'},
dependencies: ['robot'],
init() {
this.robot = this.el.components.robot;
},
tick(t, dt) {
if (!this.robot.parts) { return; } // not ready yet
// update values (left out here: acceleration etc)
this.robot.parts.dof1.rotation.x = this.data;
}
});
// more parts / dof implemented likewise
I'm assuming you've already created and rigged a 3D model using software like Blender, Maya, or Cinema4D. If not, the article Animation from Blender to three.js is a good starting point.
Once you've done that, you can import the model into A-Frame with any format that supports skinning/rigging. THREE.ObjectLoader (.json) or THREE.GLTFLoader (.gltf) are good options, and there are already A-Frame components that wrap these loaders. Assuming you're using JSON and the object-model component from A-Frame Extras, you could do:
<a-entity object-model="src: url(my-model.json)"></a-entity>
At this point you should see a model in the scene, without having written any JavaScript, but it won't be animating yet. If you know what animation you want up front, you can create the animations in keyframes or morph targets using the same modeling software: Blender, Maya, or Cinema4D. Assuming you included the animations when you exported the model, you can use the animation-mixer component (also from A-Frame Extras) as follows:
<a-entity object-model="src: url(my-model.json)"
animation-mixer="clip: *;"></a-entity>
This will play all animations at once. You could use a clip name, instead of *, to play a specific animation.
If your animations need to be computed at runtime, and can't be baked into the model, you'll need to write a custom component. This gets complicated quickly, but the basics aren't too bad:
<a-entity object-model="src: url(my-model.json)"
custom-animation></a-entity>
And the JS:
AFRAME.registerComponent('custom-animation', {
tick: function (t, dt) {
var mesh = this.el.getObject3D('mesh');
// With complex models, you may need to loop over `mesh.children`
// in case the mesh you want to animate is a child of another
// object in your model file.
if (!mesh || !mesh.isSkinnedMesh) { return; }
mesh.traverse(function (node) {
if (node.isBone && node.name === 'arm') {
node.rotation.x += dt * Math.PI / 1000;
}
});
}
});

Three.js Calculating Vertex Normals

I'm experimenting with one of the examples, namely the webgl_loader_obj.html to load an .obj file exported from blender into three.js
This works and displays the model exactly as expected.
Now i was looking at the use of material.shading = THREE.SmoothShading.
For this to work, the model needs to have vertex normals.
the exported file from blender has no normals defined.
So i looked at using computeVertexNormals to calculate the required normals.
however this doesn't appear to be doing anything, the resulting view is of the unsmoothed model.
further to this, as a test, i exported the same model, with normals.
loading it straight in, it appeared smoothed.
If i then did computeFaceNormals() & computeVertexNormals() it would result in the unsmoothed mesh again.
var loader = new THREE.OBJLoader( manager );
loader.load( 'test.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = new THREE.MeshLambertMaterial( { color: 0xff6600 });
child.geometry.computeFaceNormals();
child.geometry.computeVertexNormals();
child.material.shading = THREE.SmoothShading;
}
} );
Geometry.computeVertexNormals() "smooths" the vertex-normals by computing each vertex-normal to be the average of the face-normals of all faces that share that vertex.
If each face of your geometry has unique vertices (i.e., no vertices are shared with a neighboring face), then computeVertexNormals() will result in each face's vertex-normals being the same as the face-normal, and the mesh shading will appear faceted.
three.js r.71

Resources