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, 4 ); dirLight.color.setHSL( 0.1, 1, 0.95 ); dirLight.position.set( -12, 33, 37 ); //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 = 1000; dirLight.shadow.bias = - 0.001; 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}