ProfiledContourGeometry MultiMaterial

With reference to
ProfiledContourGeometry and Simple Rectangular Geometry Extrusion (Anyone?)
here another variant of the creation of such a geometry. An indexed BufferGeometry will be created.

Look there: http://threejs.hofk.de/ProfiledContourGeometryMM/ProfiledContourGeometryMM.html

The definition is done without using any other three.js objects except THREE.BufferGeometry( ). This makes it very easy to use MultiMaterial. There are two different modes to choose from.

In 7 :rainbow: colors:

Setting UV’s doesn’t make much sense in the general form. However, if you have a concrete example like a simple rectangular door frame, the uv values can be easily determined and added.

I will add this geometry to the THREEg addon. Addon to create special / extended geometries


<!DOCTYPE html>
<!--https://discourse.threejs.org/t/profiledcontourgeometry-multimaterial/5801 -->
<!-- http://threejs.hofk.de/ProfiledContourGeometryMM/ProfiledContourGeometryMM.html -->
<head>
	<title> ProfiledContourGeometryMM  </title>
	<meta charset="utf-8" />
	<style>
	body {
		margin: 0;
	}
	</style>
</head>
<body> </body>
<script src="../js/three.min.100.js"></script>
<script src="../js/OrbitControls.js"></script>

<script>
'use strict'

// @author hofk

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( -8, 10, 20 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var controls = new THREE.OrbitControls( camera, renderer.domElement );

var light = new THREE.DirectionalLight( 0xffffff, 0.6);
light.position.setScalar( 10 );
scene.add( light );
scene.add(new THREE.AmbientLight( 0xffffff, 0.8));

var helper = new THREE.GridHelper( 20, 20);
scene.add( helper );

var detail = 7
var profileShape1 = [];

for ( var i = 0; i < detail + 1; i ++ ){
	
	profileShape1.push ( 0.5 * Math.cos( i / detail * Math.PI * 2 ), 0.5 * Math.sin( i / detail * Math.PI * 2 ) );
	
}

var contour1 = [
	-3,  4,
	 0,  4,
	 4,  4,
	 2,  1,
	 4, -2,
	 0, -3,
	-4, -3,
	-4,  0
];

var profileShape2 = [ -1,1, 1,1, 1,-1, -1,-1 ];

var contour2 = [
	 4, 0,
	 4, 8,
	 8, 8,
	12, 8,
	12, 2, // here only to show that angle of 180° vertikal works  
	12, 0,
];

var materials = [ // rainbow-colored
	
	new THREE.MeshPhongMaterial( { color: 0xfa0001, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0xff7b00, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0xf9f901, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x008601, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x01bbbb, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x250290, side: THREE.DoubleSide } ),	
	new THREE.MeshPhongMaterial( { color: 0xfc4ea5, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } ),
	new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } )
	
]

var material = materials[ 2 ].clone();
material.wireframe = true;

//..................................................... contourClosed, openEnded, profileMaterial
var geometry1 = ProfiledContourMMgeometry(profileShape1, contour1, false, false, true );
var fullProfile1 = new THREE.Mesh( geometry1, materials );
fullProfile1.position.z = 6;
scene.add( fullProfile1 );

var fullProfile12 = new THREE.Mesh( geometry1, material );
fullProfile12.position.z = -8;
scene.add( fullProfile12 );

var geometry13 = ProfiledContourMMgeometry(profileShape1, contour1 );
var fullProfile13 = new THREE.Mesh( geometry13, materials );
fullProfile13.position.x = -5;
scene.add( fullProfile13 );

var geometry2 = ProfiledContourMMgeometry(profileShape2, contour2, false, true );
var fullProfile2 = new THREE.Mesh( geometry2, materials );
fullProfile2.scale.set( 0.5, 0.5, 0.5 );
scene.add( fullProfile2 );

render();

function render() {

	requestAnimationFrame(render);
	renderer.render(scene, camera);
	
}

function ProfiledContourMMgeometry( profileShape, contour, contourClosed, openEnded, profileMaterial ) {
	
	contourClosed = contourClosed !== undefined ? contourClosed : true;
	openEnded = openEnded !== undefined ? openEnded : false;
	openEnded = contourClosed === true ? false : openEnded;
	profileMaterial = profileMaterial !== undefined ? profileMaterial : false;
	
	if( contourClosed ) contour.push( contour[ 0 ], contour[ 1 ] );
	
	var hs1 = contour.length / 2;
	var rs1 = profileShape.length / 2;
	var hs = hs1 - 1; // height segments 
	var rs = rs1 - 1; // radius segments
	
	var faceCount = hs * rs * 2 + ( openEnded ? 0 : rs * 2 );
	var posCount = hs1 * rs1 + ( openEnded ? 0 : 2 );
	
	var g = new THREE.BufferGeometry( );
	g.indices = new Uint32Array( faceCount * 3 );
	g.positions = new Float32Array( posCount * 3 );
	
	g.setIndex( new THREE.BufferAttribute( g.indices, 1 ) );
	g.addAttribute( 'position', new THREE.BufferAttribute( g.positions, 3 ) );
	
	var a, b1, c1, b2, c2;
	var i1, i2;
	var xc0, yc0, xc1, yc1, xc2, yc2, xSh, xDiv;
	var dx0, dy0, dx2, dy2;
	var e0x, e0y,e0Length, e2x, e2y, e2Length, ex, ey, eLength;
	var phi, bend;
	var x, y, z;
	var vIdx, posIdx;
	var epsilon = 0.000001;
	var idx = 0;
	
	for ( var i = 0; i < hs; i ++ ) {
		
		if ( profileMaterial ) g.addGroup( idx, rs * 6, i ); // MultiMaterial support
		
		i1 = i + 1;
		
		for ( var j = 0; j < rs; j ++ ) {
			
			// 2 faces / segment,  3 vertex indices
			a =  rs1 * i + j;
			c1 = rs1 * i1 + j; // left 
			b1 = c1 + 1;
			//c2 = b1;         // right
			b2 = a + 1;
			
			g.indices[ idx     ] = a; // left 
			g.indices[ idx + 1 ] = b1;
			g.indices[ idx + 2 ] = c1; 
			
			g.indices[ idx + 3 ] = a; // right 
			g.indices[ idx + 4 ] = b2,
			g.indices[ idx + 5 ] = b1; // = c2
			
			if ( !profileMaterial ) g.addGroup( idx, 6, j ); // MultiMaterial support
			
			idx += 6;
			
		}
		
	}
	
	if( !openEnded ) {
		
		g.addGroup( idx, rs * 3, rs ); // MultiMaterial support
		
		a = hs1 * rs1;
		
		for ( var j = 0; j < rs; j ++ ) {
			
			g.indices[ idx     ] = a; 
			g.indices[ idx + 1 ] = j + 1;
			g.indices[ idx + 2 ] = j;
			
			idx += 3;
			
		}
		
		g.addGroup( idx, rs * 3, rs + 1 ); // MultiMaterial support
		
		a += 1; 
		
		for ( var j = rs1 + 1; j > 1; j -- ) {
			
			g.indices[ idx     ] = a; 
			g.indices[ idx + 1 ] = a - j;
			g.indices[ idx + 2 ] = a - j + 1;
			
			idx += 3;
			
		}
		
	}
	
	for ( var i = 0; i < hs1; i ++ ) {
		
		i2 = 2 * i; 
		 
		xc1 = contour[ i2 ];
		yc1 = contour[ i2 + 1 ];
				
		if ( i === 0 ) {
			
			xc0 = contour[ ( hs - 1 ) * 2 ]; // penultimate point
			yc0 = contour[ ( hs - 1 ) * 2 + 1 ];
			
		} else {
					 
			xc0 = contour[ i2 - 2 ]; 	// previous point
			yc0 = contour[ i2 - 1 ];
			
		}
		
		if ( i === hs ) {
			
			xc2 = contour[ 2 ];			// second point
			yc2 = contour[ 3 ];
			
		} else {
			 
			xc2 = contour[ i2 + 2 ]; 	// next point
			yc2 = contour[ i2 + 3 ];
			
		}	
		
		if ( !contourClosed ) {
			
			if ( i === 0 ) {
				
				// direction
				dx2 = xc2 - xc1;
				dy2 = yc2 - yc1;
				
				// unit vector
				e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
				
				e2x = dx2 / e2Length;
				e2y = dy2 / e2Length;
				
				// orthogonal
				ex = e2y;
				ey = -e2x;
				
			}
			
			if ( i === hs ) {
				
				// direction
				
				dx0 = xc1 - xc0;
				dy0 = yc1 - yc0;
				
				// unit vector
				e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
				
				e0x = dx0 / e0Length;
				e0y = dy0 / e0Length;
				
				// orthogonal
				ex = e0y;
				ey = -e0x;
				
			}
			
			xDiv = 1;
			bend = 1;
			
		}
		
		if ( ( i > 0 && i < hs ) || contourClosed ) {
			
			// directions
			
			dx0 = xc0 - xc1;
			dy0 = yc0 - yc1;
			
			dx2 = xc2 - xc1;
			dy2 = yc2 - yc1;
			
			if( Math.abs( ( dy2 / dx2 ) - ( dy0 / dx0 ) ) < epsilon ) { // prevent 0
				
				dy0 += epsilon;
				
			}
			
			if( Math.abs( ( dx2 / dy2 ) - ( dx0 / dy0 ) ) < epsilon ) { // prevent 0
				
				dx0 += epsilon;
				
			}  
			
			// unit vectors
			
			e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
			
			e0x = dx0 / e0Length;
			e0y = dy0 / e0Length;
			
			e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
			
			e2x = dx2 / e2Length;
			e2y = dy2 / e2Length;
			
			// direction transformed 
			
			ex = e0x + e2x;
			ey = e0y + e2y;
			
			eLength = Math.sqrt( ex * ex + ey * ey );
			
			ex = ex / eLength;
			ey = ey / eLength;
			
			phi = Math.acos( e2x * e0x + e2y * e0y ) / 2;
			
			bend = Math.sign( dx0 * dy2 - dy0 * dx2 ); // z cross -> curve bending
			
			xDiv = Math.sin( phi );
			
		}
		
		for ( var j = 0; j < rs1; j ++ ) {
			
			xSh = profileShape[ j * 2 ];
			
			x = xc1 + xSh / xDiv * bend * ex; 
			y = yc1 + xSh / xDiv * bend * ey;
			z = profileShape[ j * 2 + 1 ];	 // ySh
			
			vIdx = rs1 * i + j;
			
			posIdx = vIdx * 3;
			
			g.positions[ posIdx ] = x;
			g.positions[ posIdx + 1 ] = y;
			g.positions[ posIdx + 2 ] = z;
			
		}
		
	}
	
	if( !openEnded ) {
		
		g.positions[ hs1 * rs1 * 3 ] = contour[ 0 ];
		g.positions[ hs1 * rs1 * 3 + 1 ] = contour[ 1 ];
		g.positions[ hs1 * rs1 * 3 + 2 ] = 0;
		
		g.positions[ hs1 * rs1 * 3 + 3 ] = contour[ hs * 2 ];
		g.positions[ hs1 * rs1 * 3 + 4 ] = contour[ hs * 2 + 1 ];
		g.positions[ hs1 * rs1 * 3 + 5 ] = 0;
		
	}
	
	g.computeVertexNormals();
	
	return g;
	
}

</script>
</html>
2 Likes

Here Modify Extruded Geometry
there was a question about textures on frames.

I am just finishing the frame profiles for my showroom and have modified the example above. The frame is disassembled into frame strips. These are combined to a group. For the material you can choose between two variants.

   if ( matPerSquare ) { // MultiMaterial support (for each square)
    ...

    } else {  // material per frame strip
    ...

2020-06-15 20.36.46

Check it out

https://hofk.de/main/discourse.threejs/2020/ProfiledContourGeometryUVs/ProfiledContourGeometryUVs.html

(Update: There was a typo in the link. Directory …GeometrUVs without y)

<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/profiledcontourgeometry-multimaterial/5801 -->
<head>
	<title> ProfiledContourGeometryUVs  </title>
	<meta charset="utf-8" />
	<style>
	body {
		margin: 0;
	}
	</style>
</head>
<body> </body>
<script src="../js/three.min.117.js"></script>
<script src="../js/OrbitControls.117.js"></script>

<script>
'use strict'

// @author hofk

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( -6, 18, 20 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const controls = new THREE.OrbitControls( camera, renderer.domElement );

const light = new THREE.DirectionalLight( 0xffffff, 0.6);
light.position.setScalar( 10 );
scene.add( light );
scene.add(new THREE.AmbientLight( 0xffffff, 0.8));

const helper = new THREE.GridHelper( 20, 20);
scene.add( helper );

const detail = 5;
let profileShape1 = [];

for ( let i = detail; i > -1; i --){
	
	profileShape1.push ( 1.8 * Math.cos( i / detail * Math.PI * 2 ), 1.8 * Math.sin( i / detail * Math.PI * 2 ) );
	
}

const contour1 = [
	-3,  4,
	 0,  4,// here only to show that angle of 180° horizontal works  
	 4,  4,
	 2,  1,
	 4, -2,
	 0, -3,
	-4, -3,
	-4,  0
];

const profileShape2 = [ -1,1, 1,1, 1,-1, -1,-1 ];

const contour2 = [
	 2,-2,
	 2, 0,
	 4, 4,
	 9, 4,
	 9, 2, // here only to show that angle of 180° vertikal works  
	 9, 0,
];


const  tex1 = new THREE.TextureLoader().load( 'uvgrid01.png' );
const  tex2 = new THREE.TextureLoader().load( 'beech.jpg' ); // License: free, Non-commercial use
const  tex3 = new THREE.TextureLoader().load( 'pngwing-com-water.png' );// License:  free, Non-commercial use

//const m0 = new THREE.MeshPhongMaterial( { color: 0xfa0001, side: THREE.DoubleSide } );
//const m1 = new THREE.MeshPhongMaterial( { color: 0xff7b00, side: THREE.DoubleSide } );
//const m2 = new THREE.MeshPhongMaterial( { color: 0xf9f901, side: THREE.DoubleSide } );

const m0 = new THREE.MeshPhongMaterial( { map: tex3, side: THREE.DoubleSide } );
const m1 = new THREE.MeshPhongMaterial( { map: tex2, side: THREE.DoubleSide } );
const m2 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
const m3 = new THREE.MeshPhongMaterial( { color: 0x008601, side: THREE.DoubleSide } );
const m4 = new THREE.MeshPhongMaterial( { color: 0x01bbbb, side: THREE.DoubleSide } );
const m5 = new THREE.MeshPhongMaterial( { color: 0x250290, side: THREE.DoubleSide } );

//const m3 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
//const m4 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
//const m5 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );

const m6 = new THREE.MeshPhongMaterial( { color: 0xfc4ea5, side: THREE.DoubleSide } );
const m7 = new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } );
const m8 = new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } );

const materials = [ m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8 ];

//............................................................ matPerSquare, contourClosed 

const frame1 = ProfiledContourUV( profileShape1, contour1, materials, false , true );
scene.add( frame1 );
frame1.position.x = -5;
frame1.position.z = 3;
frame1.rotation.y = 2.8;

const frame2 = ProfiledContourUV( profileShape2, contour2, materials, true, false );
frame2.position.z = -4;
scene.add( frame2 );

render();

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

function ProfiledContourUV( profileShape, contour, materials, matPerSquare, contourClosed ) {
	
	// creates group of frame strips, non indexed BufferGeometry
	
	const len = ( x, y, z ) => Math.sqrt( x * x + y * y + z * z );
	const dot = (x1, y1, z1, x2, y2, z2) => ( x1 * x2 + y1 * y2 + z1 * z2 );
	
	matPerSquare = matPerSquare!== undefined ? matPerSquare : false;
	contourClosed = contourClosed !== undefined ? contourClosed : true;
	
	if( contourClosed ) contour.push( contour[ 0 ], contour[ 1 ] );
	
	const hs1 = contour.length / 2;
	const rs1 = profileShape.length / 2;
	const hs = hs1 - 1; // height segments
	const rs = rs1 - 1; // radius segments
	
	let	vtx = [];    // rs1 many vertex colums
	let	frmpos = []; // hs many geometries and meshes
	let	frmuvs = []; // hs many uv's' 
	
	for ( let j = 0; j < rs; j ++ ) {
		
		vtx.push( [] );
		frmpos.push( [] );
		frmuvs.push( [] );
		
	}
	
	vtx.push( [] ); // last colum
	
	let gFrame = []; // geometries of frame strips
	let frame = [];  // meshes of frame strips
	
	const fGroup = new THREE.Group( );
	
	let i1, i2, i3, i6, j1, j3;
	let xc0, yc0, xc1, yc1, xc2, yc2, xSh, xDiv;
	let dx, dy, dx0, dy0, dx2, dy2;
	let e0x, e0y,e0Length, e2x, e2y, e2Length, ex, ey, eLength;
	let xd, phi, bend;
	let x, y, z, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4;
	let a, u1, u2, u3, u4, d2, d3;
	const epsilon = 0.000001;
 	
	for ( let j = 0; j < rs1; j ++ ) {
		
		for ( let i = 0; i < hs1; i ++ ) {
		
			i2 = 2 * i; 
			
			xc1 = contour[ i2 ];
			yc1 = contour[ i2 + 1 ];
			
			if ( i === 0 ) {
				
				xc0 = contour[ ( hs - 1 ) * 2 ]; // penultimate point
				yc0 = contour[ ( hs - 1 ) * 2 + 1 ];
				
			} else {
						
				xc0 = contour[ i2 - 2 ]; 	// previous point
				yc0 = contour[ i2 - 1 ];
				
			}
			
			if ( i === hs ) {
				
				xc2 = contour[ 2 ];			// second point
				yc2 = contour[ 3 ];
				
			} else {
				
				xc2 = contour[ i2 + 2 ]; 	// next point
				yc2 = contour[ i2 + 3 ];
				
			}	
			
			if ( !contourClosed ) {
				
				if ( i === 0 ) {
					
					// direction
					dx2 = xc2 - xc1;
					dy2 = yc2 - yc1;
					
					// unit vector
					e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
					
					e2x = dx2 / e2Length;
					e2y = dy2 / e2Length;
					
					// orthogonal
					ex = e2y;
					ey = -e2x;
					
				}
				
				if ( i === hs ) {
					
					// direction
					
					dx0 = xc1 - xc0;
					dy0 = yc1 - yc0;
					
					// unit vector
					e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
					
					e0x = dx0 / e0Length;
					e0y = dy0 / e0Length;
					
					// orthogonal
					ex = e0y;
					ey = -e0x;
					
				}
				
				xDiv = 1;
				bend = 1;
				
			}
			
			if ( ( i > 0 && i < hs ) || contourClosed ) {
				
				// directions
				
				dx0 = xc0 - xc1;
				dy0 = yc0 - yc1;
				
				dx2 = xc2 - xc1;
				dy2 = yc2 - yc1;
				
				if( Math.abs( ( dy2 / dx2 ) - ( dy0 / dx0 ) ) < epsilon ) { // prevent 0
					
					dy0 += epsilon;
					
				}
				
				if( Math.abs( ( dx2 / dy2 ) - ( dx0 / dy0 ) ) < epsilon ) { // prevent 0
					
					dx0 += epsilon;
					
				}  
				
				// unit vectors
				
				e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
				
				e0x = dx0 / e0Length;
				e0y = dy0 / e0Length;
				
				e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
				
				e2x = dx2 / e2Length;
				e2y = dy2 / e2Length;
				
				// direction transformed 
				
				ex = e0x + e2x;
				ey = e0y + e2y;
				
				eLength = Math.sqrt( ex * ex + ey * ey );
				
				ex = ex / eLength;
				ey = ey / eLength;
				
				phi = Math.acos( e2x * e0x + e2y * e0y ) / 2;
				
				bend = Math.sign( dx0 * dy2 - dy0 * dx2 ); // z cross -> curve bending
				
				xDiv = Math.sin( phi );
				
			}
			
			xSh = profileShape[ j * 2 ];
			
			xd = xSh / xDiv;
			
			dx = xd * bend * ex;
			dy = xd * bend * ey;
			
			x = xc1 + dx; 
			y = yc1 + dy;
			z = profileShape[ j * 2 + 1 ];	// ySh
			
			vtx[ j ].push( x, y, z );	// store vertex
			
			//dApex = xd * Math.cos( phi );
		
		}
		
	}
	
	for ( let j = 0; j < rs; j ++ ) {
		
		j1 = j + 1;
		j3 = 3 * j;
		
		for ( let i = 0; i < hs; i ++ ) {
			
			i3 = 3 * i;
			i6 = i3 + 3;
			
			x1 = vtx[ j ][ i3 ];
			y1 = vtx[ j ][ i3 + 1 ];
			z1 = vtx[ j ][ i3 + 2 ] ;
			
			x2 = vtx[ j1 ][ i3 ];
			y2 = vtx[ j1 ][ i3 + 1 ];
			z2 = vtx[ j1 ][ i3 + 2 ];
			
			x3 = vtx[ j1 ][ i6 ];
			y3 = vtx[ j1 ][ i6 + 1 ];
			z3 = vtx[ j1 ][ i6 + 2 ];
			
			x4 = vtx[ j ][ i6 ];
			y4 = vtx[ j ][ i6 + 1 ];
			z4 = vtx[ j ][ i6 + 2 ];
			
			frmpos[ j ].push( x1, y1, z1, x2, y2, z2, x4, y4, z4, x2, y2, z2, x3, y3, z3, x4, y4, z4 );
			
			a = len( x4 - x1, y4 - y1, z4 - z1 );
			
			d2 = dot( x4 - x1, y4 - y1, z4 - z1, x2 - x1, y2 - y1, z2 - z1 ) / a;
			d3 = dot( x1 - x4, y1 - y4, z1 - z4, x3 - x4, y3 - y4, z3 - z4, ) / a;
			
			if ( d2 >= 0 && d3 >= 0 ) {
				
				u1 = 0;
				u2 = d2 / a;
				u3 = 1 - d3 / a;
				u4 = 1;
				
			}
			
			if ( d2 >= 0 && d3 < 0 ) {
				
				u1 = 0;
				u2 = d2 / ( a - d3 );
				u3 = 1;
				u4 = 1 + d3 / ( a - d3 ); 
				
			}
			
			if ( d2 < 0 && d3 < 0 ) {
				
				u1 = -d2 / ( a - d2 - d3 );
				u2 = 0;
				u3 = 1;
				u4 = 1 + d3 / ( a - d2 - d3  );
				
			}
			
			if ( d2 < 0 && d3 >= 0 ) {
				
				u1 = -d2 / ( a - d2  );
				u2 = 0;
				u3 = 1 - d3 / ( a - d2  );
				u4 = 1;
				
			}
			
			frmuvs[ j ].push( u1, 1, u2, 0, u4, 1, u2, 0, u3, 0, u4, 1 );
			
		}
		
	}
	
	for ( let j = 0; j < rs; j ++ ) {
		
		gFrame[ j ] = new THREE.BufferGeometry( );
		gFrame[ j ].setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( frmpos[ j ] ), 3 ) );
		gFrame[ j ].setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( frmuvs[ j ] ), 2 ) );
		
		if ( matPerSquare ) { // MultiMaterial support (for each square)
			
			for ( let i = 0; i < hs; i ++ ) {
				
				gFrame[ j ].addGroup( i * 6, 6, j * hs + i ); 
				
			}
			
			frame[ j ] = new THREE.Mesh( gFrame[ j ], materials );
			
		} else { // material per frame strip
			
			frame[ j ] = new THREE.Mesh( gFrame[ j ], materials[ j ] );
			
		}
		
		gFrame[ j ].computeVertexNormals( );
		
		fGroup.add( frame[ j ] )
		
	}
	
	return fGroup; // group of frame strips
	
}

</script>
</html>
2 Likes

A version in the Addon to create special / extended geometries - #9 by hofk is now also available. :slightly_smiling_face:

On GitHub GitHub - hofk/THREEg.js: three.js addon to create special or extended geometries. The addon generates indexed or non indexed BufferGeometries.

UPDATE:
Also the caps can now optionally be furnished with a texture.

<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/profiledcontourgeometry-multimaterial/5801/2 -->
 
<head>
	<title> ProfiledContourGeometryUVs  </title>
	<meta charset="utf-8" />
	<style>
	body {
		margin: 0;
	}
	</style>
</head>
<body> </body>
<script src="../js/three.min.120.js"></script>
<script src="../js/OrbitControls.120.js"></script>

<script>

'use strict'

// @author hofk

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( -6, 18, 20 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const controls = new THREE.OrbitControls( camera, renderer.domElement );

const light = new THREE.DirectionalLight( 0xffffff, 0.6);
light.position.setScalar( 10 );
scene.add( light );
scene.add(new THREE.AmbientLight( 0xffffff, 0.8));

const helper = new THREE.GridHelper( 20, 20);
scene.add( helper );

const detail = 5;
let profileShape1 = [];

for ( let i = detail; i > -1; i -- ){
	
	profileShape1.push ( 1.75 * Math.cos( 0.7 + i / detail * Math.PI * 2 ), 1.75 * Math.sin( 0.7 + i / detail * Math.PI * 2 ) );
	
}

const contour1 = [
	-3,  4,
	 0,  4,// here only to show that angle of 180° horizontal works  
	 4,  4,
	 2,  1,
	 4, -2,
	 0, -3,
	-4, -3,
	-4,  0
];

const profileShape2 = [ -1,1, 1.77,1, 1,-1, -1.4,-1.99 ];

const contour2 = [
	 2,-2,
	 2, 0,
	 4, 4,
	 9, 4,
	 9, 2, // here only to show that angle of 180° vertikal works  
	 9, 0,
];

const  tex1 = new THREE.TextureLoader().load( 'uvgrid01.png' );
const  tex2 = new THREE.TextureLoader().load( 'beech.jpg' ); // License: free, Non-commercial use
const  tex3 = new THREE.TextureLoader().load( 'pngwing-com-water.png' );// License:  free, Non-commercial use

//const m0 = new THREE.MeshPhongMaterial( { color: 0xfa0001, side: THREE.DoubleSide } );
//const m1 = new THREE.MeshPhongMaterial( { color: 0xff7b00, side: THREE.DoubleSide } );
//const m2 = new THREE.MeshPhongMaterial( { color: 0xf9f901, side: THREE.DoubleSide } );

const m0 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
const m1 = new THREE.MeshPhongMaterial( { map: tex2, side: THREE.DoubleSide } );
const m2 = new THREE.MeshPhongMaterial( { map: tex3, side: THREE.DoubleSide } );

//const m3 = new THREE.MeshPhongMaterial( { color: 0x008601, side: THREE.DoubleSide } );
//const m4 = new THREE.MeshPhongMaterial( { color: 0x01bbbb, side: THREE.DoubleSide } );
//const m5 = new THREE.MeshPhongMaterial( { color: 0x250290, side: THREE.DoubleSide } );

const m3 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
const m4 = new THREE.MeshPhongMaterial( { map: tex3, side: THREE.DoubleSide } );
const m5 = new THREE.MeshPhongMaterial( { map: tex2, side: THREE.DoubleSide } );

//const m6 = new THREE.MeshPhongMaterial( { color: 0xfc4ea5, side: THREE.DoubleSide } );
//const m7 = new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } );
//const m8 = new THREE.MeshPhongMaterial( { color: 0x83058a, side: THREE.DoubleSide } );

const m6 = new THREE.MeshPhongMaterial( { map: tex1, side: THREE.DoubleSide } );
const m7 = new THREE.MeshPhongMaterial( { map: tex2, side: THREE.DoubleSide } );
const m8 = new THREE.MeshPhongMaterial( { map: tex3, side: THREE.DoubleSide } );


const materials = [ m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8, m0, m1, m2, m3, m4, m5, m6, m7, m8 ];

//..................................................... matPerSquare, contourClosed, openEnded );
const frame1 = ProfiledContourUV( profileShape1, contour1, materials, false , false, false );
scene.add( frame1 );
frame1.position.x = -5;
frame1.position.z = 3;
frame1.rotation.y = 2.8;

const frame2 = ProfiledContourUV( profileShape2, contour2, materials, true, false, false );
frame2.position.z = -4;
scene.add( frame2 );

render();

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

function ProfiledContourUV( profileShape, contour, materials, matPerSquare, contourClosed, openEnded ) {
	
	// creates group of frame strips, non indexed BufferGeometry	
	
	const len = ( x, y, z ) => Math.sqrt( x * x + y * y + z * z );
	const dot = (x1, y1, z1, x2, y2, z2) => ( x1 * x2 + y1 * y2 + z1 * z2 );
	
	matPerSquare = matPerSquare!== undefined ? matPerSquare : false;
	contourClosed = contourClosed !== undefined ? contourClosed : true;
	openEnded = openEnded  !== undefined ? ( contourClosed === true ? true : openEnded ) : true;
	
	if( contourClosed ) contour.push( contour[ 0 ], contour[ 1 ] );
	
	const hs1 = contour.length / 2;
	const rs1 = profileShape.length / 2;
	const hs = hs1 - 1; // height segments
	const rs = rs1 - 1; // radius segments
	
	let	vtx = [];    // rs1 many vertex colums
	let	frmpos = []; // hs many geometries and meshes
	let	frmuvs = []; // hs many uv's' 
	
	for ( let j = 0; j < rs; j ++ ) {
		
		vtx.push( [] );
		frmpos.push( [] );
		frmuvs.push( [] );
		
	}
	
	vtx.push( [] ); // last colum	
	
	if(  !openEnded ) { // for two ends
		
		frmpos.push( [], [] ); // for two ends
		frmuvs.push( [], []  );
		
	}
	
	let gFrame = []; // geometries of frame strips
	let frame = [];  // meshes of frame strips
	
	const fGroup = new THREE.Group( );
	
	let i1, i2, i3, i6, j1, j2;
	let xc0, yc0, xc1, yc1, xc2, yc2, xSh, xDiv;
	let dx, dy, dx0, dy0, dx2, dy2;
	let e0x, e0y,e0Length, e2x, e2y, e2Length, ex, ey, eLength;
	let xd, phi, bend;
	let x, y, z, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4;
	let x1L, y1L, x2L, y2L, xMin, yMin, xMax, yMax;
	let a, u1, u1a, u1b, u2, u3, u4, v1, v2, v3, d2, d3;
	const epsilon = 0.000001;
	
	xMin = Infinity;
	yMin = Infinity;
	xMax = -Infinity;
	yMax = -Infinity;

	if( !openEnded ) {
	
		for ( let j = 0; j < rs1; j ++ ) {
			
			j2 = 2 * j;
			
			if ( profileShape[ j2 ] < xMin ) xMin = profileShape[ j2 ];
			if ( profileShape[ j2 + 1 ] < yMin ) yMin = profileShape[ j2 + 1 ];
			
			if ( profileShape[ j2 ] > xMax ) xMax = profileShape[ j2 ];
			if ( profileShape[ j2 + 1 ] > yMax ) yMax = profileShape[ j2 + 1 ];
				
		}
		
	}	
 	
	for ( let j = 0; j < rs1; j ++ ) {
		
		j2 = 2 * j;		
		
		for ( let i = 0; i < hs1; i ++ ) {
		
			i2 = 2 * i; 
			
			xc1 = contour[ i2 ];
			yc1 = contour[ i2 + 1 ];
			
			if ( i === 0 ) {
				
				xc0 = contour[ ( hs - 1 ) * 2 ]; // penultimate point
				yc0 = contour[ ( hs - 1 ) * 2 + 1 ];
				
			} else {
						
				xc0 = contour[ i2 - 2 ]; 	// previous point
				yc0 = contour[ i2 - 1 ];
				
			}
			
			if ( i === hs ) {
				
				xc2 = contour[ 2 ];			// second point
				yc2 = contour[ 3 ];
				
			} else {
				
				xc2 = contour[ i2 + 2 ]; 	// next point
				yc2 = contour[ i2 + 3 ];
				
			}	
			
			if ( !contourClosed ) {
				
				if ( i === 0 ) {
					
					// direction
					dx2 = xc2 - xc1;
					dy2 = yc2 - yc1;
					
					// unit vector
					e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
					
					e2x = dx2 / e2Length;
					e2y = dy2 / e2Length;
					
					// orthogonal
					ex = e2y;
					ey = -e2x;
					
				}
				
				if ( i === hs ) {
					
					// direction
					
					dx0 = xc1 - xc0;
					dy0 = yc1 - yc0;
					
					// unit vector
					e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
					
					e0x = dx0 / e0Length;
					e0y = dy0 / e0Length;
					
					// orthogonal
					ex = e0y;
					ey = -e0x;
					
				}
				
				xDiv = 1;
				bend = 1;
				
			}
			
			if ( ( i > 0 && i < hs ) || contourClosed ) {
				
				// directions
				
				dx0 = xc0 - xc1;
				dy0 = yc0 - yc1;
				
				dx2 = xc2 - xc1;
				dy2 = yc2 - yc1;
				
				if( Math.abs( ( dy2 / dx2 ) - ( dy0 / dx0 ) ) < epsilon ) { // prevent 0
					
					dy0 += epsilon;
					
				}
				
				if( Math.abs( ( dx2 / dy2 ) - ( dx0 / dy0 ) ) < epsilon ) { // prevent 0
					
					dx0 += epsilon;
					
				}  
				
				// unit vectors
				
				e0Length = Math.sqrt( dx0 * dx0 + dy0 * dy0 );
				
				e0x = dx0 / e0Length;
				e0y = dy0 / e0Length;
				
				e2Length = Math.sqrt( dx2 * dx2 + dy2 * dy2 );
				
				e2x = dx2 / e2Length;
				e2y = dy2 / e2Length;
				
				// direction transformed 
				
				ex = e0x + e2x;
				ey = e0y + e2y;
				
				eLength = Math.sqrt( ex * ex + ey * ey );
				
				ex = ex / eLength;
				ey = ey / eLength;
				
				phi = Math.acos( e2x * e0x + e2y * e0y ) / 2;
				
				bend = Math.sign( dx0 * dy2 - dy0 * dx2 ); // z cross -> curve bending
				
				xDiv = Math.sin( phi );
				
			}
			
			xSh = profileShape[ j2 ];
			
			xd = xSh / xDiv;
			
			dx = xd * bend * ex;
			dy = xd * bend * ey;
			
			x = xc1 + dx; 
			y = yc1 + dy;
			z = profileShape[ j2 + 1 ];	// ySh
			
			vtx[ j ].push( x, y, z );	// store vertex
			
			//dApex = xd * Math.cos( phi );
			
			if( j === 0 && i === hs && !openEnded ) { //center
				
				x1L = xc1;
				y1L = yc1;
				
			}
			
			if( j === 0 && i === 0 && !openEnded ) { //center
				
				x2L = xc1;
				y2L = yc1;
				
			}			
			
		}
		
	}
	
	for ( let j = 0; j < rs; j ++ ) {
		
		j1 = j + 1;
		
		for ( let i = 0; i < hs; i ++ ) {
			
			i3 = 3 * i;
			i6 = i3 + 3;
			
			x1 = vtx[ j ][ i3 ];
			y1 = vtx[ j ][ i3 + 1 ];
			z1 = vtx[ j ][ i3 + 2 ] ;
			
			x2 = vtx[ j1 ][ i3 ];
			y2 = vtx[ j1 ][ i3 + 1 ];
			z2 = vtx[ j1 ][ i3 + 2 ];
			
			x3 = vtx[ j1 ][ i6 ];
			y3 = vtx[ j1 ][ i6 + 1 ];
			z3 = vtx[ j1 ][ i6 + 2 ];
			
			x4 = vtx[ j ][ i6 ];
			y4 = vtx[ j ][ i6 + 1 ];
			z4 = vtx[ j ][ i6 + 2 ];
			
			frmpos[ j ].push( x1, y1, z1, x2, y2, z2, x4, y4, z4, x2, y2, z2, x3, y3, z3, x4, y4, z4 );
			
			a = len( x4 - x1, y4 - y1, z4 - z1 );
			
			d2 = dot( x4 - x1, y4 - y1, z4 - z1, x2 - x1, y2 - y1, z2 - z1 ) / a;
			d3 = dot( x1 - x4, y1 - y4, z1 - z4, x3 - x4, y3 - y4, z3 - z4, ) / a;
			
			if ( d2 >= 0 && d3 >= 0 ) {
				
				u1 = 0;
				u2 = d2 / a;
				u3 = 1 - d3 / a;
				u4 = 1;
				
			}
			
			if ( d2 >= 0 && d3 < 0 ) {
				
				u1 = 0;
				u2 = d2 / ( a - d3 );
				u3 = 1;
				u4 = 1 + d3 / ( a - d3 ); 
				
			}
			
			if ( d2 < 0 && d3 < 0 ) {
				
				u1 = -d2 / ( a - d2 - d3 );
				u2 = 0;
				u3 = 1;
				u4 = 1 + d3 / ( a - d2 - d3  );
				
			}
			
			if ( d2 < 0 && d3 >= 0 ) {
				
				u1 = -d2 / ( a - d2  );
				u2 = 0;
				u3 = 1 - d3 / ( a - d2  );
				u4 = 1;
				
			}
			
			frmuvs[ j ].push( u1, 1, u2, 0, u4, 1, u2, 0, u3, 0, u4, 1 );
			
		}
		
	}
	
	
	if( !openEnded ) {
		
		dx = xMax - xMin;
		dy = yMax - yMin;
		
		v1 = -yMin / dy;
		u1a = -xMin / dx;
		u1b = 1 - u1a;
		
		i3 = 3 * hs;
		
		for ( let j = 0; j < rs; j ++ ) {
			
			j1 = j + 1;
			j2 = 2 * j;
			
			x1 = vtx[ j ][ 0 ];
			y1 = vtx[ j ][ 1 ];
			z1 = vtx[ j ][ 2 ];
			
			x2 = vtx[ j1 ][ 0 ];
			y2 = vtx[ j1 ][ 1 ];
			z2 = vtx[ j1 ][ 2 ];
			
			frmpos[ rs ].push( x2L, y2L, 0, x1, y1, z1, x2, y2, z2 );
					
			u2 = ( profileShape[ j2 ] - xMin ) / dx;
			v2 = ( profileShape[ j2 + 1 ] - yMin ) / dy;
			
			u3 = ( profileShape[ j2 + 2 ] - xMin ) / dx;
			v3 = ( profileShape[ j2 + 3 ] - yMin ) / dy;
			
			frmuvs[ rs ].push( u1a, v1, u2, v2, u3, v3 );
			
			x1 = vtx[ j1 ][ i3 ];
			y1 = vtx[ j1 ][ i3 + 1 ];
			z1 = vtx[ j1 ][ i3 + 2 ] ;
			
			x2 = vtx[ j ][ i3 ];
			y2 = vtx[ j ][ i3 + 1 ];
			z2 = vtx[ j ][ i3 + 2 ];
			
			frmpos[ rs1 ].push( x1L, y1L, 0, x1, y1, z1, x2, y2, z2 );
			
			u2 = 1 - ( profileShape[ j2 + 2 ] - xMin ) / dx;
			v2 = ( profileShape[ j2 + 3 ] - yMin ) / dy;

			u3 = 1 - ( profileShape[ j2 ] - xMin ) / dx;
			v3 = ( profileShape[ j2 + 1 ] - yMin ) / dy;
			
			frmuvs[ rs1 ].push( u1b, v1, u2, v2, u3, v3 );
			
		}
		
	}
	
	for ( let j = 0; j < ( openEnded ? rs : rs + 2 ); j ++ ) {
		
		gFrame[ j ] = new THREE.BufferGeometry( );
		gFrame[ j ].setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( frmpos[ j ] ), 3 ) );
		gFrame[ j ].setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( frmuvs[ j ] ), 2 ) );
		
		if ( matPerSquare ) { // MultiMaterial support (for each square)
			
			for ( let i = 0; i < hs; i ++ ) {
				
				gFrame[ j ].addGroup( i * 6, 6, j * hs + i ); 
				
			}
			
			frame[ j ] = new THREE.Mesh( gFrame[ j ], materials );
			
		} else { // material per frame strip
			
			frame[ j ] = new THREE.Mesh( gFrame[ j ], materials[ j ] );
			
		}
		
		gFrame[ j ].computeVertexNormals( );
		
		fGroup.add( frame[ j ] )
		
	}
	
	return fGroup; // group of frame strips
	
}

</script>
</html>
2 Likes

@hofk I’m looking to use this code with 1 repeating texture across the mesh but with no stretching. Any ideas how best to achieve this?

I don’t quite understand what you are trying to achieve. Can you make a sketch?

In places where the geometry stretches I’d like to repeat the texture based on the size of the geometry instead of stretching it, hope the diagram shows what I mean.

This must be implemented accordingly, depending on the specific form.
My test texture const tex1 = new THREE.TextureLoader().load( 'uvgrid01.png' ); is square.

If you divide the width by the height, you get ( possibly with appropriate rounding) the repetitions.

This can be realized in the program code or calculated beforehand and the number inserted. In the example it looks good with 4. But only for the right part. To demonstrate that an angle of 180° also works, I have divided the upper area.
2021-08-06 20.28.07

2021-08-06 20.29.02

The code section

const  tex1 = new THREE.TextureLoader().load( 'uvgrid01.png' );
tex1.wrapS = THREE.RepeatWrapping;
tex1.wrapT = THREE.RepeatWrapping;
tex1.repeat.set( 4, 1 );