How to remove interior faces while keeping exterior faces untouched?

geometry

#1

Hello THREE.js users,

I am trying to delete the inner faces of a merged geometry. Looking through stackoverflow, discourse and other internet sites i have tried to piece together something that should be a simple example. For the most part the geometry is visible. But I can not seem to remove the inner faces of the whole geometry. It is a two by two cube that shows four total. The problem is in the function called removeDuplicateFaces() I had created. Any help with this is greatly appreciated. Thanks.

var scene , camera , renderer , controls;
var mesh;
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45;
var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;
var NEAR = 0.1;
var FAR = 1000;

function init ( ) {
    scene = new THREE.Scene ( );
    camera = new THREE.PerspectiveCamera ( VIEW_ANGLE , ASPECT , NEAR , FAR );
    camera.position.set ( 5 , 5 , 5 );
    camera.lookAt ( scene.position );
    renderer = new THREE.WebGLRenderer ( {
        antialias: true
    } );
    renderer.setPixelRatio ( window.devicePixelRatio );
    renderer.setSize ( SCREEN_WIDTH , SCREEN_HEIGHT );
    document.body.appendChild ( renderer.domElement );
    controls = new THREE.OrbitControls ( camera , renderer.domElement );

    var mergedGeometry = new THREE.Geometry ( );
    var geometry = new THREE.BoxGeometry ( 1 , 1 , 1 );
    var i;
    var k;
    var x;
    var y;
    var z;

    for ( i = 0; i < 2; i += 1 ) {

        for ( k = 0; k < 2; k += 1 ) {

            x = i;
            y = 0;
            z = k;
            geometry.translate ( x , y , z );
            mergedGeometry.merge ( geometry );
            geometry.translate ( -x , -y , -z );
        }
    }
    
    // the function to remove inner faces
    function removeDuplicateFaces ( geometry ) {
        var m;
        var n;
        var face;
        var face2;
        for ( m = 0; m < geometry.faces.length; m += 1 ) {
            face = geometry.faces[m];
            face.centroid = new THREE.Vector3 ( 0 , 0 , 0 );
            face.centroid.add ( geometry.vertices[ face.a ] );
            face.centroid.add ( geometry.vertices[ face.b ] );
            face.centroid.add ( geometry.vertices[ face.c ] );
            face.centroid.divideScalar ( 3 );
            console.log ( "[face] " + m + ", [vertices] " + face.a + ", " + face.b + ", " + face.c );
            for ( n = 0; n < m; n += 1 ) {
                face2 = geometry.faces[n];
                if ( face2 !== undefined ) {
                    face2.centroid = new THREE.Vector3 ( 0 , 0 , 0 );
                    face2.centroid.add ( geometry.vertices[ face2.a ] );
                    face2.centroid.add ( geometry.vertices[ face2.b ] );
                    face2.centroid.add ( geometry.vertices[ face2.c ] );
                    face2.centroid.divideScalar ( 3 );
                    console.log ( "[face2] " + n + ", [vertices] " + face2.a + ", " + face2.b + ", " + face2.c );
                    if ( face.centroid.equals ( face2.centroid ) ) {
                        delete geometry.faces[m];
                        delete geometry.faces[n];
                    }
                }
            }
        }
        return geometry;
    }

    removeDuplicateFaces ( mergedGeometry );

    var material1 = new THREE.MeshBasicMaterial ( { color: 0xff0000 , wireframe: true , side: THREE.DoubleSide } );
    var material2 = new THREE.MeshBasicMaterial ( { color: 0x00ff00 , wireframe: true , side: THREE.DoubleSide } );
    var material3 = new THREE.MeshBasicMaterial ( { color: 0x0000ff , wireframe: true , side: THREE.DoubleSide } );
    var material4 = new THREE.MeshBasicMaterial ( { color: 0xff00ff , wireframe: true , side: THREE.DoubleSide } );
    //var materialTransparent = new THREE.MeshBasicMaterial ( { transparent: true , opacity: 0 , wireframe: true , side: THREE.DoubleSide } );
    var materials = [ material1 , material2 , material2 , material3 , material3 , material4 ];
    mesh = new THREE.Mesh ( mergedGeometry , materials );
    scene.add ( mesh );
}

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

function update ( ) {
    controls.update ( );
}

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

init ( );
animate ( );

#2

I’m not sure what you mean by “inner” faces… do you mean duplicate faces that share the same vertices but just happen to be facing in opposite directions? That seems like a very niche problem to have… I’d be curious what is causing it to begin with?

If you mean generally removing all faces anywhere inside of the joined mesh, e.g. a sphere intersects a box and you want to remove parts of both that are hidden by the other, that is very different from the code you’ve shown above.


#3

Thank you for the quick reply. The meaning of “faces” I am referring to are the two triangles per side of a single geometry box. So in a single box there should be six sides and each side has two triangles so i see there are a total of twelve triangle faces. In the example there is a two by two group of boxes. With that group of boxes there should be a total of forty-eight triangle “faces”. If you were to delete the inner sixteen triangle “faces” you should have a box geometry with the top bottom and sides equaling to thirty-two triangle "faces. Hope I have further explained what I’m trying to accomplish. Here is an example of someone else that I have found of what I’m talking about. http://jsfiddle.net/majman/4sukB/2/ if you look at the handle of the sword ( horizontal part ) and change it to wire-frame (toggle button), you can see the inside triangles are gone. Thank you.


#4

Are there any other ideas on the following code in how this may be accomplished? Thanks.


#5

There is removeDuplicateFaces() function in the fiddle.
Isn’t it what you’re looking for?


#6

That code is what i tried to use and was from version r66. Since centroids is no longer available it looks like we have to create our own centroids from what I have read and have tried to do. I think its somewhere in the function that I have something wrong. if you run the code above it shows the group of four cubes but its not removing the inner triangle faces.


#7

That’s cute that you’ve provided just code instead of link to a working example :slight_smile:

What do you mean with this? They are available, just not equal and don’t coincide :slight_smile:


#8

Sorry here is a fiddle https://jsfiddle.net/rabgf3h7/ never created one before. There is an example that I have found https://stackoverflow.com/questions/21418698/three-js-removing-inner-faces that was also trying to figure out this same problem back then. Someone replied saying “Centroid has been removed from faces it looks like”. I tried to make the centroids and just not sure how to fix or convert the code from the function removeDuplicateFaces() from http://jsfiddle.net/majman/4sukB/2/. I maybe have added some stuff that does not need to be there or have just tried converting it wrong. That is where I need help on. Thanks.


#9

Do you plan to merge only cubes, like in the example, side-by-side?


#10

I plan to merge only cubes side by side yes. I have made a dungeon map using ROT.js and THREE.js together. I’ll try and make a link to show what I’m trying to achieve. If i can get this small example working i was planning to merge it with the bigger example. If they are removed then it’ll look like a dungeon once I set a viewpoint from inside.


#11

OK, I have created a fiddle https://jsfiddle.net/7y2nw834/3/ of main project. That’s why I tried to make a smaller version of just four cubes together with needing inner faces removed. Thanks.


#12

Wow. Thanks for a cool example, I’ll have a look :slight_smile:


#13

sure no problem, trying to make a walk through dungeon and not walls in front of me when i walk forward through a hall or room. was thinking of adding doors later on but just need to get through this hurdle.


#14

From what I’ve seen (and if I got it correctly):
You have an array of arrays (50 x 50).
You put a box, when you meet # sign in the array.
At this point, you can check next 4 elements around the current “box” and, if any of them is # too, then do something with the sides of the current cube in accordance with the position the next # relatively to the current one. Thus it will be much more efficient, than researching/developing an algorithm for removing inner faces.


#15

I’ve used the method described in my previous post and I believe I’ve achieved the desired result.

https://jsfiddle.net/prisoner849/Lmrh25js/

Checked 4 nearest elements around current and rebuild box geometry’s index in accordance to the results of checks.


#16

Thank you very much for helping me with this. Been trying to figure out how to check the four elements.


#17

Just added one more parameter to tile() function - dungeonMap, and then inside this function just do:

var hasTop = row - 1 < 0 ? true : dungeonMap.getTile ( row - 1, column ).getGlyph()._character !== char;
var hasBottom = row + 1 == dungeonMap._width ? true : dungeonMap.getTile ( row + 1, column ).getGlyph()._character !== char;
var hasLeft = column - 1 < 0 ? true : dungeonMap.getTile ( row, column - 1   ).getGlyph()._character !== char;
var hasRight = column + 1 == dungeonMap._height ? true : dungeonMap.getTile ( row, column + 1 ).getGlyph()._character !== char;
            
var geometry = new THREE.BoxBufferGeometry ();
var index = [];
if (hasRight) index.push(0, 2, 1, 2, 3, 1);
if (hasLeft) {index.push(4, 6, 5, 6, 7, 5);}
index.push(8, 10, 9, 10, 11, 9, 12, 14, 13, 14, 15, 13);
if (hasBottom) {index.push(16, 18, 17, 18, 19, 17);}
if (hasTop) {index.push(20, 22, 21, 22, 23, 21);}
geometry.setIndex(index);

Can’t say it’s something elegant. At least it does what it intended to :slight_smile:
The trick with index is just to know how the box buffer geometry created under the hood:


#18

Shoot your code a lot better than my spaghetti :wink: :beers: are on me. I appreciate your help on this.


#19

Another efficient solution @prisoner849.:+1:

It is also suitable for other applications.
Therefore I have packed the core of the solution into a simple basic example:

Immediately: http://discourse.threejs.hofk.de/
there
http://discourse.threejs.hofk.de/BoxLabyrinthBasic/BoxLabyrinthBasic.html


<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/how-to-remove-interior-faces-while-keeping-exterior-faces-untouched/4869/15 -->

<!-- see template @Mardonis -->
<!-- and https://jsfiddle.net/prisoner849/Lmrh25js/ -->

<head>
	<title> BoxLabyrinthBasic </title>
	<meta charset="utf-8" />
</head>
<body> 	
 change in code: var p = [ 1, 1,  1, 1,  0, 0 ]; // planes px,nx, py,ny, pz,nz  -> 0 hide, 1 show
</body>
	<script src="../js/three.min.98.js"></script>
	<script src="../js/OrbitControls.js"></script>
	
<script>

// @author prisoner849, hofk

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 0, 1, 4 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xaaaaaa, 1 );	
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 
var controls = new THREE.OrbitControls( camera, renderer.domElement );

//var texture	= new THREE.TextureLoader().load( "uvgrid01.png" );
//var material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } );

var material = new THREE.MeshBasicMaterial( { color: 0xbb00ff, side: THREE.DoubleSide, wireframe: true } );
var geometry = new THREE.BoxBufferGeometry();

var p = [ 1, 1,  1, 1,  0, 0 ]; // planes px,nx, py,ny, pz,nz  -> 0 hide, 1 show

var index = [];
if ( p[0] === 1 ) index.push( 0, 2, 1, 2, 3, 1 );
if ( p[1] === 1 ) index.push( 4, 6, 5, 6, 7, 5 );
if ( p[2] === 1 ) index.push( 8, 10, 9, 10, 11, 9 )
if ( p[3] === 1 ) index.push( 12, 14, 13, 14, 15, 13 );
if ( p[4] === 1 ) index.push( 16, 18, 17, 18, 19, 17 );
if ( p[5] === 1 ) index.push( 20, 22, 21, 22, 23, 21 );
geometry.setIndex( index );

var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

animate();

function animate() {

	requestAnimationFrame( animate );	
	renderer.render( scene, camera );
	controls.update();
	
}

</script>
</html>

#20

@hofk
That simplified example is really good, Klaus :slight_smile: :+1: