I am building a 3D Visualization and Interactive application using threejs.
Following are the key functionalities I want to provide in this application:
In this User should be able to:
I am following the vast threejs documentation and its list of examples, which really helped me a lot and I am able to achieve a little.
Also I have come across an useful threejs inspector Chrome Ext.
This threejs inspector Chrome Ext all in all does everything what I want to achieve, but unfortunately I am not able to understand that how does it work and how does it able to select and manipulate the parts of an Obj file.
I am using the following piece of code using threejs for now to just display, rotate and scale the Obj file.
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, camera, controls, scene, renderer, mesh;
var mtlObject = {};
var strDownloadMime = "image/octet-stream";
init();
animate();
function init() {
var saveLink = document.createElement('div');
saveLink.style.position = 'absolute';
saveLink.style.top = '10px';
saveLink.style.width = '100%';
saveLink.style.color = 'white !important';
saveLink.style.textAlign = 'center';
saveLink.innerHTML ='<a href="#" id="saveLink">Save Frame</a>';
document.body.appendChild(saveLink);
document.getElementById("saveLink").addEventListener('click', saveAsImage);
renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true
});
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 500;
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 2.0;
controls.zoomSpeed = 2.0;
controls.panSpeed = 2.0;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
controls.addEventListener( 'change', render );
// world
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 ).normalize();
scene.add( directionalLight );
// model
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round(percentComplete, 2) + '% downloaded' );
}
};
var onError = function ( xhr ) { };
//mtl loader
THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath( 'obj/' );
mtlLoader.load( 'arm.mtl', function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( 'obj/' );
objLoader.load( 'arm.obj', function ( object ) {
object.name = "object_name";
object.position.y = - 95;
scene.add( object );
//As 'TheJim01' suggested
//I have used an object variable.
//then traverse through the scene nodes
//and target some particular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Pad01")) {
node.visible = false;
}
if (node.name == ("Arm_01")) {
node.visible = false;
}
if (node.name == ("Pad02")) {
node.visible = false;
}
if (node.name == ("Arm_02")) {
node.visible = false;
}
});
}, onProgress, onError );
});
// lights
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
var light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
//
render();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
render();
}
function animate() {
requestAnimationFrame( animate );
controls.update();
}
function render() {
renderer.render( scene, camera );
}
//my next challenge is to save the canvas as image
//after making all the changes to the obj file
function saveAsImage() {
var imgData, imgNode;
try {
var strMime = "image/jpeg";
imgData = renderer.domElement.toDataURL(strMime);
saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");
} catch (e) {
console.log(e);
return;
}
}
var saveFile = function (strData, filename) {
var link = document.createElement('a');
if (typeof link.download === 'string') {
document.body.appendChild(link); //Firefox requires the link to be in the body
link.download = filename;
link.href = strData;
link.click();
document.body.removeChild(link); //remove the link when done
} else {
location.replace(uri);
}
}
$(document).ready(function() {
//Set Color of the Obj parts accordingly
$('#armblock').on('click', function(){
$(this).children('ul').slideToggle(400);
$(this).children('ul').children('li').on('click', function(){
$color = new THREE.Color($(this).css('backgroundColor'));
var selectedColor = '#' + $color.getHexString();
//As 'TheJim01' suggested
//I have used and object variable.
//then traverse through the scene nodes
//and target some perticular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Arm_01")) {
node.visible = true;
nameToObject["Arm_01"].material.color.set(selectedColor);
}
if (node.name == ("Arm_02")) {
node.visible = true;
nameToObject["Arm_02"].material.color.set(selectedColor);
}
});
});
});
$('#padblock').on('click', function(){
$(this).children('ul').slideToggle(400);
$(this).children('ul').children('li').on('click', function(){
$color = new THREE.Color($(this).css('backgroundColor'));
var selectedColor = '#' + $color.getHexString();
//As 'TheJim01' suggested
//I have used an object variable.
//then traverse through the scene nodes
//and target some particular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Pad01")) {
node.visible = true;
nameToObject["Pad01"].material.color.set(selectedColor);
}
if (node.name == ("Pad02")) {
node.visible = true;
nameToObject["Pad02"].material.color.set(selectedColor);
}
});
});
});
});
Please if anyone can help me out in this. Thanks in advance and please comment if I am missing anything.
Update
Next Challenges
PS
TheJim01 helped me a lot into understanding the basic but very important concept of traversing the obj file and its parts. That leads me to build at least closer to something what I want.
Another Microsoft app that you can try to edit OBJ files is 3D Builder. Like Paint 3D, it is also a native 3D modeling app in Windows 10. Windows 11 users can download and install this free app from Microsoft Store. You can simply open an OBJ model and then modify it using several 3D modeling tools.
An OBJ file is a standard 3D image format that can be exported and opened by various 3D image editing programs. It contains a three-dimensional object, including 3D coordinates, texture maps, polygonal faces, and other object information. OBJ files may also store references to one or more .
All three.js inspector is doing is parsing the scene, and displaying the various properties of the objects in an interactive UI.
Let's say you have an OBJ file arranged like this:
bike
frame
seat
drive
pedals
frontSprocket
chain
rearSprocket
rearWheel
steering
handlebars
fork
frontWheel
OBJLoader
would create a scene hierarchy like this:
bike // THREE.Group
frame // THREE.Mesh
seat // THREE.Mesh
drive // THREE.Group
pedals // THREE.Mesh
frontSprocket // THREE.Mesh
chain // THREE.Mesh
rearSprocket // THREE.Mesh
rearWheel // THREE.Mesh
steering // THREE.Group
handlebars // THREE.Mesh
fork // THREE.Mesh
frontWheel // THREE.Mesh
three.js inspector displays this same hierarchy, using the names of the objects. When you click on an object in its tree, it references the object in the scene, and grabs/displays its properties, such as its position
, rotation
, visible
state, etc. When you make a change in the three.js inspector UI, it sets the value on the object in the scene, resulting in the changes you see.
You can do all of this yourself, and you don't even need to be so general about it. Say you want to create a map of object name to the scene object for easier reference (searching the scene is fast enough, but it's recursive). So you could do this:
var nameToObject = {};
scene.traverse(function(node){
// watch out for duplicate empty names!
nameToObject[node.name] = node;
});
(That doesn't give you the hierarchy, but this is just an example.)
Now you can get and update any object by name:
// enlarge the rear tire
nameToObject["rearTire"].scale.addScalar(0.1);
You can read and set all properties of the object. For example, if MTLLoader
created a basic material for the frame, you could do something like this:
// make the frame red
nameToObject["frame"].material.color.setRGB(1.0, 0.0, 0.0);
Or you could outright replace the entire material.
For your example of replacing an object, let's say you already loaded a new Mesh
called newRearTire
...
// replace the rear tire
var drive = nameToObject["drive"]; // the parent of rearTire
drive.remove(nameToObject["rearTire"]);
drive.add(newRearTire);
(Of course you would need to re-build your name map at this point.)
These are just very general examples, but should get you started. If you encounter any problems accessing your data, leave a comment and I'll try to clarify.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With