Problems applying texture to Extruded Geometry

Hello,

I am trying to make some 3d playing cards in the browser. I have generated a rounded-rectangle shape by using ExtrudeGeometry with some lines I’ve drawn. I’m having three issues:

  1. The Image texture isn’t applying to the full shape.
  2. I cant specify an image for the back of the extruded texture.
  3. The shadows are catching on the rounded corner pieces:

function createCardGeometry( width, depth, height, radius) {
    let shape = new THREE.Shape();
    shape.moveTo(0, width-radius)

    shape.lineTo(0, width-radius)
    shape.absarc(radius, width-radius, radius, 0, Math.PI/2, true)

    shape.lineTo(height-radius, width)
    shape.absarc(height-radius, width-radius, radius, 3*Math.PI/2, 2*Math.PI, true);

    shape.lineTo(height,radius)
    shape.absarc(height-radius, radius, radius, Math.PI/2, 3*Math.PI/2,true)

    shape.lineTo(radius, 0)
    shape.absarc(radius, radius, radius, 0, Math.PI, true)
    const extrudeSettings = {
        steps: 16,
        depth: depth,
        bevelEnabled: false
    }
    let card_shape = new THREE.ExtrudeGeometry( shape, extrudeSettings )    
    card_shape.lookAt(new THREE.Vector3(0,1,0))
    return card_shape;
}

The absarc sections were done almost entirely through trial and error. It’s pretty likely those are causing the shading issue

I call that as follows:

function createCard(x,y,z,rotation,image){
    
    let pos = {x: x, y: y, z: z};
    let scale = {width: 6.91, depth: 0.1, height: 10.56};
    let quat = {x: 0, y: 0, z: 0, w: 1};
    let mass = 1;

    //threeJS Section
    let texture = new THREE.TextureLoader().load( `../../card_images/${image}.png` );
    texture.anisotropy = 16;
    let texture_back = new THREE.TextureLoader().load( `../../card_images/card_back.png` );
    texture_back.anisotropy = 16;


    var cardMaterialArray = [];
    cardMaterialArray.push(new THREE.MeshStandardMaterial({map: texture}))

    // cardMaterialArray.push(new THREE.MeshPhongMaterial({map: texture_back, color: 0xffffff}))
    var cardTextures = new THREE.MeshStandardMaterial({map: texture})
    let cardGeometry = createCardGeometry(scale.height, scale.depth, scale.width, 0.5)

    let card = new THREE.Mesh(cardGeometry, cardTextures);


    card.castShadow = true;
    card.receiveShadow = true;

    scene.add(card);

    //Ammojs Section
    let transform = new Ammo.btTransform();
    transform.setIdentity();
    transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );

    let quaternion = new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w )
    quaternion.setEulerZYX(0,0,0)
    transform.setRotation(quaternion);

    let motionState = new Ammo.btDefaultMotionState( transform );

    let colShape = new Ammo.btBoxShape( new Ammo.btVector3( scale.width * 0.5, scale.depth * 0.5, scale.height * 0.5 ) );
    colShape.setMargin( 0.05 );

    let localInertia = new Ammo.btVector3( 0, 0, 0 );
    colShape.calculateLocalInertia( mass, localInertia );

    let rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, colShape, localInertia );
    let body = new Ammo.btRigidBody( rbInfo );


    physicsWorld.addRigidBody(body);
    card.userData.physicsBody = body;
    rigidBodies.push(card);
}

I’m pretty sure Ammo handles the positioning in the world as well as the physics.

I’ve googled a lot and found some things about about UV mapping, but every example I’ve found I don’t really understand.

If there’s an easier way of going about this instead of extruding Geometry i’d like to hear it, though I dont see why I shouldn’t be able to get this to work.

Maybe you can easily achieve the result with the combination of the two techniques?

Thanks for the reply hofk.

The RoundedRectangle I don’t understand. It doesn’t create a rectangle with any depth which I need for physics simulation, and I’m still new to this so adding depth into it is non trivial for me.

Could you explain how the uv’s work? They look like a bunch of magic numbers to me

See e.g.
UV mapping - Wikipedia
A Brief Introduction to Texture Mapping | Discover three.js


For a map with thickness it is necessary to generate the rounded rectangle with different z ( i.e. + - half thickness, in the plane variant equal to 0, ).

 let positions = [

    -wi, -h2, 0,  ...

Then you need to connect the two rounded rectangles. This is easy to do because you already have all the coordinates of the two rounded rectangles.

The start is made, give me a little bit more time. I am not the fastest.
:hourglass_flowing_sand:


The code for the two pages still without the connection.
Interesting was the necessary change in the calculation of the uv’s.


<!DOCTYPE html>
<!--   --> 
<head>
	<title> RoundedEdgeBoxFlat </title>
	<meta charset="utf-8" />
	<style>	
	body {  margin: 0; }
	</style>
</head>
<body>
 
</body>
 
 <script type="module">
 
// @author hofk
 
import * as THREE from "../jsm/three.module.130.js";
import { OrbitControls } from "../jsm/OrbitControls.130.js";
 
const scene = new THREE.Scene( );
const camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.01, 10000 );
camera.position.set( 0, 1, 12 );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xdedede, 1 );	
const container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement );
const axesHelper = new THREE.AxesHelper( 10 );
scene.add( axesHelper );

const controls = new OrbitControls( camera, renderer.domElement );

const material = new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( ).load( 'uvgrid01.png' ), side: THREE.DoubleSide, wireframe: false } );
//const material = new THREE.MeshBasicMaterial( { color: 0xff00ff, side: THREE.DoubleSide, wireframe: true } );

const w = 16;	// width
const h =  9;	// height
const t = 0.2;	// thick
const r =  2;	// radius corner
const s =  18;	// smoothness

const geometry = RoundedRectangle2( w, h, t, r, s );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

animate( );  

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

// non indexed BufferGeometry

function RoundedRectangle2( w, h, t, r, s ) { // width, height, thick, radius corner, smoothness
	
	// helper const's and let's
	const wi = w / 2 - r;	// inner width
	const hi = h / 2 - r;	// inner height
	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;
		
	}
	
	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 ) );
	
	return geometry;
	
}
 
</script>
</html>

It works as intended.

For the front side, the back side and the connection, one material each can be selected.


The code still needs to be optimized. When it is done, I will post it as a resource and attach a link here.


UPDATE
link => Round-edged box flat

2 Likes