How to remove interior faces while keeping exterior faces untouched?

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
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 are on me. I appreciate your help on this.

1 Like

Another efficient solution @prisoner849.

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

``````<!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

The labyrinth @Mardonis and the solution @prisoner849 gave me the idea of a
BoxBufferLabyrinthGeometry3D.

This should be very easy to generate in many forms.

One result:

I just added it to the collection. Collection of examples from discourse.threejs.org
http://discourse.threejs.hofk.de/

Have fun with it

``````<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/how-to-remove-interior-faces-while-keeping-exterior-faces-untouched/4869/19 --> <!-- http://threejs.hofk.de/BoxLabyrinthCreation3D/BoxLabyrinthCreation3D.html  -->

<head>
<title> BoxLabyrinthCreation3D </title>
<meta charset="utf-8" />
</head>
<body>

</body>
<script src="../js/three.min.98.js"></script>
<script src="../js/BufferGeometryUtils.js"></script> <!-- not in the core -->
<script src="../js/OrbitControls.js"></script>
<script src="../js/THREEx.WindowResize.js"></script>

<script>

// @author hofk

'use strict';
/*_______________________________________________

icons
The characters on the keyboard have been chosen so that they roughly reflect the form.

wall description
sides l f r b is left front right back, with floor and roof

char sides
G	l f r b   can only be achieved by beaming
M	l f r
C	b l f
3	f r b
U	l b r
H	l r
:	f b
F	l f
7	f r
L	l b
J	b r
I	l
1	r
-	f
.	b

without walls
since extra character not possible on the wall
* roof and floor
^ roofless
v floorless
x roofless and floorless

with four side walls but roofless and floorless
#
_________________________________________________*/

var designBoxLabyrinth3D = [
// upper storey first
//23456789.......
[
'     M         G', // 1
'     H          ', // 2
'     H          ', // 3
'   F-*--7       ', // 4
'   I*7**1       ', // 5
' C:v*L.**:::7   ', // 6
'   L*...J   U   ', // 7
'    H           ', // 8
'    L::::3      '  // 9
],[
'                ', // 1
'                ', // 2
'                ', // 3
'                ', // 4
'                ', // 5
'   #            ', // 6
'                ', // 7
'                ', // 8
'                '  // 9
],[
'F::3            ', // 1
'H    F:::::7    ', // 2
'H    H     H    ', // 3
'H  F-*-7   H    ', // 4
'H  I****:::1    ', // 5
'L::x***1   H    ', // 6
'   I...J   H    ', // 7
'   H   F:7 L:::7', // 8
'   L:::J L:::::J'  // 9
]];

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

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

var boxes = [];
var index = [];
var storeys = designBoxLabyrinth3D.length;

for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < designBoxLabyrinth3D[ f ].length; r ++ ) {

for( var c = 0; c < designBoxLabyrinth3D[ f ][ r ].length; c ++ ) {

boxes.push( createBox( designBoxLabyrinth3D[ storeys - 1 - f ][ r ][ c ] ) );

}

}

}

var labyrinthGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries( boxes );
var labyrinthMesh = new THREE.Mesh( labyrinthGeometry, material );
scene.add( labyrinthMesh );

animate();

function animate() {

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

}

function createBox( icon ) {

var g = new THREE.BoxBufferGeometry( );
index = [];

switch ( icon ) {
case 'G': box_G( );     break;
case 'M': box_M( );     break;
case 'C': box_C( );     break;
case '3': box_3( );     break;
case 'U': box_U( );     break;
case 'H': box_H( );     break;
case ':': box_colon( ); break;
case 'F': box_F( );     break;
case '7': box_7( );     break;
case 'L': box_L( );     break;
case 'J': box_J( );     break;
case 'I': box_I( );     break;
case '1': box_1( );     break;
case '-': box_minus( ); break;
case '.': box_dot( );   break;
case '*': box_multi( ); break;
case '^': box_caret( ); break;
case 'v': box_v( );     break;
case 'x': box_x( );     break;
case '#': box_sharp( ); break;
default: box_x( );
}

g.setIndex( index );

// position  c, f, r

for ( let xidx = 0; xidx < 72; xidx += 3 ) {

g.getAttribute('position').array[ xidx ] = g.getAttribute('position').array[ xidx ] + c;

}

for ( let yidx = 1; yidx < 72; yidx += 3 ) {

g.getAttribute('position').array[ yidx ] = g.getAttribute('position').array[ yidx ] + f;

}

for ( let zidx = 2; zidx < 72; zidx += 3 ) {

g.getAttribute('position').array[ zidx ] = g.getAttribute('position').array[ zidx ] + r;

}

return g;

}

function px( ) { index.push( 0, 2, 1, 2, 3, 1 ) }
function nx( ) { index.push( 4, 6, 5, 6, 7, 5 ) }
function py( ) { index.push( 8, 10, 9, 10, 11, 9 ) }
function ny( ) { index.push( 12, 14, 13, 14, 15, 13 ) }
function pz( ) { index.push( 16, 18, 17, 18, 19, 17 ) }
function nz( ) { index.push( 20, 22, 21, 22, 23, 21 ) }

function box_G( ) { px( ); nx( ); py( ); ny( ); pz( ); nz( ) }
function box_M( ) { px( ); nx( ); py( ); ny( ); nz( ) }
function box_C( ) { nx( ); py( ); ny( ); pz( ); nz( ) }
function box_3( ) { px( ); py( ); ny( ); pz( ); nz( ) }
function box_U( ) { px( ); nx( ); py( ); ny( ); pz( ) }
function box_H( ) { px( ); nx( ); py( ); ny( ) }
function box_colon( ) { py( ); ny( ); pz( ); nz( ) }
function box_F( ) { nx( ); py( ); ny( ); nz( ) }
function box_7( ) { px( ); py( ); ny( );  nz( ) }
function box_L( ) { nx( ); py( ); ny( ); pz( ) }
function box_J( ) { px( ); py( ); ny( ); pz( ) }
function box_I( ) { nx( ); py( ); ny( ) }
function box_1( ) { px( ); py( ); ny( ) }
function box_minus( ) { py( ); ny( ); nz( ) }
function box_dot( ) { py( ); ny( ); pz( ) }
function box_multi( ) { py( ); ny( ) };
function box_caret( ) { ny( ) }
function box_v( ) { py( ) }
function box_x( ) {  }
function box_sharp( ) { px( ); nx( ); pz( ); nz( ) }

</script>

</html>``````
2 Likes

Thank you for that collection group of examples and that example of a multi level labyrinth is exactly what I’m trying to make.

I also wanted to quickly add different materials for floor/ceiling/walls.

At the beginning there was nothing to see with multi material.

In the docs:

BufferGeometryUtils

.mergeBufferGeometries ( geometries : Array ) : BufferGeometry
geometries – Array of BufferGeometry instances.
Merges a set of geometries into a single instance. All geometries must have compatible attributes. If merge does not succeed, the method returns null.

mergeBufferGeometries: function ( geometries, useGroups ) { …

That’s what I used, but then the complete boxes are provided with the material one after the other.

So you would have to set the groups yourself.

1 Like

So with your designBoxLabyrinth3D array that looks like you can create manually (appreciate that example), would I have to create another function to automatically convert an automatically made one or just modify my generator.create function(which I’m leaning toward)? For example https://jsfiddle.net/7y2nw834/3/ I use ROT.js library to create a random map every time:

``````var generator = new ROT.Map.Digger ( DUNGEON_WIDTH , DUNGEON_HEIGHT );

generator.create ( function ( x , y , v ) {

if ( v === 1 ) {

map[x][y] = Dungeon.Tile.floorTile;
//console.log ( "    Floor Materials Added --> x: " + x + ", y: " + y + ", glyph: " + v );

} else {

map[x][y] = Dungeon.Tile.wallTile;
//console.log ( "    Wall Materials Added --> x: " + x + ", y: " + y + ", glyph: " + v );

}

} );``````

'F::3 ', // 1
With row one it looks like a corridor. How would you read original map array and then convert it to the following below converted array through a function? Example:

Original map

``````'   xxxxxx   x   ', // 1
'    x           ', // 2
'    xxxxxx      '  // 3
``````

Converted map

``````'   L*...J   U   ', // 1
'    H           ', // 2
'    L::::3      '  // 3``````

Gives from the front

from behind

It’s not quite trivial.

Well, I think creating an extra BufferGeometry for the labyrinth is more efficient.
That’s why I tried. It is not at all so complex. I just adapted the basic structure. Additionally you can have different materials for the stories.

``````<!DOCTYPE html>

<head>
<title> BufferLabyrinthCreation3D </title>
<meta charset="utf-8" />
</head>
<body>

</body>
<script src="three.min.98.js"></script>

<script src="OrbitControls.js"></script>
<script src="THREEx.WindowResize.js"></script>

<script>

// @author hofk

'use strict';
/*_______________________________________________

icons
The characters on the keyboard have been chosen so that they roughly reflect the form.

wall description
sides l f r b is left front right back, with floor and roof

char sides
G	l f r b   can only be achieved by beaming
M	l f r
C	b l f
3	f r b
U	l b r
H	l r
:	f b
F	l f
7	f r
L	l b
J	b r
I	l
1	r
-	f
.	b

without walls
since extra character not possible on the wall
* roof and floor
^ roofless
v floorless
x roofless and floorless

with four side walls but roofless and floorless
#
_________________________________________________*/

var designBoxLabyrinth3D = [
// upper storey first
//23456789.......
[
'     M         G', // 1
'     H          ', // 2
'     H          ', // 3
'   F-*--7       ', // 4
'   I*7**1       ', // 5
' C:v*L.**:::7   ', // 6
'   L*...J   U   ', // 7
'    H           ', // 8
'    L::::3      '  // 9
],[
'                ', // 1
'                ', // 2
'          G     ', // 3
'                ', // 4
'                ', // 5
'   #            ', // 6
'                ', // 7
'                ', // 8
'                '  // 9
],[
'F::3            ', // 1
'H    F:::::7    ', // 2
'H    H     H    ', // 3
'H  F-*-7   H    ', // 4
'H  I****:::1    ', // 5
'L::x***1   H    ', // 6
'   I...J   H    ', // 7
'   H   F:7 L:::7', // 8
'   L:::J L:::::J'  // 9
]];

// labyrinth material index
var m = [
// upper storey first
// px, nx, py, ny, pz, nz
[ 0, 1, 2, 3, 4, 5 ],
[ 0, 0, 1, 1, 2, 2 ],
[ 6, 7, 6, 7, 6, 7 ],
];

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 2, 10, 25 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x111111, 1 );
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );
THREEx.WindowResize( renderer, camera );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
var AxesHelper = new THREE.AxesHelper( 5 );
scene.add( AxesHelper );

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

var texture0	= new THREE.TextureLoader().load( "brick.jpg" );
var texture1	= new THREE.TextureLoader().load( "uvSpray.png" );
var texture2	= new THREE.TextureLoader().load( "Granit.jpg" );
var texture3	= new THREE.TextureLoader().load( "kunst1.png" );
var texture4	= new THREE.TextureLoader().load( "uvgrid01.png" );
var texture5	= new THREE.TextureLoader().load( "Fliese03.png" );
var texture6	= new THREE.TextureLoader().load( "Fliese01.png" );

//var material = new THREE.MeshBasicMaterial( { map: texture0 , side: THREE.DoubleSide, wireframe: false } );
var material = [
new THREE.MeshBasicMaterial( { map: texture0, side: THREE.DoubleSide } ), // 0 material index
new THREE.MeshBasicMaterial( { map: texture1, side: THREE.DoubleSide } ), // 1
new THREE.MeshBasicMaterial( { map: texture2, side: THREE.DoubleSide } ), // 2
new THREE.MeshBasicMaterial( { map: texture3, side: THREE.DoubleSide } ), // 3
new THREE.MeshBasicMaterial( { map: texture4, side: THREE.DoubleSide } ), // 4
new THREE.MeshBasicMaterial( { map: texture5, side: THREE.DoubleSide } ), // 5
new THREE.MeshBasicMaterial( { map: texture6, side: THREE.DoubleSide } ), // 6
new THREE.MeshBasicMaterial( { map: texture0, side: THREE.DoubleSide } ), // 7
];

var g = new THREE.BufferGeometry( );

var storeys = designBoxLabyrinth3D.length;
var s;
var icon;
var c1, f1, r1; // next column x, floor y, row z
var planesPos = [];
var posIdx = 0;
var planesUVs = [];
var uvIdx = 0;
var groupStart = 0;

// count faces
g.faceCount = 0;

for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < designBoxLabyrinth3D[ f ].length; r ++ ) {

for( var c = 0; c < designBoxLabyrinth3D[ f ][ r ].length; c ++ ) {

icon = designBoxLabyrinth3D[ storeys - 1 - f ][ r ][ c ];

if ( icon !== ' ' ) { countFaces( icon ) }

}

}

}

g.positions = new Float32Array( g.faceCount * 9 );
//g.normals = new Float32Array( g.faceCount * 9 );
g.uvs = new Float32Array( g.faceCount * 6 );  // uv's to positions

g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) );
//g.addAttribute( 'normal', new THREE.BufferAttribute( g.normals, 3 ) );
g.addAttribute( 'uv', new THREE.BufferAttribute( g.uvs, 2 ) );

// create faces
for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < designBoxLabyrinth3D[ f ].length; r ++ ) {

for( var c = 0; c < designBoxLabyrinth3D[ f ][ r ].length; c ++ ) {

icon = designBoxLabyrinth3D[ storeys - 1 - f ][ r ][ c ];

if ( icon !== ' ' ) {

c1 = c + 1;
f1 = f + 1;
r1 = r + 1;

createBox( icon )

}

}

}

}

var labyrinthMesh = new THREE.Mesh( g, material );
scene.add( labyrinthMesh );

animate();

function animate() {

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

}

function countFaces( icon ) {

switch ( icon ) {
case 'G':
g.faceCount += 12;
break;
case 'M':
case 'C':
case '3':
case 'U':
g.faceCount += 10;
break;
case 'H':
case ':':
case 'F':
case '7':
case 'L':
case 'J':
case '#': // only 4 side walls
g.faceCount += 8;
break;
case 'I':
case '1':
case '-':
case '.':
g.faceCount += 6;
break;
case '*':
g.faceCount += 4;
break;
case '^':
case 'v':
g.faceCount += 2;
break;
// case 'x':
//  g.faceCount += 0
//  break;
default:
//  g.faceCount += 0
}

}

function createBox( icon ) {

planesPos = [];
planesUVs = [];

s = storeys - 1 - f; // upper storey first

switch ( icon ) {
case 'G': box_G( );     break;
case 'M': box_M( );     break;
case 'C': box_C( );     break;
case '3': box_3( );     break;
case 'U': box_U( );     break;
case 'H': box_H( );     break;
case ':': box_colon( ); break;
case 'F': box_F( );     break;
case '7': box_7( );     break;
case 'L': box_L( );     break;
case 'J': box_J( );     break;
case 'I': box_I( );     break;
case '1': box_1( );     break;
case '-': box_minus( ); break;
case '.': box_dot( );   break;
case '*': box_multi( ); break;
case '^': box_caret( ); break;
case 'v': box_v( );     break;
case 'x': box_x( );     break;
case '#': box_sharp( ); break;
default: box_x( );
}

for ( let i = 0; i < planesPos.length; i ++ ) {

g.positions[ posIdx + i ] = planesPos[ i ];

}

posIdx += planesPos.length;

for ( let i = 0; i < planesUVs.length; i ++ ) {

g.uvs[ uvIdx + i ] = planesUVs[ i ];

}

uvIdx += planesUVs.length;

}

// function pushUVs( ) { planesUVs.push( 1,0, 1,1, 0,1,  1,0, 0,1, 0,0 ) } // outside
function pushUVs( ) { planesUVs.push( 0,0, 0,1, 1,1,  0,0, 1,1, 1,0 ) }    // inside
function groupAdd( m ) { g.addGroup( groupStart, 6, m ); groupStart += 6 }

function px( ) { planesPos.push( c1,f,r, c1,f1,r, c1,f1,r1,   c1,f,r, c1,f1,r1, c1,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 0 ] ) }
function nx( ) { planesPos.push( c,f,r1, c,f1,r1, c,f1,r,   c,f,r1, c,f1,r, c,f,r ); pushUVs( ); groupAdd( m[ s ][ 1 ] ) }
function py( ) { planesPos.push( c,f1,r1, c1,f1,r1, c1,f1,r,   c,f1,r1, c1,f1,r, c,f1,r ); pushUVs( ); groupAdd( m[ s ][ 2 ] ) }
function ny( ) { planesPos.push( c,f,r, c1,f,r, c1,f,r1,   c,f,r, c1,f,r1, c,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 3 ] ) }
function pz( ) { planesPos.push( c1,f,r1, c1,f1,r1, c,f1,r1,   c1,f,r1, c,f1,r1, c,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 4 ] ) }
function nz( ) { planesPos.push( c,f,r, c,f1,r, c1,f1,r,   c,f,r, c1,f1,r, c1,f,r ); pushUVs( ); groupAdd( m[ s ][ 5 ] ) }

function box_G( ) { px( ); nx( ); py( ); ny( ); pz( ); nz( ) }
function box_M( ) { px( ); nx( ); py( ); ny( ); nz( ) }
function box_C( ) { nx( ); py( ); ny( ); pz( ); nz( ) }
function box_3( ) { px( ); py( ); ny( ); pz( ); nz( ) }
function box_U( ) { px( ); nx( ); py( ); ny( ); pz( ) }
function box_H( ) { px( ); nx( ); py( ); ny( ) }
function box_colon( ) { py( ); ny( ); pz( ); nz( ) }
function box_F( ) { nx( ); py( ); ny( ); nz( ) }
function box_7( ) { px( ); py( ); ny( );  nz( ) }
function box_L( ) { nx( ); py( ); ny( ); pz( ) }
function box_J( ) { px( ); py( ); ny( ); pz( ) }
function box_I( ) { nx( ); py( ); ny( ) }
function box_1( ) { px( ); py( ); ny( ) }
function box_minus( ) { py( ); ny( ); nz( ) }
function box_dot( ) { py( ); ny( ); pz( ) }
function box_multi( ) { py( ); ny( ) };
function box_caret( ) { ny( ) }
function box_v( ) { py( ) }
function box_x( ) {  }
function box_sharp( ) { px( ); nx( ); pz( ); nz( ) }

</script>

</html>``````
1 Like

Thank you for the awesome materials examples. @hofk How would you convert a simple dungeon array to your more intricate array that you have made above in an earlier example? Trying to figure out how to make a function that would say, if i encounter a “X” that has another “X” before and after it but not above or below then I know I need to convert that “X” to a “:” to represent that it must be part of a hall. Do you know how I would go about doing that? Thanks.

How to make function to convert from:

``````"xxxxxxxxx"
"x       x"
"x   x   x"
"x       x"
"xxxxxxxxx"
``````

To:

``````"F:::::::7"
"H       H"
"H   G   H"
"H       H"
"L:::::::J"``````
``````oldArray =

// j-1 j+1
//   - -
//    j
[
//012345678
"xxxxxxxxx" // 0  // i-1
"x       x" // 1  // i
"x   x   x" // 2  // i+1
"x       x" // 3
"xxxxxxxxx" // 4
];
``````

With two (or three 3D) nested loops (as with the evaluation of designBoxLabyrinth3D = [ … ], go through all elements (i , j). Check all surrounding elements. Of course treat the boundary elements ( 0 , max) accordingly.

From this you can set the corresponding symbol in newArray = [ … ] .

Ok, it took me most of the day to come up with the following fiddle https://jsfiddle.net/Mardonis/3y8mp940/4/ to have a start on the process of finding a way to convert a basic layout to the more intricate array you provided. It shows a console.log version of the original and a html version to show the converted way once its figured out. For now it just shows the same.

If I see it right, the dungeon has no entrance/exit and is 2D. This only requires a real subset of characters from my definition field.

This makes things easier.

I converted your definition with the # characters.
But instead of a field of single characters I gave a string. This is easier/ clearer but compatible. You can easily transform it again.

You can take dots or spaces or anything for the unused places.

``````// dungeon 2D is subset of labyrinth 3D
var designDungeon2D = [  // will be converted to labyrinth design 3D
' ##########  ',
' ###  ##  ##  ',
' ###  ###     ',
'## ## ########  ',
' ##### # #   #',
'   #  ## #####',
'   ####  #'
];
``````

``````<!DOCTYPE html>

<head>
<title> DungeonCreation2D </title>
<meta charset="utf-8" />
</head>
<body>

</body>
<script src="../js/three.min.98.js"></script>

<script src="../js/OrbitControls.js"></script>
<script src="../js/THREEx.WindowResize.js"></script>

<script>

// @author hofk

'use strict';
/*_______________________________________________

icons for labyrinth design 3D
The characters on the keyboard have been chosen so that they roughly reflect the form.

wall description
sides l f r b is left front right back, with floor and roof

char sides
//G	l f r b   can only be achieved by beaming
M	l f r
C	b l f
3	f r b
U	l b r
H	l r
:	f b
F	l f
7	f r
L	l b
J	b r
I	l
1	r
-	f
.	b

without walls
since extra character not possible on the wall
* roof and floor
//^ roofless
//v floorless
//x roofless and floorless

with four side walls but roofless and floorless
// #
_________________________________________________*/

// dungeon 2D is subset of labyrinth 3D
var designDungeon2D = [  // will be converted to labyrinth design 3D
' ##########  ',
' ###  ##  ##  ',
' ###  ###     ',
'## ## ########  ',
' ##### # #   #',
'   #  ## #####',
'   ####  #'
];

// labyrinth material index
var m = [

// only 1 storey in dungeon 2D
// px, nx, py, ny, pz, nz
[ 0, 1, 2, 3, 4, 5 ]

];

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 2, 10, 25 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x111111, 1 );
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );
THREEx.WindowResize( renderer, camera );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
var AxesHelper = new THREE.AxesHelper( 5 );
scene.add( AxesHelper );

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

var texture0	= new THREE.TextureLoader().load( "brick.jpg" );
var texture1	= new THREE.TextureLoader().load( "uvSpray.png" );
var texture2	= new THREE.TextureLoader().load( "Granit.jpg" );
var texture3	= new THREE.TextureLoader().load( "kunst1.png" );
var texture4	= new THREE.TextureLoader().load( "uvgrid01.png" );
var texture5	= new THREE.TextureLoader().load( "Fliese03.png" );
var texture6	= new THREE.TextureLoader().load( "Fliese01.png" );

var material = new THREE.MeshBasicMaterial( { map: texture0 , side: THREE.DoubleSide, wireframe: false } );
var material = [
new THREE.MeshBasicMaterial( { map: texture0, side: THREE.DoubleSide } ), // 0 material index
new THREE.MeshBasicMaterial( { map: texture1, side: THREE.DoubleSide } ), // 1
new THREE.MeshBasicMaterial( { map: texture2, side: THREE.DoubleSide } ), // 2
new THREE.MeshBasicMaterial( { map: texture3, side: THREE.DoubleSide } ), // 3
new THREE.MeshBasicMaterial( { map: texture4, side: THREE.DoubleSide } ), // 4
new THREE.MeshBasicMaterial( { map: texture5, side: THREE.DoubleSide } ), // 5
new THREE.MeshBasicMaterial( { map: texture6, side: THREE.DoubleSide } ), // 6
new THREE.MeshBasicMaterial( { map: texture0, side: THREE.DoubleSide } ), // 7
];

var g = new THREE.BufferGeometry( );

var s;
var icon;
var c1, f1, r1; // next column x, floor y, row z
var planesPos = [];
var posIdx = 0;
var planesUVs = [];
var uvIdx = 0;
var groupStart = 0;

// convert "dungeon design 2D"  style to "labyrinth3D" style
var storeys = 1; // like var storeys = designBoxLabyrinth3D.length;

var designBoxLabyrinth3D = [];
designBoxLabyrinth3D.push( [] );

var dRows = designDungeon2D.length;
var dCols;
var row;

for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < dRows; r ++ ) {

dCols = designDungeon2D[ r ].length;
row = '';

for( var c = 0; c < dCols; c ++ ) {

row = row + convertDesignDungeon2D( designDungeon2D );

}

designBoxLabyrinth3D[ 0 ].push( row );

}

}

///////////////////////////////////
console.log( designBoxLabyrinth3D );
///////////////////////////////////

function convertDesignDungeon2D( dsgn ) {

var left = false;
var front = false;
var right = false;
var back = false;

if ( dsgn[ r ][ c ] === '#' ) {

if( c === 0 ) { left = true }	else { if( dsgn[ r ][ c-1 ] !== '#' ) { left = true } };
if( r === 0 ) { front = true }	else { if( dsgn[ r-1 ][ c ] !== '#' ) { front = true } };
if( c === dCols - 1 ) { right = true }	else { if( dsgn[ r ][ c+1 ] !== '#' ) { right = true } };
if( r === dRows - 1 ) { back = true }	else { if( dsgn[ r+1 ][ c ] !== '#' ) { back = true } };

///////////////////////////////////
console.log( r, c, left, front, right, back );
///////////////////////////////////

if ( left && front && right && back ) return 'G';
if ( left && front && right && !back ) return 'M';
if ( left && front && !right && back ) return 'C';
if ( !left && front && right && back ) return '3';
if ( left && !front && right && back ) return 'U';
if ( left && !front && right && !back ) return 'H';
if ( !left && front && !right && back ) return ':';
if ( left && front && !right && !back ) return 'F';
if ( !left && front && right && !back ) return '7';
if ( left && !front && !right && back ) return 'L';
if ( !left && !front && right && back ) return 'J';
if ( left && !front && !right && !back ) return 'I';
if ( !left && !front && right && !back ) return '1';
if ( !left && front && !right && !back ) return '-';
if ( !left && !front && !right && back ) return '.';
if ( !left && !front && !right && !back ) return '*';

} else {
// elseif ( dsgn[ r ][ c ] === '.' )
return  ' ';  // String.fromCharCode(32); // space

}

}

// count faces
g.faceCount = 0;

for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < designBoxLabyrinth3D[ f ].length; r ++ ) {

for( var c = 0; c < designBoxLabyrinth3D[ f ][ r ].length; c ++ ) {

icon = designBoxLabyrinth3D[ storeys - 1 - f ][ r ][ c ];

if ( icon !== ' ' ) { countFaces( icon ) }

}

}

}

g.positions = new Float32Array( g.faceCount * 9 );
//g.normals = new Float32Array( g.faceCount * 9 );
g.uvs = new Float32Array( g.faceCount * 6 );  // uv's to positions

g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) );
//g.addAttribute( 'normal', new THREE.BufferAttribute( g.normals, 3 ) );
g.addAttribute( 'uv', new THREE.BufferAttribute( g.uvs, 2 ) );

// create faces
for( var f = 0; f < storeys; f ++ ) {

for( var r = 0; r < designBoxLabyrinth3D[ f ].length; r ++ ) {

for( var c = 0; c < designBoxLabyrinth3D[ f ][ r ].length; c ++ ) {

icon = designBoxLabyrinth3D[ storeys - 1 - f ][ r ][ c ];

if ( icon !== ' ' ) {

c1 = c + 1;
f1 = f + 1;
r1 = r + 1;

createBox( icon )

}

}

}

}

var labyrinthMesh = new THREE.Mesh( g, material );
scene.add( labyrinthMesh );

animate();

function animate() {

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

}

function countFaces( icon ) {

switch ( icon ) {
case 'G':
g.faceCount += 12;
break;
case 'M':
case 'C':
case '3':
case 'U':
g.faceCount += 10;
break;
case 'H':
case ':':
case 'F':
case '7':
case 'L':
case 'J':
case '#': // only 4 side walls
g.faceCount += 8;
break;
case 'I':
case '1':
case '-':
case '.':
g.faceCount += 6;
break;
case '*':
g.faceCount += 4;
break;
case '^':
case 'v':
g.faceCount += 2;
break;
// case 'x':
//  g.faceCount += 0
//  break;
default:
//  g.faceCount += 0
}

}

function createBox( icon ) {

planesPos = [];
planesUVs = [];

s = storeys - 1 - f; // upper storey first

switch ( icon ) {
case 'G': box_G( );     break;
case 'M': box_M( );     break;
case 'C': box_C( );     break;
case '3': box_3( );     break;
case 'U': box_U( );     break;
case 'H': box_H( );     break;
case ':': box_colon( ); break;
case 'F': box_F( );     break;
case '7': box_7( );     break;
case 'L': box_L( );     break;
case 'J': box_J( );     break;
case 'I': box_I( );     break;
case '1': box_1( );     break;
case '-': box_minus( ); break;
case '.': box_dot( );   break;
case '*': box_multi( ); break;
case '^': box_caret( ); break;
case 'v': box_v( );     break;
case 'x': box_x( );     break;
case '#': box_sharp( ); break;
default: box_x( );
}

for ( let i = 0; i < planesPos.length; i ++ ) {

g.positions[ posIdx + i ] = planesPos[ i ];

}

posIdx += planesPos.length;

for ( let i = 0; i < planesUVs.length; i ++ ) {

g.uvs[ uvIdx + i ] = planesUVs[ i ];

}

uvIdx += planesUVs.length;

}

// function pushUVs( ) { planesUVs.push( 1,0, 1,1, 0,1,  1,0, 0,1, 0,0 ) } // outside
function pushUVs( ) { planesUVs.push( 0,0, 0,1, 1,1,  0,0, 1,1, 1,0 ) }    // inside
function groupAdd( m ) { g.addGroup( groupStart, 6, m ); groupStart += 6 }

function px( ) { planesPos.push( c1,f,r, c1,f1,r, c1,f1,r1,   c1,f,r, c1,f1,r1, c1,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 0 ] ) }
function nx( ) { planesPos.push( c,f,r1, c,f1,r1, c,f1,r,   c,f,r1, c,f1,r, c,f,r ); pushUVs( ); groupAdd( m[ s ][ 1 ] ) }
function py( ) { planesPos.push( c,f1,r1, c1,f1,r1, c1,f1,r,   c,f1,r1, c1,f1,r, c,f1,r ); pushUVs( ); groupAdd( m[ s ][ 2 ] ) }
function ny( ) { planesPos.push( c,f,r, c1,f,r, c1,f,r1,   c,f,r, c1,f,r1, c,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 3 ] ) }
function pz( ) { planesPos.push( c1,f,r1, c1,f1,r1, c,f1,r1,   c1,f,r1, c,f1,r1, c,f,r1 ); pushUVs( ); groupAdd( m[ s ][ 4 ] ) }
function nz( ) { planesPos.push( c,f,r, c,f1,r, c1,f1,r,   c,f,r, c1,f1,r, c1,f,r ); pushUVs( ); groupAdd( m[ s ][ 5 ] ) }

function box_G( ) { px( ); nx( ); py( ); ny( ); pz( ); nz( ) }
function box_M( ) { px( ); nx( ); py( ); ny( ); nz( ) }
function box_C( ) { nx( ); py( ); ny( ); pz( ); nz( ) }
function box_3( ) { px( ); py( ); ny( ); pz( ); nz( ) }
function box_U( ) { px( ); nx( ); py( ); ny( ); pz( ) }
function box_H( ) { px( ); nx( ); py( ); ny( ) }
function box_colon( ) { py( ); ny( ); pz( ); nz( ) }
function box_F( ) { nx( ); py( ); ny( ); nz( ) }
function box_7( ) { px( ); py( ); ny( );  nz( ) }
function box_L( ) { nx( ); py( ); ny( ); pz( ) }
function box_J( ) { px( ); py( ); ny( ); pz( ) }
function box_I( ) { nx( ); py( ); ny( ) }
function box_1( ) { px( ); py( ); ny( ) }
function box_minus( ) { py( ); ny( ); nz( ) }
function box_dot( ) { py( ); ny( ); pz( ) }
function box_multi( ) { py( ); ny( ) };
function box_caret( ) { ny( ) }
function box_v( ) { py( ) }
function box_x( ) {  }
function box_sharp( ) { px( ); nx( ); pz( ); nz( ) }

</script>

</html>``````
1 Like

In response to a question in a message @Mardonis , I randomly placed an object in the dungeon.

It uses the version from the THREEg.js addon.https://github.com/hofk/THREEg.js/blob/master/THREEg.js

``````var insideLabyrinth = [];

for ( let i = 0; i < design2D.length; i ++) {

for ( let j = 0; j < design2D[ i ].length; j ++) {

if ( design2D[ i ][ j ]  === '+' )	insideLabyrinth.push( j , i );  // x, z positions

}

}

var tetra = new THREE.TetrahedronBufferGeometry( 0.45 );
var tetraMesh = new THREE.Mesh( tetra, materialUV );

var  random = new Date().getMilliseconds() / 1000 ;

console.log( random +'s' ); ///////////////////

var idx =  Math.floor( random * insideLabyrinth.length / 2 );

console.log( idx + ' idx ' ); ///////////////////

var posX = insideLabyrinth[ idx * 2 ];
var posZ = insideLabyrinth[ idx * 2 + 1 ];;

console.log( posX, posZ ); /////////////

tetraMesh.position.set( posX + 0.5, 0.3, posZ + 0.5);
mesh2D.add( tetraMesh );``````
2 Likes

This worked like a charm. Greatly appreciate all of these great examples.