/* eslint-disable import/no-cycle */ import * as BABYLON from 'babylonjs'; import * as loaders from 'babylonjs-loaders'; import * as MATERIALS from 'babylonjs-materials'; import { allAvatars } from '../model/user'; import { RankingTrackProps, RankingTrackTeamItem } from '../model/rankingTrack'; BABYLON.SceneLoader.ShowLoadingScreen = false; class Engine { public clearColor = BABYLON.Color4.FromHexString('#dce4e8ff'); private canvas: HTMLCanvasElement; public engine: BABYLON.Engine; private running: boolean; public scene: BABYLON.Scene; public pipeline: BABYLON.DefaultRenderingPipeline; public camera: BABYLON.ArcRotateCamera; public lights: BABYLON.Light[]; public rankingProps: RankingTrackProps; public teamsRefs: React.MutableRefObject[]; public constructor(canvas: HTMLCanvasElement, avoidRenderLoop = false) { this.canvas = canvas; this.lights = new Array(); this.setupEngine(avoidRenderLoop); } private render = (): void => { if (this.running) { if (this.teamsRefs) { for (let i = 0; i < this.characters.length; i++) { if (this.teamsRefs[i] && this.teamsRefs[i].current) { const divDOMRef = this.teamsRefs[i].current; if (divDOMRef) { const character = this.characters[i]; const pointInScreen = BABYLON.Vector3.Project( new BABYLON.Vector3( character.position.x, character.position.y + 1.4, character.position.z, ), BABYLON.Matrix.Identity(), this.scene.getTransformMatrix(), this.camera.viewport.toGlobal( window.innerWidth, window.innerHeight, ), ); divDOMRef.style.left = `${pointInScreen.x}px`; divDOMRef.style.top = `${pointInScreen.y}px`; const zIndex = Math.round(100000 - (pointInScreen.z * 100000)); divDOMRef.style.zIndex = `${zIndex}`; } } } } this.scene.render(); } }; public fixRatio = 1; private resize = (): void => { if (this.running) { this.engine.resize(); } }; public setCamera = (camera: BABYLON.ArcRotateCamera): void => { this.camera.dispose(); this.camera = camera; this.setupRenderPipeline(); }; private setupEngine = (avoidRenderLoop = false): void => { this.engine = new BABYLON.Engine(this.canvas, true, { preserveDrawingBuffer: true, stencil: true, }); this.scene = new BABYLON.Scene(this.engine, { useClonedMeshMap: true, useMaterialMeshMap: true, useGeometryUniqueIdsMap: true, }); this.scene.clearColor = this.clearColor; // the canvas/window resize event handler window.addEventListener('resize', this.resize); // this.setupRenderPipeline(); // run the render loop this.running = true; if (!avoidRenderLoop) { this.setupRenderLoop(); } }; public track: BABYLON.AbstractMesh; public shadowGenerator: BABYLON.ShadowGenerator; public characters = new Array(); public setupScene = async (): Promise => { loaders.OBJFileLoader.INVERT_Y = true; const camera = new BABYLON.ArcRotateCamera( 'rankingCamera', 0.788, 1.157, 30, new BABYLON.Vector3(0, 0, 0), this.scene, ); camera.wheelDeltaPercentage = 0.001; camera.pinchDeltaPercentage = 0.001; camera.panningSensibility = 0; camera.lowerRadiusLimit = 25; camera.upperRadiusLimit = 25; camera.upperBetaLimit = 1.5; camera.lowerBetaLimit = 0; // This attaches the camera to the canvas camera.attachControl(this.canvas, true); camera.targetScreenOffset = new BABYLON.Vector2( this.rankingProps.xOffset, this.rankingProps.yOffset, ); this.camera = camera; const spotLight = new BABYLON.SpotLight( 'spotLight', new BABYLON.Vector3(-15, 20, 15), new BABYLON.Vector3(0.467, -0.751, -0.489), Math.PI / 5, 1, this.scene, ); const shadowGenerator = new BABYLON.ShadowGenerator(1024, spotLight); shadowGenerator.bias = 0.001; spotLight.shadowMaxZ = 50; spotLight.shadowMinZ = 10; shadowGenerator.useContactHardeningShadow = true; shadowGenerator.contactHardeningLightSizeUVRatio = 0.2; shadowGenerator.setDarkness(0.5); this.shadowGenerator = shadowGenerator; this.lights.push(spotLight); const ground = BABYLON.Mesh.CreateGround( 'ground', 200, 200, 1, this.scene, false, ); ground.receiveShadows = true; ground.position.y = -2.5; ground.material = new MATERIALS.ShadowOnlyMaterial( 'shadowOnly', this.scene, ); ground.material.alpha = 0.4; const mirror = BABYLON.Mesh.CreateBox('Mirror', 1.0, this.scene); mirror.scaling = new BABYLON.Vector3(11.9, 0.2, 11.9); const mirrorMaterial = new BABYLON.StandardMaterial('mirror', this.scene); mirror.material = mirrorMaterial; mirrorMaterial.diffuseColor = BABYLON.Color3.Black(); mirrorMaterial.ambientColor = BABYLON.Color3.Black(); mirrorMaterial.alpha = 0.15; const mirrorTexture = new BABYLON.MirrorTexture( 'mirror', { ratio: 0.5 }, this.scene, true, ); mirrorMaterial.reflectionTexture = mirrorTexture; mirrorTexture.mirrorPlane = new BABYLON.Plane(0, -1.7, 0, -2.0); mirrorTexture.renderList = this.characters; mirrorTexture.level = 1.0; mirrorTexture.adaptiveBlurKernel = 32; mirror.position = new BABYLON.Vector3(-0.6, -1.33, 0); await BABYLON.SceneLoader.AppendAsync( '/model/scene/', `scene-running.vox-${this.rankingProps.teams.length - 1}.obj`, this.scene, ); loaders.OBJFileLoader.INVERT_Y = false; this.track = this.scene.meshes.find((i) => i.name === ''); this.track.name = 'track'; (this.track.material as BABYLON.StandardMaterial).emissiveColor = BABYLON.Color3.Gray(); this.track.position.z = -this.track.getBoundingInfo().boundingBox.center.z; this.track.position.x = -this.track.getBoundingInfo().boundingBox.center.x; this.track.position.y = -2.75; this.track.rotation.z = Math.PI; for (let i = 0; i < this.rankingProps.teams.length; i++) { // eslint-disable-next-line no-await-in-loop await this.loadTeamCharacter(this.rankingProps.teams[i]); } this.track.receiveShadows = true; this.shadowGenerator.addShadowCaster(this.track); this.setupRenderPipeline(); }; public totalFramesForTeamAnimation = 60; public loadTeamCharacter = async ( team: RankingTrackTeamItem, ): Promise => { await BABYLON.SceneLoader.AppendAsync( '/model/characters/', `characters.vox-${allAvatars.indexOf(team.mainAvatar)}.obj`, this.scene, ); const teamMesh = this.scene.meshes.find((i) => i.name === ''); (teamMesh.material as BABYLON.StandardMaterial).emissiveColor = BABYLON.Color3.Gray(); this.shadowGenerator.addShadowCaster(teamMesh); teamMesh.receiveShadows = true; teamMesh.name = team.name; teamMesh.id = `${team.id}`; const teamTrackX = (this.rankingProps.teams.indexOf(team) - this.rankingProps.teams.length / 2) * 1; teamMesh.position.x = teamTrackX - 0.05; teamMesh.position.z = -3.5; const teamMeshAnimation = new BABYLON.Animation( `${team.id}animation`, 'position', 60, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, ); const keys = new Array(); keys.push({ frame: 0, value: teamMesh.position.clone(), }); this.characters.push(teamMesh); for (let i = 0; i < this.rankingProps.totalStages; i++) { const advanceToPosition = 9 * (team.points[i] / this.rankingProps.maxPoints); keys.push({ frame: this.totalFramesForTeamAnimation * (i + 1), value: teamMesh.position.add( BABYLON.Vector3.Forward().scale(advanceToPosition), ), }); } teamMeshAnimation.setKeys(keys); teamMesh.animations.push(teamMeshAnimation); this.scene.beginAnimation( teamMesh, 0, this.totalFramesForTeamAnimation * this.rankingProps.totalStages, false, ); }; public setupRenderLoop = (): void => { this.engine.runRenderLoop(this.render); }; public setupRenderPipeline = (): void => { this.pipeline = new BABYLON.DefaultRenderingPipeline( 'defaultPipeline', true, this.scene, [this.camera], ); this.pipeline.samples = 2; this.pipeline.fxaaEnabled = true; this.pipeline.bloomEnabled = true; this.pipeline.bloomWeight = 0.5; this.pipeline.imageProcessingEnabled = false; }; public dispose = (): void => { window.removeEventListener('resize', this.resize); this.lights.forEach((light) => light.dispose()); this.running = false; this.engine.stopRenderLoop(this.render); if (this.scene) { try { this.scene.dispose(); } catch (e) { console.log(e.message); } } if (this.engine) { this.engine.dispose(); } }; public showInspector = (): void => { if (this.scene.debugLayer.isVisible()) { this.scene.debugLayer.hide(); this.scene.debugLayer.show({ // embedMode: true, overlay: true, }); } else { this.scene.debugLayer.show({ // embedMode: true, overlay: true, }); } }; } export default Engine;