airframe.io / src /game /rendering /InstancedFoliage.ts
LATTICEWORKS's picture
feat: integrate Google Earth 3D Tiles and optimize engine hooks
565f6e7
Raw
History Blame Contribute Delete
2.11 kB
import * as THREE from 'three/webgpu';
import { color, mix, positionLocal, positionWorld, time, sin, vec3 } from 'three/tsl';
export class InstancedFoliage {
public mesh: THREE.InstancedMesh;
constructor(count: number, positions: THREE.Vector3[]) {
// A simple blade of grass geometry
const geometry = new THREE.ConeGeometry(0.5, 4, 3);
geometry.translate(0, 2, 0); // Origin at bottom
// Create a TSL material
const material = new THREE.MeshStandardNodeMaterial({
roughness: 0.8,
metalness: 0.1,
side: THREE.DoubleSide
});
// Wind Sway Logic using TSL
const t = time;
// Use positionWorld for wind phase so each blade of grass differs
const windPhase = positionWorld.x.mul(0.1).add(positionWorld.z.mul(0.1)).add(t);
const windStrength = sin(windPhase).mul(0.5).add(0.5); // 0 to 1
// Base sway only affects the top of the grass blade
// positionLocal.y goes from 0 to 4
const heightFactor = positionLocal.y.div(4.0);
const swayX = sin(t.add(positionWorld.x)).mul(heightFactor).mul(windStrength);
const swayZ = sin(t.mul(1.3).add(positionWorld.z)).mul(heightFactor).mul(windStrength);
material.positionNode = positionLocal.add(vec3(swayX, 0.0, swayZ));
// Color gradient from root to tip
const rootColor = color("#1E3F11");
const tipColor = color("#4A7A25");
material.colorNode = mix(rootColor, tipColor, heightFactor);
this.mesh = new THREE.InstancedMesh(geometry, material, count);
this.mesh.castShadow = true;
this.mesh.receiveShadow = true;
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
if (i < positions.length) {
dummy.position.copy(positions[i]);
// Random rotation around Y
dummy.rotation.y = Math.random() * Math.PI * 2;
// Random scale variation
const scale = 0.8 + Math.random() * 0.6;
dummy.scale.set(scale, scale, scale);
dummy.updateMatrix();
this.mesh.setMatrixAt(i, dummy.matrix);
}
}
this.mesh.instanceMatrix.needsUpdate = true;
}
}