interactive objects parametrization

This commit is contained in:
2025-11-02 18:03:46 +02:00
parent 6253fc32d7
commit d8618c69f4
15 changed files with 411 additions and 474 deletions
@@ -2,6 +2,9 @@
<slot name="activator" v-bind="activatorProps" @click="dialog = !dialog"></slot> <slot name="activator" v-bind="activatorProps" @click="dialog = !dialog"></slot>
<v-dialog transition="dialog-bottom-transition" fullscreen v-model="dialog"> <v-dialog transition="dialog-bottom-transition" fullscreen v-model="dialog">
<v-card title="Assets"> <v-card title="Assets">
<v-container v-if="type?.includes('GameObject')">
<v-btn v-for="(v, i) in InteractiveObjectTypes" @click="select(v, 'InteractiveObject')">{{ v.id }}</v-btn>
</v-container>
<AssetBrowser :query="query" @select="select" :hideFilter="true"></AssetBrowser> <AssetBrowser :query="query" @select="select" :hideFilter="true"></AssetBrowser>
<v-card-actions> <v-card-actions>
<v-btn text="Close" color="primary" @click="dialog = false"></v-btn> <v-btn text="Close" color="primary" @click="dialog = false"></v-btn>
@@ -12,6 +15,8 @@
<script> <script>
import { InteractiveObjectTypes } from '../InteractiveObjects/InteractiveObject';
export default { export default {
props:[ props:[
'modelValue', 'type' 'modelValue', 'type'
@@ -19,6 +24,7 @@ export default {
emits:['select'], emits:['select'],
data(){ data(){
return { return {
InteractiveObjectTypes,
query: { query: {
type: { $in: this.$p.objectTypes.filter(t=>!this.type || this.type.includes(t.type)).map(t=>t.value) } type: { $in: this.$p.objectTypes.filter(t=>!this.type || this.type.includes(t.type)).map(t=>t.value) }
}, },
@@ -30,8 +36,8 @@ export default {
async created(){}, async created(){},
methods:{ methods:{
select(v){ select(v, type){
this.$emit('select', { id: v.id, name: v.name }); this.$emit('select', { id: v.id, name: v.name, type });
this.dialog = false; this.dialog = false;
} }
} }
+4 -160
View File
@@ -66,15 +66,18 @@
<script> <script>
import { GameEngine } from '@/lib/GameEngine'; import { GameEngine } from '@/lib/GameEngine';
import GameEnvironmentMixin from '@/mixins/GameEnvironmentMixin';
let gameEngine = null; let gameEngine = null;
export default { export default {
mixins: [GameEnvironmentMixin],
props:{ props:{
modelValue: Object, modelValue: Object,
}, },
data(){ data(){
return { return {
env: 'GameDesigner',
scenesList: [], scenesList: [],
objectsList: [], objectsList: [],
mode: 'translate', mode: 'translate',
@@ -84,168 +87,9 @@ export default {
cameraType: 'perspective' cameraType: 'perspective'
} }
}, },
computed:{
scenes(){
return this.scenario?.scenes || [];
},
scene(){
return this.scenesList[0];
},
currentObject(){
console.log('currentObject', this.objectsList[0]);
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){
gameEngine.transformControls.setMode(n)
},
async 'object.scenario'(n){
await this.loadScenario()
},
currentObject(n){
gameEngine.transformControls.attach(n.__o);
gameEngine.gizmo.target = n.__o.position;
//gameEngine.camera.lookAt(n.__o.position)
gameEngine.camera.updateProjectionMatrix()
},
renderType(v){
gameEngine.renderType = v;
},
cameraType(v){
if (v == 'perspective'){
gameEngine.setCameraPerspective();
}else{
gameEngine.setCameraOrthographic();
}
}
},
async mounted(){
gameEngine = new GameEngine();
//this.gameEngine = gameEngine;
await gameEngine.init(this.$refs.target, { xr: true, gizmo: true, designMode: true, depthSense: false });
gameEngine.scene.add(gameEngine.transformControls.getHelper());
//gameEngine.scene.add(new gameEngine.$.GridHelper(100,100));
this.resize();
//gameEngine.setCamera(gameEngine.orthographicCamera)
//gameEngine.setCameraOrthographic();
await this.loadScenario();
window.addEventListener('resize', this.resize);
},
unmounted(){
window.removeEventListener('resize', this.resize);
gameEngine.stop();
},
methods:{ methods:{
async loadScenario(){
if (this.object.scenario){
this.scenario = (await this.$api.scenario.load(this.object.scenario)).data;
}else{
this.scenario = null;
}
},
/**
* loads all environment objects
* @param scene Scene object from the Scenario Module
* @param target Target scene definition from Game Module
*/
async loadEnvironment(scene, target){
//await gameEngine.loadPanorama(`/asset/default/43.webp`);
gameEngine.activeObjects.clear();
await this.expandScenarioData(scene);
target.objects = target.objects || {};
let l = target.objects;
if (this.scene.data.$environment.type == 'panorama2d'){
await gameEngine.loadPanorama(`/asset/default/${this.scene.data.$environment.asset.name}`);
}else{
let env = await gameEngine.load(`/asset/default/${this.scene.data.$environment.asset.name}`);
this.setObjectAttributes(l, this.scene.data, env, 100);
gameEngine.activeObjects.add(env.scene);
}
for (let i of this.scene.data.items || []) {
let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
this.setObjectAttributes(l, i.data, gltf, 1);
gameEngine.activeObjects.add(gltf.scene);
//console.log(JSON.stringify(l));
//window.gameEngine = gameEngine;
//console.log(new gameEngine.$.Euler({"isEuler":true,"_x":0,"_y":0,"_z":0,"_order":"XYZ"}));
}
// let camera = new gameEngine.$.PerspectiveCamera();
// let cameraHelper = new gameEngine.$.CameraHelper(camera);
// gameEngine.activeObjects.add(cameraHelper);
// gameEngine.activeObjects.add(camera);
// this.setObjectAttributes(l, { id: 'camera', 'title': 'Main camera' }, { scene: camera })
// cameraHelper.update();
//this is needed cause when mounted canvas has different size
this.resize();
},
async expandScenarioData(scene){
scene.data.$environment = (await this.$api.gameObject.load(scene.data.environment)).data
for (let i of scene.data.items || []) {
i.data.$go = (await this.$api.gameObject.load(i.data.go)).data;
}
},
targetPointerDown(){
this.pointerDownTime = performance.now();
},
targetClick(e){
if (performance.now() - this.pointerDownTime < 200){
let intersects = gameEngine.intersect(e, this.$refs.target, gameEngine.activeObjects.children, true);
//console.log(intersects)
if (intersects.length){
//console.log('attaching controls to', intersects[0].object)
//gameEngine.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{
gameEngine.transformControls.detach();
}
}
},
setObjectAttributes(l, data, o, autoScaleFactor = 1){
if (l[data.id]){
['position', 'scale', 'rotation'].forEach(p=>{
o.scene[p].copy(l[data.id][p])
})
}else{
gameEngine.autoScale(o.scene, autoScaleFactor);
}
l[data.id] = l[data.id] || {};
['position', 'scale', 'rotation', 'visible'].forEach(p=>{
l[data.id][p] = o.scene[p];
})
l[data.id].__o = o.scene;
l[data.id].__g = o;
l[data.id].__title = data.title;
o.scene.__pn_id = data.id;
},
async toggleAnimation(animation){
animation.playing = !animation.playing;
gameEngine.playAnimation(gameEngine.scene, animation.a, animation.playing);
},
resize(){
let r = this.$refs.target;
gameEngine.resize(r.clientWidth, r.clientHeight);
//this.zoom = Math.min(r.clientWidth / this.viewBox.w, r.clientHeight / this.viewBox.h);
},
} }
} }
</script> </script>
+116 -240
View File
@@ -47,31 +47,32 @@
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
<video class="d-none" src="/asset/default/44.mp4" ref="videoPlayer"></video> <video class="d-none" src="/asset/default/57.mp4" ref="videoPlayer"></video>
</template> </template>
<script> <script>
import { GameEngine } from '@/lib/GameEngine'; import { GameEngine } from '@/lib/GameEngine';
import { Hero } from '@/lib/Hero'; import { Hero } from '@/lib/Hero';
import { Game1 } from '@/components/InteractiveObjects/PuzzleGame1';
import { Game2 } from '@/components/InteractiveObjects/PuzzleGame2';
import { Game4 } from '@/components/InteractiveObjects/PuzzleGame4';
import { Grass } from '@/components/InteractiveObjects/Grass'; import { Grass } from '@/components/InteractiveObjects/Grass';
import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer'; import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer';
import { useAppStore } from '@/stores/app'; import { useAppStore } from '@/stores/app';
import { MazeQuizGame } from '../InteractiveObjects/MazeQuizGame/MazeQuizGame'; import { MazeQuizGame } from '../InteractiveObjects/MazeQuizGame/MazeQuizGame';
import GameEnvironmentMixin from '@/mixins/GameEnvironmentMixin';
const store = useAppStore(); const store = useAppStore();
let gameEngine = null; let gameEngine = null;
export default { export default {
mixins:[GameEnvironmentMixin],
props:{ props:{
modelValue: Object, modelValue: Object,
}, },
data(){ data(){
return { return {
env: 'GamePlaying',
scenesList: [], scenesList: [],
objectsList: [], objectsList: [],
mode: 'translate', mode: 'translate',
@@ -82,251 +83,126 @@ export default {
store store
} }
}, },
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){
gameEngine.transformControls.setMode(n)
},
async 'object.scenario'(n){
await this.loadScenario()
},
currentObject(n){
// gameEngine.transformControls.attach(n.__o);
// gameEngine.gizmo.target = n.__o.position;
//gameEngine.camera.lookAt(n.__o.position)
gameEngine.camera.updateProjectionMatrix()
},
renderType(v){
gameEngine.renderType = v;
},
cameraType(v){
if (v == 'perspective'){
gameEngine.setCameraPerspective();
}else{
gameEngine.setCameraOrthographic();
}
}
},
async mounted(){
gameEngine = new GameEngine();
//this.gameEngine = gameEngine;
await gameEngine.init(this.$refs.target, { xr: true, depthSense: this.store.prefs.xr.depthSense});
gameEngine.scene.add(gameEngine.transformControls.getHelper());
//gameEngine.scene.add(new gameEngine.$.GridHelper(100,100));
this.resize();
//gameEngine.setCamera(gameEngine.orthographicCamera)
//gameEngine.setCameraOrthographic();
await this.loadScenario();
window.addEventListener('resize', this.resize);
},
unmounted(){
window.removeEventListener('resize', this.resize);
gameEngine.stop();
},
methods:{ methods:{
async loadScenario(){ // async loadEnvironment(scene, target){
if (this.object.scenario){ // //await gameEngine.loadPanorama(`/asset/default/55/panorama-vaya.webp`);
this.scenario = (await this.$api.scenario.load(this.object.scenario)).data; // await gameEngine.loadPanorama(`/asset/default/36.jpg`);
}else{ // await this.expandScenarioData(scene);
this.scenario = null; // //gameEngine.activeObjects.scale.set(0.033, 0.033, 0.033)
} // gameEngine.activeObjects.clear();
}, // target.objects = target.objects || {};
async loadEnvironment(scene, target){ // let l = target.objects;
//await gameEngine.loadPanorama(`/asset/default/55/panorama-vaya.webp`); // if (this.scene.data.$environment.type == 'panorama2d'){
await gameEngine.loadPanorama(`/asset/default/36.jpg`); // //await gameEngine.loadPanorama(`/asset/default/${this.scene.data.$environment.asset.name}`);
await this.expandScenarioData(scene); // }else{
//gameEngine.activeObjects.scale.set(0.033, 0.033, 0.033) // let env = await gameEngine.load(`/asset/default/${this.scene.data.$environment.asset.name}`);
gameEngine.activeObjects.clear(); // this.setObjectAttributes(l, this.scene.data, env, 100);
target.objects = target.objects || {}; // gameEngine.activeObjects.add(env.scene);
let l = target.objects; // gameEngine.scene.environmentRotation.y = Math.PI / 8;
if (this.scene.data.$environment.type == 'panorama2d'){ // // if (env.scene.children[0]?.material?.map){
//await gameEngine.loadPanorama(`/asset/default/${this.scene.data.$environment.asset.name}`); // // let emap = env.scene.children[0].material.map.clone()
}else{ // // emap = new gameEngine.$.Texture(emap.source.data, gameEngine.$.EquirectangularReflectionMapping)
let env = await gameEngine.load(`/asset/default/${this.scene.data.$environment.asset.name}`); // // emap.mapping = gameEngine.$.EquirectangularReflectionMapping;
this.setObjectAttributes(l, this.scene.data, env, 100); // // emap.needsUpdate = true;
gameEngine.activeObjects.add(env.scene); // // emap.updateMatrix()
gameEngine.scene.environmentRotation.y = Math.PI / 8; // // emap.repeat.set(-1,-1)
// if (env.scene.children[0]?.material?.map){ // // gameEngine.scene.environment = gameEngine.scene.background = emap
// let emap = env.scene.children[0].material.map.clone() // // console.log('env',emap)
// emap = new gameEngine.$.Texture(emap.source.data, gameEngine.$.EquirectangularReflectionMapping) // // gameEngine.scene.environmentRotation.y = Math.PI / 2;
// emap.mapping = gameEngine.$.EquirectangularReflectionMapping; // // }
// emap.needsUpdate = true; // }
// emap.updateMatrix() // for (let i of this.scene.data.items || []) {
// emap.repeat.set(-1,-1) // let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
// gameEngine.scene.environment = gameEngine.scene.background = emap // this.setObjectAttributes(l, i.data, gltf, 1);
// console.log('env',emap) // gameEngine.activeObjects.add(gltf.scene);
// gameEngine.scene.environmentRotation.y = Math.PI / 2; // if (i.data.$go.type == 'player3d'){
// } // let hero = new Hero(gltf, i.data.$go);
} // hero.init(gameEngine);
for (let i of this.scene.data.items || []) { // }else{
let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`); // if (gltf.animations?.length){
this.setObjectAttributes(l, i.data, gltf, 1); // gameEngine.playAnimation(gameEngine.scene, gltf.animations[0]);
gameEngine.activeObjects.add(gltf.scene); // }
if (i.data.$go.type == 'player3d'){ // }
let hero = new Hero(gltf, i.data.$go); // //console.log(JSON.stringify(l));
hero.init(gameEngine); // //window.gameEngine = gameEngine;
}else{ // //console.log(new gameEngine.$.Euler({"isEuler":true,"_x":0,"_y":0,"_z":0,"_order":"XYZ"}));
if (gltf.animations?.length){ // }
gameEngine.playAnimation(gameEngine.scene, gltf.animations[0]); // if (l.camera){
} // // gameEngine.camera.position.copy(l.camera.position)
} // // gameEngine.camera.rotation.copy(l.camera.position)
//console.log(JSON.stringify(l)); // // gameEngine.camera.scale.copy(l.camera.position)
//window.gameEngine = gameEngine; // }
//console.log(new gameEngine.$.Euler({"isEuler":true,"_x":0,"_y":0,"_z":0,"_order":"XYZ"}));
}
if (l.camera){
// gameEngine.camera.position.copy(l.camera.position)
// gameEngine.camera.rotation.copy(l.camera.position)
// gameEngine.camera.scale.copy(l.camera.position)
}
// let testGame1 = new Game1(gameEngine, '/static/textures/game1-test.jpg', 2, 3); // // let testGame1 = new Game1(gameEngine, '/static/textures/game1-test.jpg', 2, 3);
// gameEngine.activeObjects.add(testGame1.object); // // gameEngine.activeObjects.add(testGame1.object);
// testGame1.object.position.set(0, 1, -15); // // testGame1.object.position.set(0, 1, -15);
// let testGame2 = new Game2(gameEngine, '/static/textures/game2-test.jpg', 3, 3); // // let testGame2 = new Game2(gameEngine, '/static/textures/game2-test.jpg', 3, 3);
// gameEngine.activeObjects.add(testGame2.object); // // gameEngine.activeObjects.add(testGame2.object);
// testGame2.object.position.set(0, 1, 15); // // testGame2.object.position.set(0, 1, 15);
// testGame2.object.rotation.y += Math.PI; // // testGame2.object.rotation.y += Math.PI;
// let testGame4 = new Game4(gameEngine, '/static/feathers-game.glb', 3, 4); // // let testGame4 = new Game4(gameEngine, '/static/feathers-game.glb', 3, 4);
// gameEngine.activeObjects.add(testGame4.object); // // gameEngine.activeObjects.add(testGame4.object);
// testGame4.object.position.set(15, 1, 5); // // testGame4.object.position.set(15, 1, 5);
// let vp = new VideoPlayer(gameEngine, this.$refs.videoPlayer, 16, 9); // // let vp = new VideoPlayer(gameEngine, this.$refs.videoPlayer, 16, 9);
// gameEngine.activeObjects.add(vp.object); // // gameEngine.activeObjects.add(vp.object);
// vp.object.position.set(37, 5.5, 15); // // vp.object.position.set(37, 5.5, 15);
// vp.object.rotation.y += -Math.PI/2; // // vp.object.rotation.y += -Math.PI/2;
let maze = new MazeQuizGame(gameEngine, {}, [ // // let maze = new MazeQuizGame(gameEngine, {}, [
{ // // {
q: 'Атанасовското езеро е дълго около 10км.', // // q: 'Атанасовското езеро е дълго около 10км.',
a:['Вярно', 'Невярно'], // // a:['Вярно', 'Невярно'],
h: 'Грешен отговор. Атанасовското езеро е дълго около 10км.' // // h: 'Грешен отговор. Атанасовското езеро е дълго около 10км.'
}, // // },
{ // // {
q: 'Колко дълбоко е Атанасовското езеро?', // // q: 'Колко дълбоко е Атанасовското езеро?',
a:['Около 35см', 'Около 3.5м', 'Метър и половина'], // // a:['Около 35см', 'Около 3.5м', 'Метър и половина'],
h: 'Грешен отговор. Атанасовското езеро е дълбоко средно около 35см.' // // h: 'Грешен отговор. Атанасовското езеро е дълбоко средно около 35см.'
}, // // },
{ // // {
q: 'Колко вида птици се наблюдават в Атанасовското езеро?', // // q: 'Колко вида птици се наблюдават в Атанасовското езеро?',
a: ['Повече от 330 вида', 'Над 10 вида', 'Над 450 вида'], // // a: ['Повече от 330 вида', 'Над 10 вида', 'Над 450 вида'],
h: 'Грешен отговор. В Атанасовското езеро са наблюдавани над 330 вида птици' // // h: 'Грешен отговор. В Атанасовското езеро са наблюдавани над 330 вида птици'
}, // // },
{ // // {
q: 'Какво е Via Pontica?', // // q: 'Какво е Via Pontica?',
a: ['Миграционният път на птиците, минаващ покрай Бургаските езера', 'Рядък вид птица', 'Местност в гр. Бургас'], // // a: ['Миграционният път на птиците, минаващ покрай Бургаските езера', 'Рядък вид птица', 'Местност в гр. Бургас'],
h: 'Грешен отговор. Via Pontica наричаме миграционния път на птиците' // // h: 'Грешен отговор. Via Pontica наричаме миграционния път на птиците'
}, // // },
{ // // {
q: 'What stands for "Via Pontica"?', // // q: 'What stands for "Via Pontica"?',
a: ['The migration route of birds passing by the Burgas lakes', 'A rare species of bird', 'Location in Burgas'], // // a: ['The migration route of birds passing by the Burgas lakes', 'A rare species of bird', 'Location in Burgas'],
h: 'Wrong answer. Via Pontica is the name given to the migratory route of birds.' // // h: 'Wrong answer. Via Pontica is the name given to the migratory route of birds.'
}, // // },
{ // // {
q: 'Къдроглавият пеликан...', // // q: 'Къдроглавият пеликан...',
a: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'], // // a: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'],
h: 'Грешен отговор. Къдроглавият пеликан има огромен жълт клюн' // // h: 'Грешен отговор. Къдроглавият пеликан има огромен жълт клюн'
}, // // },
]) // // ])
maze.load().then(o=>{ // // maze.load().then(o=>{
gameEngine.activeObjects.add(o); // // gameEngine.activeObjects.add(o);
//o.scale.set(5,5,5); // // //o.scale.set(5,5,5);
}) // // })
// new Grass(Grass.positions(1000,50,50), '/static/textures/grass01.png', 1, .5).then(mesh=>{ // // new Grass(Grass.positions(1000,50,50), '/static/textures/grass01.png', 1, .5).then(mesh=>{
// console.log('adding grass') // // console.log('adding grass')
// gameEngine.scene.add(mesh); // // gameEngine.scene.add(mesh);
// }) // // })
// new Grass(Grass.positions(250,50,50), '/static/textures/flowers01.png', 1, .75).then(mesh=>{ // // new Grass(Grass.positions(250,50,50), '/static/textures/flowers01.png', 1, .75).then(mesh=>{
// gameEngine.scene.add(mesh); // // gameEngine.scene.add(mesh);
// console.log('adding grass') // // console.log('adding grass')
// }) // // })
// new Grass(Grass.positions(250,50,50), '/static/textures/flowers02.png', 1, .75).then(mesh=>{ // // new Grass(Grass.positions(250,50,50), '/static/textures/flowers02.png', 1, .75).then(mesh=>{
// gameEngine.scene.add(mesh); // // gameEngine.scene.add(mesh);
// console.log('adding grass') // // console.log('adding grass')
// }) // // })
}, // },
async expandScenarioData(scene){
scene.data.$environment = (await this.$api.gameObject.load(scene.data.environment)).data
for (let i of scene.data.items || []) {
i.data.$go = (await this.$api.gameObject.load(i.data.go)).data;
}
},
targetPointerDown(){
this.pointerDownTime = performance.now();
},
targetClick(e){
// if (performance.now() - this.pointerDownTime < 200){
// let intersects = gameEngine.intersect(e, this.$refs.target, gameEngine.activeObjects.children, true);
// //console.log(intersects)
// if (intersects.length){
// }else{
// }
// }
gameEngine.onClick(e, this.$refs.target);
},
setObjectAttributes(l, data, o, autoScaleFactor = 1){
if (l[data.id]){
['position', 'scale', 'rotation'].forEach(p=>{
o.scene[p].copy(l[data.id][p])
})
}else{
gameEngine.autoScale(o.scene, autoScaleFactor);
}
l[data.id] = l[data.id] || {};
['position', 'scale', 'rotation', 'visible'].forEach(p=>{
l[data.id][p] = o.scene[p];
})
l[data.id].__o = o.scene;
l[data.id].__g = o;
l[data.id].__title = data.title;
o.scene.__pn_id = data.id;
},
async toggleAnimation(animation){
animation.playing = !animation.playing;
gameEngine.playAnimation(gameEngine.scene, animation.a, animation.playing);
},
resize(){
let r = this.$refs.target;
gameEngine.resize(r.clientWidth, r.clientHeight);
//this.zoom = Math.min(r.clientWidth / this.viewBox.w, r.clientHeight / this.viewBox.h);
},
control(){
gameEngine.hero.lockControls();
}
} }
} }
</script> </script>
@@ -2,16 +2,16 @@
import { Group, AnimationMixer, LoopPingPong, Vector3 } from "three"; import { Group, AnimationMixer, LoopPingPong, Vector3 } from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { assignMaterial, assignParams } from "@/lib/MeshUtils"; import { assignMaterial, assignParams } from "@/lib/MeshUtils";
import { Game1 } from "./PuzzleGame1"; import { PuzzleGame1 } from "./PuzzleGame1";
import { Game2 } from "./PuzzleGame2"; import { PuzzleGame2 } from "./PuzzleGame2";
// import { Game3 } from "./games/Game3"; // import { Game3 } from "./games/Game3";
import { Game4 } from "./PuzzleGame4"; import { PuzzleGame4 } from "./PuzzleGame4";
// import { Game5 } from "./games/Game5"; // import { Game5 } from "./games/Game5";
// import { Game6 } from "./games/Game6"; // import { Game6 } from "./games/Game6";
import { TextObject } from "./TextObject"; import { TextObject } from "./TextObject";
import { ImageObject } from "./ImageObject"; import { ImageObject } from "./ImageObject";
const games = {Game1, Game2, Game4}; const games = {PuzzleGame1, PuzzleGame2, PuzzleGame4};
class InteractiveObject { class InteractiveObject {
constructor(obj, context) { constructor(obj, context) {
@@ -75,12 +75,12 @@ class InteractiveObject {
assignMaterial(mesh, obj, context); assignMaterial(mesh, obj, context);
resolve(mesh); resolve(mesh);
break; break;
case 'Game1': case 'PuzzleGame1':
case 'Game2': case 'PuzzleGame2':
case 'Game3': case 'PuzzleGame3':
case 'Game4': case 'PuzzleGame4':
case 'Game5': case 'PuzzleGame5':
case 'Game6': case 'PuzzleGame6':
var game = new games[obj.type](context, obj.args[0], obj.args[1], obj.args[2]); var game = new games[obj.type](context, obj.args[0], obj.args[1], obj.args[2]);
mesh = game.object; mesh = game.object;
mesh.game = game; mesh.game = game;
@@ -129,16 +129,14 @@ class InteractiveObject {
} }
} }
const InteractiveObjectTypes = [
// function textObject(text, context){ {
// const geometry = new TextGeometry( text, { id: 'PuzzleGame1', name: 'Puzzle Game 1'
// font: context.font, }, {
// size: .05, id: 'MazeQuizGame', name: 'Maze Quiz Game'
// height: .01, }, {
// curveSegments: 1 id: 'VideoPlayer', name: 'Video Player'
// } ); }
// return new Mesh(geometry, context.fontMaterial); ]
// }
export { InteractiveObject, InteractiveObjectTypes }
export {InteractiveObject}
@@ -96,7 +96,7 @@ class MazeQuizGame {
) )
let dd; let dd;
if (d == 'f'){ if (d == 'f'){
dd = Math.round() > 0.5 ? 'l' : 'r'; dd = Math.random() > 0.5 ? 'l' : 'r';
}else { }else {
dd = d == 'l' ? 'r' : 'l' dd = d == 'l' ? 'r' : 'l'
} }
@@ -0,0 +1,11 @@
<template>
<div>
MAZE
</div>
</template>
<script>
export default {
}
</script>
@@ -2,7 +2,7 @@ import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
import { TextureLoader } from 'three/src/loaders/TextureLoader'; import { TextureLoader } from 'three/src/loaders/TextureLoader';
import { MotionEngine } from '../../lib/MotionEngine'; import { MotionEngine } from '../../lib/MotionEngine';
class Game1 { class PuzzleGame1 {
constructor(context, image, w, h) { constructor(context, image, w, h) {
this.object = new Group(); this.object = new Group();
const aq = new MotionEngine(); const aq = new MotionEngine();
@@ -93,4 +93,4 @@ class Game1 {
} }
} }
export {Game1} export { PuzzleGame1 }
@@ -0,0 +1,8 @@
<template>
</template>
<script>
export default {
}
</script>
@@ -2,7 +2,7 @@ import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
import { TextureLoader } from 'three/src/loaders/TextureLoader'; import { TextureLoader } from 'three/src/loaders/TextureLoader';
import { MotionEngine } from '../../lib/MotionEngine'; import { MotionEngine } from '../../lib/MotionEngine';
class Game2 { class PuzzleGame2 {
constructor(context, image, w, h) { constructor(context, image, w, h) {
const texture = new TextureLoader().load(image); const texture = new TextureLoader().load(image);
//texture.encoding = sRGBEncoding; //texture.encoding = sRGBEncoding;
@@ -143,4 +143,4 @@ class Game2 {
} }
} }
export { Game2 }; export { PuzzleGame2 };
@@ -2,7 +2,7 @@ import { Group, RGBAFormat } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { MotionEngine } from '../../lib/MotionEngine'; import { MotionEngine } from '../../lib/MotionEngine';
var Game4 = function(context, gltf, w, h){ var PuzzleGame4 = function(context, gltf, w, h){
this.object = new Group(); this.object = new Group();
const aq = new MotionEngine(); const aq = new MotionEngine();
const pr = []; const pr = [];
@@ -121,4 +121,4 @@ var Game4 = function(context, gltf, w, h){
} }
} }
export { Game4 } export { PuzzleGame4 }
@@ -1,24 +1,20 @@
<template> <template>
<v-card v-if="selected" :title="modelValue.title" class="mx-2" variant="text">
<asset-selector @select="assignVideoObject" :type="['Descriptive']"> <asset-selector @select="assignVideoObject" :type="['Descriptive']">
<template v-slot:activator="props"> <template v-slot:activator="props">
<v-btn v-bind="props" prepend-icon="mdi-panorama-outline" color="success" block>Choose video object</v-btn> <v-btn v-bind="props" prepend-icon="mdi-video-box" color="deep-orange-darken-4" block>Choose video object</v-btn>
</template> </template>
</asset-selector> </asset-selector>
<v-form class="py-4"> <v-card v-if="modelValue.go">
<v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field> <v-card-item>
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea> <v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
<v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field> <div class="text-caption text-center">{{ modelValue.title }}</div>
</v-form> </v-card-item>
</v-card> </v-card>
</template> </template>
<script> <script>
import Utils from '@/lib/Utils';
export default { export default {
emits:['target', 'preview'],
data(){ data(){
return { return {
active: false active: false
@@ -28,32 +24,12 @@ export default {
this.active = true; this.active = true;
}, },
props:{ props:{
//context: Object, modelValue: Object
modelValue: Object,
vd: Object,
selected: Boolean,
cid:String,
visible: Boolean,
parent: Object
}, },
computed:{
showInView(){
this.vd.__showInView = this.visible && this.parent.visible;
return this.vd.__showInView;
}
},
steps: [['x1', 'y1']],
name: 'game-object',
modifiers: ['x1', 'y1'],
methods:{ methods:{
intersect(v){ assignVideoObject(e){
return Utils.intersectPointRect([this.vd.x1, this.vd.y1], v);
},
assignGameObject(e){
this.modelValue.go = e.id; this.modelValue.go = e.id;
if (this.modelValue.id == this.modelValue.title){ this.modelValue.title = e.name
this.modelValue.title = e.name
}
} }
} }
} }
+16 -2
View File
@@ -16,21 +16,29 @@
<v-btn v-bind="props" prepend-icon="mdi-panorama-outline" color="success" block>Choose game object</v-btn> <v-btn v-bind="props" prepend-icon="mdi-panorama-outline" color="success" block>Choose game object</v-btn>
</template> </template>
</asset-selector> </asset-selector>
<v-form class="py-4"> <v-form class="pt-4">
<v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field> <v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field>
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea> <v-textarea :label="l.description" v-model="modelValue.description"></v-textarea>
<v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field> <v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field>
</v-form> </v-form>
</v-card> </v-card>
<v-container v-if="selected && modelValue.io">
<component :is="modelValue.io.id" v-model="modelValue.io"></component>
</v-container>
</template> </template>
<script> <script>
import SvgIcon from './SvgIcon.vue'; import SvgIcon from './SvgIcon.vue';
import Utils from '@/lib/Utils'; import Utils from '@/lib/Utils';
import VideoPlayer from '../InteractiveObjects/VideoPlayer.vue';
import PuzzleGame1 from '../InteractiveObjects/PuzzleGame1.vue';
import MazeQuizGame from '../InteractiveObjects/MazeQuizGame/MazeQuizGame.vue';
export default { export default {
emits:['target', 'preview'], emits:['target', 'preview'],
components: { SvgIcon }, components: { SvgIcon, VideoPlayer, PuzzleGame1, MazeQuizGame },
data(){ data(){
return { return {
active: false active: false
@@ -65,6 +73,12 @@ export default {
this.modelValue.go = e.id; this.modelValue.go = e.id;
if (this.modelValue.id == this.modelValue.title){ if (this.modelValue.id == this.modelValue.title){
this.modelValue.title = e.name this.modelValue.title = e.name
delete this.modelValue.io;
}
if (e.type == 'InteractiveObject'){
this.modelValue.io = {
id: e.id
};
} }
} }
} }
+205
View File
@@ -0,0 +1,205 @@
import { GameEngine } from '@/lib/GameEngine';
import { Hero } from '@/lib/Hero';
let gameEngine = null;
export default {
async mounted(){
gameEngine = new GameEngine();
//this.gameEngine = gameEngine;
await gameEngine.init(this.$refs.target, {
xr: true,
gizmo: this.env == 'GameDesigner',
designMode: this.env == 'GameDesigner',
depthSense: this.env == 'GameDesigner' ? false : this.store.prefs.xr.depthSense
});
gameEngine.scene.add(gameEngine.transformControls.getHelper());
//gameEngine.scene.add(new gameEngine.$.GridHelper(100,100));
this.resize();
//gameEngine.setCamera(gameEngine.orthographicCamera)
//gameEngine.setCameraOrthographic();
await this.loadScenario();
window.addEventListener('resize', this.resize);
},
unmounted(){
window.removeEventListener('resize', this.resize);
gameEngine.stop();
},
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){
gameEngine.transformControls.setMode(n)
},
async 'object.scenario'(n){
await this.loadScenario()
},
currentObject(n){
gameEngine.transformControls.attach(n.__o);
gameEngine.gizmo.target = n.__o.position;
//gameEngine.camera.lookAt(n.__o.position)
gameEngine.camera.updateProjectionMatrix()
},
renderType(v){
gameEngine.renderType = v;
},
cameraType(v){
if (v == 'perspective'){
gameEngine.setCameraPerspective();
}else{
gameEngine.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 = [];
promises.push(this.$api.gameObject.load(scene.data.environment).then(r=>scene.data.$environment = r.data))
for (let i of scene.data.items || []) {
if (i.data.io){
if (i.data.io.go){
promises.push(this.$api.gameObject.load(i.data.io.go).then(r=>i.data.io.$go = r.data));
}
}else{
promises.push(this.$api.gameObject.load(i.data.go).then(r=>i.data.$go = 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){
//await gameEngine.loadPanorama(`/asset/default/43.webp`);
gameEngine.activeObjects.clear();
await this.expandScenarioData(scene);
target.objects = target.objects || {};
let l = target.objects;
if (this.scene.data.$environment.type == 'panorama2d'){
await gameEngine.loadPanorama(`/asset/default/${this.scene.data.$environment.asset.name}`);
}else{
let env = await gameEngine.load(`/asset/default/${this.scene.data.$environment.asset.name}`);
this.setObjectAttributes(l, this.scene.data, env, 100);
gameEngine.activeObjects.add(env.scene);
}
for (let i of this.scene.data.items || []) {
if (i.data.io){
}else{
let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
this.setObjectAttributes(l, i.data, gltf, 1);
gameEngine.activeObjects.add(gltf.scene);
if (this.env == 'GamePlaying'){
if (i.data.$go.type == 'player3d'){
let hero = new Hero(gltf, i.data.$go);
hero.init(gameEngine);
}else{
if (gltf.animations?.length){
gameEngine.playAnimation(gameEngine.scene, gltf.animations[0]);
}
}
}
}
}
// let camera = new gameEngine.$.PerspectiveCamera();
// let cameraHelper = new gameEngine.$.CameraHelper(camera);
// gameEngine.activeObjects.add(cameraHelper);
// gameEngine.activeObjects.add(camera);
// this.setObjectAttributes(l, { id: 'camera', 'title': 'Main camera' }, { scene: camera })
// cameraHelper.update();
//this is needed cause when mounted canvas has different size
this.resize();
},
targetPointerDown(){
this.pointerDownTime = performance.now();
},
targetClick(e){
if (performance.now() - this.pointerDownTime < 200){
let intersects = gameEngine.intersect(e, this.$refs.target, gameEngine.activeObjects.children, true);
//console.log(intersects)
if (intersects.length){
//console.log('attaching controls to', intersects[0].object)
//gameEngine.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{
gameEngine.transformControls.detach();
}
}
},
setObjectAttributes(l, data, o, autoScaleFactor = 1){
if (l[data.id]){
['position', 'scale', 'rotation'].forEach(p=>{
o.scene[p].copy(l[data.id][p])
})
}else{
gameEngine.autoScale(o.scene, autoScaleFactor);
}
l[data.id] = l[data.id] || {};
['position', 'scale', 'rotation', 'visible'].forEach(p=>{
l[data.id][p] = o.scene[p];
})
l[data.id].__o = o.scene;
l[data.id].__g = o;
l[data.id].__title = data.title;
o.scene.__pn_id = data.id;
},
async toggleAnimation(animation){
animation.playing = !animation.playing;
gameEngine.playAnimation(gameEngine.scene, animation.a, animation.playing);
},
resize(){
let r = this.$refs.target;
gameEngine.resize(r.clientWidth, r.clientHeight);
//this.zoom = Math.min(r.clientWidth / this.viewBox.w, r.clientHeight / this.viewBox.h);
},
control(){
gameEngine.hero.lockControls();
}
}
}
+6 -6
View File
@@ -12,7 +12,7 @@ export default {
icon: 'panorama-sphere-outline', icon: 'panorama-sphere-outline',
render: true, render: true,
type: 'Scene', type: 'Scene',
color: 'orange-darken-4' color: 'orange-darken-3'
}, { }, {
value: 'object3d', value: 'object3d',
icon: 'video-3d', icon: 'video-3d',
@@ -30,15 +30,15 @@ export default {
type: 'Player', type: 'Player',
render: true, render: true,
color: 'yellow-accent-4' color: 'yellow-accent-4'
}, {
value: 'audio',
icon: 'volume-medium',
color: 'deep-purple-accent-4'
}, { }, {
value: 'video', value: 'video',
icon: 'filmstrip', icon: 'filmstrip',
type: 'Descriptive', type: 'Descriptive',
color: 'blue-lighten-5' color: 'deep-orange-darken-4'
}, {
value: 'audio',
icon: 'volume-medium',
color: 'deep-purple-accent-4'
}] }]
} }
} }
-1
View File
@@ -15,7 +15,6 @@ video{
z-index: 100; z-index: 100;
} }
:root { :root {
--svg-scale: 1; --svg-scale: 1;
} }