756 lines
29 KiB
JavaScript
756 lines
29 KiB
JavaScript
import * as THREE from 'three';
|
|
import { GLTFLoader, DRACOLoader, OrbitControls } from 'three/examples/jsm/Addons.js';
|
|
//import { Controller as OrbitControls } from './3rd-party/phy/3TH/Controller.js';
|
|
import { ViewportGizmo } from "three-viewport-gizmo";
|
|
import Stats from 'three/examples/jsm/libs/stats.module';
|
|
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';
|
|
import { PointerControls } from './PointerControls';
|
|
import { ARButton } from 'three/addons/webxr/ARButton.js';
|
|
import { XRButton } from 'three/addons/webxr/XRButton.js';
|
|
import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
|
|
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
|
|
import { Physics } from './Physics.js';
|
|
import { Clickable } from './Clickable.js';
|
|
import { DashBoard } from './Dashboard.js';
|
|
import { MotionEngine } from './MotionEngine.js';
|
|
import { Draggable } from './Draggable.js';
|
|
import { EventManager } from "./EventManager";
|
|
import { Telemetry } from './Telemetry.js';
|
|
import { MeshUtils } from './MeshUtils.js';
|
|
|
|
THREE.Cache.enabled = true
|
|
|
|
const assetPath = '/asset/default/';
|
|
const defaultLightIntensity = 11;
|
|
const sceneScale = 1.33;
|
|
|
|
class GameEngine extends EventManager{
|
|
|
|
async initScene(){
|
|
this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.001, 99);
|
|
//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
|
|
);
|
|
|
|
const scene = new THREE.Scene();
|
|
|
|
this.sceneWrapper = new THREE.Group();
|
|
scene.add(this.sceneWrapper);
|
|
this.activeObjects = new THREE.Group();
|
|
this.sceneWrapper.add(this.activeObjects);
|
|
this.activeObjects.scale.setScalar(this.scale);
|
|
//this.camera.scale.setScalar(0.1);
|
|
|
|
this.scene = scene;
|
|
|
|
this.initCameraPivot()
|
|
|
|
this.ambientLight = new THREE.AmbientLight( 0x404040, defaultLightIntensity ); // soft white light
|
|
scene.add( this.ambientLight );
|
|
|
|
// var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 10 );
|
|
// hemiLight.position.set(0, 33, 0);
|
|
//scene.add( hemiLight );
|
|
|
|
// scene.fog = new THREE.Fog(0xbbaaaa, 11, 33);
|
|
// scene.fog = new THREE.FogExp2(0xaaaaaa, 0.037);
|
|
|
|
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
dirLight.color.setHSL(0.1, 1, 0.95);
|
|
dirLight.position.set(12, 33, -37);
|
|
dirLight.position.multiplyScalar( 0.20 );
|
|
// hemiLight.position.multiplyScalar( 0.20 );
|
|
scene.add(dirLight);
|
|
dirLight.castShadow = true;
|
|
dirLight.shadow.mapSize.width = 1024;
|
|
dirLight.shadow.mapSize.height = 1024;
|
|
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 = 100;
|
|
dirLight.shadow.bias = - 0.001;
|
|
|
|
// const spotLight = new THREE.SpotLight(0xffffff);
|
|
// scene.add(spotLight);
|
|
|
|
this.listener = new THREE.AudioListener();
|
|
this.camera.add(this.listener);
|
|
this.ambientSound = new THREE.Audio(this.listener);
|
|
|
|
if (this.opts.gizmo) {
|
|
this.orbitControls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
//this.orbitControls.enableZoom = false;
|
|
const gizmo = new ViewportGizmo(this.camera, this.renderer, {
|
|
container: '.renderer-gizmo',
|
|
//type:'cube'
|
|
});
|
|
gizmo.attachControls(this.orbitControls);
|
|
this.gizmo = gizmo;
|
|
this.perspectiveCamera.position.set(0, 0, 10);
|
|
this.orthographicCamera.position.set(0, 0, 100);
|
|
this.cameraRig.rotation.y = 0;
|
|
}
|
|
//this.scene.scale.setScalar(1.25);
|
|
|
|
//const controls = new MapControls( camera, renderer.domElement );
|
|
|
|
if (this.opts.mode == 'GameDesigner'){
|
|
const gameEngine = this;
|
|
this.transformControls = new TransformControls(this.camera, this.renderer.domElement);
|
|
this.transformControls.addEventListener('dragging-changed', function (event) {
|
|
if (gameEngine.orbitControls){
|
|
gameEngine.orbitControls.enabled = !event.value;
|
|
}
|
|
});
|
|
scene.add(this.transformControls.getHelper());
|
|
}else if (['GamePlay', 'GamePreview'].includes(this.opts.mode)){
|
|
const dashboard = new DashBoard(this);
|
|
this.dashboard = dashboard;
|
|
dashboard.enable();
|
|
}
|
|
|
|
this.pointerControls = new PointerControls(this);
|
|
|
|
scene.background = new THREE.Color(1, 1, 1);
|
|
|
|
const mixer = new THREE.AnimationMixer(this.scene);
|
|
const timer = new THREE.Timer();
|
|
|
|
this.timer = timer;
|
|
this.mixers = [mixer];
|
|
|
|
await this.initPhysics();
|
|
|
|
if (this.opts.xr){
|
|
this.cameraRig.add(this.xrController1);
|
|
this.cameraRig.add(this.xrController2);
|
|
this.cameraRig.add(this.xrControllerGrip1);
|
|
this.cameraRig.add(this.xrControllerGrip2);
|
|
|
|
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, - 1)]);
|
|
|
|
let line = new THREE.Line(geometry);
|
|
line.scale.z = 7;
|
|
|
|
this.controllerLine = line;
|
|
}
|
|
}
|
|
|
|
async init(domNode, opts = {}) {
|
|
this.scale = sceneScale;
|
|
this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800;
|
|
this.aspect = this.w / this.h
|
|
this.opts = opts;
|
|
this.raycaster = new THREE.Raycaster();
|
|
this.meshUtils = new MeshUtils(this);
|
|
|
|
const renderer = new THREE.WebGLRenderer({
|
|
antialias: true,
|
|
alpha: false,
|
|
preserveDrawingBuffer: true, //this is important for screenshots capturing
|
|
//powerPreference: "high-performance",
|
|
//precision: 'mediump'
|
|
});
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
renderer.toneMapping = THREE.CineonToneMapping;
|
|
renderer.toneMappingExposure = 1.0;
|
|
renderer.shadowMap.enabled = true;
|
|
renderer.shadowMap.type = THREE.VSMShadowMap; // 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;
|
|
//renderer.alpha = true
|
|
this.renderer = renderer;
|
|
this.pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
|
|
this.anaglyph = new AnaglyphEffect(renderer);
|
|
this.anaglyph.setSize(this.w, this.h);
|
|
|
|
this.stereo = new StereoEffect(renderer);
|
|
this.stereo.setSize(this.w, this.h);
|
|
|
|
this.motionQueue = new MotionEngine();
|
|
this.assetPath = assetPath;
|
|
this.tm = new Telemetry(opts.mode);
|
|
// controls.enableDamping = true;
|
|
// controls.screenSpacePanning = true;
|
|
|
|
this.renderType = 'ST';
|
|
|
|
this.handleXrAction = ['ObjectPreview', 'DesignMode'].includes(opts.mode) ? this.handleXrActionDesignMode : this.handleXrActionGameMode;
|
|
|
|
if (opts.stats){
|
|
this.stats = new Stats();
|
|
this.stats.dom.classList.add('engine-stats');
|
|
document.body.appendChild(this.stats.dom);
|
|
}
|
|
|
|
if (opts.ar) {
|
|
renderer.xr.enabled = true;
|
|
this.arBtn = ARButton.createButton(renderer, {});
|
|
this.arBtn.classList.add('engine-ar-btn');
|
|
document.body.appendChild(this.arBtn);
|
|
}
|
|
if (opts.xr) {
|
|
renderer.xr.enabled = true;
|
|
this.xrBtn = XRButton.createButton(renderer, opts.depthSense ? {
|
|
requiredFeatures: ['depth-sensing'],
|
|
depthSensing: {
|
|
usagePreference: ["gpu-optimized"],
|
|
dataFormatPreference: ["unsigned-short"],
|
|
matchDepthView: false
|
|
}
|
|
} : {
|
|
|
|
})
|
|
this.xrBtn.classList.add('engine-xr-btn');
|
|
document.body.appendChild(this.xrBtn);
|
|
this.initXr();
|
|
}
|
|
|
|
this.clickable = new Clickable(this, 20);
|
|
this.draggable = new Draggable(this, 20);
|
|
|
|
await this.initScene();
|
|
|
|
function animate() {
|
|
this.timer.update();
|
|
let delta = this.timer.getDelta();
|
|
this.physics?.step();
|
|
this.handleXrAction(this, delta);
|
|
this.hero?.update(delta);
|
|
this.mixers.forEach(m => m.update(delta));
|
|
this.dispatchEvent({type: 'beforeRender'})
|
|
this.processHideIfFar();
|
|
this.motionQueue.update(delta);
|
|
|
|
this.render(this.scene, this.camera);
|
|
if (!renderer.xr.isPresenting) {
|
|
this.gizmo?.render();
|
|
}
|
|
}
|
|
renderer.setAnimationLoop(animate.bind(this));
|
|
|
|
this.loadedObjects = [];
|
|
|
|
domNode.appendChild(renderer.domElement);
|
|
|
|
this._wheelEvent = ((event) => {
|
|
event.preventDefault();
|
|
if (this.hero){
|
|
if (!this.pointerControls.isLocked){
|
|
this.hero.cameraZ += event.deltaY * 0.005;
|
|
}
|
|
}else{
|
|
this.camera.zoom -= event.deltaY / (1000 / this.camera.zoom);
|
|
this.camera.zoom = Math.max(this.camera.zoom, .01);
|
|
//controls.rotateSpeed = 1 / Math.sqrt(gameEngine.camera.zoom);
|
|
this.camera.updateProjectionMatrix();
|
|
this.orbitControls.panSpeed = 1 / this.camera.zoom;
|
|
}
|
|
}).bind(this)
|
|
|
|
renderer.domElement.addEventListener('wheel', this._wheelEvent)
|
|
GameEngine.ktxLoader.detectSupport(renderer);
|
|
}
|
|
|
|
initXr() {
|
|
this.xrHandlers = {
|
|
onControllerEvent: (event=>{
|
|
const controller = event.target;
|
|
if (!controller.userData?.active) return;
|
|
|
|
if (this.opts.designMode){
|
|
this.transformControls.getRaycaster().setFromXRController(controller);
|
|
switch (event.type) {
|
|
case 'selectstart':
|
|
this.transformControls.pointerDown(null);
|
|
break;
|
|
case 'selectend':
|
|
this.transformControls.pointerUp(null);
|
|
break;
|
|
case 'move':
|
|
this.transformControls.pointerHover(null);
|
|
this.transformControls.pointerMove(null);
|
|
break;
|
|
}
|
|
}else{
|
|
this.draggable?.handleController(controller, event.type)
|
|
}
|
|
}).bind(this),
|
|
|
|
onSelect: (event => {
|
|
const controller = event.target;
|
|
this.xrController1.userData.active = false;
|
|
this.xrController2.userData.active = false;
|
|
|
|
if (controller === this.xrController1) {
|
|
this.xrController1.userData.active = true;
|
|
this.xrController1.add(this.controllerLine);
|
|
}
|
|
|
|
if (controller === this.xrController2) {
|
|
this.xrController2.userData.active = true;
|
|
this.xrController2.add(this.controllerLine);
|
|
}
|
|
|
|
if (this.opts.designMode){
|
|
this.raycaster.setFromXRController(controller);
|
|
const intersects = this.raycaster.intersectObjects(this.activeObjects.children, true);
|
|
|
|
intersects.forEach(o => {
|
|
while (o.object && !this.activeObjects.children.includes(o.object)) {
|
|
o.object = o.object.parent;
|
|
}
|
|
})
|
|
|
|
if (intersects.length > 0) {
|
|
setTimeout(() => {
|
|
this.transformControls.attach(intersects[0].object);
|
|
}, 100);
|
|
}
|
|
}else{
|
|
this.clickable.handleController(controller, event);
|
|
}
|
|
}).bind(this),
|
|
|
|
onConnect: e => {
|
|
e.target.gamepad = e.data.gamepad;
|
|
}
|
|
}
|
|
|
|
let c1 = this.renderer.xr.getController(0);
|
|
c1.userData.active = false;
|
|
|
|
let c2 = this.renderer.xr.getController(1);
|
|
c2.userData.active = true;
|
|
|
|
[c1, c2].forEach(c=>{
|
|
c.addEventListener('select', this.xrHandlers.onSelect);
|
|
c.addEventListener('selectstart', this.xrHandlers.onControllerEvent);
|
|
c.addEventListener('selectend', this.xrHandlers.onControllerEvent);
|
|
c.addEventListener('move', this.xrHandlers.onControllerEvent);
|
|
c.addEventListener('connected', this.xrHandlers.onConnect)
|
|
})
|
|
|
|
const controllerModelFactory = new XRControllerModelFactory();
|
|
|
|
let controllerGrip1 = this.renderer.xr.getControllerGrip(0);
|
|
controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
|
|
|
|
let controllerGrip2 = this.renderer.xr.getControllerGrip(1);
|
|
controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
|
|
|
|
this.xrController1 = c1
|
|
this.xrController2 = c2
|
|
|
|
this.xrControllerGrip1 = controllerGrip1;
|
|
this.xrControllerGrip2 = controllerGrip2;
|
|
|
|
this.xrCamera = this.renderer.xr.getCamera();
|
|
}
|
|
|
|
disposeXr(){
|
|
[this.xrController1, this.xrController2].forEach(c=>{
|
|
c.removeEventListener('select', this.xrHandlers.onSelect);
|
|
c.removeEventListener('selectstart', this.xrHandlers.onControllerEvent);
|
|
c.removeEventListener('selectend', this.xrHandlers.onControllerEvent);
|
|
c.removeEventListener('move', this.xrHandlers.onControllerEvent);
|
|
c.removeEventListener('connected', this.xrHandlers.onConnect);
|
|
})
|
|
}
|
|
|
|
initCameraPivot() {
|
|
this.cameraWorld = new THREE.Group();
|
|
this.cameraRig = new THREE.Group();
|
|
this.cameraRig.add(this.perspectiveCamera);
|
|
this.cameraRig.add(this.orthographicCamera);
|
|
this.cameraRig.rotation.y = Math.PI;
|
|
this.cameraWorld.add(this.cameraRig);
|
|
this.sceneWrapper.add(this.cameraWorld);
|
|
}
|
|
|
|
async initPhysics() {
|
|
this.physics = new Physics(this);
|
|
await this.physics.init();
|
|
}
|
|
|
|
handleXrActionGameMode(gameEngine, delta) {
|
|
|
|
}
|
|
|
|
handleXrActionDesignMode(gameEngine, delta) {
|
|
if (gameEngine.xrController2?.gamepad) {
|
|
let gp2 = gameEngine.xrController2.gamepad;
|
|
let gp1 = gameEngine.xrController1.gamepad;
|
|
// if (gp.axes[3] != 0){
|
|
// let sc = gameEngine.scene.scale.x + gp.axes[3] * delta * 0.5;
|
|
// gameEngine.scene.scale.set(sc, sc, sc);
|
|
// }
|
|
if (gp2.axes[2] != 0 || gp2.axes[3] != 0) {
|
|
if (gp1.buttons[4]?.pressed) {
|
|
gameEngine.activeObjects.position.y += gp2.axes[2] * delta;
|
|
} else if (gp1.buttons[5]?.pressed) {
|
|
let sc = gameEngine.activeObjects.scale.x
|
|
* (gp2.axes[2] * delta * 0.5 + 1)
|
|
* (gp2.axes[3] * delta * 0.05 + 1);
|
|
gameEngine.activeObjects.scale.setScalar(sc);
|
|
} else {
|
|
gameEngine.activeObjects.rotation.y += gp2.axes[2] * delta * 0.5 + gp2.axes[3] * delta * 0.05;
|
|
}
|
|
}
|
|
if (gp2.buttons[5]?.pressed && (gp1.axes[3] != 0 || gp1.axes[2] != 0)) {
|
|
gameEngine.ambientLight.intensity += gp1.axes[3] * delta * 5 + gp1.axes[2] * delta;
|
|
return;
|
|
}
|
|
if (gp2.buttons[4]?.pressed) {
|
|
let x = performance.now() % 1000;
|
|
if (x < 333) {
|
|
gameEngine.transformControls?.setMode('translate');
|
|
}else if (x < 666) {
|
|
gameEngine.transformControls?.setMode('scale');
|
|
}else{
|
|
gameEngine.transformControls?.setMode('rotate');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gameEngine.xrController1?.gamepad) {
|
|
let gp1 = gameEngine.xrController1.gamepad;
|
|
if (gp1.axes[3] != 0) {
|
|
gameEngine.activeObjects.position.z += (Math.exp(1+Math.abs(gp1.axes[3]))-2) * delta * Math.sign(gp1.axes[3]) * 0.2;
|
|
}
|
|
if (gp1.axes[2] != 0) {
|
|
gameEngine.activeObjects.position.x += (Math.exp(1+Math.abs(gp1.axes[2]))-2) * delta * Math.sign(gp1.axes[2]) * 0.2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$ = THREE;
|
|
|
|
async load(url, path = assetPath, progress) {
|
|
return new Promise((resolve, reject) => {
|
|
GameEngine.gltfLoader.load(`${path}${url}`, o => {
|
|
o.scene.traverse(object => {
|
|
if (object.name == 'environment' || object.material){
|
|
//console.log('env recomputing')
|
|
object.material.shading = THREE.SmoothShading;
|
|
object.geometry.computeVertexNormals(true);
|
|
//object.material.metalness = 0;
|
|
//if (object.material.map) object.material.map.colorSpace = THREE.SRGBColorSpace;
|
|
}
|
|
if ( object instanceof THREE.Mesh ) {
|
|
//object.material.envMap = this.scene.environment;
|
|
if (object.material.map) {
|
|
object.material.map.colorSpace = THREE.SRGBColorSpace;
|
|
}
|
|
|
|
if (object.userData){
|
|
if (object.userData['re.renderOrder'] !== undefined){
|
|
object.renderOrder = object.userData['re.renderOrder'];
|
|
//object.material.alphaTest = true;
|
|
}
|
|
}
|
|
}
|
|
object.frustumCulled = false;
|
|
object.castShadow = true;
|
|
object.receiveShadow = true;
|
|
});
|
|
resolve(o);
|
|
this.loadedObjects.push(o);
|
|
}, progress, reject)
|
|
})
|
|
}
|
|
|
|
async loadPanorama(url, path = assetPath) {
|
|
let t = await GameEngine.loadTexture(url, path);
|
|
t.mapping = THREE.EquirectangularReflectionMapping;
|
|
t.generateMipmaps = false;
|
|
t.minFilter = THREE.LinearFilter;
|
|
this.scene.background?.dispose?.();
|
|
this.scene.environment?.dispose?.();
|
|
this.scene.background = t;
|
|
this.scene.environment = t;
|
|
//this.scene.environment = this.pmremGenerator.fromEquirectangular(t).texture;
|
|
}
|
|
|
|
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.mixers[0].clipAction(clip, object);
|
|
action[play ? 'play' : 'stop']();
|
|
return action;
|
|
}
|
|
|
|
stop() {
|
|
this.renderer?.setAnimationLoop(null);
|
|
this.stats?.dom?.remove();
|
|
}
|
|
|
|
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;
|
|
}
|
|
})
|
|
}
|
|
//console.log('intersects', intersects);
|
|
return intersects;
|
|
}
|
|
|
|
onClick(mouseEvent, domElement){
|
|
let mouse = this.getMouseVector(mouseEvent, domElement);
|
|
//this.raycaster.setFromCamera(mouse, this.camera);
|
|
this.clickable.handleMouse(mouse, mouseEvent);
|
|
this.hero?.idleReset();
|
|
}
|
|
|
|
onPointer(mouseEvent, domElement, type){
|
|
let mouse = this.getMouseVector(mouseEvent, domElement);
|
|
this.draggable?.handleMouse(mouse, type);
|
|
}
|
|
|
|
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;
|
|
if (this.gizmo) {
|
|
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;
|
|
if (this.gizmo) {
|
|
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);
|
|
}
|
|
this.stats?.update()
|
|
}
|
|
|
|
farArray = [];
|
|
|
|
hideIfFar(object, distance){
|
|
object.visible = false;
|
|
this.farArray.push({object, distance})
|
|
}
|
|
|
|
processHideIfFar(){
|
|
let v = new THREE.Vector3(), cv = new THREE.Vector3();
|
|
this.cameraWorld.getWorldPosition(cv);
|
|
this.farArray.forEach(e=>{
|
|
e.object.getWorldPosition(v);
|
|
let dst = v.distanceTo(cv) / this.scale;
|
|
if (dst <= e.distance && !e.object.visible) {
|
|
e.object.visible = true;
|
|
}else if (dst > e.distance && e.object.visible){
|
|
e.object.visible = false;
|
|
}
|
|
})
|
|
}
|
|
|
|
async playAmbientSound(source, path){
|
|
let buffer = await GameEngine.loadAudio(source, path);
|
|
this.ambientSound.setBuffer(buffer);
|
|
this.ambientSound.setLoop(true);
|
|
this.ambientSound.play();
|
|
}
|
|
|
|
immersive(show, t = 1){
|
|
this.motionQueue.add([
|
|
{
|
|
o: this.scene,
|
|
a: {
|
|
backgroundIntensity: show ? 0.1 : 1, environmentIntensity: show ? 0.1 : 1
|
|
},
|
|
t
|
|
},{
|
|
o: this.ambientLight,
|
|
a: { intensity: show ? 0.1 : defaultLightIntensity},
|
|
t
|
|
}
|
|
])
|
|
}
|
|
|
|
clearScene(){
|
|
this.hero?.dispose();
|
|
this.transformControls?.dispose();
|
|
this.pointerControls.dispose();
|
|
//this.activeObjects.clear();
|
|
this.physics.stop();
|
|
this.physics.clear();
|
|
this.clickable.removeAll();
|
|
this.gizmo?.dispose();
|
|
this.motionQueue.clearAll();
|
|
this.ambientSound.stop();
|
|
this.loadedObjects.forEach(o=>this.meshUtils.clearObject(o.scene))
|
|
GameEngine.loadedTextures.forEach(t=>{
|
|
t.dispose();
|
|
});
|
|
GameEngine.loadedTextures.splice(0, GameEngine.loadedTextures.length);
|
|
this.scene.background?.dispose?.();
|
|
this.scene.environment?.dispose?.();
|
|
this.meshUtils.clearObject(this.scene);
|
|
this.scene = null;
|
|
this.tm?.setScene(null);
|
|
this.removeAllListenersOfType('beforeRender');
|
|
}
|
|
|
|
async resetScene(){
|
|
this.dashboard?.reset();//moved from clearscene to resetscene, because an updateText sync callback waits infinitely after gameengine destroy
|
|
this.clearScene();
|
|
await this.initScene();
|
|
}
|
|
|
|
dispose(){
|
|
this.stop();
|
|
this.clearScene();
|
|
this.disposeXr();
|
|
this.renderer.dispose();
|
|
this.pmremGenerator.dispose();
|
|
this.arBtn?.remove();
|
|
this.xrBtn?.remove();
|
|
this.stats?.dom?.remove();
|
|
this.renderer.renderLists.dispose();
|
|
this.renderer.properties.dispose();
|
|
this.renderer.domElement.removeEventListener('wheel', this._wheelEvent)
|
|
this.renderer.domElement.remove();
|
|
super.dispose();
|
|
//console.log('Engine Disposed', this.renderer.info.memory.textures );
|
|
}
|
|
|
|
static textureLoader = new THREE.TextureLoader();
|
|
static audioLoader = new THREE.AudioLoader();
|
|
static draco = new DRACOLoader().setDecoderPath('/3rdparty/draco/');
|
|
static ktxLoader = new KTX2Loader().setTranscoderPath( '/3rdparty/basis/' );
|
|
static gltfLoader = new GLTFLoader().setDRACOLoader(this.draco).setKTX2Loader(this.ktxLoader);
|
|
|
|
static loadedTextures = []
|
|
|
|
static async loadTexture(url, path = assetPath, progress, assignTo) {
|
|
let loader = url.toLowerCase().endsWith('.ktx2') ? GameEngine.ktxLoader : GameEngine.textureLoader;
|
|
return new Promise((resolve, reject) => {
|
|
loader.load(`${path}${url}`, texture => {
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
texture.name = url;
|
|
if (assignTo){
|
|
assignTo[0][assignTo[1]] = texture;
|
|
}
|
|
resolve(texture)
|
|
GameEngine.loadedTextures.push(texture);
|
|
}, progress, reject)
|
|
})
|
|
}
|
|
|
|
static async loadAudio(url, path = assetPath, progress){
|
|
return new Promise((resolve, reject) => {
|
|
GameEngine.audioLoader.load(`${path}${url}`, buffer => {
|
|
resolve(buffer)
|
|
}, progress, reject)
|
|
})
|
|
}
|
|
|
|
static scale = 1.33;
|
|
|
|
loadTexture = GameEngine.loadTexture
|
|
}
|
|
|
|
export { GameEngine } |