A-Frame with aframe-physics-system - address cannon elements from javsascript? - three.js

I'm in an A-Frame scene, using its underlying three.js to create objects like:
var boxMaterial = new THREE.MeshPhongMaterial( { map: boxTexture, side: THREE.BackSide, bumpMap: boxBumpMap } );
var boxGeometry = new THREE.BoxBufferGeometry( 20, 20, 20 );
var box01 = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box01);
How can I, using javascript, address the underlying Cannon.js of aframe-physics-system to add the StaticBody attribute?

I'm quite sure you cannot add DOM attributes to js non-DOM objects, such as THREE objects.
The static body attribute is actually creating a new object (CANNON.Body with its type set to Cannon.Body.STATIC). What aframe-physics does is synchronization - the THREE.js mesh with the Cannon.Body.
Source code: body creation here, and syncing here.
You could create a CANNON.Body and synchronize its position with your box, but i would approach it differently:
You can have an empty a-frame entity with the physics attribute:
<a-entity position="0 2 -3" three-setup dynamic-body></a-entity>
but with the material and geometry set in a a-frame component:
AFRAME.registerComponent("three-setup", {
init: function() {
var boxMaterial = new THREE.MeshPhongMaterial({
side: THREE.FrontSide,
});
var boxGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
var box01 = new THREE.Mesh(boxGeometry, boxMaterial);
this.el.setObject3D('mesh', box01)
}
})
Check it out in my fiddle
As Don McCurdy pointed out, having a custom a-frame component has more advantages:
- You can listen for the body-loaded event which will notice you when the CANNON.Body is initialized
- it will be accessible with a simple reference: this.el.body
Otherwise, you would need to create a CANNON.Body, and on each render loop apply its position and rotation to your box.

Related

How to control size and color of textGeometry with dat.gui?

I found out that I could use dat.gui to control size and color of textGeometry besides changing color and size for every other scene through editing .js file. But probably bad architecture of my code, I am not able to control or even add gui folder to the scene. It probably has something to do with FontLoader that I'm using.
I tried placing dat.gui inside and outside my textGeometry creation function, none of them worked. As far as I understood, every time size or color changes it should dispose and remove the created mesh to create a new one with the new color/size parameters (i also update for every each keydown event to create a new mesh so that's my thought).
textGeometry, textMesh, textMaterial etc. are defined in global
function textCreation() {
const fontLoader = new FontLoader();
fontLoader.load(
"node_modules/three/examples/fonts/droid/droid_sans_bold.typeface.json",
(droidFont) => {
textGeometry = new TextGeometry(initText, { //initText variable gets updated with each keydown
size: 5,
height: 2,
font: droidFont,
parameters: {
size: 5,
height: 2,
},
});
textMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
textMesh = new THREE.Mesh(textGeometry, textMaterial);
group = new THREE.Group();
group.add(textMesh);
scene.add(group);
}
And here is the dat.gui controller that I tried to place in and out of this function.
let textFolder = gui.addFolder("Text Controls");
textFolder.add(textGeometry, "size", 10, 20).onChange(function () {
textGeometry.setSize(textGeometry.size);
// Probably dispose and remove old mesh from scene and create mesh with these new parameters
});
textFolder.addColor(textGeometry, "color").onChange(function () {
textGeometry.setColor(textGeometry.color);
I couldn't manage to add ANY dat.gui controller without breaking the scene. By the way I'm kinda new to JavaScript and three.JS so further explanations are welcome if there are any.
textGeometry.setSize(textGeometry.size);
This does not work since there isn't a setSize() method. You have to call dispose() on the existing geometry and then create a new one based on your updated parameters. You then assign the new geometry to your text mesh. So something like:
const params = {
color: '#ffffff',
size: 5
};
textFolder.add(params, "size", 10, 20).onChange(function (value) {
textGeometry.dispose();
textGeometry = new TextGeometry(initText, {
size: value,
height: 2,
font: droidFont
});
textMesh.geometry = textGeometry;
});
The material's color can be changed without creating a new object.
textFolder.addColor(params, 'color').onChange(function (value) {
textMaterial.color.set(value);
});

Merging multiple TextBufferGeometries in a scene

I'm trying to change the following code to use TextBufferGeometry instead of TextGeometry as I feel like it may improve the performance in my use case. The code below works for rendering multiple text elements into my scene however when I change..
let geometry = new THREE.TextGeometry(... to let geometry = new THREE.TextBufferGeometry(...
this code no longer renders text into the scene. I'm unsure of what needs changing in order to make use of TextBufferGeometry
const materialWhite = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide });
const textArray = [
{ text: `Message 12345`, zDistance: 100 },
{ text: `Message 67890`, zDistance: 200 },
{ text: `Message 13579`, zDistance: 300 },
];
var singleFontGeometry = new THREE.Geometry();
for (let index = 0; index < textArray.length; index++) {
loaderFonts.load('./assets/fonts/Oxanium.json', function(font) {
let geometry = new THREE.TextGeometry(`${textArray[index].text}`, {
font: font,
size: 20,
height: 1
});
let mesh = new THREE.Mesh(geometry, materialWhite);
mesh.position.set(100, 100, textArray[index].zDistance);
singleFontGeometry.mergeMesh(mesh);
});
}
var meshFont = new THREE.Mesh(singleFontGeometry, materialWhite);
this.scene.add(meshFont);
The code above will require three main changes:
In the loop, create THREE.TextBufferGeometry instances instead of TextGeometry.
Instead of setting a position on each mesh, bake that transform into the geometry before merging, with geometry.translate(...).
Instead of adding geometries to a THREE.Geometry instance, create a merged BufferGeometry instance using BufferGeometryUtils. BufferGeometry does not have a .mergeMesh() method, and .merge() overwrites vertices rather than creating a union, so neither are available here.
Also, but somewhat unrelated to the question, you probably don't want to load the font inside your For-loop, to avoid unnecessary requests. With these changes, the code should look something like this:
import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
var mergedGeometry;
// Load the font once.
loaderFonts.load('./assets/fonts/Oxanium.json', function(font) {
// Then create a *BufferGeometry for each item of text.
var geometries = textArray.map(function(text) {
var geometry = new THREE.TextBufferGeometry(text.text, {
font: font,
size: 20,
height: 1
});
// Take each item's unique Z offset, and bake it into the geometry.
geometry.translate(0, 0, text.zDistance);
return geometry;
});
// Merge all text geometries.
mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
var mesh = new THREE.Mesh(mergedGeometry, materialWhite);
// Put the shared x=100,y=100 offset onto the mesh, so it can be changed later.
mesh.position.set(100, 100, 0);
scene.add(mesh);
});
^Note that BufferGeometryUtils is not included in the three.js build itself, and must be added to your application separately, like loaders, controls, and other things in the examples/js* folder.

A-frame, Polygon animation

I'm trying to animate a polygon in A-frame, seen also here Add polygon in A-frame:
// Create new shape out of the points:
var shape = new THREE.Shape( vector2List );
// Create geometry out of the shape
var geometry = new THREE.ShapeGeometry( shape );
// Give it a basic material
var material = new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 1} );
// Create a mesh using our geometry and material
var mesh = new THREE.Mesh( geometry, material ) ;
// add it to the entity:
this.el.object3D.add( mesh );
The goal now is to change the opacity of the shape in an animation. I don't know how to access the shape/polygon attributes within the animation - maybe something like this:
// animation
let opacityAnimation = document.createElement( 'a-animation' );
following lines are not clear:
opacityAnimation.setAttribute( 'mesh.material', 'opacity' );
opacityAnimation.setAttribute( 'to', '0' );
opacityAnimation.setAttribute( 'dur', '5000' );
this.el.appendChild( opacityAnimation );
edit:
here is a live-example: fiddle
You set mesh.material to opacity. I believe You want to specify the animated attribute. As the animation element should look like this:
<a-animation attribute="material.opacity"
to = "0"
dur = "5000">
</a-animation>
Your js must be setting those accordingly:
// animation
let opacityAnimation = document.createElement( 'a-animation' );
opacityAnimation.setAttribute( 'attribute', 'material.opacity' );
opacityAnimation.setAttribute( 'to', '0' );
opacityAnimation.setAttribute( 'dur', '5000' );
this.el.appendChild( opacityAnimation );
Now, if you want to use the material component with a custom/polygon geometry, You need to create it a bit differently.
Instead of creating a new mesh, consisting of its own geometry, and material, lets make the geometry in three.js, and use the existing material component.
So i simply replaced adding the new mesh with a simple:
var myShape = new THREE.Shape(points);
var geometry = new THREE.ShapeGeometry(myShape);
this.el.getObject3D('mesh').geometry = geometry;
You should wait till the object3D is loaded to do it properly, in my fiddle i just made a simple timeout.
Now we can use the animation component as intended, and change the material.opacity.
Check it live on a box here.
Check it live on a polygon here.

Three.js: Add a texture to an object just on the outside

I'm very new with Three.js and I'm trying to make a ring:
http://www.websuvius.it/atma/myring/preview.html
I have a background texture ( the silver one ) and another one with a text.
I want the text only on the ring external face.
This is part of my code:
var loader = new THREE.OBJLoader( manager );
var textureLoader = new THREE.TextureLoader( manager );
loader.load( 'assets/3d/ring.obj', function ( event ) {
var object = event;
var geometry = object.children[ 0 ].geometry;
var materials = [];
var backgroundTexture = textureLoader.load('img/texture/silver.jpg');
backgroundTexture.flipY = false;
var background = new THREE.MeshBasicMaterial({
map: backgroundTexture,
color: 0xffffff
});
materials.push(background);
var customTexture = textureLoader.load('img/text.png');
customTexture.flipY = false;
var custom = new THREE.MeshBasicMaterial({
map: customTexture,
transparent: true,
opacity: 1,
color: 0xffffff
});
materials.push(custom);
mesh = THREE.SceneUtils.createMultiMaterialObject(geometry, materials);
mesh.position.y=-50;
scene.add(mesh);
}, onProgress, onError );
It is possible?
Thanks
The reason behind your issue appears to be in your .obj file. Judging from a quick glance at the texture coordinates stored in the file, the inside of the ring uses the same part of the texture image as the outside of the ring.
Increasing the transparent parts of the image won't help. Neither will the attempts to stop the texture from repeating. Those would help if the texture coordinates were larger than 1 but this is not your case unfortunately.
However, there are several solutions:
Split the object in a 3D modeling software to two objects - outside and inside of the ring - and apply the texture only to the first one.
Adjust the UV coordinates of the object in a 3D modeling software.
Adjust the UV coordinates of the vertices programmatically after loading the object to Three.JS

Add a 3D model to a exsisting THREE.Scene()

I create a scene, add a couple of boxes and I can move the camera with the keyboard just fine.
I want to add a 3D model. In several tutorials, I saw something like:
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load( "test.js", function( geometry ) { createScene( geometry) } );
function createScene( geometry ) {
var mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial({color: 0xbbbbbb}) );
mesh.scale.set(10, 10, 10);
mesh.position.y = -350;
mesh.position.x = -650;
group.add(mesh);
}
But for the other element I wrote something like:
MovingCube = new THREE.Mesh(MovingCubeGeom, new THREE.MeshFaceMaterial());
MovingCube.position.set(0, 25, 0);
scene.add(MovingCube);
How can I add a 3D model from a .js converted .obj at my scene?
The first one loads the model from and external file which contains a JSON representation of the geometry and sends it to the createScene function as an instance of the THREE.Geometry class when the external file has finished loading.
The second one the geometry is already in the variable MovingCubeGeom.
The second example is basically the same as what is in the createScene function of the first example.
You don't need to convert an obj to js, you can just use the THREE.OBJLoader class

Resources