three
Version:
JavaScript 3D library
128 lines (91 loc) • 4.66 kB
JavaScript
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 };