3

I´ve been several days struggling with a particular Three.js issue, and I cannot find any way to do it. This is my case:

1) I have a floating mesh, formed by several triangled faces. This mesh is created from the geometry returned by a loader, after obtaining its vertices and faces using getAttribute('position'): How to smooth mesh triangles in STL loaded BufferGeometry

enter image description here

2) What I want to do now is to "project" the bottom face agains the floor.

enter image description here

3) Later, with this new face added, create the resulting mesh of filling the space between the 3 vertices of both faces.

enter image description here

I already have troubles in step 2... To create a new face I´m supossed to have its 3 vertices already added to geometry.vertices. I did it, cloning the original face vertices. I use geometry.vertices.push() results to know their new indexes, and later I use that indexes (-1) to finally create the new face. But its shape is weird, also the positions and the size. I think I´m not getting the world/scene/vector position equivalence theory right :P

I tried applying this, with no luck: How to get the absolute position of a vertex in three.js? Converting World coordinates to Screen coordinates in Three.js using Projection http://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works

I discovered that if I directly clone the full original face and simply add it to the mesh, the face is added but in the same position, so I cannot then change its vertices to place it on the floor (or at least without modifying the original face vertices!). I mean, I can change their x, y, z properties, but they are in a very small measure that doesn´t match the original mesh dimensions.

Could someone help me get this concept right?

EDIT: source code

            // Create geometry
            var geo = new THREE.Geometry();
            var geofaces = [];
            var geovertices = [];

            original_geometry.updateMatrixWorld();

            for(var index in original_geometry.faces){          
                // Get original face vertexNormals to know its 3 vertices
                var face = original_geometry[index];
                var vertexNormals = face.vertexNormals;

                // Create 3 new vertices, add it to the array and then create a new face using the vertices indexes
                var vertexIndexes = [null, null, null];
                for (var i = 0, l = vertexNormals.length; i < l; i++) {
                    var vectorClone = vertexNormals[i].clone();
                    vectorClone.applyMatrix4( original_geometry.matrixWorld );
                    //vectorClone.unproject(camera); // JUST TESTING
                    //vectorClone.normalize(); // JUST TESTING

                    var vector = new THREE.Vector3(vectorClone.x, vectorClone.z, vectorClone.y)
                    //vector.normalize(); // JUST TESTING
                    //vector.project(camera); // JUST TESTING
                    //vector.unproject(camera); // JUST TESTING
                    vertexIndexes[i] = geovertices.push( vector ) - 1;
                }
                var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
                geofaces.push(newFace);
            }

            // Assign filled arrays to the geometry
            geo.faces = geofaces;
            geo.vertices = geovertices;

            geo.mergeVertices();
            geo.computeVertexNormals();
            geo.computeFaceNormals();

            // Create a new mesh with resulting geometry and add it to scene (in this case, to the original mesh to keep the positions)
            new_mesh = new THREE.Mesh( geo, new THREE.MeshFaceMaterial(material) ); // material is defined elsewhere
            new_mesh.position.set(0, -100, 0);
            original_mesh.add( new_mesh );
Community
  • 1
  • 1
spacorum
  • 495
  • 6
  • 16

2 Answers2

1

I created a fully operational JSFiddle with the case to try things and see the problem more clear. With this STL (smaller than my local example) I cannot even see the badly cloned faces added to the scene.. Maybe they are too small or out of focus.

Take a look to the calculateProjectedMesh() function, here is where I tried to clone and place the bottom faces (already detected because they have a different materialIndex):

JSFiddle: https://jsfiddle.net/tc39sgo1/

var container;
var stlPath = 'https://dl.dropboxusercontent.com/s/p1xp4lhy4wxmf19/Handle_Tab_floating.STL';

var camera, controls, scene, renderer, model;

var mouseX = 0,
    mouseY = 0;

var test = true;
var meshPlane = null, meshStl = null, meshCube = null, meshHang = null;

var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;

/*THREE.FrontSide = 0;
THREE.BackSide = 1;
THREE.DoubleSide = 2;*/

var materials = [];
materials.push( new THREE.MeshPhongMaterial({color : 0x00FF00, side:0, shading: THREE.FlatShading, transparent: true, opacity: 0.9, overdraw : true, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0xFF0000, transparent: true, opacity: 0.8, side:0, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0x0000FF, side:2, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.05 });

init();
animate();

function webglAvailable() {
    try {
        var canvas = document.createElement('canvas');
        return !!(window.WebGLRenderingContext && (
        canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
    } catch (e) {
        return false;
    }
}

function init() {
    container = document.createElement('div');
    document.body.appendChild(container);

    camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100000000);
    camera.position.x = 1500;
    camera.position.z = -2000;
    camera.position.y = 1000;

    controls = new THREE.OrbitControls(camera);

    // scene
    scene = new THREE.Scene();

    var ambient = new THREE.AmbientLight(0x101030); //0x101030
    scene.add(ambient);

    var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
    directionalLight.position.set(0, 3, 0).normalize();
    scene.add(directionalLight);

    var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
    directionalLight.position.set(0, 1, -2).normalize();
    scene.add(directionalLight);

        if (webglAvailable()) {
        renderer = new THREE.WebGLRenderer();
    } else {
        renderer = new THREE.CanvasRenderer();
    }
        renderer.setClearColor( 0xCDCDCD, 1 );

    // renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    document.addEventListener('mousemove', onDocumentMouseMove, false);
    window.addEventListener('resize', onWindowResize, false);

        createPlane(500, 500);
        createCube(500);
        loadStl();
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function onDocumentMouseMove(event) {
    mouseX = (event.clientX - windowHalfX) / 2;
    mouseY = (event.clientY - windowHalfY) / 2;
}

function animate() {
    requestAnimationFrame(animate);
    render();
}

function render() {
    renderer.render(scene, camera);
}

function createPlane(width, height) {
        var planegeometry = new THREE.PlaneBufferGeometry(width, height, 0, 0);
        var material = new THREE.MeshLambertMaterial({
            color: 0xFFFFFF,
            side: THREE.DoubleSide
        });
        planegeometry.computeBoundingBox();
        planegeometry.center();

        meshPlane = new THREE.Mesh(planegeometry, material);
        meshPlane.rotation.x = 90 * (Math.PI/180);
        //meshPlane.position.y = -height/2;
        scene.add(meshPlane);
}

function createCube(size) {
    var geometry = new THREE.BoxGeometry( size, size, size );                       
        geometry.computeFaceNormals();
        geometry.mergeVertices();
        geometry.computeVertexNormals();
        geometry.center();

    var material = new THREE.MeshPhongMaterial({
              color: 0xFF0000,
                opacity: 0.04,
                transparent: true,
                wireframe: true,
                side: THREE.DoubleSide
        });
        meshCube = new THREE.Mesh(geometry, material);
        meshCube.position.y = size/2;
        scene.add(meshCube);
}

function loadStl() {        
        var loader = new THREE.STLLoader();             
        loader.load( stlPath, function ( geometry ) {   
                        // Convert BufferGeometry to Geometry
                        var geometry = new THREE.Geometry().fromBufferGeometry( geometry );

                        geometry.computeBoundingBox();
                        geometry.computeVertexNormals();
                        geometry.center();

                        var faces = geometry.faces;
                        for(var index in faces){
                                var face = faces[index];
                                var faceNormal = face.normal;
                                var axis = new THREE.Vector3(0,-1,0);
                                var angle = Math.acos(axis.dot(faceNormal));
                                var angleReal = (angle / (Math.PI/180));
                                if(angleReal <= 70){
                                    face.materialIndex = 1;
                                }
                                else{
                                    face.materialIndex = 0;
                                }
                        }

                geometry.computeFaceNormals();
                        geometry.computeVertexNormals();

                    meshStl = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
                        meshStl.position.x = 0;
                        meshStl.position.y = 400;
            scene.add( meshStl );

                        // Once loaded, calculate projections mesh
                        calculateProjectedMesh();
        });
}

function calculateProjectedMesh(){
            var geometry = meshStl.geometry;
            var faces = geometry.faces;
            var vertices = geometry.vertices;

            var geometry_projected = new THREE.Geometry();
            var faces_projected = [];
            var vertices_projected = [];

            meshStl.updateMatrixWorld();

            for(var index in faces){
                    var face = faces[index];

                    // This are the faces
                    if(face.materialIndex == 1){

                            var vertexIndexes = [face.a, face.b, face.c];
                            for (var i = 0, l = vertexIndexes.length; i < l; i++) {
                                    var relatedVertice = vertices[ vertexIndexes[i] ];
                                    var vectorClone = relatedVertice.clone();
                                    console.warn(vectorClone);
                                    vectorClone.applyMatrix4( meshStl.matrixWorld );

                                    ////////////////////////////////////////////////////////////////
                                    // TEST: draw line
                                    var geometry = new THREE.Geometry();
                                    geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
                                    //geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
                                    geometry.vertices.push(new THREE.Vector3(vectorClone.x, meshPlane.position.y, vectorClone.z));
                                    var line = new THREE.Line(geometry, lineMaterial);
                                    scene.add(line);
                                    console.log("line added");
                                    ////////////////////////////////////////////////////////////////    

                                    vectorClone.y = 0;
                                    var vector = new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z);
                                    vertexIndexes[i] = vertices_projected.push( vector ) - 1;
                            }
                            var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
                            newFace.materialIndex = 2;
                            faces_projected.push(newFace);
                    }
            }
            geometry_projected.faces = faces_projected;
            geometry_projected.vertices = vertices_projected;
            geometry_projected.mergeVertices();
            console.info(geometry_projected);

            meshHang = new THREE.Mesh(geometry_projected, new THREE.MeshFaceMaterial(materials));
            var newY = -(2 * meshStl.position.y) + 0;
            var newY = -meshStl.position.y;
            meshHang.position.set(0, newY, 0);
            meshStl.add( meshHang );        
}

EDIT: Finally!! I got it! To clone the original faces I must access their 3 original vertices using "a", "b" and "c" properties, which are indexes referencing Vector3 instances in the "vertices" array of the original geometry.

I cloned the 3 vertices flatting the Z position to zero, use their new indexes to create the new face and add it to the projection mesh (in blue).

I´m also adding lines as a visual union between both faces. Now I´m ready for step 3, but I think this is complex enough to close this question.

Thanks for the updateMatrixWorld clue! It was vital to achieve my goal ;)

spacorum
  • 495
  • 6
  • 16
0

try this

 original_geometry.updateMatrixWorld();
            var vertexIndexes = [null, null, null];
            for (var i = 0, l = vertexNormals.length; i < l; i++) {
              var position = original_geometry.geometry.vertices[i].clone();
              position.applyMatrix4( original_geometry.matrixWorld );

                var vector = new THREE.Vector3(position.x, position.y, position.z)

                vertexIndexes[i] = geovertices.push( vector ) - 1;
            }
jonsoft
  • 9
  • 4
  • Hi and thanks for your time! Mmmm, I though I was doing exactly that...! Take a look at the source code I added to the first post. First problem with this strategy: the original vertices of the original face has different values on its x,y,z properties than the resulting clone "vectorClone". I guess I must somehow transport the vector coordinates to real-world coordinates? – spacorum Feb 07 '16 at 20:01
  • Are we doing the same thing..? In your code what is i, i2 and i3? I´m starting to think that vertexNormals is not what I need to get the 3 vertices of a face? – spacorum Feb 07 '16 at 20:16
  • first You must set the lower face of your original_geometry (the Bottom) and to do that you must compare all faces and returne the lowest one in Z . and then creat the same lower face but in Zfloor . in virtex(samX samY Zfloor) – jonsoft Feb 07 '16 at 20:40
  • Yes, I´ve just ommited the calculation where I know which one is the bottom face, but that part it´s already done, thanks. My problem is to get the 3 vertices of the face. How are you getting them? I use the vertexNormals property of the original face (which is an array of 3 vectors, each one has its x,y,z properties). But the units... are very low, like -0.2, 0.4, 2, etc.. This are vector units, right? Is my way of adding the vectors/faces correct? I first add the 3 cloned vectors, get its indexes and use them to add the face. But I see you only use 0,1,2 numbers as params, I´m confussed. – spacorum Feb 07 '16 at 20:45
  • I think you need to updatting your original_geometry befor clone it – jonsoft Feb 07 '16 at 20:58
  • original_geometry.updateMatrixWorld(); in your render – jonsoft Feb 07 '16 at 20:59
  • to get the real 3 virtices try this vectorClone .applyMatrix4( original_geometry.matrixWorld ); – jonsoft Feb 07 '16 at 21:12
  • So one of the links I mentioned was pointing in the right direction: http://stackoverflow.com/questions/11495089/how-to-get-the-absolute-position-of-a-vertex-in-three-js I applied this to the source code as you can see. But I always get an error trying it: **original_geometry.updateMatrixWorld is not a function** The geometry is valid, and it has its vertices, its faces, etc. But the method is not. Could this have something to do with the type of geometry/object that the loader returns? – spacorum Feb 07 '16 at 21:30
  • Wow, this is my Google nightmare :) https://www.google.es/search?num=20&safe=off&q=%22updateMatrixWorld+is+not+a+function%22+-%22you+can+replicate+this%22 Removing the only result (a bug report from something that has nothing to do with this), there is no other mention to "updateMatrixWorld is not a function". So lost right now. – spacorum Feb 07 '16 at 22:12
  • hi m friend . updateMatrixWorld() it is your new position loop founction. when you change the position .you need to put it befor clone it – jonsoft Feb 08 '16 at 09:25
  • As I told you befor I am new at three.js , but I want to help you To find a solution – jonsoft Feb 08 '16 at 09:31
  • Thanks ;) I´m still looking for a way to launch "updateMatrixWorld()" without getting that error... It seems that my geometry doesn´t have it! :( – spacorum Feb 08 '16 at 10:02