scatterMeshes static method
Scatter a mesh across the terrain.
BufferGeometry geometry
The terrain's geometry (or the highest-resolution version of it).
TerrainOptions options
A map of settings that controls how the meshes are scattered, with the
following properties:
mesh: ATHREE.Meshinstance to scatter across the terrain.spread: A number or a function that affects where meshes are placed. If it is a number, it represents the percent of faces of the terrain onto which a mesh should be placed. If it is a function, it takes a vertex from the terrain and the key of a related face and returns a boolean indicating whether to place a mesh on that face or not. An example could befunction(v, k) { return v.z > 0 && !(k % 4); }. Defaults to 0.025.smoothSpread: If thespreadoption is a number, this affects how much placement is "eased in." Specifically, if therandomnessfunction returns a value for a face that is withinsmoothSpreadpercentiles abovespread, then the probability that a mesh is placed there is interpolated between zero andspread. This creates a "thinning" effect near the edges of clumps, if the randomness function creates clumps.scene: ATHREE.Object3Dinstance to which the scattered meshes will be added. This is expected to be either a return value of a call toTHREE.Terrain()or added to that return value; otherwise the position and rotation of the meshes will be wrong.sizeVariance: The percent by which instances of the mesh can be scaled up or down when placed on the terrain.randomness: Ifoptions.spreadis a number, then this property is a function that determines where meshes are placed. Specifically, it returns an array of numbers, where each number is the probability that a mesh is NOT placed on the corresponding face. Valid values includeMath.randomand the return value of a call toTHREE.Terrain.ScatterHelper.maxSlope: The angle in radians between the normal of a face of the terrain and the "up" vector above which no mesh will be placed on the related face. Defaults to ~0.63, which is 36 degrees.maxTilt: The maximum angle in radians a mesh can be tilted away from the "up" vector (towards the normal vector of the face of the terrain). Defaults to Infinity (meshes will point towards the normal).w: The number of horizontal segments of the terrain.h: The number of vertical segments of the terrain.
@return {THREE.Object3D}
An Object3D containing the scattered meshes. This is the value of the
options.scene parameter if passed. This is expected to be either a
return value of a call to THREE.Terrain() or added to that return value;
otherwise the position and rotation of the meshes will be wrong.
Implementation
static Object3D scatterMeshes(BufferGeometry geometry, ScatterOptions options) {
options.scene ??= Object3D();
final spreadIsNumber = options.spreadFunction == null,
spreadRange = 1 / options.smoothSpread,
doubleSizeVariance = options.sizeVariance*2,
vertex1 = Vector3.zero(),
vertex2 = Vector3.zero(),
vertex3 = Vector3.zero(),
faceNormal = Vector3.zero(),
up = options.mesh.up.clone().applyAxisAngle(Vector3(1, 0, 0), 0.5*math.pi);
final dynamic randomHeightmap;
dynamic randomness;
if (spreadIsNumber) {
randomHeightmap = options.randomness;
randomness = (k) { return randomHeightmap(k) ?? math.Random().nextDouble();};
}
geometry = geometry.toNonIndexed();
final gArray = geometry.attributes['position'].array;
for (int i = 0; i < geometry.attributes['position'].array.length; i += 9) {
vertex1.setValues(gArray[i + 0], gArray[i + 1], gArray[i + 2]);
vertex2.setValues(gArray[i + 3], gArray[i + 4], gArray[i + 5]);
vertex3.setValues(gArray[i + 6], gArray[i + 7], gArray[i + 8]);
Triangle.staticGetNormal(vertex1, vertex2, vertex3, faceNormal);
bool place = false;
if (spreadIsNumber) {
final rv = randomness(i/9);
if (rv < options.spread) {
place = true;
}
else if (rv < options.spread + options.smoothSpread) {
// Interpolate rv between spread and spread + smoothSpread,
// then multiply that "easing" value by the probability
// that a mesh would get placed on a given face.
place = Easing.easeInOut((rv - options.spread)*spreadRange)*options.spread > math.Random().nextDouble();
}
}
else {
place = options.spreadFunction!(vertex1, i / 9, faceNormal, i);
}
if (place) {
// Don't place a mesh if the angle is too steep.
if (faceNormal.angleTo(up) > options.maxSlope) {
continue;
}
final mesh = options.mesh.clone();
mesh.position.add2(vertex1, vertex2).add(vertex3).divideScalar(3);
if (options.maxTilt > 0) {
final normal = mesh.position.clone().add(faceNormal);
mesh.lookAt(normal);
final tiltAngle = faceNormal.angleTo(up);
if (tiltAngle > options.maxTilt) {
final ratio = options.maxTilt / tiltAngle;
mesh.rotation.x *= ratio;
mesh.rotation.y *= ratio;
mesh.rotation.z *= ratio;
}
}
mesh.rotation.x += 90 / 180*math.pi;
mesh.rotateY(math.Random().nextDouble()*2*math.pi);
if (options.sizeVariance != 0) {
final variance = math.Random().nextDouble()*doubleSizeVariance - options.sizeVariance;
mesh.scale.x = mesh.scale.z = 1 + variance;
mesh.scale.y += variance;
}
mesh.updateMatrix();
options.scene?.add(mesh);
}
}
return options.scene!;
}