UNPKG

three

Version:

JavaScript 3D library

128 lines (91 loc) 4.66 kB
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 };