init
This commit is contained in:
305
src/OceanScene.js
Normal file
305
src/OceanScene.js
Normal file
@@ -0,0 +1,305 @@
|
||||
import * as THREE from 'three';
|
||||
import { Water } from 'three/addons/objects/Water.js';
|
||||
import { Sky } from 'three/addons/objects/Sky.js';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { TerrainGenerator } from './TerrainGenerator.js';
|
||||
import { VegetationSystem } from './VegetationSystem.js';
|
||||
|
||||
export class OceanScene {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.scene = null;
|
||||
this.camera = null;
|
||||
this.renderer = null;
|
||||
this.controls = null;
|
||||
this.water = null;
|
||||
this.sky = null;
|
||||
this.sun = new THREE.Vector3();
|
||||
this.terrain = null;
|
||||
this.vegetation = null;
|
||||
this.pmremGenerator = null;
|
||||
this.renderTarget = null;
|
||||
this.sunLight = null;
|
||||
|
||||
this.params = {
|
||||
elevation: 2,
|
||||
azimuth: 180,
|
||||
exposure: 0.5,
|
||||
turbidity: 10,
|
||||
rayleigh: 2,
|
||||
mieCoefficient: 0.005,
|
||||
mieDirectionalG: 0.8
|
||||
};
|
||||
|
||||
this.clock = new THREE.Clock();
|
||||
this.frameCount = 0;
|
||||
this.lastTime = performance.now();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.initRenderer();
|
||||
this.initScene();
|
||||
this.initCamera();
|
||||
this.initControls();
|
||||
this.initLighting();
|
||||
await this.initSky();
|
||||
await this.initWater();
|
||||
await this.initTerrain();
|
||||
await this.initVegetation();
|
||||
this.initSunPosition();
|
||||
this.initEventListeners();
|
||||
}
|
||||
|
||||
initRenderer() {
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
powerPreference: 'high-performance'
|
||||
});
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
this.renderer.toneMappingExposure = this.params.exposure;
|
||||
this.renderer.shadowMap.enabled = true;
|
||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
this.container.appendChild(this.renderer.domElement);
|
||||
}
|
||||
|
||||
initScene() {
|
||||
this.scene = new THREE.Scene();
|
||||
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0008);
|
||||
}
|
||||
|
||||
initCamera() {
|
||||
this.camera = new THREE.PerspectiveCamera(
|
||||
60,
|
||||
window.innerWidth / window.innerHeight,
|
||||
1,
|
||||
20000
|
||||
);
|
||||
this.camera.position.set(100, 50, 200);
|
||||
}
|
||||
|
||||
initControls() {
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls.enableDamping = true;
|
||||
this.controls.dampingFactor = 0.05;
|
||||
this.controls.maxPolarAngle = Math.PI * 0.48;
|
||||
this.controls.minDistance = 30;
|
||||
this.controls.maxDistance = 1000;
|
||||
this.controls.target.set(0, 10, 0);
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
initLighting() {
|
||||
const ambientLight = new THREE.AmbientLight(0x555555);
|
||||
this.scene.add(ambientLight);
|
||||
|
||||
this.sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||
this.sunLight.castShadow = true;
|
||||
this.sunLight.shadow.mapSize.width = 2048;
|
||||
this.sunLight.shadow.mapSize.height = 2048;
|
||||
this.sunLight.shadow.camera.near = 0.5;
|
||||
this.sunLight.shadow.camera.far = 500;
|
||||
this.sunLight.shadow.camera.left = -100;
|
||||
this.sunLight.shadow.camera.right = 100;
|
||||
this.sunLight.shadow.camera.top = 100;
|
||||
this.sunLight.shadow.camera.bottom = -100;
|
||||
this.scene.add(this.sunLight);
|
||||
}
|
||||
|
||||
async initSky() {
|
||||
this.sky = new Sky();
|
||||
this.sky.scale.setScalar(10000);
|
||||
this.scene.add(this.sky);
|
||||
|
||||
const skyUniforms = this.sky.material.uniforms;
|
||||
skyUniforms['turbidity'].value = this.params.turbidity;
|
||||
skyUniforms['rayleigh'].value = this.params.rayleigh;
|
||||
skyUniforms['mieCoefficient'].value = this.params.mieCoefficient;
|
||||
skyUniforms['mieDirectionalG'].value = this.params.mieDirectionalG;
|
||||
|
||||
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
|
||||
}
|
||||
|
||||
async initWater() {
|
||||
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
|
||||
|
||||
const waterNormals = await new Promise((resolve) => {
|
||||
new THREE.TextureLoader().load(
|
||||
'https://threejs.org/examples/textures/waternormals.jpg',
|
||||
(texture) => {
|
||||
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
||||
resolve(texture);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.water = new Water(waterGeometry, {
|
||||
textureWidth: 512,
|
||||
textureHeight: 512,
|
||||
waterNormals: waterNormals,
|
||||
sunDirection: new THREE.Vector3(),
|
||||
sunColor: 0xffffff,
|
||||
waterColor: 0x001e0f,
|
||||
distortionScale: 3.7,
|
||||
fog: true
|
||||
});
|
||||
|
||||
this.water.rotation.x = -Math.PI / 2;
|
||||
this.water.position.y = 0;
|
||||
this.scene.add(this.water);
|
||||
}
|
||||
|
||||
async initTerrain() {
|
||||
const terrainGen = new TerrainGenerator({
|
||||
size: 1200,
|
||||
segments: 200,
|
||||
maxHeight: 30,
|
||||
waterLevel: 0,
|
||||
seed: 42
|
||||
});
|
||||
|
||||
this.terrain = terrainGen.generate();
|
||||
this.scene.add(this.terrain);
|
||||
|
||||
this.terrainGenerator = terrainGen;
|
||||
}
|
||||
|
||||
async initVegetation() {
|
||||
const vegSystem = new VegetationSystem(this.terrainGenerator, {
|
||||
grassCount: 30000,
|
||||
treeCount: 300,
|
||||
terrainSize: 1200,
|
||||
waterLevel: 1
|
||||
});
|
||||
|
||||
this.vegetation = vegSystem.generate();
|
||||
vegSystem.addToScene(this.scene);
|
||||
}
|
||||
|
||||
initSunPosition() {
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
updateSun() {
|
||||
const phi = THREE.MathUtils.degToRad(90 - this.params.elevation);
|
||||
const theta = THREE.MathUtils.degToRad(this.params.azimuth);
|
||||
|
||||
this.sun.setFromSphericalCoords(1, phi, theta);
|
||||
|
||||
this.sky.material.uniforms['sunPosition'].value.copy(this.sun);
|
||||
this.water.material.uniforms['sunDirection'].value.copy(this.sun).normalize();
|
||||
|
||||
if (this.sunLight) {
|
||||
const sunDistance = 100;
|
||||
this.sunLight.position.set(
|
||||
this.sun.x * sunDistance,
|
||||
this.sun.y * sunDistance,
|
||||
this.sun.z * sunDistance
|
||||
);
|
||||
}
|
||||
|
||||
if (this.renderTarget) {
|
||||
this.renderTarget.dispose();
|
||||
}
|
||||
|
||||
const sceneEnv = new THREE.Scene();
|
||||
sceneEnv.add(this.sky);
|
||||
this.renderTarget = this.pmremGenerator.fromScene(sceneEnv);
|
||||
this.scene.environment = this.renderTarget.texture;
|
||||
this.scene.add(this.sky);
|
||||
|
||||
this.scene.fog.color.setHex(this.getFogColor());
|
||||
}
|
||||
|
||||
getFogColor() {
|
||||
const elevation = this.params.elevation;
|
||||
|
||||
if (elevation < 0) {
|
||||
return 0x1a2a3a;
|
||||
} else if (elevation < 10) {
|
||||
return 0x4a5a6a;
|
||||
} else if (elevation < 20) {
|
||||
return 0x8cb8d4;
|
||||
} else if (elevation < 45) {
|
||||
return 0x9ec5db;
|
||||
} else if (elevation < 70) {
|
||||
return 0xb8d4e8;
|
||||
} else {
|
||||
return 0xd4e8f4;
|
||||
}
|
||||
}
|
||||
|
||||
initEventListeners() {
|
||||
window.addEventListener('resize', () => this.onWindowResize());
|
||||
}
|
||||
|
||||
onWindowResize() {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
setSunElevation(value) {
|
||||
this.params.elevation = value;
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
setSunAzimuth(value) {
|
||||
this.params.azimuth = value;
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
setExposure(value) {
|
||||
this.params.exposure = value;
|
||||
this.renderer.toneMappingExposure = value;
|
||||
}
|
||||
|
||||
setTurbidity(value) {
|
||||
this.params.turbidity = value;
|
||||
this.sky.material.uniforms['turbidity'].value = value;
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
setRayleigh(value) {
|
||||
this.params.rayleigh = value;
|
||||
this.sky.material.uniforms['rayleigh'].value = value;
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
animate() {
|
||||
requestAnimationFrame(() => this.animate());
|
||||
|
||||
const time = this.clock.getElapsedTime();
|
||||
|
||||
if (this.water) {
|
||||
this.water.material.uniforms['time'].value += 1.0 / 60.0;
|
||||
}
|
||||
|
||||
this.controls.update();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
|
||||
this.frameCount++;
|
||||
const currentTime = performance.now();
|
||||
if (currentTime - this.lastTime >= 1000) {
|
||||
const fps = Math.round(this.frameCount * 1000 / (currentTime - this.lastTime));
|
||||
const fpsElement = document.getElementById('fps');
|
||||
if (fpsElement) {
|
||||
fpsElement.textContent = fps;
|
||||
}
|
||||
this.frameCount = 0;
|
||||
this.lastTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.opacity = '0';
|
||||
loading.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => {
|
||||
loading.style.display = 'none';
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user