diff --git a/src/components/AssetsManagement/AssetPreview.vue b/src/components/AssetsManagement/AssetPreview.vue index f309b27..a805e84 100644 --- a/src/components/AssetsManagement/AssetPreview.vue +++ b/src/components/AssetsManagement/AssetPreview.vue @@ -23,7 +23,7 @@ \ No newline at end of file diff --git a/src/components/GameDesigner/GameDesigner.vue b/src/components/GameDesigner/GameDesigner.vue index 547edae..9df600a 100644 --- a/src/components/GameDesigner/GameDesigner.vue +++ b/src/components/GameDesigner/GameDesigner.vue @@ -14,23 +14,23 @@ - +
@@ -47,11 +47,9 @@ - - - - - + + + \ No newline at end of file diff --git a/src/components/GamePreview/GamePreview.vue b/src/components/GamePreview/GamePreview.vue index 99cc70d..fd71c2a 100644 --- a/src/components/GamePreview/GamePreview.vue +++ b/src/components/GamePreview/GamePreview.vue @@ -1,61 +1,98 @@ \ No newline at end of file diff --git a/src/components/InteractiveObjects/ClassicPuzzle.js b/src/components/InteractiveObjects/ClassicPuzzle.js index e4fa9f9..69ca6f6 100644 --- a/src/components/InteractiveObjects/ClassicPuzzle.js +++ b/src/components/InteractiveObjects/ClassicPuzzle.js @@ -1,6 +1,6 @@ import { Color, Group, DoubleSide, RepeatWrapping, MeshStandardMaterial, VideoTexture } from "three" import { EventManager } from '@/lib/EventManager'; -import { centerOrigin } from "@/lib/MeshUtils"; +import { centerOrigin, clearMaterial, clearObject } from "@/lib/MeshUtils"; import Utils from "#/app/Utils"; class ClassicPuzzle extends EventManager { @@ -20,7 +20,7 @@ class ClassicPuzzle extends EventManager { vi.addEventListener('loadedmetadata', async ()=>{ map = new VideoTexture( vi ); resolve(); - }); + }, { once: true }); //'once' is needed in order to avoid memory leaks (the listener gets removed after called) }) }else{ map = await engine.loadTexture(data.$go.asset.name); @@ -101,6 +101,12 @@ class ClassicPuzzle extends EventManager { engine.draggable.remove(done0) container.add(dragZone); this.object = centerOrigin(container); + this.dispose = () => { + //console.log('disposing!!!!!!!') + clearMaterial(defaultMaterial); + clearObject(gltf.scene); + super.dispose(); + } resolve(this); }) } diff --git a/src/components/InteractiveObjects/InteractiveObject.js b/src/components/InteractiveObjects/InteractiveObject.js index 0c18950..ae802c6 100644 --- a/src/components/InteractiveObjects/InteractiveObject.js +++ b/src/components/InteractiveObjects/InteractiveObject.js @@ -92,23 +92,15 @@ class InteractiveObject extends EventManager{ if (obj.distance) { engine.hideIfFar(this.object, obj.distance) - // const o = this.object; - // let dstm = obj.distance; - // let v = new Vector3(); - // o.visible = false; - // engine.addEventListener('beforeRender', function () { - // o.getWorldPosition(v); - // var dst = engine.cameraWorld.position.distanceTo(v); - // if (dst <= dstm && !o.visible) { - // o.visible = true; - // }else if (dst > dstm && o.visible){ - // o.visible = false; - // } - // }); } + this.object.userData._io = this; resolve(this); }); } + dispose(){ + super.dispose(); + this.io?.dispose?.(); + } } class VisibilityActivator{ diff --git a/src/components/InteractiveObjects/PairMatchingGame.js b/src/components/InteractiveObjects/PairMatchingGame.js index a0c91dd..1d9b94c 100644 --- a/src/components/InteractiveObjects/PairMatchingGame.js +++ b/src/components/InteractiveObjects/PairMatchingGame.js @@ -1,6 +1,6 @@ import { BoxGeometry, Mesh, MeshStandardMaterial, Group, Vector3, CatmullRomCurve3, Color } from 'three'; import { LineMaterial, LineGeometry, Line2 } from 'three/examples/jsm/Addons.js'; -import { centerOrigin } from '@/lib/MeshUtils'; +import { centerOrigin, clearMaterial } from '@/lib/MeshUtils'; import { EventManager } from '@/lib/EventManager'; import Utils from '#/app/Utils'; @@ -111,6 +111,12 @@ class PairMatchingGame extends EventManager { this.object = centerOrigin(container); + this.dispose = () => { + clearMaterial(material); + bm.dispose(); + super.dispose(); + } + resolve(this); }) } diff --git a/src/components/InteractiveObjects/Particles.js b/src/components/InteractiveObjects/Particles.js index e8384a3..3c9939d 100644 --- a/src/components/InteractiveObjects/Particles.js +++ b/src/components/InteractiveObjects/Particles.js @@ -36,7 +36,7 @@ class Particles { gg.translate(position.x, position.y, position.z); arr.push(gg); - var gg = geometry.clone(); + gg = geometry.clone(); gg.rotateY(angle + Math.PI); gg.translate(position.x, position.y, position.z); arr.push(gg); diff --git a/src/components/InteractiveObjects/PuzzleGame1.js b/src/components/InteractiveObjects/PuzzleGame1.js index cdd5c18..ed39f7e 100644 --- a/src/components/InteractiveObjects/PuzzleGame1.js +++ b/src/components/InteractiveObjects/PuzzleGame1.js @@ -1,5 +1,5 @@ import { BoxGeometry, Mesh, MeshBasicMaterial, Group, VideoTexture } from 'three'; -import { centerOrigin } from '@/lib/MeshUtils'; +import { centerOrigin, clearMaterial } from '@/lib/MeshUtils'; import { EventManager } from '@/lib/EventManager'; class PuzzleGame1 extends EventManager { @@ -24,7 +24,7 @@ class PuzzleGame1 extends EventManager { vi.addEventListener('loadedmetadata', async ()=>{ map = new VideoTexture( vi ); resolve(); - }); + }, { once: true }); }) }else{ map = await engine.loadTexture(data.$go.asset.name); @@ -73,9 +73,9 @@ class PuzzleGame1 extends EventManager { container.add(mesh); } - container.children[0].onBeforeRender = () => { - this.update(); - }; + // container.children[0].onBeforeRender = () => { + // this.update(); + // }; var check = () => { let i = 0; @@ -121,8 +121,17 @@ class PuzzleGame1 extends EventManager { } }; + engine.addEventListener('beforeRender', this.update) + this.object = centerOrigin(container); + this.dispose = () => { + //console.log('disposing PG1') + clearMaterial(material); + bm.dispose(); + super.dispose(); + } + resolve(this); }) } diff --git a/src/components/InteractiveObjects/PuzzleGame2.js b/src/components/InteractiveObjects/PuzzleGame2.js index 4acfe66..aa0ba54 100644 --- a/src/components/InteractiveObjects/PuzzleGame2.js +++ b/src/components/InteractiveObjects/PuzzleGame2.js @@ -17,7 +17,7 @@ class PuzzleGame2 extends EventManager { vi.addEventListener('loadedmetadata', async ()=>{ map = new VideoTexture( vi ); resolve(); - }); + }, { once: true }); }) }else{ map = await engine.loadTexture(data.$go.asset.name); @@ -109,9 +109,9 @@ class PuzzleGame2 extends EventManager { } last = container.children[lidx]; - container.children[0].onBeforeRender = () => { - this.update(); - }; + // container.children[0].onBeforeRender = () => { + // this.update(); + // }; this.shuffle(); @@ -164,6 +164,8 @@ class PuzzleGame2 extends EventManager { //engine.dashboard.addPoints(10); } }; + engine.addEventListener('beforeRender', this.update) + this.object = centerOrigin(container) resolve(this) }); diff --git a/src/components/InteractiveObjects/PuzzleGame4.js b/src/components/InteractiveObjects/PuzzleGame4.js index 3f8457f..48db7e3 100644 --- a/src/components/InteractiveObjects/PuzzleGame4.js +++ b/src/components/InteractiveObjects/PuzzleGame4.js @@ -45,9 +45,10 @@ var PuzzleGame4 = function(context, gltf, w, h){ context.clickable.add(c, clickFn); }) - this.object.children[0].onBeforeRender = ()=>{ - this.update(); - } + // this.object.children[0].onBeforeRender = ()=>{ + // this.update(); + // } + engine.addEventListener('beforeRender', this.update) }); var check = ()=>{ diff --git a/src/components/InteractiveObjects/VideoPlayer.js b/src/components/InteractiveObjects/VideoPlayer.js index cc9106b..f073199 100644 --- a/src/components/InteractiveObjects/VideoPlayer.js +++ b/src/components/InteractiveObjects/VideoPlayer.js @@ -77,7 +77,7 @@ class VideoPlayer extends EventManager { } resolve(this); - }) + }, { once: true }) vi.src = engine.assetPath + data.$go.asset.name; }) } diff --git a/src/lib/Api.js b/src/lib/Api.js new file mode 100644 index 0000000..d28f2cd --- /dev/null +++ b/src/lib/Api.js @@ -0,0 +1,92 @@ +import axios from 'axios'; +import Utils from '#/app/Utils'; + +const $ax = axios.create({ + baseURL: '/api/', + transformRequest: [ + (data, headers)=>{ + if (data && !(data instanceof FormData)){ + data = Utils.deepMerge({}, data, (k, v)=>{ + return k.startsWith('__') ? undefined : v; + }) + data = JSON.stringify(data); + headers['Content-Type'] = 'application/json;charset=utf-8'; + } + return data; + } + , ...axios.defaults.transformRequest + ] +}) + +const api = { + gameObject:{ + async save(data){ + return await $ax.put('/game-object', data); + }, + async load(id){ + return await $ax.get(`/game-object/${id}`); + }, + async search(query){ + return await $ax.post('/game-object', query); + }, + async remove(id){ + return await $ax.delete(`/game-object/${id}`) + }, + async getTags(q){ + return await $ax.post('/game-object/tags', {q}); + } + }, + scenario:{ + async save(data){ + return await $ax.put('/scenario', data); + }, + async load(id){ + return await $ax.get(`/scenario/${id}`); + }, + async search(query){ + return await $ax.post('/scenario', query); + }, + async remove(id){ + return await $ax.delete(`/scenario/${id}`) + } + }, + game:{ + async save(data){ + return await $ax.put('/game', data); + }, + async load(id){ + return await $ax.get(`/game/${id}`); + }, + async search(query){ + return await $ax.post('/game', query); + }, + async remove(id){ + return await $ax.delete(`/game/${id}`) + }, + async setHeader(id, data){ + return await $ax.post(`/game/${id}/header`, data); + } + }, + user:{ + async tm(action, object, data){ + return await $ax.post('/user/tm', {action, object, data}); + }, + async signin(data){ + return await $ax.post('/user/signin', data); + }, + async signup(data){ + return await $ax.post('/user/signup', data); + }, + async signout(){ + return await $ax.get('/user/signout'); + }, + async load(){ + return await $ax.get('/user/info'); + }, + async update(data){ + return await $ax.post('/user/update', data); + } + } +} + +export { api }; \ No newline at end of file diff --git a/src/lib/EventManager.js b/src/lib/EventManager.js index 20cc1b3..86c169b 100644 --- a/src/lib/EventManager.js +++ b/src/lib/EventManager.js @@ -16,6 +16,12 @@ class EventManager extends EventDispatcher{ removeAllListenersOfType(type){ this._listeners?.[ type ]?.splice(0, this._listeners[ type ].length) } + removeAllListeners(){ + this._listeners && Object.keys(this._listeners).forEach(k=>this.removeAllListenersOfType(k)); + } + dispose(){ + this.removeAllListeners(); + } } export { EventManager } \ No newline at end of file diff --git a/src/lib/GameEngine.js b/src/lib/GameEngine.js index fbf3d09..79758f3 100644 --- a/src/lib/GameEngine.js +++ b/src/lib/GameEngine.js @@ -20,6 +20,7 @@ import { MotionEngine } from './MotionEngine.js'; import { Draggable } from './Draggable.js'; import { EventManager } from "./EventManager"; import { Telemetry } from './Telemetry.js'; +import { clearObject } from './MeshUtils.js'; THREE.Cache.enabled = true @@ -181,10 +182,7 @@ class GameEngine extends EventManager{ this.motionQueue = new MotionEngine(); this.assetPath = assetPath; - - if (opts.telemetry){ - this.tm = new Telemetry(opts.telemetry, opts.mode); - } + this.tm = new Telemetry(opts.mode); // controls.enableDamping = true; // controls.screenSpacePanning = true; @@ -662,44 +660,8 @@ class GameEngine extends EventManager{ ]) } - clearObject(o){ - let disposables = [] - o.traverse(object => { - if (object.isMesh) { - disposables.push(object); - } - if (object.isLight){ - object.shadow?.map?.dispose(); - object.shadow?.dispose(); - object.dispose(); - } - }); - disposables.forEach(object=>{ - object.removeFromParent(); - object.geometry.dispose(); - if (object.material.isMaterial) { - this.clearMaterial(object.material) - } else { - for (const material of object.material) this.clearMaterial(material) - } - }) - } - - clearMaterial(material) { - material.dispose(); - for (const key of Object.keys(material)) { - const value = material[key] - if (value && typeof value == 'object' && 'minFilter' in value) { - //console.log('Disposing', value.name, this.renderer.info.memory.textures ); - value.dispose(); - //console.log('Disposed', value.name, this.renderer.info.memory.textures ); - } - } - } - clearScene(){ this.hero?.dispose(); - this.dashboard?.reset(); this.transformControls?.dispose(); this.pointerControls.dispose(); //this.activeObjects.clear(); @@ -709,7 +671,7 @@ class GameEngine extends EventManager{ this.gizmo?.dispose(); this.motionQueue.clearAll(); this.ambientSound.stop(); - this.loadedObjects.forEach(o=>this.clearObject(o.scene)) + this.loadedObjects.forEach(o=>clearObject(o.scene)) GameEngine.loadedTextures.forEach(t=>{ //console.log('Disposing', t.name, this.renderer.info.memory.textures ); t.dispose(); @@ -717,7 +679,7 @@ class GameEngine extends EventManager{ }); this.scene.background?.dispose?.(); this.scene.environment?.dispose?.(); - this.clearObject(this.scene); + clearObject(this.scene); this.scene = null; this.tm?.setScene(null); this.removeAllListenersOfType('beforeRender'); @@ -726,6 +688,7 @@ class GameEngine extends EventManager{ } 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(); } @@ -741,14 +704,15 @@ class GameEngine extends EventManager{ this.stats?.dom?.remove(); 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 gltfLoader = new GLTFLoader().setDRACOLoader(this.draco); static ktxLoader = new KTX2Loader().setTranscoderPath( '/3rdparty/basis/' ); + static gltfLoader = new GLTFLoader().setDRACOLoader(this.draco).setKTX2Loader(this.ktxLoader); static loadedTextures = [] diff --git a/src/lib/GameManager.js b/src/lib/GameManager.js new file mode 100644 index 0000000..0d7780c --- /dev/null +++ b/src/lib/GameManager.js @@ -0,0 +1,186 @@ +import { InteractiveObject } from "@/components/InteractiveObjects/InteractiveObject"; +import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer'; +import { Hero } from "./Hero"; +import { getBoundingBox, getBoundingBoxSize, autoScale } from "./MeshUtils"; +import { api } from "./Api"; + +class GameManager{ + constructor(engine, gameData, scenarioData, opts = {}){ + return new Promise(async (resolve, reject)=>{ + if (typeof gameData != 'object'){ + gameData = (await api.game.load(gameData)).data; + } + if (!scenarioData){ + scenarioData = (await api.scenario.load(gameData.scenario)).data; + } + this.gameData = gameData; + this.scenarioData = scenarioData; + this.opts = opts; + + this.loadScene = async function(sceneId){ + let scene = scenarioData.scenes.find(sc=>sc.data.id == sceneId); + let sceneObjects = gameData.scenes[sceneId].objects || {}; + engine.tm?.setGame(gameData.id); + let intro; + await engine.resetScene(); + engine.activeObjects.visible = false; + await engine.dashboard?.ready; + await this.expandScenarioData(scene); + engine.dashboard?.initScene(scene, async ()=>{ + if (scene.data.$audio){ + await engine.playAmbientSound(scene.data.$audio.asset.name); + engine.ambientSound.setVolume( 0.5 ); + } + engine.tm?.setScene(scene.data.id); + if (intro){ + intro.play(); + }else{ + engine.activeObjects.visible = true; + } + }); + engine.dashboard?.loading(0.05); + + //this is needed cause when mounted canvas has different size + //this.resize(); + if (scene.data.$environment){ + await engine.loadPanorama(scene.data.$environment.asset.name); + } + if (scene.data.$scene){ + let env = await engine.load(scene.data.$scene.asset.name); + this.setObjectAttributes(sceneObjects, scene.data, env.scene, env, null); + if (engine.opts.mode != 'GameDesigner'){ + env.scene.traverse(o=>{ + if (o.name.startsWith('land') || o.name == 'Sphere'){ + //this.debug('Fixing ground. TODO!!!') + engine.physics.add(o, 'fixed', true, undefined, 'mesh', { root: env.scene}) + } + }) + } + engine.activeObjects.add(env.scene); + } + let expectToFinish = 0, finished = 0, iobjs = []; + if (scene.data.items){ + let loaded = 0; + for (let i of scene.data.items) { + //this.debug('Loading', i.data.id); + if (engine.opts.mode != 'GameDesigner'){ + if (i.data.activationTriggers?.length || i.data.activationScore){ + i.data.shouldBeLocked = true; + } + } + let io = await new InteractiveObject(engine, i.data) + iobjs.push({io, data: i.data}) + //i.__io = io; + if (io.emits?.includes('finish')){ + expectToFinish++; + } + this.setObjectAttributes(sceneObjects, i.data, io.object, io.source, 1); + engine.activeObjects.add(io.object); + if (engine.opts.mode != 'GameDesigner'){ + if (i.data.$go?.type == 'player3d'){ + let hero = new Hero(engine, io); + }else{ + if (io.source?.animations?.length){ + engine.playAnimation(engine.scene, io.source.animations[0]); + } + + if (!i.data.noPhysics){ + let bb = getBoundingBox(io.object); + let bbs = getBoundingBoxSize(bb); + engine.physics.add(io.object, 'fixed', false, undefined, 'capsule', { + radius: Math.max(bbs.x, bbs.z)/2, halfHeight: bbs.y/2 + }) + } + } + io.addEventListener('finish', ()=>{ + finished ++; + if (!i.data.pointsGiven){ + engine.dashboard?.addPoints(i.data.points) + } + i.data.pointsGiven = true; + iobjs.forEach(di=>{ + if (di.data.activationTriggers?.includes(i.data.id)){ + let idx = di.data.activationTriggers.indexOf(i.data.id) + di.data.activationTriggers.splice(idx, 1); + } + if (!di.data.activationTriggers?.length && di.io.object.__active === false && + engine.dashboard.points > di.data.activationScore){ + di.io.activator.activate(); + } + }); + // if (finished == expectToFinish){ + // //GO TO NEXT LEVEL + // this.debug('LEVEL FINISHED') + // } + engine.tm?.setGameObject(null); + }); + io.addEventListener('sceneSwitch', (e)=>{ + //this.scenesList = [this.scenes.find(s=>s.data.id == e.scene)] + //switch to next scene: + this.loadScene(e.scene); + iobjs.forEach(di=>di.io.dispose?.()); + }) + } + loaded += 1/scene.data.items.length + engine.dashboard?.loading(0.1 + 0.89*loaded); + } + } + + if (engine.opts.mode == 'GameDesigner'){ + engine.activeObjects.visible = true; + }else if (scene.data.$intro){ + intro = await new VideoPlayer(engine, { + $go: scene.data.$intro, + skipTransition: true, + playInHud: true, + immersive: true + }); + engine.activeObjects.add(intro.object); + intro.video.addEventListener('pause',()=>{ + intro.object.removeFromParent(); + engine.clickable.remove(intro.object); //TODO!!!! + engine.activeObjects.visible = true; + }); + } + + engine.dashboard?.loading(1) + engine.physics.start(); + //this.debug('Scene loaded', engine.renderer.info.memory) + } + resolve(this); + }) + } + + async expandScenarioData(scene){ + if (scene.expanded) return; + const promises = []; + ['environment', 'scene', 'intro', 'audio'].filter(e=>scene.data[e]).forEach(e=>{ + promises.push(api.gameObject.load(scene.data[e]).then(r=>scene.data['$'+e] = r.data)) + }) + + for (let i of scene.data.items || []) { + Object.keys(i.data).filter(k=>k == 'go' || k.startsWith('go_')).forEach(k=>{ + promises.push(api.gameObject.load(i.data[k]).then(r=>i.data['$'+k] = r.data)); + }) + } + await Promise.all(promises); + scene.expanded = true; + } + + setObjectAttributes(sceneObjects, data, object3d, source, autoScaleFactor = 1){ + if (sceneObjects[data.id]){ + ['position', 'scale', 'rotation'].forEach(p=>{ + object3d[p].copy(sceneObjects[data.id][p]) + }) + }else if (!data.type || data.type == 'GenericObject'){ + autoScale(object3d, autoScaleFactor); + } + sceneObjects[data.id] = sceneObjects[data.id] || {}; + if (this.opts.onObjectLoad){ + this.opts.onObjectLoad(sceneObjects, data, object3d, source) + } + object3d.__pn_id = data.id; + } +} + +export{ GameManager } \ No newline at end of file diff --git a/src/lib/MeshUtils.js b/src/lib/MeshUtils.js index 6221ff3..cb28620 100644 --- a/src/lib/MeshUtils.js +++ b/src/lib/MeshUtils.js @@ -86,7 +86,46 @@ function bottomOrigin(object){ return group; } +function clearObject(o){ + let disposables = [] + o.traverse(object => { + if (object.isMesh) { + disposables.push(object); + } + if (object.isLight){ + object.shadow?.map?.dispose(); + object.shadow?.dispose(); + object.dispose(); + } + if (object.userData?._io){ + object.userData._io.dispose?.(); + } + }); + disposables.forEach(object=>{ + object.removeFromParent(); + object.geometry.dispose(); + if (object.material.isMaterial) { + clearMaterial(object.material) + } else { + for (const material of object.material) clearMaterial(material) + } + }) +} + +function clearMaterial(material) { + material.dispose(); + for (const key of Object.keys(material)) { + const value = material[key] + if (value && typeof value == 'object' && 'minFilter' in value) { + //console.log('Disposing', value.name, this.renderer.info.memory.textures ); + value.dispose(); + //console.log('Disposed', value.name, this.renderer.info.memory.textures ); + } + } +} + export { assignParams, assignMaterial, autoScale, centerOrigin, wrapInGroup, bottomOrigin, - getBoundingBox, getBoundingBoxSize, getBoundingBoxMaxLength, getBoundingBoxCenterPoint + getBoundingBox, getBoundingBoxSize, getBoundingBoxMaxLength, getBoundingBoxCenterPoint, + clearObject, clearMaterial } \ No newline at end of file diff --git a/src/lib/Physics.js b/src/lib/Physics.js index 0691e90..58eb350 100644 --- a/src/lib/Physics.js +++ b/src/lib/Physics.js @@ -103,7 +103,7 @@ class Physics{ if (colliderSettings.root){ collider.setTranslationWrtParent(colliderSettings.root.position) collider.setRotationWrtParent(colliderSettings.root.quaternion) - console.log(colliderSettings.root.position, mesh.position) + //console.log(colliderSettings.root.position, mesh.position) } const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate } this.physicsObjects.push(physicsObject) diff --git a/src/lib/Telemetry.js b/src/lib/Telemetry.js index d160cfd..90830a9 100644 --- a/src/lib/Telemetry.js +++ b/src/lib/Telemetry.js @@ -1,12 +1,13 @@ +import { api } from "./Api"; class Telemetry { game = null; scene = null; gameObject = null; #af = null; - constructor(apiFunction, mode){ + constructor(mode){ this.mode = mode; - this.#af = apiFunction; + this.#af = api.user.tm; } setGame(game){ if (this.game == game) { return; } diff --git a/src/mixins/GameEnvironmentMixin.js b/src/mixins/GameEnvironmentMixin.js deleted file mode 100644 index 7bdefec..0000000 --- a/src/mixins/GameEnvironmentMixin.js +++ /dev/null @@ -1,320 +0,0 @@ -import { InteractiveObject } from '@/components/InteractiveObjects/InteractiveObject'; -import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer'; -import { GameEngine } from '@/lib/GameEngine'; -import { Hero } from '@/lib/Hero'; -import { autoScale, getBoundingBox, getBoundingBoxSize } from '@/lib/MeshUtils'; -let engine = null; - -export default { - - async mounted(){ - engine = new GameEngine(); - await engine.init(this.$refs.target, { - xr: true, - gizmo: this.env == 'GameDesigner', - stats: this.env != 'GamePlay', - designMode: this.env == 'GameDesigner', - depthSense: this.env == 'GameDesigner' ? false : this.store.prefs.xr.depthSense, - telemetry: this.$api.user.tm, - mode: this.env - }); - //engine.scene.add(new engine.$.GridHelper(100,100)); - this.resize(); - - if (!this.scenario) { - await this.loadScenario(); - } - window.addEventListener('resize', this.resize); - }, - - async unmounted(){ - this.debug('Disposing scene') - window.removeEventListener('resize', this.resize); - engine.tm?.setGame(null); - engine.dispose(); - this.debug('Disposed scene', engine.renderer.info.memory); - engine = null; - }, - - computed:{ - scenes(){ - return this.scenario?.scenes || []; - }, - scene(){ - return this.scenesList[0]; - }, - currentObject(){ - return this.objectsList[0]; - }, - sceneObjects(){ - return this.object.scenes?.[this.scene?.data?.id]?.objects; - }, - object(){ - return this.modelValue; - }, - objectAnimations(){ - return this.currentObject?.__g?.animations?.map(a => ({ - name: a.name, id: a.uuid, a - })); - } - }, - - watch:{ - async scene(n){ - this.object.scenes = this.object.scenes || {} - this.object.scenes[n.data.id] = this.object.scenes[n.data.id] || {} - await this.loadEnvironment(n, this.object.scenes[n.data.id]); - }, - mode(n){ - engine.transformControls.setMode(n) - }, - currentObject(n){ - if (this.env == 'GameDesigner'){ - engine.transformControls.attach(n.__o); - engine.gizmo.target = n.__o.position; - engine.camera.updateProjectionMatrix() - } - }, - renderType(v){ - engine.renderType = v; - }, - cameraType(v){ - if (v == 'perspective'){ - engine.setCameraPerspective(); - }else{ - engine.setCameraOrthographic(); - } - } - }, - - methods:{ - async loadScenario(){ - if (this.object.scenario){ - this.scenario = (await this.$api.scenario.load(this.object.scenario)).data; - }else{ - this.scenario = null; - } - }, - - async expandScenarioData(scene){ - const promises = []; - ['environment', 'scene', 'intro', 'audio'].filter(e=>scene.data[e]).forEach(e=>{ - promises.push(this.$api.gameObject.load(scene.data[e]).then(r=>scene.data['$'+e] = r.data)) - }) - - for (let i of scene.data.items || []) { - Object.keys(i.data).filter(k=>k == 'go' || k.startsWith('go_')).forEach(k=>{ - promises.push(this.$api.gameObject.load(i.data[k]).then(r=>i.data['$'+k] = r.data)); - }) - } - await Promise.all(promises); - }, - - /** - * loads all environment objects - * @param scene Scene object from the Scenario Module - * @param target Target scene definition from Game Module - */ - async loadEnvironment(scene, target){ - this.debug('loading environment'); - engine.tm?.setGame(this.object?.id); - //await engine.loadPanorama(`/asset/default/43.webp`); - let intro; - await engine.resetScene(); - engine.activeObjects.visible = false; - await engine.dashboard?.ready; - engine.dashboard?.initScene(scene, async ()=>{ - if (this.scene.data.$audio){ - await engine.playAmbientSound(this.scene.data.$audio.asset.name); - engine.ambientSound.setVolume( 0.5 ); - } - engine.tm?.setScene(scene.data.id); - if (intro){ - intro.play(); - }else{ - engine.activeObjects.visible = true; - } - }); - await this.expandScenarioData(scene); - engine.dashboard?.loading(0.05); - - //engine.orbitControls.enableRotate = this.env == 'GameDesigner' - - //this is needed cause when mounted canvas has different size - this.resize(); - target.objects = target.objects || {}; - let l = target.objects; - if (this.scene.data.$environment){ - this.debug('loading panorama', this.scene.data.$environment.asset.name) - await engine.loadPanorama(this.scene.data.$environment.asset.name); - } - if (this.scene.data.$scene){ - let env = await engine.load(this.scene.data.$scene.asset.name); - this.setObjectAttributes(l, this.scene.data, env.scene, env, null); - if (this.env != 'GameDesigner'){ - env.scene.traverse(o=>{ - if (o.name.startsWith('land') || o.name == 'Sphere'){ - this.debug('Fixing ground. TODO!!!') - engine.physics.add(o, 'fixed', true, undefined, 'mesh', { root: env.scene}) - } - }) - } - engine.activeObjects.add(env.scene); - } - let expectToFinish = 0, finished = 0;; - if (this.scene.data.items){ - let loaded = 0; - for (let i of this.scene.data.items) { - this.debug('Loading', i.data.id); - if (this.env != 'GameDesigner'){ - if (i.data.activationTriggers?.length || i.data.activationScore){ - i.data.shouldBeLocked = true; - } - } - let io = await new InteractiveObject(engine, i.data) - i.__io = io; - if (io.emits?.includes('finish')){ - expectToFinish++; - } - this.setObjectAttributes(l, i.data, io.object, io.source, 1); - engine.activeObjects.add(io.object); - if (this.env != 'GameDesigner'){ - if (i.data.$go?.type == 'player3d'){ - let hero = new Hero(engine, io); - }else{ - if (io.source?.animations?.length){ - engine.playAnimation(engine.scene, io.source.animations[0]); - } - - if (!i.data.noPhysics){ - let bb = getBoundingBox(io.object); - let bbs = getBoundingBoxSize(bb); - engine.physics.add(io.object, 'fixed', false, undefined, 'capsule', { - radius: Math.max(bbs.x, bbs.z)/2, halfHeight: bbs.y/2 - }) - } - } - io.addEventListener('finish', ()=>{ - finished ++; - if (!i.data.pointsGiven){ - engine.dashboard?.addPoints(i.data.points) - } - i.data.pointsGiven = true; - this.scene.data.items.forEach(di=>{ - if (di.data.activationTriggers?.includes(i.data.id)){ - let idx = di.data.activationTriggers.indexOf(i.data.id) - di.data.activationTriggers.splice(idx, 1); - } - if (!di.data.activationTriggers?.length && di.__io.object.__active === false && - engine.dashboard.points > di.data.activationScore){ - di.__io.activator.activate(); - } - }); - if (finished == expectToFinish){ - //GO TO NEXT LEVEL - this.debug('LEVEL FINISHED') - } - engine.tm?.setGameObject(null); - }); - io.addEventListener('sceneSwitch', (e)=>{ - this.scenesList = [this.scenes.find(s=>s.data.id == e.scene)] - }) - } - loaded += 1/this.scene.data.items.length - engine.dashboard?.loading(0.1 + 0.89*loaded); - } - } - - if (this.env == 'GameDesigner'){ - engine.activeObjects.visible = true; - }else if (this.scene.data.$intro){ - intro = await new VideoPlayer(engine, { - $go: this.scene.data.$intro, - skipTransition: true, - playInHud: true, - immersive: true - }); - engine.activeObjects.add(intro.object); - intro.video.addEventListener('pause',()=>{ - intro.object.removeFromParent(); - engine.clickable.remove(intro.object); //TODO!!!! - engine.activeObjects.visible = true; - }); - } - - engine.dashboard?.loading(1) - engine.physics.start(); - this.debug('Scene loaded', engine.renderer.info.memory) - }, - - targetPointerDown(){ - this.pointerDownTime = performance.now(); - }, - targetClick(e){ - if (this.env == 'GameDesigner'){ - if (performance.now() - this.pointerDownTime < 200){ - let intersects = engine.intersect(e, this.$refs.target, engine.activeObjects.children, true); - //console.log(intersects) - if (intersects.length){ - //console.log('attaching controls to', intersects[0].object) - //engine.transformControls.attach(intersects[0].object); - //console.log(this.sceneObjects[intersects[0].object.__pn_id]) - this.objectsList[0] = this.sceneObjects[intersects[0].object.__pn_id] - }else{ - engine.transformControls.detach(); - } - } - }else{ - engine.onClick(e, this.$refs.target); - } - }, - - targetPointer(e, t){ - engine.onPointer(e, this.$refs.target, t); - }, - - setObjectAttributes(l, data, object, source, autoScaleFactor = 1){ - if (l[data.id]){ - ['position', 'scale', 'rotation'].forEach(p=>{ - object[p].copy(l[data.id][p]) - }) - }else if (!data.type || data.type == 'GenericObject'){ - autoScale(object, autoScaleFactor); - } - l[data.id] = l[data.id] || {}; - ['position', 'scale', 'rotation', 'visible'].forEach(p=>{ - l[data.id][p] = object[p]; - }) - l[data.id].__o = object; - l[data.id].__g = source; - l[data.id].__title = data.title; - object.__pn_id = data.id; - }, - - async toggleAnimation(animation){ - animation.playing = !animation.playing; - engine.playAnimation(engine.scene, animation.a, animation.playing); - }, - resize(){ - let r = this.$refs.target; - this.debug('resizing!!', r.clientWidth, r.clientHeight, r) - engine.resize(r.clientWidth, r.clientHeight); - //this.zoom = Math.min(r.clientWidth / this.viewBox.w, r.clientHeight / this.viewBox.h); - }, - - control(){ - engine.pointerControls.lock(true); - }, - - async fullScreen(){ - await engine.renderer.domElement.requestFullscreen() - }, - - async setGameHeader(){ - let screenshot = await engine.captureScreenshot(); - let fd = new FormData(); - fd.append('file', screenshot); - await this.$api.game.setHeader(this.modelValue.id, fd); - } - } -} \ No newline at end of file diff --git a/src/pages/manage/game-objects/[[id]].vue b/src/pages/manage/game-objects/[[id]].vue index 1cf9c68..3a1fd68 100644 --- a/src/pages/manage/game-objects/[[id]].vue +++ b/src/pages/manage/game-objects/[[id]].vue @@ -118,7 +118,7 @@ export default { }, async captureThumbnail() { - this.object.thumb = await this.$refs.assetPreview.gameEngine.captureScreenshot(); + this.object.thumb = await this.$refs.assetPreview.captureScreenshot(); await this.save({ thumbOnly: true }); }, diff --git a/src/pages/manage/games/[[id]].vue b/src/pages/manage/games/[[id]].vue index 5b6d96f..37e0739 100644 --- a/src/pages/manage/games/[[id]].vue +++ b/src/pages/manage/games/[[id]].vue @@ -4,7 +4,7 @@ {{ id == 'add' ? l.createGame : l.editGame }} - + {{ l.gameDesigner }} @@ -13,7 +13,7 @@ - + @@ -62,9 +62,8 @@ export default { async save(params) { this.loading = true; try { - console.log('saving', this.object) + this.debug('saving', this.object) let result = await this.$api.game.save(this.object); - //Object.assign(this.object, result.data.object); this.object.id = result.data.object.id; if (this.id == 'add') { this.$router.replace({ params: { id: this.object.id } }); diff --git a/src/pages/manage/preview/[[id]].vue b/src/pages/manage/preview/[[id]].vue index 5fa458e..9c1d8b0 100644 --- a/src/pages/manage/preview/[[id]].vue +++ b/src/pages/manage/preview/[[id]].vue @@ -1,47 +1,15 @@ diff --git a/src/pages/playground/game/[[id]].vue b/src/pages/playground/game/[[id]].vue index 5873966..29e8346 100644 --- a/src/pages/playground/game/[[id]].vue +++ b/src/pages/playground/game/[[id]].vue @@ -1,35 +1,10 @@ \ No newline at end of file diff --git a/src/plugins/api.js b/src/plugins/api.js index 15996a5..ebf3bc9 100644 --- a/src/plugins/api.js +++ b/src/plugins/api.js @@ -1,94 +1,7 @@ -import axios from 'axios'; -import Utils from '#/app/Utils'; - -const $ax = axios.create({ - baseURL: '/api/', - transformRequest: [ - (data, headers)=>{ - if (data && !(data instanceof FormData)){ - data = Utils.deepMerge({}, data, (k, v)=>{ - return k.startsWith('__') ? undefined : v; - }) - data = JSON.stringify(data); - headers['Content-Type'] = 'application/json;charset=utf-8'; - } - return data; - } - , ...axios.defaults.transformRequest - ] -}) +import { api } from "@/lib/Api"; export default { install: (app, options) => { - app.config.globalProperties.$api = { - gameObject:{ - async save(data){ - return await $ax.put('/game-object', data); - }, - async load(id){ - return await $ax.get(`/game-object/${id}`); - }, - async search(query){ - return await $ax.post('/game-object', query); - }, - async remove(id){ - return await $ax.delete(`/game-object/${id}`) - }, - async getTags(q){ - return await $ax.post('/game-object/tags', {q}); - } - }, - scenario:{ - async save(data){ - return await $ax.put('/scenario', data); - }, - async load(id){ - return await $ax.get(`/scenario/${id}`); - }, - async search(query){ - return await $ax.post('/scenario', query); - }, - async remove(id){ - return await $ax.delete(`/scenario/${id}`) - } - }, - game:{ - async save(data){ - return await $ax.put('/game', data); - }, - async load(id){ - return await $ax.get(`/game/${id}`); - }, - async search(query){ - return await $ax.post('/game', query); - }, - async remove(id){ - return await $ax.delete(`/game/${id}`) - }, - async setHeader(id, data){ - return await $ax.post(`/game/${id}/header`, data); - } - }, - user:{ - async tm(action, object, data){ - return await $ax.post('/user/tm', {action, object, data}); - }, - async signin(data){ - return await $ax.post('/user/signin', data); - }, - async signup(data){ - return await $ax.post('/user/signup', data); - }, - async signout(){ - return await $ax.get('/user/signout'); - }, - async load(){ - return await $ax.get('/user/info'); - }, - async update(data){ - return await $ax.post('/user/update', data); - } - } - } + app.config.globalProperties.$api = api } } \ No newline at end of file