UNPKG

three

Version:

JavaScript 3D library

107 lines (79 loc) 4.35 kB
import { BoxGeometry, CylinderGeometry, InterpolationSamplingMode, InterpolationSamplingType } from 'three'; import { MeshStandardNodeMaterial } from 'three/webgpu'; import { attribute, color, float, select, varying } from 'three/tsl'; import { mergeGeometries } from '../../utils/BufferGeometryUtils.js'; import { InstancedMeshGenerator } from './InstancedMeshGenerator.js'; import { part } from './CityGeneratorUtils.js'; /** * A NYC traffic signal: a round pole at the curb with a horizontal mast arm * reaching out over the roadway to a vertical three-section signal head, plus a * pedestrian-signal box on the pole. Built once and instanced across a list of * placements, dressed with one cheap material that branches on a baked `partId` * ( dark metal, three coloured lenses ). * * The canonical model stands on `y = 0`, centred in X / Z, with the arm reaching * toward `+Z`, so a placement whose local `+Z` faces the road throws the head * over it with its lenses aimed back at oncoming traffic. * * ```js * const lights = new TrafficlightGenerator(); * scene.add( lights.build( placements ) ); // placements: Matrix4[] * ``` */ class TrafficlightGenerator extends InstancedMeshGenerator { constructor( parameters = {} ) { super( parameters, buildTrafficlightGeometry, createTrafficlightMaterial, 'Trafficlights' ); } } TrafficlightGenerator.defaults = { height: 6.5, // pole height at the curb, so the mast arm clears traffic reach: 5.5, // how far the mast arm reaches out over the road radius: 0.14 // pole radius }; // material-zone codes baked per vertex const METAL = 0, RED = 1, AMBER = 2, GREEN = 3; // a short lens disc on the front of the signal head, facing back down the arm function lensDisc( y, z, id ) { return part( new CylinderGeometry( 0.13, 0.13, 0.08, 12 ).rotateX( Math.PI / 2 ).translate( 0, y, z ), id ); } function buildTrafficlightGeometry( p ) { const h = p.height, reach = p.reach; const armY = h - 0.3; // the arm rides just below the pole top const base = new CylinderGeometry( 0.2, 0.26, 0.5, 8 ).translate( 0, 0.25, 0 ); const pole = new CylinderGeometry( p.radius, p.radius * 1.2, h, 8 ).translate( 0, h / 2, 0 ); // horizontal, slightly tapered mast arm out over the roadway toward +Z const arm = new CylinderGeometry( 0.07, 0.1, reach, 8 ).rotateX( Math.PI / 2 ).translate( 0, armY, reach / 2 ); // pedestrian-signal box clamped to the pole at eye level, facing the crossing const pedBox = new BoxGeometry( 0.4, 0.42, 0.2 ).translate( 0, 2.6, p.radius + 0.1 ); // the three-section head hangs from the arm's far end, its bottom ~4.7 m over // the road for truck clearance, lenses aimed back at oncoming traffic const headZ = reach - 0.6, headY = armY - 1.05; const housing = new BoxGeometry( 0.36, 0.95, 0.32 ).translate( 0, headY, headZ ); const dropLen = armY - ( headY + 0.475 ); const drop = new CylinderGeometry( 0.05, 0.05, dropLen, 6 ).translate( 0, headY + 0.475 + dropLen / 2, headZ ); // bracket hanging the head off the arm const lensZ = headZ - 0.19; // discs stand proud of the housing's -Z face return mergeGeometries( [ part( base, METAL ), part( pole, METAL ), part( arm, METAL ), part( drop, METAL ), part( pedBox, METAL ), part( housing, METAL ), lensDisc( headY + 0.28, lensZ, RED ), lensDisc( headY, lensZ, AMBER ), lensDisc( headY - 0.28, lensZ, GREEN ) ] ); } function createTrafficlightMaterial() { const partId = varying( attribute( 'partId', 'float' ) ).setInterpolation( InterpolationSamplingType.FLAT, InterpolationSamplingMode.EITHER ); const isGreen = partId.equal( GREEN ); const isLens = partId.greaterThan( 0.5 ); // any coloured lens, never the metal const material = new MeshStandardNodeMaterial(); material.colorNode = select( partId.equal( RED ), color( 0x3a0a06 ), select( partId.equal( AMBER ), color( 0x402608 ), select( isGreen, color( 0x2bd24b ), color( 0x233029 ) ) ) ); // dark glass, lit green, dark olive metal material.roughnessNode = select( isLens, float( 0.3 ), float( 0.5 ) ); material.metalnessNode = select( isLens, float( 0 ), float( 0.7 ) ); material.emissiveNode = select( isGreen, color( 0x2bd24b ).mul( 18 ), color( 0x000000 ) ); // only the go signal glows, at LED-aspect radiance that still reads in full sun return material; } export { TrafficlightGenerator };