Accueil / npm / three@0.186.1 / examples / jsm / generators / city / BenchGenerator.js

BenchGenerator.js

import {
	BoxGeometry,
	InterpolationSamplingMode,
	InterpolationSamplingType
} from 'three';

import { MeshStandardNodeMaterial } from 'three/webgpu';
import { attribute, color, float, floor, fract, mix, mx_fractal_noise_float, positionGeometry, select, sin, varying, vec3 } from 'three/tsl';

import { mergeGeometries } from '../../utils/BufferGeometryUtils.js';

import { InstancedMeshGenerator } from './InstancedMeshGenerator.js';
import { part } from './CityGeneratorUtils.js';

/**
 * A public street bench: timber slats carried on two cast-iron end frames that
 * curl into armrests, with a reclined slatted backrest. Built once and instanced
 * across a list of placements, dressed with one cheap material that branches on a
 * baked `partId` ( warm stained wood, dark iron ).
 *
 * The canonical model stands on `y = 0`, centred in X / Z, runs along X and seats
 * toward `+Z`, so a placement whose local `+Z` faces the road sits a passer-by
 * looking out over it.
 *
 * ```js
 * const benches = new BenchGenerator();
 * scene.add( benches.build( placements ) ); // placements: Matrix4[]
 * ```
 */
class BenchGenerator extends InstancedMeshGenerator {

	constructor( parameters = {} ) {

		super( parameters, buildBenchGeometry, createBenchMaterial, 'Benches' );

	}

}

BenchGenerator.defaults = {
	length: 1.8, // spans X
	depth: 0.55, // front-to-back
	seatHeight: 0.45,
	backHeight: 0.85
};

// material-zone codes baked per vertex
const WOOD = 0, IRON = 1;

function buildBenchGeometry( p ) {

	const halfLength = p.length / 2;
	const halfDepth = p.depth / 2;
	const seatY = p.seatHeight;
	const backY = p.backHeight;

	const frontZ = halfDepth - 0.075; // legs set just inside the depth
	const backZ = - frontZ;

	const parts = [];

	// two cast-iron end frames brace the slats and curl into armrests
	for ( const side of [ - 1, 1 ] ) {

		const x = side * ( halfLength - 0.07 );

		const frontLeg = new BoxGeometry( 0.05, seatY, 0.06 ).translate( x, seatY / 2, frontZ );
		const backLeg = new BoxGeometry( 0.05, backY, 0.06 ).translate( x, backY / 2, backZ ); // doubles as the backrest post
		const seatRail = new BoxGeometry( 0.06, 0.05, frontZ - backZ + 0.1 ).translate( x, seatY - 0.04, 0 );
		const armPost = new BoxGeometry( 0.05, 0.22, 0.05 ).translate( x, seatY + 0.11, frontZ );
		const armRest = new BoxGeometry( 0.05, 0.05, frontZ - backZ + 0.06 ).translate( x, seatY + 0.22, 0 );
		const armCurl = new BoxGeometry( 0.05, 0.16, 0.05 ).rotateX( - 0.6 ).translate( x, seatY + 0.18, frontZ + 0.07 ); // front scroll of the armrest

		parts.push( part( frontLeg, IRON ), part( backLeg, IRON ), part( seatRail, IRON ), part( armPost, IRON ), part( armRest, IRON ), part( armCurl, IRON ) );

	}

	// timber seat slats spaced front-to-back
	const slatLength = p.length - 0.1;
	const z0 = backZ + 0.02, z1 = frontZ - 0.02;
	for ( let i = 0; i < 5; i ++ ) {

		const z = z0 + ( z1 - z0 ) * ( i / 4 );
		parts.push( part( new BoxGeometry( slatLength, 0.03, 0.07 ).translate( 0, seatY, z ), WOOD ) );

	}

	// backrest slats climb the rear posts, reclined a touch
	for ( let i = 0; i < 3; i ++ ) {

		const t = i / 2;
		const y = seatY + 0.13 + t * 0.27;
		const z = backZ + 0.01 - t * 0.06;
		parts.push( part( new BoxGeometry( slatLength, 0.09, 0.025 ).rotateX( 0.18 ).translate( 0, y, z ), WOOD ) );

	}

	return mergeGeometries( parts );

}

function createBenchMaterial() {

	const partId = varying( attribute( 'partId', 'float' ) ).setInterpolation( InterpolationSamplingType.FLAT, InterpolationSamplingMode.EITHER );
	const isWood = partId.equal( WOOD );

	const p = positionGeometry;

	// timber: grain streaks stretched along the slats, each slat cut from its own
	// board ( a tone hashed from the slat's row position ) and greyed by weathering
	const grain = mx_fractal_noise_float( p.mul( vec3( 3, 60, 60 ) ), 2 ).mul( 0.5 ).add( 0.5 );
	const board = fract( sin( floor( p.z.mul( 10.5 ) ).add( floor( p.y.mul( 8.0 ) ).mul( 7.3 ) ).mul( 17.53 ) ).mul( 43758.5453 ) );
	const weather = mx_fractal_noise_float( p.mul( 1.6 ), 2 ).mul( 0.5 ).add( 0.5 );

	const stained = mix( color( 0x5e412a ), color( 0x8a6242 ), grain.mul( 0.7 ).add( board.mul( 0.3 ) ) );
	const wood = mix( stained, color( 0x7c7468 ), weather.mul( 0.45 ) ); // sun-bleached grey where the stain has worn

	const material = new MeshStandardNodeMaterial();
	material.colorNode = select( isWood, wood, color( 0x232322 ) ); // weathered timber, dark cast iron
	material.roughnessNode = select( isWood, grain.mul( 0.25 ).add( 0.65 ), float( 0.5 ) );
	material.metalnessNode = select( isWood, float( 0 ), float( 0.6 ) );

	return material;

}

export { BenchGenerator };