UNPKG

three

Version:

JavaScript 3D library

101 lines (71 loc) 3.8 kB
import { CylinderGeometry, IcosahedronGeometry, InterpolationSamplingMode, InterpolationSamplingType } from 'three'; import { MeshStandardNodeMaterial } from 'three/webgpu'; import { attribute, color, float, Fn, fract, mix, mx_fractal_noise_float, positionGeometry, select, smoothstep, step, uv, varying } from 'three/tsl'; import { mergeGeometries } from '../../utils/BufferGeometryUtils.js'; import { InstancedMeshGenerator } from './InstancedMeshGenerator.js'; import { part } from './CityGeneratorUtils.js'; /** * The NYC green wire-mesh litter basket: a slightly tapered drum with a heavy top * rim, a foot ring and a dark bag of trash inside. Built once and instanced across * a list of placements; one cheap material branches on a baked `partId`, drawing * the mesh weave procedurally from the drum's own UVs so no alpha or extra geometry * is needed. Canonical model stands on `y = 0`, centred in X / Z. * * ```js * const cans = new TrashcanGenerator(); * scene.add( cans.build( placements ) ); // placements: Matrix4[] * ``` */ class TrashcanGenerator extends InstancedMeshGenerator { constructor( parameters = {} ) { super( parameters, buildTrashcanGeometry, createTrashcanMaterial, 'Trashcans' ); } } TrashcanGenerator.defaults = { radius: 0.28, // ~0.55 m diameter basket height: 0.8 }; const MESH = 0, RIM = 1, TRASH = 2; function buildTrashcanGeometry( p ) { const r = p.radius, h = p.height; const drum = new CylinderGeometry( r, r * 0.92, h, 16, 1, true ).translate( 0, h / 2, 0 ); // open-ended weave drum const rim = new CylinderGeometry( r + 0.03, r + 0.03, 0.07, 16 ).translate( 0, h, 0 ); const foot = new CylinderGeometry( r * 0.92, r * 0.86, 0.06, 16 ).translate( 0, 0.03, 0 ); const bag = new CylinderGeometry( r * 0.86, r * 0.7, h * 0.9, 12 ).translate( 0, h * 0.5, 0 ); // dark refuse, just inside // the basket never quite closes: a lumpy mound of refuse crests over the rim const mound = new IcosahedronGeometry( r * 0.82, 1 ).scale( 1, 0.55, 1 ).translate( 0.02, h + 0.02, - 0.01 ); return mergeGeometries( [ part( drum, MESH ), part( rim, RIM ), part( foot, RIM ), part( bag, TRASH ), part( mound, TRASH ) ] ); } function createTrashcanMaterial() { const partId = varying( attribute( 'partId', 'float' ) ).setInterpolation( InterpolationSamplingType.FLAT, InterpolationSamplingMode.EITHER ); const isRim = partId.equal( RIM ); const isTrash = partId.equal( TRASH ); // expanded-metal weave on the drum, drawn from its UV so the mesh reads as // open without alpha: thin crossing diagonal wires around dark diamond holes const weave = Fn( () => { const u = uv().x.mul( 26 ); const v = uv().y.mul( 9 ); const a = fract( u.add( v ) ).sub( 0.5 ).abs(); const b = fract( u.sub( v ) ).sub( 0.5 ).abs(); return smoothstep( 0.3, 0.2, a.min( b ) ); // 1 on the wires, 0 in the diamond holes } )(); const p = positionGeometry; // street grime creeps up from the foot of the basket const grime = smoothstep( 0.45, 0.05, p.y ).mul( 0.5 ).oneMinus(); const green = color( 0x39543a ).mul( grime ); const drumColor = mix( color( 0x101210 ), green, weave ); // holes fall into the dark interior // the mound of refuse: dark bags flecked with scraps of paper and litter const fleck = step( 0.82, mx_fractal_noise_float( p.mul( 34 ), 2 ).mul( 0.5 ).add( 0.5 ) ); const trash = mix( color( 0x24221d ), color( 0xa89e88 ), fleck.mul( smoothstep( 0.6, 0.75, p.y ) ) ); const material = new MeshStandardNodeMaterial(); material.colorNode = select( isTrash, trash, select( isRim, green.mul( 1.15 ), drumColor ) ); material.roughnessNode = select( isTrash, float( 0.9 ), float( 0.55 ) ); material.metalnessNode = select( isTrash, float( 0 ), float( 0.5 ) ); return material; } export { TrashcanGenerator };