How to remove interior faces while keeping exterior faces untouched?

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 ( );

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.

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.

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

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

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.

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:

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.

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

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.

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.

1 Like

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

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.

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.

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.

1 Like

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

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:

2 Likes

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

1 Like

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>
2 Likes

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