icy3dw/src/client/object/model/grass.ts

98 lines
2.7 KiB
TypeScript

import {
BufferGeometry,
Float32BufferAttribute,
InstancedMesh,
Material,
Matrix4,
Quaternion,
Vector2,
Vector3,
} from 'three';
import { rand } from '../../../common/helper';
let instance: Grass;
export class Grass {
private geom!: BufferGeometry;
initialize() {
const grassindices = [0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7];
const grassuvs = [0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0];
const grassverts = [
-0.25, 0.5, 0, -0.25, 0, 0, 0.25, 0.5, 0, 0.25, 0, 0, 0, 0.5, -0.25, 0, 0,
-0.25, 0, 0.5, 0.25, 0, 0, 0.25,
];
const grassnormals = Array.from({ length: grassverts.length }, (_, k) => {
return k % 2 === 0 ? 1 : 0;
});
this.geom = new BufferGeometry();
this.geom.setIndex(grassindices);
this.geom.setAttribute(
'position',
new Float32BufferAttribute(grassverts, 3),
);
this.geom.setAttribute(
'normal',
new Float32BufferAttribute(grassnormals, 3),
);
this.geom.setAttribute('uv', new Float32BufferAttribute(grassuvs, 2));
}
public static getInstance(): Grass {
if (!instance) {
instance = new Grass();
instance.initialize();
}
return instance;
}
public createInstance(
positions: Vector3[],
material: Material,
): InstancedMesh {
const mesh = new InstancedMesh(this.geom, material, positions.length);
positions.forEach((pos, index) => {
const mat = new Matrix4();
const quat = new Quaternion();
quat.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI * Math.random());
mat.compose(pos, quat, new Vector3(1, 1, 1));
mesh.setMatrixAt(index, mat);
});
return mesh;
}
public createGrassPatch(
origin: Vector3,
radius: number,
density: number,
pluck: number,
getHeight: (x: number, y: number) => number,
): Vector3[] {
const positions: Vector3[] = [];
const posc = new Vector2(origin.x, origin.z);
const grassPerUnit = 1 / density;
const actualRadius = radius / density;
// Create patch circle
for (let x = -actualRadius; x < actualRadius; x++) {
for (let y = -actualRadius; y < actualRadius; y++) {
const posi = new Vector2(origin.x + x, origin.z + y);
if (posc.distanceTo(posi) <= actualRadius) {
for (let u = 0; u < grassPerUnit; u++) {
if (pluck > 0 && rand(Math.random, 0, pluck) === 0) continue;
const pos = new Vector3(
origin.x + x * density,
0,
origin.z + y * density,
);
pos.x += Math.random() / 2 - 1;
pos.z += Math.random() / 2 - 1;
pos.y = getHeight(pos.x, pos.z);
positions.push(pos);
}
}
}
}
return positions;
}
}