Files
pronature-platform/src/lib/gameEngine.js
T
2025-03-22 12:44:42 +02:00

303 lines
11 KiB
JavaScript

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
import { DRACOLoader } from 'three/examples/jsm/Addons.js';
import { OrbitControls } from 'three/examples/jsm/Addons.js';
import { ViewportGizmo } from "three-viewport-gizmo";
//import { AnaglyphEffect } from './three/AnaglyphEffect';
import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';
import { StereoEffect } from 'three/addons/effects/StereoEffect.js';
import { MapControls } from 'three/addons/controls/MapControls.js';
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
class GameEngine {
async init(domNode) {
this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800;
this.aspect = this.w / this.h
const gameEngine = this;
this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 1000);
this.raycaster = new THREE.Raycaster();
this.perspectiveCamera.position.set(0, 0, 10);
this.camera = this.perspectiveCamera;
this.frustumSize = 50;
this.orthographicCamera = new THREE.OrthographicCamera(
this.frustumSize * this.aspect / - 2,
this.frustumSize * this.aspect / 2,
this.frustumSize / 2,
this.frustumSize / - 2,
0.01, 1000);
this.orthographicCamera.position.set( 0, 0, 100 );
const scene = new THREE.Scene();
// let light = new THREE.AmbientLight( 0x404040, 300 ); // soft white light
// scene.add( this.light );
const dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
dirLight.color.setHSL( 0.1, 1, 0.95 );
dirLight.position.set( - 1, 1.75, 1 );
dirLight.position.multiplyScalar( 20 );
scene.add( dirLight );
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
const d = 50;
dirLight.shadow.camera.left = - d;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = - d;
dirLight.shadow.camera.far = 3500;
dirLight.shadow.bias = - 0.0001;
const renderer = new THREE.WebGLRenderer({
antialias: true,
// alpha: true,
// preserveDrawingBuffer: true
//powerPreference: "high-performance",
});
renderer.setPixelRatio( window.devicePixelRatio );
// renderer.toneMapping = THREE.CineonToneMapping;
// renderer.toneMappingExposure = 1.2;
// renderer.shadowMap.enabled = true;
// renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
// renderer.toneMapping = THREE.ACESFilmicToneMapping;
// renderer.toneMappingExposure = 1;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(this.w, this.h);
renderer.setViewport( 0, 0, this.w, this.h );
renderer.autoClear = true;
this.anaglyph = new AnaglyphEffect( renderer );
this.anaglyph.setSize( this.w, this.h );
this.stereo = new StereoEffect(renderer);
this.stereo.setSize( this.w, this.h );
const controls = new OrbitControls( this.camera, renderer.domElement );
const gizmo = new ViewportGizmo(this.camera, renderer, {
container:'.renderer-gizmo',
//type:'cube'
});
gizmo.attachControls(controls);
this.gizmo = gizmo;
this.orbitControls = controls;
//controls.enableZoom = true;
//const controls = new MapControls( camera, renderer.domElement );
this.transformControls = new TransformControls( this.camera, renderer.domElement );
this.transformControls.addEventListener( 'dragging-changed', function ( event ) {
controls.enabled = ! event.value;
} );
// controls.enableDamping = true;
// controls.screenSpacePanning = true;
this.renderType = 'ST';
function animate(time) {
let delta = clock.getDelta();
mixer.update(delta);
gameEngine.render(scene, gameEngine.camera);
gizmo.render();
}
renderer.setAnimationLoop(animate);
const mixer = new THREE.AnimationMixer(this.scene);
const clock = new THREE.Clock();
this.clock = clock;
this.renderer = renderer;
this.scene = scene;
this.draco = new DRACOLoader().setDecoderPath( '/3rdparty/draco/' );
this.loader = new GLTFLoader();
this.loader.setDRACOLoader(this.draco);
this.mixer = mixer;
this.activeObjects = new THREE.Group();
scene.add(this.activeObjects);
domNode.appendChild(renderer.domElement);
let texture = await this.loadTexture('/static/textures/bck.webp');
// let bck = await this.loadTexture('/static/textures/bck.webp');
// bck.premultiplyAlpha = true;
texture.mapping = THREE.EquirectangularReflectionMapping;
// scene.background = bck; //new THREE.Color(0.7,0.7,0.7);
scene.environment = texture;
scene.background = new THREE.Color(1,1,1);
console.log('GameEngine started')
renderer.domElement.addEventListener('wheel', (event)=>{
gameEngine.camera.zoom -= event.deltaY / 1000;
gameEngine.camera.zoom = Math.max(gameEngine.camera.zoom, .4);
controls.rotateSpeed = 1 / gameEngine.camera.zoom;
gameEngine.camera.updateProjectionMatrix();
})
}
$ = THREE;
async load(url, progress){
return new Promise((resolve, reject)=>{
this.loader.load(url, o=>{
o.scene.traverse(object=>{
// if (object.name == 'environment'){
// console.log('env recomputing')
// object.material.shading = THREE.SmoothShading;
// object.geometry.computeVertexNormals(true);
// }
object.frustumCulled = false;
object.castShadow = true;
object.receiveShadow = true;
});
resolve(o);
}, progress, reject)
})
}
async loadTexture(url, progress){
return new Promise((resolve, reject)=>{
new THREE.TextureLoader().load(url, texture=>{
//texture.encoding = THREE.sRGBEncoding;
texture.colorSpace = THREE.SRGBColorSpace;
resolve(texture)
}, progress, reject)
})
}
async loadPanorama(url){
let t = await this.loadTexture(url);
t.mapping = THREE.EquirectangularReflectionMapping;
this.scene.background = t;
this.scene.environment = t;
}
async captureScreenshot(type = 'image/webp', quality = 80){
return new Promise((resolve, reject)=>{
this.renderer.domElement.toBlob(resolve, type, quality)
})
}
playAnimation(object, clip, play = true){
let action = this.mixer.clipAction(clip, object);
if (play) action.play();
else action.stop();
}
stop(){
this.renderer?.setAnimationLoop(null);
}
getMouseVector(mouseEvent, domElement){
//console.log(mouseEvent, domElement)
const mouse = new THREE.Vector2();
if (this.renderType == 'VR'){
let x;
if (mouseEvent.offsetX > window.innerWidth / 2){
x = (mouseEvent.offsetX - window.innerWidth / 2) * 2
}else {
x = mouseEvent.offsetX * 2;
}
mouse.x = ( x / window.innerWidth ) * 2 - 1;
mouse.y = - ( mouseEvent.offsetY / window.innerHeight ) * 2 + 1;
}else{
mouse.x = ( mouseEvent.offsetX / domElement.clientWidth ) * 2 - 1;
mouse.y = - ( mouseEvent.offsetY / domElement.clientHeight ) * 2 + 1;
}
return mouse;
}
intersect(mouseEvent, domElement, objects, recursive = false, returnInputObjects = true){
let mouse = this.getMouseVector(mouseEvent, domElement);
this.raycaster.setFromCamera(mouse, this.camera);
let intersects = this.raycaster.intersectObjects(objects.filter(o=>o.visible), recursive);
if (returnInputObjects && recursive){
intersects.forEach(o=>{
while (o.object && !objects.includes(o.object)) {
o.object = o.object.parent;
}
})
}
return intersects;
}
autoScale(object, mk = 1){
let bb = new THREE.Box3().setFromObject(object);
let k = Math.max(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z);
object.scale.multiplyScalar(mk/k);
}
setCamera(camera){
//camera.updateProjectionMatrix();
this.camera = camera;
//this.transformControls.camera = camera;
this.orbitControls.object = camera;
}
setCameraOrthographic() {
let o = this.orthographicCamera;
// o.position.copy(o.position);
// const distance = o.position.distanceTo(this.orbitControls.target);
// const halfWidth = this.frustumWidthAtDistance(o, distance) / 2;
// const halfHeight = this.frustumHeightAtDistance(o, distance) / 2;
// o.top = halfHeight;
// o.bottom = -halfHeight;
// o.left = -halfWidth;
// o.right = halfWidth;
// o.zoom = 1;
// o.lookAt(this.orbitControls.target);
// o.updateProjectionMatrix();
this.camera = o;
this.transformControls.camera = o;
this.orbitControls.object = o;
this.gizmo.camera = o;
}
setCameraPerspective() {
let o = this.perspectiveCamera;
// const oldY = o.position.y;
// o.position.copy(o.position);
// o.position.y = oldY / o.zoom;
// o.updateProjectionMatrix();
this.camera = o;
this.transformControls.camera = o;
this.orbitControls.object = o;
this.gizmo.camera = o;
}
resize(w, h){
this.w = w;
this.h = h;
this.aspect = this.w / this.h
this.perspectiveCamera.aspect = this.aspect;
this.perspectiveCamera.updateProjectionMatrix();
this.orthographicCamera.left = - this.frustumSize * this.aspect / 2;
this.orthographicCamera.right = this.frustumSize * this.aspect / 2;
this.orthographicCamera.top = this.frustumSize / 2;
this.orthographicCamera.bottom = - this.frustumSize / 2;
this.renderer.setSize(w, h);
this.renderer.setViewport( 0, 0, this.w, this.h );
this.anaglyph.setSize( w, h );
this.stereo.setSize( w, h );
this.gizmo.update();
}
render(scene, camera){
if (this.renderType == 'VR'){
this.stereo?.render( scene, camera );
}else if (this.renderType == 'AG'){
this.anaglyph?.render( scene, camera );
}else{
this.renderer.render( scene, camera );
}
}
}
export {GameEngine}