Round-edged box flat

There is already a series of boxes with rounded corners. Links to some rounded boxes see:
Addon to create special / extended geometries - #3 by hofk

In response to a question ( Problems applying texture to Extruded Geometry), I created another special one - based on RoundedRectangle + Squircle .

In the Collection of examples from discourse.threejs.org see RoundEdgedBoxFlat

2021-09-17 19.59.09


// non indexed BufferGeometry

function RoundEdgedBoxFlat( w, h, t, r, s ) { // width, height, thick, radius corner, smoothness
	
	// helper const's and let's
	const wi = w / 2 - r;		// inner width, half
	const hi = h / 2 - r;		// inner height, half 
	const w2 = w / 2;			// half width
	const h2 = h / 2;			// half height

	let ul = r / w;				// u left front side
	let ur = ( w - r ) / w;		// u right front side
	const vl = r / h;			// v low
	const vh = ( h - r ) / h;	// v high
	
	let phia, phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;
	
	let positions = [];
	let uvs = [];
	
	// for front side
	let t2 = t / 2;			// +  half thick
	let u0 = ul;
	let u1 = ur;
	let u2 = 0;
	let u3 = 1;
	let sign = 1;
		
	for( let k = 0; k < 2; k ++ ) {  // front and back side
		
		positions.push(
		
			-wi, -h2, t2,  wi, -h2, t2,  wi, h2, t2,
			-wi, -h2, t2,  wi,  h2, t2, -wi, h2, t2,
			-w2, -hi, t2, -wi, -hi, t2, -wi, hi, t2,
			-w2, -hi, t2, -wi,  hi, t2, -w2, hi, t2,
			 wi, -hi, t2,  w2, -hi, t2,  w2, hi, t2,
			 wi, -hi, t2,  w2,  hi, t2,  wi, hi, t2
			
		);
		
		uvs.push(
		
			u0,  0, u1,  0, u1,  1,
			u0,  0, u1,  1, u0,  1,
			u2, vl, u0, vl, u0, vh,
			u2, vl, u0, vh, u2, vh,
			u1, vl, u3, vl, u3, vh,
			u1, vl, u3, vh,	u1, vh
		
		);
			
		phia = 0; 
		
		for ( let i = 0; i < s * 4; i ++ ) {
		
			phib = Math.PI * 2 * ( i + 1 ) / ( 4 * s );
			
			cosa = Math.cos( phia );
			sina = Math.sin( phia );
			cosb = Math.cos( phib );
			sinb = Math.sin( phib );
			
			xc = i < s || i >= 3 * s ? wi : -wi;
			yc = i < 2 * s ? hi : -hi;
		
			positions.push( xc, yc, t2,  xc + r * cosa, yc + r * sina, t2,  xc + r * cosb, yc + r * sinb, t2 );
			
			uc = i < s || i >= 3 * s ? u1 : u0;
			vc = i < 2 * s ? vh : vl;
			
			uvs.push( uc, vc, uc + sign * ul * cosa, vc + vl * sina, uc + sign * ul * cosb, vc + vl * sinb );
 
			phia = phib;
				
		}
		
		// for back side
		t2 = -t2;	// - half thick
		u0 = ur;	// right left exchange
		u1 = ul;
		u2 = 1;
		u3 = 0;
		sign = -1;
		
	}
	
	// framing
	
	t2 = t / 2;	// + half thick (again)
	 
	positions.push(
		
		-wi, -h2,  t2, -wi, -h2, -t2,  wi, -h2, -t2,
		-wi, -h2,  t2,  wi, -h2, -t2,  wi, -h2,  t2,
		 w2, -hi,  t2,  w2, -hi, -t2,  w2,  hi, -t2,
		 w2, -hi,  t2,  w2,  hi, -t2,  w2,  hi,  t2,
		 wi,  h2,  t2,  wi,  h2, -t2, -wi,  h2, -t2,
		 wi,  h2,  t2, -wi,  h2, -t2, -wi,  h2,  t2,
		-w2,  hi,  t2, -w2,  hi, -t2, -w2, -hi, -t2,
		-w2,  hi,  t2, -w2, -hi, -t2, -w2, -hi,  t2
		
	);

	const cf = 2 * ( ( w + h - 4 * r ) + Math.PI * r ); // circumference
	const cc4 = Math.PI * r / 2 / cf  // circle-circumference / 4 / circumference
	u0 = 0;
	u1 = 2 * wi / cf;
	u2 = u1 + cc4;
	u3 = u2 + 2 * hi / cf;
	
	const u4 = u3 + cc4;
	const u5 = u4 + 2 * wi / cf;
	const u6 = u5 + cc4;
	const u7 = u6 + 2 * hi / cf;
	
	uvs.push(
		
		u0, 1,  0, 0, u1, 0,
		u0, 1, u1, 0, u1, 1,
		u2, 1, u2, 0, u3, 0,
		u2, 1, u3, 0, u3, 1,
		u4, 1, u4, 0, u5, 0,
		u4, 1, u5, 0, u5, 1,
		u6, 1, u6, 0, u7, 0, 
		u6, 1, u7, 0, u7, 1
		
	);
	
	phia = 0; 
	let u, j, j1;
	const ccs = cc4 / s; // partial value according to smoothness
	
	for ( let i = 0; i < s * 4; i ++ ) {
	
		phib = Math.PI * 2 * ( i + 1 ) / ( 4 * s );
		
		cosa = Math.cos( phia );
		sina = Math.sin( phia );
		cosb = Math.cos( phib );
		sinb = Math.sin( phib );
		
		xc = i < s || i >= 3 * s ? wi : -wi;
		yc = i < 2 * s ? hi : -hi;
		
		positions.push( xc + r * cosa, yc + r * sina, t2,  xc + r * cosa, yc + r * sina, -t2,  xc + r * cosb, yc + r * sinb, -t2 );
		positions.push( xc + r * cosa, yc + r * sina, t2,  xc + r * cosb, yc + r * sinb, -t2,  xc + r * cosb, yc + r * sinb,  t2 );
		
		u = i < s ? u3 : ( i < 2 * s ? u5 : ( i < 3 * s ? u7 : u1 ) ); // Attention! different start to front/back
		
		j = i % s;
		j1 = j + 1;
		
		uvs.push( u + j * ccs, 1,  u + j  * ccs, 0,  u + j1 * ccs, 0 );
		uvs.push( u + j * ccs, 1,  u + j1 * ccs, 0,  u + j1 * ccs, 1 );
		
		phia = phib;
			
	}
	
	const geometry = new THREE.BufferGeometry( );
	geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );
	geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );
	
	// add multimaterial groups for front, back, framing
	
	const vtc = ( 6 + 4 * s ) * 3;		// vertex count one side
	geometry.addGroup ( 0, vtc , 0 );
	geometry.addGroup ( vtc, vtc , 1 );
	geometry.addGroup ( 2 * vtc, 24 +  2 * 3  *  4 * s, 2 );
	
	return geometry;
	
}

8 Likes

UPDATE 2022: :clipboard: :pencil2: :triangular_ruler: :straight_ruler: :scissors:

Another construction:

RoundedBoxFlat

function RoundedBoxFlat( w, h, d, r, s ) {
    
    const pi2 = Math.PI * 2;
    const n = ( s + 1 ) * 4; // number of segments
    
    let indices = [];
    let positions = [];
    let uvs = [];
    
    makeFronts( n,  1,     0 ); // segments, front is 1, start index 0 is center front
    makeFronts( n, -1, n + 1 ); // segments, back is -1, start index n + 1 is center back
    
    makeFrame( n, 2 * n + 2, 1, n + 2 ); // segments, start index framing ,start index front, start index back
    
    const geometry = new THREE.BufferGeometry( );
    geometry.setIndex( new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );
    geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );
    geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );
    
    // add multimaterial groups for front, back, framing
    
    const vtc = n * 3 ;        // vertex count
    geometry.addGroup ( 0, vtc, 0 );
    geometry.addGroup ( vtc, vtc, 1 );
    geometry.addGroup ( 2 * vtc, 2 * vtc + 3, 2  ); 
    
    geometry.computeVertexNormals( );
    
    return geometry;
    
    function makeFronts( n, side, idx ) {
        
        const d0 = side === 1 ? 0 : 1;
        const d1 = side === 1 ? 1 : 0;
        
        for ( let j = 1; j < n; j ++ ) {
    
            indices.push( idx, idx + d0 + j, idx + d1 + j  );
            
        }
        
        const d2 = side === 1 ? n : 1;
        const d3 = side === 1 ? 1 : n;
        
        indices.push( idx, idx + d2 , idx + d3 );
        
        positions.push( 0, 0, side * d / 2 ); // center
        uvs.push( 0.5, 0.5 );
        
        for ( let j = 0; j < n; j ++ ) { // contour
        
            const qu = Math.trunc( 4 * j / n ) + 1 ; // quadrant  qu: 1..4         
            const sgn = ( qu === 1 || qu === 4 ? 1: -1) // signum left/right
            
            const c = { x: sgn * ( w / 2 - r ), y: ( qu < 3 ? 1 : -1 ) * ( h / 2 - r ), z: side * d / 2 } // quadrant center
            
            const x = c.x + r * Math.cos( pi2 * ( j - qu + 1 ) / ( n - 4 ) );
            const y = c.y + r * Math.sin( pi2 * ( j - qu + 1 ) / ( n - 4 ) );      
            const z = c.z;
            positions.push( x, y, z );
            
            const u0 = side === 1 ? 0 : 1; 
            uvs.push( u0 + side * ( 0.5 + x / w ), 0.5 + y / h ); 
        
        }
        
    }
          
    function makeFrame( n, sidx, sif,  sib ) {
    
        let a, b, c, d, xf, yf, zf, xb, yb, zb;
        const pif = sif * 3; // position start index  front
        const pib = sib * 3; // position start index back
        let st = [];
        
        let idx = sidx;
        
        for ( let j = 0; j < n; j ++ ) {
        
            a = idx;
            b = idx + 1;
            c = idx + 2;
            d = idx + 3; 
            
            indices.push( a, b, d, a, d, c );
            
            idx += 2;
            
        }
        
        for ( let j = 0; j < n ; j ++ ) {   
            
            const j3 = j * 3;
            
            xf = positions[ pif + j3 ]; 
            yf = positions[ pif + j3 + 1 ]; 
            zf = positions[ pif + j3 + 2 ];
            
            xb = positions[ pib + j3 ]; 
            yb = positions[ pib + j3 + 1 ]; 
            zb = positions[ pib + j3 + 2 ];
            
            positions.push( xf, yf, zf, xb, yb, zb );
            
            if ( j === 0 ) st = [ xf, yf, zf, xb, yb, zb ]; // memorize
            
            const v = j / n; // here only independent of section height
            uvs.push( 0, v, 1, v );
            
        }
        
        positions.push( st[ 0 ], st[ 1 ], st[ 2 ], st[ 3 ], st[ 4 ], st[ 5 ] ); // end = start
        uvs.push( 0, 1, 1, 1 );
        
    }
    
}

Note: In this version the uv values of the frame are different and only independent of the segment size.
v = j / n;

Addition:
However, it is not a problem to calculate the v values according to the length of the partial circumference sections, if necessary.

It is also easy to swap u and v to obtain the representation as in the first construction.

The makeFrame function with “neat” uv values for a texture.

function makeFrame( n, sidx, sif,  sib ) {

    let a, b, c, d, xf, yf, zf, xb, yb, zb;
    const pif = sif * 3; // position start index  front
    const pib = sib * 3; // position start index back
    let st = [];
    
    let idx = sidx;
    
    for ( let j = 0; j < n; j ++ ) {
    
        a = idx;
        b = idx + 1;
        c = idx + 2;
        d = idx + 3; 
        
        indices.push( a, b, d, a, d, c );
        
        idx += 2;
        
    }
    
    const dx = positions[ 3 ] - positions[ 6 ]; // first of contour, 0 is center
    const dy = positions[ 4 ] - positions[ 7 ];
    const ls = Math.sqrt( dx * dx + dy * dy ); // length of contour of one corner segment
    const peri = 2 * ( w - 2 * r ) + 2 * ( h - 2 * r ) +  4 * s * ls; // perimeter  
    const uds = ls / peri; // u difference of one corner segment
    
    const j0 = s;
    const j1 = j0 + s + 1;
    const j2 = j1 + s + 1;
    
    let u = 1;
    
    for ( let j = 0; j < n ; j ++ ) {   
        
        xf = positions[ pif + j * 3 ]; 
        yf = positions[ pif + j * 3 + 1 ]; 
        zf = positions[ pif + j * 3 + 2 ];
        
        xb = positions[ pib + j * 3 ]; 
        yb = positions[ pib + j * 3 + 1 ]; 
        zb = positions[ pib + j * 3 + 2 ];

        positions.push( xf, yf, zf, xb, yb, zb );
        
        if ( j === 0 ) st = [ xf, yf, zf, xb, yb, zb ]; // memorize

        uvs.push(  u, 0, u, 1 );
        
        u -= ( j === j0 || j === j2 ) ? ( w - 2 * r ) / peri : ( j === j1 ? ( h - 2 * r ) / peri : uds );
        
    }
    
    positions.push( st[ 0 ], st[ 1 ], st[ 2 ], st[ 3 ], st[ 4 ], st[ 5 ] ); // end = start
    uvs.push( 0, 0, 0, 1 );
    
}
3 Likes

Yet another variant.

RoundedBoxFlatUV

The disadvantage of the previous variants is the rigid assignment of the u values of the frame to the respective construction. Therefore, both variants are different regarding the position of the texture to the quadrants. Also, the texture is not applied symmetrically.

With 4 more triangles compared to the previous variant and the duplication of the positions in the middle of the four sides I solved the problem. Thereby I like even the quadrant-wise construction very much. It is clearer than the previous ones.

2022-12-09 12.21.06

The texture can be wrapped neatly around the object.
2022-12-09 12.18.58

Other variant
2022-12-09 12.19.30

An additional optional parameter allows to select the starting quadrant for the texture.

RoundedBoxFlat( width, height, depth, radiusCorner, smoothness, uStartQuadr ); // uStartQuadr optional



function RoundedBoxFlat( w, h, d, r, s, q ) {

    let qu = q || 1;            // qu: start quadrant regarding u, optional
    const pi = Math.PI;
    let indices = [];
    let positions = [];
    let uvs = [];
    
    makeFronts( s,  1, 0 ); // smoothness, front is 1, start index  center front
    makeFronts( s, -1, 4 * ( s + 3 ) + 1 ); // smoothness, back is -1, start index center back
    makeFrame( s, 2 * ( 4 * ( s + 3 ) + 1 ), 1,  4 * ( s + 3 ) + 2 ); // smoothness, start index framing ,start index front, start index back

    const geometry = new THREE.BufferGeometry( );
    geometry.setIndex( new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );
    geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) );
    geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) );
    
    // add multimaterial groups for front, back, framing
    
    const vtc = 4 * ( s + 2 ) * 3;
    geometry.addGroup ( 0, vtc, 0 );
    geometry.addGroup ( vtc , vtc, 1 );
    geometry.addGroup ( 2 * vtc, 2 * vtc + 3, 2 ); 
   
    geometry.computeVertexNormals( );
    
    return geometry;
    
    function makeFronts( s, side, idx ) {
        
        const d0 = side === 1 ? 0 : 1;
        const d1 = side === 1 ? 1 : 0;
        
        let id = 0;
        
        for ( let q = 1; q < 5; q ++ ) { // quadrants
        
            id ++;
            
            for ( let j = 0; j < s + 2; j ++ ) {
        
                indices.push( idx, idx + d0 + id, idx + d1 + id  );
                id ++;
                
            }
            
        }

        positions.push( 0, 0, side * d / 2 ); // center
        uvs.push( 0.5, 0.5 );
        
        let x, y, z, sgnX, sgnY;
        let phi = 0;
        const u0 = side === 1 ? 0 : 1;
        
        for ( let q = 1; q < 5; q ++ ) {
        
            sgnX = q === 1 || q === 4 ? 1 : -1;
            sgnY = q < 3 ? 1 : -1 ;
            
            x = Math.cos( phi ) * w / 2;
            y = Math.sin( phi ) * h / 2;
            z = side * d / 2;
            
            positions.push( x, y, z );
            uvs.push( u0 + side * ( 0.5 + x / w ), 0.5 + y / h  );
            
            for ( let j = 0; j < s + 1; j ++ ) {
            
                const c = { x: sgnX * ( w / 2 - r ), y: sgnY * ( h / 2 - r ), z: side * d / 2 } // quadrant center
                
                const dPhi = pi / 2 * j / s;
                
                x = c.x + r * Math.cos( phi + dPhi );
                y = c.y + r * Math.sin( phi + dPhi );      
                z = c.z;
                positions.push( x, y, z ); 
                uvs.push( u0 + side * ( 0.5 + x / w ), 0.5 + y / h ); 
            
            }
            
            phi = phi + pi / 2;
             
            x = Math.cos( phi ) * w / 2;
            y = Math.sin( phi ) * h / 2;
            z = side * d / 2;
            
            positions.push( x, y, z );               
            uvs.push( u0 + side * ( 0.5 + x / w ), 0.5 + y / h  );
            
        }
        
    }
          
    function makeFrame( s, sidx, sif, sib ) {
    
        let a, b, c, d, xf, yf, zf, xb, yb, zb;
        const pif = sif * 3; // position start index front
        const pib = sib * 3; // position start index back
        
        let idx = sidx;
        
        for ( let q = 1; q < 5; q ++ ) {
        
            for ( let j = 0; j < s + 2; j ++ ) {
            
                a = idx;
                b = idx + 1;
                c = idx + 2;
                d = idx + 3; 
                
                indices.push( a, b, d, a, d, c );
                
                idx += 2;
                
            }
            
            idx += 2;
            
        }
        
        const ls = 2 * r * Math.sin( pi / ( s * 4 ) ); // length of the outer line of a corner segment
        const w2r = w / 2 - r;
        const h2r = h / 2 - r;
        const peri = 4 * w2r + 4 * h2r +  4 * s * ls; // perimeter
        
        let u;
        idx = 0; // reset
        
        for ( let q = 1; q < 5; q ++ ) {
            
            // console.log ( 'qu', qu );
             
            u = qu / 4;
            
            for ( let j = 0; j < s + 3; j ++ ) {   
                
                xf = positions[ pif + idx ]; 
                yf = positions[ pif + idx + 1 ]; 
                zf = positions[ pif + idx + 2 ];
                
                xb = positions[ pib + idx ]; 
                yb = positions[ pib + idx + 1 ]; 
                zb = positions[ pib + idx + 2 ];
                
                positions.push( xf, yf, zf, xb, yb, zb );
                
                idx += 3;
                
                // console.log ( 'u ', u );
                
                uvs.push( u , 0, u, 1 );
                
                if ( j === 0 )     { u -= q === 1 || q === 3 ? h2r / peri : w2r / peri; }
                if ( j === s + 1 ) { u -= q === 1 || q === 3 ? w2r / peri : h2r / peri; }
                if ( j > 0 && j < s + 1 ) { u -= ls / peri; }
                                   
            }
            
            qu = 4 - ( ( 5 - qu ) % 4 ); // cyclic next quadrant with respect to u
            
        }
        
    }
    
}
5 Likes

Hello @hofk and thank you for sharing these! I’m trying to use the RoundedBoxFlatUV variant but I can’t seem to be able to apply different textures for the top/bottom, sides. I’m only able to add one Material that gets applied to the top/bottom and to all the sides. I’m very new to threejs, so please do excuse my question if the answer is obvious. Thank you!

EDIT:
I figured out that only 3 textures can be applied (I was trying with 6) and they match, in order:

  1. the top (one texture)
  2. the bottom (one texture)
  3. the sides (one texture only)

This is correct, see the lines of code

   // add multimaterial groups for front, back, framing
    
    const vtc = 4 * ( s + 2 ) * 3;
    geometry.addGroup ( 0, vtc, 0 );
    geometry.addGroup ( vtc , vtc, 1 );
    geometry.addGroup ( 2 * vtc, 2 * vtc + 3, 2 );

You can split the material group “framing” if required. The code must be adapted for this.

1 Like

Thanks a lot! I’ve seen your posts and you’ve created amazing stuff helping the community. I’m still learning about 3d graphics and it’s a whole different world! Trying to understand the code and I think I’m beginning to get a hold of it with your help and examples :slight_smile:

2 Likes

You can also work with texture parts.

Note: in a real application it would be better to load the 2 textures only once and then use them!


const material = [
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uvgrid01.png' ), wireframe: false} ),
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uv_grid_opengl.jpg' ), wireframe: false} ),
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uvgrid01.png' ), wireframe: false } ),
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uv_grid_opengl.jpg' ), wireframe: false } ),
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uvgrid01.png' ), wireframe: false } ),
    new THREE.MeshBasicMaterial( { map: texturLoader.load( 'uv_grid_opengl.jpg' ), wireframe: false } ),
];
const vtc = 4 * ( s + 2 ) * 3;

geometry.addGroup ( 0, vtc, 0 );
geometry.addGroup ( vtc , vtc, 1 );

//geometry.addGroup ( 2 * vtc, 2 * vtc + 3, 2 );

geometry.addGroup ( 2 * vtc, vtc / 2, 2 ); 
geometry.addGroup ( 2.5 * vtc, vtc / 2 , 3 ); 
geometry.addGroup ( 3 * vtc, vtc / 2, 4 );
geometry.addGroup ( 3.5 * vtc, vtc / 2 + 3, 5 );
2 Likes
import * as THREE from 'three';


export function RoundEdgedBoxFlat(w, h, t, r, s) { // width, height, thick, radius corner, smoothness

    // helper const's and let's
    const wi = w / 2 - r;		// inner width, half
    const hi = h / 2 - r;		// inner height, half 
    const w2 = w / 2;			// half width
    const h2 = h / 2;			// half height

    let ul = r / w;				// u left front side
    let ur = (w - r) / w;		// u right front side
    const vl = r / h;			// v low
    const vh = (h - r) / h;	// v high

    let phia, phib, xc, yc, uc, vc, cosa, sina, cosb, sinb;

    let positions = [];
    let uvs = [];

    // for front side
    let t2 = t / 2;			// +  half thick
    let u0 = ul;
    let u1 = ur;
    let u2 = 0;
    let u3 = 1;
    let sign = 1;

    for (let k = 0; k < 2; k++) {  // front and back side

        positions.push(

            -wi, -h2, t2, wi, -h2, t2, wi, h2, t2,
            -wi, -h2, t2, wi, h2, t2, -wi, h2, t2,
            -w2, -hi, t2, -wi, -hi, t2, -wi, hi, t2,
            -w2, -hi, t2, -wi, hi, t2, -w2, hi, t2,
            wi, -hi, t2, w2, -hi, t2, w2, hi, t2,
            wi, -hi, t2, w2, hi, t2, wi, hi, t2

        );

        uvs.push(

            u0, 0, u1, 0, u1, 1,
            u0, 0, u1, 1, u0, 1,
            u2, vl, u0, vl, u0, vh,
            u2, vl, u0, vh, u2, vh,
            u1, vl, u3, vl, u3, vh,
            u1, vl, u3, vh, u1, vh

        );

        phia = 0;

        for (let i = 0; i < s * 4; i++) {

            phib = Math.PI * 2 * (i + 1) / (4 * s);

            cosa = Math.cos(phia);
            sina = Math.sin(phia);
            cosb = Math.cos(phib);
            sinb = Math.sin(phib);

            xc = i < s || i >= 3 * s ? wi : -wi;
            yc = i < 2 * s ? hi : -hi;

            positions.push(xc, yc, t2, xc + r * cosa, yc + r * sina, t2, xc + r * cosb, yc + r * sinb, t2);

            uc = i < s || i >= 3 * s ? u1 : u0;
            vc = i < 2 * s ? vh : vl;

            uvs.push(uc, vc, uc + sign * ul * cosa, vc + vl * sina, uc + sign * ul * cosb, vc + vl * sinb);

            phia = phib;

        }

        // for back side
        t2 = -t2;	// - half thick
        u0 = ur;	// right left exchange
        u1 = ul;
        u2 = 1;
        u3 = 0;
        sign = -1;

    }

    // framing

    t2 = t / 2;	// + half thick (again)

    positions.push(

        -wi, -h2, t2, -wi, -h2, -t2, wi, -h2, -t2,
        -wi, -h2, t2, wi, -h2, -t2, wi, -h2, t2,
        w2, -hi, t2, w2, -hi, -t2, w2, hi, -t2,
        w2, -hi, t2, w2, hi, -t2, w2, hi, t2,
        wi, h2, t2, wi, h2, -t2, -wi, h2, -t2,
        wi, h2, t2, -wi, h2, -t2, -wi, h2, t2,
        -w2, hi, t2, -w2, hi, -t2, -w2, -hi, -t2,
        -w2, hi, t2, -w2, -hi, -t2, -w2, -hi, t2

    );

    const cf = 2 * ((w + h - 4 * r) + Math.PI * r); // circumference
    const cc4 = Math.PI * r / 2 / cf  // circle-circumference / 4 / circumference
    u0 = 0;
    u1 = 2 * wi / cf;
    u2 = u1 + cc4;
    u3 = u2 + 2 * hi / cf;

    const u4 = u3 + cc4;
    const u5 = u4 + 2 * wi / cf;
    const u6 = u5 + cc4;
    const u7 = u6 + 2 * hi / cf;

    uvs.push(

        u0, 1, 0, 0, u1, 0,
        u0, 1, u1, 0, u1, 1,
        u2, 1, u2, 0, u3, 0,
        u2, 1, u3, 0, u3, 1,
        u4, 1, u4, 0, u5, 0,
        u4, 1, u5, 0, u5, 1,
        u6, 1, u6, 0, u7, 0,
        u6, 1, u7, 0, u7, 1

    );

    phia = 0;
    let u, j, j1;
    const ccs = cc4 / s; // partial value according to smoothness

    for (let i = 0; i < s * 4; i++) {

        phib = Math.PI * 2 * (i + 1) / (4 * s);

        cosa = Math.cos(phia);
        sina = Math.sin(phia);
        cosb = Math.cos(phib);
        sinb = Math.sin(phib);

        xc = i < s || i >= 3 * s ? wi : -wi;
        yc = i < 2 * s ? hi : -hi;

        positions.push(xc + r * cosa, yc + r * sina, t2, xc + r * cosa, yc + r * sina, -t2, xc + r * cosb, yc + r * sinb, -t2);
        positions.push(xc + r * cosa, yc + r * sina, t2, xc + r * cosb, yc + r * sinb, -t2, xc + r * cosb, yc + r * sinb, t2);

        u = i < s ? u3 : (i < 2 * s ? u5 : (i < 3 * s ? u7 : u1)); // Attention! different start to front/back

        j = i % s;
        j1 = j + 1;

        uvs.push(u + j * ccs, 1, u + j * ccs, 0, u + j1 * ccs, 0);
        uvs.push(u + j * ccs, 1, u + j1 * ccs, 0, u + j1 * ccs, 1);

        phia = phib;

    }

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
    geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2));

    // add multimaterial groups for front, back, framing

    const vtc = (6 + 4 * s) * 3;		// vertex count one side
    geometry.addGroup(0, vtc, 0);
    geometry.addGroup(vtc, vtc, 1);
    geometry.addGroup(2 * vtc, 24 + 2 * 3 * 4 * s, 2);

    return geometry;

}

I am testing this with the latest three.js and I get a distorted box, I wanted to ask if this code works with latest since it’s been published a while ago. Thanks

As far as I know, the elementary geometry definitions (BufferGeometry) have not changed. This can hardly be the reason. A live example like jsfiddle/codepen would be helpful.

        const geometry = new RoundEdgedBoxFlat(1,5,1,3,1)
        const transparentMaterial = new THREE.MeshBasicMaterial({
            color: 0x000000,
            opacity: 0.5,
            transparent: true
        });
        
        const mesh = new THREE.Mesh(geometry, transparentMaterial);
        this.webGLScene.add(mesh);

my guess is that I’m doing something wrong, but I have just moved to making it on blender and importing the obj … thank you anyways

Did you cross reference your implementation with the original source Round-edged box flat - #3 by hofk

Your parameters do not make sense geometrically.

w = 1 // width
h = 5 // height
t = 1 // thick

r = 3 // radius corner => double radius of the corners is 6, but the width and height is much smaller
s = 1 // smoothness

I have not included a check, as nonsensical values sometimes result in interesting things.

See RoundedRectangle + Squircle (5th and 6th picture)

3 Likes