I'm using a-frame and loading a gltf file, constructing HTML DOM from JavaScript with JQuery like this:
let arHolder = $('#arHolder');
let gltfFilePath = MY_GLTF_PATH;
// Build the scene
let sceneElement = document.createElement('a-scene');
sceneElement.setAttribute("embedded", '');
sceneElement.setAttribute("arjs", '');
let markerElement = document.createElement('a-marker');
markerElement.setAttribute("preset", 'hiro');
let glftElement = document.createElement('a-entity');
glftElement.setAttribute("id", 'glftElement');
glftElement.setAttribute("gltf-model", gltfFilePath);
let cameraElement = document.createElement('a-entity');
cameraElement.setAttribute("camera", '');
markerElement.append(glftElement);
sceneElement.append(markerElement);
sceneElement.append(cameraElement);
arHolder.append(sceneElement);
sceneElement.addEventListener("loaded", (event) => {
var gltfScene = document.querySelector('#glftElement').object3D;
gltfScene.traverse( function( node ) {
console.log(node);
console.log("NAME:", node.name);
});
});
I believe that I should get a list of the names of the objects in the gltf scene as traverse is meant to go down into the descendants (i.e. children) of the gltf scene and get every item. However, I just get the one top node.
If I inspect the gltfScene object in the Chome console I can see the meshes in the gltf scene.
Any idea why this isn't working, please?
Thank you.
Piotr was correct. The gltf model hadn't loaded.
This solves it:
document.querySelector('#glftElement').addEventListener("model-loaded", (event) => {
var gltfScene = document.querySelector('#glftElement').object3D;
gltfScene.traverse( function( node ) {
console.log(node);
});
});
Related
I have been buying animated 3D models from TurboSquid and they fall into two starkly different categories:
Ready to use - I just load the FBX file and it looks fine in my ThreeJS scene
Missing textures - The loaded FBX model looks geometrically fine and the animations work, but all the surfaces are white
I want to know how to load the provided textures using the FBXLoader module. I have noticed that all of the FBX models, which I believe are PBR material based, have this set of files:
*002_BaseColor.png
*002_Emissive.png
*002_Height.png
*002_Metallic.png
*002_Roughness.png
*002_Normal.png
I have these questions:
How would I use the FBXLoader module to load the textures "in the right places"? In other words, how do I make the calls so that the BaseColor texture goes to the right place, the Emissive texture so it goes where it should, etc?
Do I need to pass it a custom material loader to the FBXLoader and if so, which one and how?
UPDATE: I trace through the ThreeJS FBX loader code I discovered that the FBX model that is not showing any textures does not have a Texture node at all. The FBX model that does show the textures does, as you can see in this screenshot of the debugger:
I'll keep tracing. If worse comes to worse I'll write custom code to add a Textures node with the right fields since I don't know how to use Blender to add the textures there. My concern is of course that even if I create a Textures node at run-time, they still won't end up in the proper place on the model.
This is the code I am currently using to load FBX models:
this.initializeModel = function (
parentAnimManagerObj,
initModelArgs= null,
funcCallWhenInitialized = null,
) {
const methodName = self.constructor.name + '::' + `initializeModel`;
const errPrefix = '(' + methodName + ') ';
self.CAC.parentAnimManagerObj = parentAnimManagerObj;
self.CAC.modelLoader = new FBXLoader();
self.CAC.modelLoader.funcTranslateUrl =
((url) => {
// Fix up the texture file names.
const useRobotColor = misc_shared_lib.uppercaseFirstLetter(robotColor);
let useUrl =
url.replace('low_Purple_TarologistRobot', `Tarologist${useRobotColor}`);
return useUrl;
});
// PARAMETERS: url, onLoad, onProgress, onError
self.CAC.modelLoader.load(
MODEL_FILE_NAME_TAROLOGIST_ROBOT_1,
(fbxObj) => {
CommonAnimationCode.allChildrenCastAndReceiveShadows(fbxObj);
fbxObj.scale.x = initModelArgs.theScale;
fbxObj.scale.y = initModelArgs.theScale;
fbxObj.scale.z = initModelArgs.theScale;
self.CAC.modelInstance = fbxObj;
// Set the initial position.
self.CAC.modelInstance.position.x = initModelArgs.initialPos_X;
self.CAC.modelInstance.position.y = initModelArgs.initialPos_Y;
self.CAC.modelInstance.position.z = initModelArgs.initialPos_Z;
// Create an animation mixer for this model.
self.CAC.modelAnimationMixer = new THREE.AnimationMixer(self.CAC.modelInstance);
// Speed
self.CAC.modelAnimationMixer.timeScale = initModelArgs.theTimeScale;
// Add the model to the scene.
g_ThreeJsScene.add(self.CAC.modelInstance);
// Don't let this part of the code crash the entire
// load call. We may have just added a new model and
// don't know certain values yet.
try {
// Play the active action.
self.CAC.activeAction = self.CAC.modelActions[DEFAULT_IDLE_ANIMATION_NAME_FOR_TAROLOGIST_ROBOT_1];
self.CAC.activeAction.play();
// Execute the desired callback function, if any.
if (funcCallWhenInitialized)
funcCallWhenInitialized(self);
} catch (err) {
const errMsg =
errPrefix + misc_shared_lib.conformErrorObjectMsg(err);
console.error(`${errMsg} - try`);
}
},
// onProgress
undefined,
// onError
(err) => {
// Log the error message from the loader.
console.error(errPrefix + misc_shared_lib.conformErrorObjectMsg(err));
}
);
}
I need to implement a functionality where user can upload a OBJ 3D model and then I will show different sub-parts of that model in browser. (Same as https://www.sculpteo.com is doing.).
But I am unable to find sub-parts of OBJ model. I am using three.js to show OBJ model in browser.
Is there any way to find sub-parts to OBJ model using three.js?
I am sharing code to load OBJ model =>
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath( 'path' );
mtlLoader.load( 'model.mtl', function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( 'path' );
objLoader.load( 'model.obj', function ( object ) {
scene.add( object);
});
});
Now I don't know how to find sub-parts of this "model.obj". Kindly help. Thanks in advance.
OJBLoader returns (THREE.Object3D() (it was in r71)) THREE.Group() (r81) with children of THREE.Mesh.
You'll get sub-parts as child meshes of the object only if your .obj file has groups of objects. Without it, you'll get just a sinlge child mesh.
Read about Wavefront .obj file format.
So, if your data of sub-parts grouped under
o [object name]
tag, then you'll have as many child meshes as you have "o" groups in your .obj file and then you can traverse.
upd#1: It also works with "g" tags.
g [group name]
The example based on "webgl_interactive_cubes" from Threejs.org, the data of sub-parts grouped with "g" tags (skull, uteeth, jaw, lteeth) in the .obj file
When you load the objects you can do this inside your callback function :
objLoader.load('yourUrl.obj', function (object) {
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
console.log(child);
// here you can make what you want with the children of object
}
});
});
How can I get a list of the available contexts on a given canvas? Unfortunately, using getContext on every possible option would not work since https://stackoverflow.com/a/13406681/2054629
So
var possibleContexts = ['2d', 'webgl', 'webgl2', 'experimental-webgl', 'experimental-webgl2', ...];
var availableContexts = possibleContexts.filter(function(c) { return canvas.getContext(c); });
won't work
I'm trying to get a better understanding while at some point in my code webgl context is available and sometime not. I have users for who a newly created canvas will have a webgl context and the one I'm interested in fails. I'm trying to better understand that. So solutions that would create new canvas won't work here...
At the moment I'm doing things like:
var testWebGl = function() {
var canvas = document.createElement('canvas');
return !!(canvas.getContext('webgl') || canvas.getContext('canvas.getContext('webgl')');
};
and then
if (testWebGl())
doSomeWebGlStuff(myCanvas);
And occasionally doSomeWebGlStuff will fails because I can't get a webgl context on myCanvas. So I'd like to get all the available contexts on the exact canvas I'm interested in.
The code won't work since you use the same canvas object for getContext() in all cases. The tests must be performed on different elements than the one you intend to actually use, which means you have to create a new canvas element instance before each getContext() as #tommie showed in his now deleted answer (running the snippet was evidence of that).
For reference, this is what tommie had in his answer with minor modifications/comments:
var possibleContexts = ['bitmaprenderer', '2d', 'webgl', 'webgl2', 'experimental-webgl', 'experimental-webgl2'];
var availableContexts = possibleContexts.filter(function(c) {
var canvas = document.createElement("canvas"); // new canvas element for test only
if (canvas.getContext(c)) {return c}
});
You can alternatively check for the existence of context objects directly:
var availableContexts = [];
if (typeof CanvasRenderingContext2D !== "undefined")
availableContexts.push("2d");
// prefer webgl over experimental-webgl
var hasWebGL = !!document.createElement("canvas").getContext("webgl");
if (typeof WebGLRenderingContext !== "undefined")
availableContexts.push(hasWebGL ? "webgl" : "experimental-webgl");
...etc.
Adjust as needed.
I would like to detect when JSONLoader loaded the model and all of its UV textures.
I tried to use the LoadingMonitor, but it was not useful. It didn't log "Loaded" message.
I don't know why. I did everything according to the example. What did I wrong?
var manager = new THREE.LoadingManager(function() {
console.log("Loaded");
});
var loader = new THREE.JSONLoader(manager);
loader.load("model.js", function(geometry, materials) {
...
});
Otherwise can it fire when textures are loaded? Or is it detectable?
Thanks for all replies.
I've a problem with object3d selection on my scene.
I use the three.js r61 and the domevent.js libraries.
I have added the THREE.Object3D Patch, but doesn't seems to works.
I have a list of objects that are mesh or object3d.
When i try to capture an object click, all works fine if object is a mesh but not if it is a group.
Each object is added with objects.push(object); // mesh or object3d
Here is the code :
// Objects selection
THREE.Object3D._threexDomEvent.camera(camera);
THREE.Object3D._threexDomEvent.domElement(renderer.domElement);
for ( object in objects )
{
// Bind depends on object type
var bindControl = objects[object];
console.log('bind ' + bindControl.name);
bindControl.on('click', function(object3d) {
console.log('ok');
seletedObject = object3d.target;
console.log('selected : ' + object3d.target.name);
});
...
}
So the domevent works fine for a mesh that is directly in the scene, but if i click on a mesh that is in a group, as the group only is in the objects list, the click event is not fired.
I don't know how to make my object3d to be clickable.
If someone has an idea, it will be greatly appreciated.
Thanks
The code of domevents.js doesn't works with groups because of two reasons:
To detect intersection with or without the descendants you need to pass the second parameter as true on the intersectObjects function:
var intersects = this._raycaster.intersectObjects( boundObjs, true);
The event is attached to the parent element (the object3d or THREE.Group object) and when intersection is detected it returns the descendant object with the nearest intersection. So the _bound function will ty to get context of child element instead of parent. You have to change the behaviour of _objectCtxGet function:
THREEx.DomEvents.prototype._objectCtxGet = function(object3d){
if(typeof object3d._3xDomEvent == 'undefined' && typeof object3d.parent != 'undefined')
{
return object3d.parent._3xDomEvent;
}
return object3d._3xDomEvent;
}
So i found a solution don't know if it is the better one but it works at least with a group of 2 mesh.
Here is the code i modified in the ThreeX.domevent.js (in THREEx.DomEvent.prototype._onEvent)
var objectCtx = this._objectCtxGet(object3d);
var objectParent = object3d.parent;
while ( typeof(objectCtx) == 'undefined' && objectParent )
{
objectCtx = this._objectCtxGet(objectParent);
objectParent = objectParent.parent;
}
This is beacause the object clicked is not the one that is in the objects list, so we must found the parent that is in the list, and it work with this code.
Be aware that i've only tested it for my case and tested it in only some case, so take it carrefully.
Bye