更换新的植被生成系统
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -8,12 +8,22 @@
|
|||||||
"name": "realistic-ocean-scene",
|
"name": "realistic-ocean-scene",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dgreenheck/ez-tree": "^1.1.0",
|
||||||
"three": "^0.171.0"
|
"three": "^0.171.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^6.0.0"
|
"vite": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dgreenheck/ez-tree": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dgreenheck/ez-tree/-/ez-tree-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-6pvS6hD6B6h00dm0SnkgYeT4ABU5Y1Z9M44p1tXiV5C0eKrQy2sKECXshoaUv0qAOqYVL68w/PwadUxDFDiHUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">=0.167"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dgreenheck/ez-tree": "^1.1.0",
|
||||||
"three": "^0.171.0"
|
"three": "^0.171.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export class OceanScene {
|
|||||||
this.sun = new THREE.Vector3();
|
this.sun = new THREE.Vector3();
|
||||||
this.terrain = null;
|
this.terrain = null;
|
||||||
this.vegetation = null;
|
this.vegetation = null;
|
||||||
|
this.vegetationSystem = null;
|
||||||
this.pmremGenerator = null;
|
this.pmremGenerator = null;
|
||||||
this.renderTarget = null;
|
this.renderTarget = null;
|
||||||
this.sunLight = null;
|
this.sunLight = null;
|
||||||
@@ -304,13 +305,15 @@ export class OceanScene {
|
|||||||
|
|
||||||
async initVegetation() {
|
async initVegetation() {
|
||||||
const vegSystem = new VegetationSystem(this.terrainGenerator, {
|
const vegSystem = new VegetationSystem(this.terrainGenerator, {
|
||||||
grassCount: 30000,
|
grassCount: 140000, // 草簇数量,越大地表覆盖越密
|
||||||
treeCount: 300,
|
shrubCount: 12, // 灌木和低矮植物数量
|
||||||
terrainSize: 1200,
|
treeCount: 9, // 树木数量
|
||||||
waterLevel: 1
|
terrainSize: 1200, // 植被允许分布的地形范围
|
||||||
|
waterLevel: 1 // 植被生成时参考的水位,避免贴近海边
|
||||||
});
|
});
|
||||||
|
|
||||||
this.vegetation = vegSystem.generate();
|
this.vegetation = vegSystem.generate();
|
||||||
|
this.vegetationSystem = vegSystem;
|
||||||
vegSystem.addToScene(this.scene);
|
vegSystem.addToScene(this.scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,6 +483,10 @@ export class OceanScene {
|
|||||||
layer.texture.offset.y = time * layer.speedY;
|
layer.texture.offset.y = time * layer.speedY;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.vegetationSystem) {
|
||||||
|
this.vegetationSystem.update(time);
|
||||||
|
}
|
||||||
|
|
||||||
this.controls.update();
|
this.controls.update();
|
||||||
if (this.composer) {
|
if (this.composer) {
|
||||||
|
|||||||
@@ -1,195 +1,281 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { Tree } from '@dgreenheck/ez-tree';
|
||||||
import { SimplexNoise } from './utils/SimplexNoise.js';
|
import { SimplexNoise } from './utils/SimplexNoise.js';
|
||||||
|
|
||||||
export class VegetationSystem {
|
export class VegetationSystem {
|
||||||
constructor(terrain, options = {}) {
|
constructor(terrain, options = {}) {
|
||||||
this.terrain = terrain;
|
this.terrain = terrain;
|
||||||
this.options = {
|
this.options = {
|
||||||
grassCount: options.grassCount || 50000,
|
grassCount: options.grassCount || 18000,
|
||||||
treeCount: options.treeCount || 500,
|
shrubCount: options.shrubCount || 420,
|
||||||
|
treeCount: options.treeCount || 120,
|
||||||
terrainSize: options.terrainSize || 1000,
|
terrainSize: options.terrainSize || 1000,
|
||||||
waterLevel: options.waterLevel || 0
|
waterLevel: options.waterLevel || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.noise = new SimplexNoise(12345);
|
this.noise = new SimplexNoise(12345);
|
||||||
|
this.group = new THREE.Group();
|
||||||
this.grass = null;
|
this.grass = null;
|
||||||
this.trees = [];
|
this.plants = [];
|
||||||
|
this.animatedPlants = [];
|
||||||
|
this.treePositions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
|
this.group.clear();
|
||||||
|
this.plants = [];
|
||||||
|
this.animatedPlants = [];
|
||||||
|
this.treePositions = [];
|
||||||
|
|
||||||
this.generateGrass();
|
this.generateGrass();
|
||||||
this.generateTrees();
|
this.generateTrees();
|
||||||
|
this.generateShrubs();
|
||||||
return {
|
|
||||||
grass: this.grass,
|
if (this.grass) {
|
||||||
trees: this.trees
|
this.group.add(this.grass);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
this.plants.forEach((plant) => this.group.add(plant));
|
||||||
|
|
||||||
|
return this.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateGrass() {
|
generateGrass() {
|
||||||
const geometry = new THREE.BufferGeometry();
|
const placements = this.collectPlacements(this.options.grassCount, {
|
||||||
const positions = [];
|
areaRatio: 0.78,
|
||||||
const colors = [];
|
minHeight: this.options.waterLevel + 1.2,
|
||||||
const uvs = [];
|
maxHeight: this.options.waterLevel + 12,
|
||||||
const indices = [];
|
maxSlope: 1.35,
|
||||||
|
densityScale: 0.02,
|
||||||
const grassBladeHeight = 0.8;
|
densityThreshold: -0.18
|
||||||
const grassBladeWidth = 0.1;
|
});
|
||||||
|
|
||||||
let vertexIndex = 0;
|
const grassGeometries = [
|
||||||
|
this.createGrassBladeGeometry(0),
|
||||||
for (let i = 0; i < this.options.grassCount; i++) {
|
this.createGrassBladeGeometry(Math.PI / 3),
|
||||||
const x = (Math.random() - 0.5) * this.options.terrainSize * 0.7;
|
this.createGrassBladeGeometry(-Math.PI / 3)
|
||||||
const z = (Math.random() - 0.5) * this.options.terrainSize * 0.7;
|
];
|
||||||
const y = this.terrain.getHeightAt(x, z);
|
|
||||||
|
|
||||||
if (y < this.options.waterLevel + 0.5 || y > this.options.waterLevel + 10) continue;
|
|
||||||
|
|
||||||
const bendX = this.noise.noise2D(x * 0.1, z * 0.1) * 0.3;
|
|
||||||
const bendZ = this.noise.noise2D(x * 0.1 + 100, z * 0.1) * 0.3;
|
|
||||||
|
|
||||||
positions.push(
|
|
||||||
x - grassBladeWidth / 2, y, z,
|
|
||||||
x + grassBladeWidth / 2, y, z,
|
|
||||||
x + bendX + grassBladeWidth / 4, y + grassBladeHeight, z + bendZ
|
|
||||||
);
|
|
||||||
|
|
||||||
const greenVariation = 0.7 + Math.random() * 0.3;
|
|
||||||
const baseColor = new THREE.Color().setHSL(0.3, 0.6 * greenVariation, 0.25 * greenVariation);
|
|
||||||
|
|
||||||
colors.push(
|
|
||||||
baseColor.r, baseColor.g, baseColor.b,
|
|
||||||
baseColor.r * 0.9, baseColor.g * 0.9, baseColor.b * 0.9,
|
|
||||||
baseColor.r * 0.8, baseColor.g * 0.8, baseColor.b * 0.8
|
|
||||||
);
|
|
||||||
|
|
||||||
uvs.push(0, 0, 1, 0, 0.5, 1);
|
|
||||||
|
|
||||||
indices.push(
|
|
||||||
vertexIndex, vertexIndex + 1, vertexIndex + 2,
|
|
||||||
vertexIndex + 2, vertexIndex + 1, vertexIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
vertexIndex += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
||||||
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
||||||
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
||||||
geometry.setIndex(indices);
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
const material = new THREE.MeshStandardMaterial({
|
||||||
vertexColors: true,
|
color: 0x7aa35a,
|
||||||
side: THREE.DoubleSide,
|
roughness: 0.96,
|
||||||
roughness: 0.9,
|
metalness: 0,
|
||||||
metalness: 0.0
|
side: THREE.DoubleSide
|
||||||
});
|
});
|
||||||
|
|
||||||
this.grass = new THREE.Mesh(geometry, material);
|
const grassGroup = new THREE.Group();
|
||||||
this.grass.castShadow = true;
|
const dummy = new THREE.Object3D();
|
||||||
this.grass.receiveShadow = true;
|
|
||||||
}
|
grassGeometries.forEach((geometry, layerIndex) => {
|
||||||
|
const mesh = new THREE.InstancedMesh(geometry, material, placements.length);
|
||||||
generateTrees() {
|
mesh.castShadow = false;
|
||||||
const treePositions = [];
|
mesh.receiveShadow = true;
|
||||||
|
|
||||||
for (let i = 0; i < this.options.treeCount * 10; i++) {
|
placements.forEach((placement, index) => {
|
||||||
if (treePositions.length >= this.options.treeCount) break;
|
dummy.position.set(placement.x, placement.y, placement.z);
|
||||||
|
dummy.rotation.set(0, placement.rotation + layerIndex * 0.35, placement.tilt);
|
||||||
const x = (Math.random() - 0.5) * this.options.terrainSize * 0.6;
|
dummy.scale.setScalar(placement.scale);
|
||||||
const z = (Math.random() - 0.5) * this.options.terrainSize * 0.6;
|
dummy.updateMatrix();
|
||||||
const y = this.terrain.getHeightAt(x, z);
|
mesh.setMatrixAt(index, dummy.matrix);
|
||||||
|
|
||||||
if (y < this.options.waterLevel + 1 || y > this.options.waterLevel + 10) continue;
|
const color = new THREE.Color().setHSL(
|
||||||
|
THREE.MathUtils.lerp(0.22, 0.31, placement.colorMix),
|
||||||
const densityNoise = this.noise.noise2D(x * 0.005, z * 0.005);
|
THREE.MathUtils.lerp(0.34, 0.52, placement.colorMix),
|
||||||
if (densityNoise < 0.3) continue;
|
THREE.MathUtils.lerp(0.24, 0.4, placement.colorMix)
|
||||||
|
);
|
||||||
let tooClose = false;
|
mesh.setColorAt(index, color);
|
||||||
for (const pos of treePositions) {
|
|
||||||
const dist = Math.sqrt((pos.x - x) ** 2 + (pos.z - z) ** 2);
|
|
||||||
if (dist < 10) {
|
|
||||||
tooClose = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tooClose) {
|
|
||||||
treePositions.push({ x, y, z });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pos of treePositions) {
|
|
||||||
const tree = this.createTree(pos);
|
|
||||||
this.trees.push(tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createTree(pos) {
|
|
||||||
const tree = new THREE.Group();
|
|
||||||
|
|
||||||
const trunkHeight = 5 + Math.random() * 5;
|
|
||||||
const trunkRadius = 0.3 + Math.random() * 0.2;
|
|
||||||
|
|
||||||
const trunkGeometry = new THREE.CylinderGeometry(
|
|
||||||
trunkRadius * 0.7,
|
|
||||||
trunkRadius,
|
|
||||||
trunkHeight,
|
|
||||||
8
|
|
||||||
);
|
|
||||||
const trunkMaterial = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x4a3728,
|
|
||||||
roughness: 0.9,
|
|
||||||
metalness: 0.0
|
|
||||||
});
|
|
||||||
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
|
|
||||||
trunk.position.y = trunkHeight / 2;
|
|
||||||
trunk.castShadow = true;
|
|
||||||
trunk.receiveShadow = true;
|
|
||||||
tree.add(trunk);
|
|
||||||
|
|
||||||
const foliageLayers = 3 + Math.floor(Math.random() * 2);
|
|
||||||
let foliageY = trunkHeight;
|
|
||||||
|
|
||||||
for (let i = 0; i < foliageLayers; i++) {
|
|
||||||
const foliageRadius = (2.5 - i * 0.4) + Math.random() * 0.5;
|
|
||||||
const foliageHeight = 2 + Math.random() * 1;
|
|
||||||
|
|
||||||
const foliageGeometry = new THREE.ConeGeometry(
|
|
||||||
foliageRadius,
|
|
||||||
foliageHeight,
|
|
||||||
8
|
|
||||||
);
|
|
||||||
|
|
||||||
const greenVariation = 0.8 + Math.random() * 0.2;
|
|
||||||
const foliageMaterial = new THREE.MeshStandardMaterial({
|
|
||||||
color: new THREE.Color().setHSL(0.28 + Math.random() * 0.05, 0.5 * greenVariation, 0.2 * greenVariation),
|
|
||||||
roughness: 0.8,
|
|
||||||
metalness: 0.0
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const foliage = new THREE.Mesh(foliageGeometry, foliageMaterial);
|
mesh.instanceMatrix.needsUpdate = true;
|
||||||
foliage.position.y = foliageY;
|
if (mesh.instanceColor) {
|
||||||
foliage.castShadow = true;
|
mesh.instanceColor.needsUpdate = true;
|
||||||
foliage.receiveShadow = true;
|
}
|
||||||
tree.add(foliage);
|
grassGroup.add(mesh);
|
||||||
|
});
|
||||||
foliageY += foliageHeight * 0.6;
|
|
||||||
}
|
this.grass = grassGroup;
|
||||||
|
|
||||||
tree.position.set(pos.x, pos.y, pos.z);
|
|
||||||
|
|
||||||
const scale = 0.8 + Math.random() * 0.4;
|
|
||||||
tree.scale.setScalar(scale);
|
|
||||||
|
|
||||||
tree.rotation.y = Math.random() * Math.PI * 2;
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateShrubs() {
|
||||||
|
const prototypes = [
|
||||||
|
this.createPlantPrototype('Bush 1', 2001, 0.85),
|
||||||
|
this.createPlantPrototype('Bush 2', 2002, 0.95),
|
||||||
|
this.createPlantPrototype('Bush 3', 2003, 1.05)
|
||||||
|
];
|
||||||
|
|
||||||
|
const placements = this.collectPlacements(this.options.shrubCount, {
|
||||||
|
areaRatio: 0.72,
|
||||||
|
minHeight: this.options.waterLevel + 1.5,
|
||||||
|
maxHeight: this.options.waterLevel + 13,
|
||||||
|
maxSlope: 1.1,
|
||||||
|
minSpacing: 5,
|
||||||
|
densityScale: 0.006,
|
||||||
|
densityThreshold: 0.05
|
||||||
|
});
|
||||||
|
|
||||||
|
placements.forEach((placement, index) => {
|
||||||
|
const prototype = prototypes[index % prototypes.length];
|
||||||
|
const shrub = prototype.clone(true);
|
||||||
|
shrub.position.set(placement.x, placement.y, placement.z);
|
||||||
|
shrub.rotation.y = placement.rotation;
|
||||||
|
shrub.scale.setScalar(THREE.MathUtils.lerp(0.75, 1.35, placement.scaleMix));
|
||||||
|
shrub.traverse((child) => {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
});
|
||||||
|
this.plants.push(shrub);
|
||||||
|
this.animatedPlants.push(shrub);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTrees() {
|
||||||
|
const prototypes = [
|
||||||
|
this.createPlantPrototype('Pine Small', 3001, 1.05),
|
||||||
|
this.createPlantPrototype('Aspen Small', 3002, 1.0),
|
||||||
|
this.createPlantPrototype('Oak Small', 3003, 1.15),
|
||||||
|
this.createPlantPrototype('Pine Medium', 3004, 1.25)
|
||||||
|
];
|
||||||
|
|
||||||
|
const placements = this.collectPlacements(this.options.treeCount, {
|
||||||
|
areaRatio: 0.66,
|
||||||
|
minHeight: this.options.waterLevel + 2.5,
|
||||||
|
maxHeight: this.options.waterLevel + 18,
|
||||||
|
maxSlope: 0.95,
|
||||||
|
minSpacing: 15,
|
||||||
|
densityScale: 0.0042,
|
||||||
|
densityThreshold: 0.14
|
||||||
|
});
|
||||||
|
|
||||||
|
placements.forEach((placement, index) => {
|
||||||
|
const prototype = prototypes[index % prototypes.length];
|
||||||
|
const tree = prototype.clone(true);
|
||||||
|
tree.position.set(placement.x, placement.y, placement.z);
|
||||||
|
tree.rotation.y = placement.rotation;
|
||||||
|
tree.scale.setScalar(THREE.MathUtils.lerp(1.1, 1.9, placement.scaleMix));
|
||||||
|
tree.traverse((child) => {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
});
|
||||||
|
this.plants.push(tree);
|
||||||
|
this.animatedPlants.push(tree);
|
||||||
|
this.treePositions.push(new THREE.Vector2(placement.x, placement.z));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlantPrototype(presetName, seed, tintJitter) {
|
||||||
|
const plant = new Tree();
|
||||||
|
plant.loadPreset(presetName);
|
||||||
|
plant.options.seed = seed;
|
||||||
|
plant.options.bark.tint = this.jitterColor(plant.options.bark.tint, tintJitter * 0.04);
|
||||||
|
plant.options.leaves.tint = this.jitterColor(plant.options.leaves.tint, tintJitter * 0.08);
|
||||||
|
plant.generate();
|
||||||
|
return plant;
|
||||||
|
}
|
||||||
|
|
||||||
|
createGrassBladeGeometry(rotationY) {
|
||||||
|
const width = 0.24;
|
||||||
|
const height = 1.4;
|
||||||
|
const lean = 0.18;
|
||||||
|
const geometry = new THREE.PlaneGeometry(width, height, 1, 3);
|
||||||
|
geometry.translate(0, height * 0.5, 0);
|
||||||
|
|
||||||
|
const position = geometry.attributes.position;
|
||||||
|
for (let i = 0; i < position.count; i++) {
|
||||||
|
const y = position.getY(i);
|
||||||
|
const bend = (y / height) ** 1.8;
|
||||||
|
const taper = THREE.MathUtils.lerp(1, 0.18, y / height);
|
||||||
|
position.setX(i, position.getX(i) * taper + lean * bend);
|
||||||
|
position.setZ(i, 0.06 * bend);
|
||||||
|
}
|
||||||
|
position.needsUpdate = true;
|
||||||
|
geometry.computeVertexNormals();
|
||||||
|
geometry.rotateY(rotationY);
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
collectPlacements(targetCount, config) {
|
||||||
|
const placements = [];
|
||||||
|
const maxAttempts = targetCount * 14;
|
||||||
|
|
||||||
|
for (let i = 0; i < maxAttempts && placements.length < targetCount; i++) {
|
||||||
|
const x = (Math.random() - 0.5) * this.options.terrainSize * config.areaRatio;
|
||||||
|
const z = (Math.random() - 0.5) * this.options.terrainSize * config.areaRatio;
|
||||||
|
const y = this.terrain.getHeightAt(x, z);
|
||||||
|
|
||||||
|
if (y < config.minHeight || y > config.maxHeight) continue;
|
||||||
|
|
||||||
|
const slope = this.getSlopeAt(x, z);
|
||||||
|
if (slope > config.maxSlope) continue;
|
||||||
|
|
||||||
|
const densityNoise = this.noise.noise2D(x * config.densityScale, z * config.densityScale);
|
||||||
|
if (densityNoise < config.densityThreshold) continue;
|
||||||
|
|
||||||
|
if (config.minSpacing && !this.isFarEnoughFromPlants(placements, x, z, config.minSpacing)) continue;
|
||||||
|
if (config.minSpacing && !this.isFarEnoughFromPlants(this.treePositions, x, z, config.minSpacing * 0.8)) continue;
|
||||||
|
|
||||||
|
const colorMix = THREE.MathUtils.clamp((densityNoise + 1) * 0.5, 0, 1);
|
||||||
|
placements.push({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
rotation: Math.random() * Math.PI * 2,
|
||||||
|
tilt: this.noise.noise2D(x * 0.03, z * 0.03) * 0.08,
|
||||||
|
scale: THREE.MathUtils.lerp(0.75, 1.45, Math.random()),
|
||||||
|
scaleMix: Math.random(),
|
||||||
|
colorMix
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return placements;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSlopeAt(x, z) {
|
||||||
|
const sample = 2.5;
|
||||||
|
const left = this.terrain.getHeightAt(x - sample, z);
|
||||||
|
const right = this.terrain.getHeightAt(x + sample, z);
|
||||||
|
const down = this.terrain.getHeightAt(x, z - sample);
|
||||||
|
const up = this.terrain.getHeightAt(x, z + sample);
|
||||||
|
|
||||||
|
return Math.hypot((right - left) / (sample * 2), (up - down) / (sample * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
isFarEnoughFromPlants(positions, x, z, minSpacing) {
|
||||||
|
const minSpacingSq = minSpacing * minSpacing;
|
||||||
|
|
||||||
|
for (const pos of positions) {
|
||||||
|
const px = pos.x;
|
||||||
|
const pz = pos.z ?? pos.y;
|
||||||
|
const dx = px - x;
|
||||||
|
const dz = pz - z;
|
||||||
|
if (dx * dx + dz * dz < minSpacingSq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
jitterColor(hex, amount) {
|
||||||
|
const color = new THREE.Color(hex);
|
||||||
|
const hsl = {};
|
||||||
|
color.getHSL(hsl);
|
||||||
|
hsl.h = (hsl.h + this.noise.noise2D(hex * 0.0001, amount * 100) * amount + 1) % 1;
|
||||||
|
hsl.s = THREE.MathUtils.clamp(hsl.s + amount * 0.6, 0, 1);
|
||||||
|
hsl.l = THREE.MathUtils.clamp(hsl.l + amount * 0.3, 0, 1);
|
||||||
|
color.setHSL(hsl.h, hsl.s, hsl.l);
|
||||||
|
return color.getHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(elapsedTime) {
|
||||||
|
this.animatedPlants.forEach((plant) => {
|
||||||
|
if (typeof plant.update === 'function') {
|
||||||
|
plant.update(elapsedTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addToScene(scene) {
|
addToScene(scene) {
|
||||||
if (this.grass) scene.add(this.grass);
|
scene.add(this.group);
|
||||||
this.trees.forEach(tree => scene.add(tree));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user