98 lines
2.7 KiB
TypeScript
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;
|
|
}
|
|
}
|