I've been trying to import a gltf model from blender into three js, but I'm having trouble rotating it as whenever I try to set its quaternion to anything but (0,0,0,1), the model is deformed. I've included screenshots of how the model looks with the (0,0,0,1) quaternion and then what it looks like when I change the quaternion to (0,1,0,1).
Here's my code for importing the model
loader.load('./resources/Machina_Colored.gltf',
function(gltf){
//pos and quat are a THREE.Vector3 and THREE.Quaternion respectively
const mMass = 0;
const size = new THREE.Vector3(4,8,4); //this is the size of the model in blender
const mShape = new Ammo.btBoxShape(new Ammo.btVector3(size.x * 0.5, size.y * 0.5, size.z * 0.5));
mShape.setMargin(0.05);
const mObj = gltf.scene.children[0];
createRigidBody(mObj,mShape,mMass,pos,quat); //this seems to work with any quaternion it's just the model itself that is having problems
},
function(xhr){
console.log(((xhr.loaded/xhr.total) * 100) + "% loaded");
},
function(error){
console.log('An error occured');
});
Here is the model (working normally) with the (0,0,0,1) Quaternion
And then here's what happens when I use the (0,1,0,1) Quaternion
I've also included a screenshot of what my file looks like in blender just in case
Thanks to WestLangley's comment I added these lines of code to the top of my program
const eul = new THREE.Euler(0,0,0); //this now controls the rotation of the model
quat.setFromEuler(eul);
and the rotation is reflected without deformation
Related
I am trying to apply an image dynamically on a gltf loaded mesh.
The code to load the model looks like:
const gltfLoader = new GLTFLoader();
const url = 'resources/models/mesh.gltf';
gltfLoader.load(url, (gltf) => {
const root = gltf.scene;
scene.add(root);
})
When looking from top the element looks like a rounded rect:
When inspecting the imported mesh I can see that the BufferGeometry has a count of 18.000 points:
Everything works fine however if I apply the texture like this:
const texture = new THREE.TextureLoader().load( 'textures/land_ocean_ice_cloud_2048.jpg' );
const material = new THREE.MeshBasicMaterial( { map: texture } );
root.children[0].material = material;
The image is not visible but the mesh is now colored in 1 color.
Is it possible to apply the image just on the top face of the rect?
Hard to tell what the problem is without seeing the resulting image. However, I would just assign a new texture like this: root.children[0].material.map = texture instead of creating a whole new material, since you don't want to lose all the material attributes that came in the GLTF.
Additionally, MeshBasicMaterial always looks flat because it is not affected by lights.
I've been learning how to integrate ThreeJS with Mapbox, using this example. It struck me as weird that the approach is to leave the loaded model in its own coordinate system, and transform the camera location on render. So I attempted to rewrite the code, so that the GLTF model is transformed when loaded, then the ThreeJS camera is just synchronised with the Mapbox camera, with no further modifications.
The code now looks like this:
function newScene() {
const scene = new THREE.Scene();
// create two three.js lights to illuminate the model
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight2);
return scene;
}
function newRenderer(map, gl) {
// use the Mapbox GL JS map canvas for three.js
const renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
renderer.autoClear = false;
return renderer;
}
// create a custom layer for a 3D model per the CustomLayerInterface
export function addModel(modelPath, origin, altitude = 0, orientation = [Math.PI / 2, 0, 0]) {
const coords = mapboxgl.MercatorCoordinate.fromLngLat(origin, altitude);
// transformation parameters to position, rotate and scale the 3D model onto the map
const modelTransform = {
translateX: coords.x,
translateY: coords.y,
translateZ: coords.z,
rotateX: orientation[0],
rotateY: orientation[1],
rotateZ: orientation[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: coords.meterInMercatorCoordinateUnits()
};
const scaleVector = new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale)
return {
id: "3d-model",
type: "custom",
renderingMode: "3d",
onAdd: function(map, gl) {
this.map = map;
this.camera = new THREE.Camera();
this.scene = newScene();
this.renderer = newRenderer(map, gl);
// use the three.js GLTF loader to add the 3D model to the three.js scene
new THREE.GLTFLoader()
.load(modelPath, gltf => {
gltf.scene.position.fromArray([coords.x, coords.y, coords.z]);
gltf.scene.setRotationFromEuler(new THREE.Euler().fromArray(orientation));
gltf.scene.scale.copy(scaleVector);
this.scene.add(gltf.scene);
const bbox = new THREE.Box3().setFromObject(gltf.scene);
console.log(bbox);
this.scene.add(new THREE.Box3Helper(bbox, 'blue'));
});
},
render: function(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
// this.map.triggerRepaint();
}
}
}
It basically works, in that a model is loaded and drawn in the right location in the Mapbox world. However, instead of looking like this:
It now looks like this, a mangled mess that jitters around chaotically as the camera moves:
I'm not yet familiar enough with ThreeJS to have any idea what I did wrong.
Here's a side-by-side comparison of the old, functional code on the right, vs the new broken code on the left.
Further investigation
I suspect possibly the cause is to do with shrinking all the coordinates down to within the [0..1] range of the projected coordinate system, and losing mathematical precision, perhaps. When I scale the model up by 100 times, it renders like this - messy and glitchy, but at least recognisable as something.
I have a working GLTF animation that automatically starts playing when the page loads, however whenever I try to add a material to it, the animation no longer plays but the material appears. How do I fix this or if there is an easier way just to add a block colour to a gltf model please let me know, thanks.
var loader = new THREE.GLTFLoader();
loader.setDRACOLoader( new THREE.DRACOLoader() );
// Load a glTF resource
loader.load(
// resource URL
'../models/fox3.gltf',
// called when the resource is loaded
function ( gltf ) {
gltf.animations; // Array<THREE.AnimationClip>
gltf.scene; // THREE.Scene
gltf.scenes; // Array<THREE.Scene>
gltf.cameras; // Array<THREE.Camera>
gltf.asset; // Object
//Loading in and positioning model
var object = gltf.scene;
object.scale.set(10,10,10);
object.position.set (-300, 20,-400);
object.rotation.y = 0.5;
//Playing Animation
mixer = new THREE.AnimationMixer(gltf.scene);
console.log(gltf.animations)
mixer.clipAction( gltf.animations[0] ).play();
//Adding texture/colour to model (causes animation to stop playing)
// materialObj = new THREE.MeshBasicMaterial( { color: "#9E4300"} );
// object.traverse(function(child){
// if (child instanceof THREE.Mesh){
// //child.material = materialObj;
// }
// });
console.log(object);
scene.add( object )
});
or if there is an easier way just to add a block colour to a gltf model please let me know, thanks.
I'll address this last part of your question. The MeshBasicMaterial has the lighting calculations turned off, and in glTF this is supported with an extension called KHR_materials_unlit.
Here's a sample model called BoxUnlit.gltf that shows this extension in action. Two key places to note are ExtensionsUsed at the top, and the material near the bottom.
One major gotcha here is that the material's BaseColorFactor is specified in linear colorspace, while textures are provided in sRGB colorspace. So you have to take your chosen color and convert it to linear, typically by mathematically raising each component to the power of 2.2.
For example, your question contains color value #9E4300, which in 0..255 scale is equal to (158, 67, 0). Divide each number by 255, then raise by 2.2:
Red == (158 / 255.0) ** 2.2 == 0.348864834
Green == ( 67 / 255.0) ** 2.2 == 0.052841625
Blue == ( 0 / 255.0) ** 2.2 == 0.0
Use those values as the RGB values of the glTF model's BaseColorFactor, along with an alpha value of 1.0, like so:
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.348864834,
0.052841625,
0.0,
1.0
]
},
"extensions": {
"KHR_materials_unlit": {}
}
}
],
With that, ThreeJS should automatically select the MeshBasicMaterial for the model.
Try changing the skinning to true on the material of gltb and it should work. I had the same problem earlier.
I am trying to make an app which adds models from Google poly in GLTF format.
I had the issue of some models being extremely large when added on the scene which I solved by computing their size with bounding box max and min values and setting the scale amount.
Now after adding the objects to the scene when I open the inspector and drag to scale objects, even with a small amount of drag the objects becomes very large.
If there is any way to reset the scale value of loaded objects so that default value can be the value which I computed and this can also solve the drag to scale issue.
Note: The computed scale factor for some elements goes to 0.00001 for x, y, z.
Use the three.js API within A-Frame components to compute a bounding box of the model, then scale it down to the size you prefer. Example:
AFRAME.registerComponent('autoscale', {
schema: {type: 'number', default: 1},
init: function () {
this.scale();
this.el.addEventListener('object3dset', () => this.scale());
},
scale: function () {
const el = this.el;
const span = this.data;
const mesh = el.getObject3D('mesh');
if (!mesh) return;
// Compute bounds.
const bbox = new THREE.Box3().setFromObject(mesh);
// Normalize scale.
const scale = span / bbox.getSize().length();
mesh.scale.set(scale, scale, scale);
// Recenter.
const offset = bbox.getCenter().multiplyScalar(scale);
mesh.position.sub(offset);
}
});
HTML:
<a-entity autoscale="2" gltf-model="stereo.gltf"></a-entity>
The code above will fit your model to a ~2m box, and re-center it. For more information see THREE.Object3D documentation.
three.js r89, A-Frame 0.8.0.
I'm trying to port some legacy OpenGL 1.x code to WebGL / Three.JS:
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(...)
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(...)
// rest of rendering
I am setting my Three.JS camera's projection like so (note that I do not want to use a PerspectiveCamera, my projection matrix is pre-calculated):
var camera = new THREE.Camera()
camera.projectionMatrix.fromArray(...)
And I am setting my Three.JS camera's pose like so:
var mat = new THREE.Matrix4();
mat.fromArray(...);
mat.decompose(camera.position, camera.quaternion, camera.scale);
camera.updateMatrix();
scene.updateMatrixWorld(true);
I am testing this with the following:
var geometry = new THREE.SphereGeometry(10, 35, 35);
var material = new THREE.MeshLambertMaterial({color: 0xffff00});
mesh = new THREE.Mesh(geometry, material);
camera.add(mesh);
mesh.position.set(0, 0, -40); // fix in front of the camera
scene.add(mesh);
I can see that my camera's pose is being set correctly (by logging it), but nothing is being rendered to the screen. Am I setting the projection matrix incorrectly?
Are you sure your projection matrix is correct? (and as Sepehr Well pointed out, are you adding the camera to the scene?)
There are a few places that updateProjectionMatrix is called in the camera code, which will overwrite your matrix, so I'd put a break point in there to see if it is doing that.
The issue turned out to be my modelView matrix. This is how I ported my legacy code to ThreeJS:
// OpenGL 1.x code
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projectionMatrix)
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(modelViewMatrix)
// ThreeJS code
/* GL_PROJECTION matrix can be directly applied here */
camera = new THREE.Camera()
camera.projectionMatrix.copy(projectionMatrix)
/* GL_MODELVIEW should be inverted first */
modelViewMatrix.getInverse(modelViewMatrix)
modelViewMatrix.decompose(camera.position, camera.quaternion, camera.scale)
Looking at ThreeJS WebGL renderer source, ThreeJS' modelViewMatrix is calculated by multiplying camera's matrixWorldInverse into object's matrix.
Matrix4's decompose updates camera's matrixWorld, hence the actual matrix used in model-view calculation ends up inverted.
EDIT: here's a plug and play ThreeJS camera to use in this scenario:
/**
* #author Sepehr Laal
* #file OpenGLCamera.js
*/
function OpenGLCamera () {
THREE.Camera.call(this)
this.type = 'OpenGLCamera'
}
OpenGLCamera.prototype = Object.assign(Object.create(THREE.Camera.prototype), {
constructor: OpenGLCamera,
isOpenGLCamera: true,
/*
* Equivalent to OpenGL 1.x:
* glMatrixMode(GL_PROJECTION);
* glLoadMatrixf(...)
*/
setProjectionFromArray: function (arr) {
this.projectionMatrix.fromArray(arr)
},
/*
* Equivalent to OpenGL 1.x:
* glMatrixMode(GL_MODELVIEW);
* glLoadMatrixf(...)
*/
setModelViewFromArray: function () {
var m = new THREE.Matrix4();
return function (arr) {
m.fromArray(arr)
m.getInverse(m)
m.decompose(this.position, this.quaternion, this.scale)
};
}()
})