Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select, Update and Manipulate an Obj file using threejs

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:

  • Rotate and Scale the Obj. -- done
  • Manipulate some certain parts of the Obj like, changing its color, replace that part with another. -- pending

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.

Updated Code:

        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

  • I am able to change the color of a particular node(part of the obj) but its not spontaneous as I have to click on the canvas/obj again to see the changes.
  • I am able to hide/show a particular node(part of the obj) but I want to replace that particular node(part of the obj) with another one.
  • I want to save the final obj file after making all the changes as an Image, but in future as an gif or video so that user can visualize 360deg view of the final product.

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.

like image 346
vivekkupadhyay Avatar asked Jul 10 '17 13:07

vivekkupadhyay


People also ask

How do I manipulate OBJ files?

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.

What is OBJ format for 3D model?

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 .


1 Answers

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.

like image 172
TheJim01 Avatar answered Sep 28 '22 03:09

TheJim01