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

TrashcanGenerator.js

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 };