interactive objects integration
This commit is contained in:
@@ -2,24 +2,24 @@ import { TextureLoader, MeshStandardMaterial, MeshBasicMaterial, PlaneGeometry,
|
|||||||
import { assignParams } from "@/lib/MeshUtils";
|
import { assignParams } from "@/lib/MeshUtils";
|
||||||
|
|
||||||
class ImageObject {
|
class ImageObject {
|
||||||
constructor(obj, context) {
|
constructor(obj, engine, params) {
|
||||||
var t = new TextureLoader().setPath(context.path).load(obj.value);
|
var t = new TextureLoader().setPath(params.path).load(obj.value);
|
||||||
//t.encoding = sRGBEncoding;
|
//t.encoding = sRGBEncoding;
|
||||||
var mp = {
|
var mp = {
|
||||||
map: t,
|
map: t,
|
||||||
alphaTest: 0.5
|
alphaTest: 0.5
|
||||||
};
|
};
|
||||||
if (obj.nm) {
|
if (obj.nm) {
|
||||||
mp.normalMap = new TextureLoader().setPath(context.path).load(obj.nm);
|
mp.normalMap = new TextureLoader().setPath(params.path).load(obj.nm);
|
||||||
}
|
}
|
||||||
if (obj.em) {
|
if (obj.em) {
|
||||||
mp.emissiveMap = new TextureLoader().setPath(context.path).load(obj.em);
|
mp.emissiveMap = new TextureLoader().setPath(params.path).load(obj.em);
|
||||||
}
|
}
|
||||||
if (obj.am) {
|
if (obj.am) {
|
||||||
mp.alphaMap = new TextureLoader().setPath(context.path).load(obj.am);
|
mp.alphaMap = new TextureLoader().setPath(params.path).load(obj.am);
|
||||||
}
|
}
|
||||||
obj.material && Object.assign(mp, obj.material);
|
obj.material && Object.assign(mp, obj.material);
|
||||||
let geo = new PlaneGeometry(context.wallSize * (obj.w || 1), context.wallSize * (obj.h || 1));
|
let geo = new PlaneGeometry(params.wallSize * (obj.w || 1), params.wallSize * (obj.h || 1));
|
||||||
if (obj.uv) {
|
if (obj.uv) {
|
||||||
var uvAttribute = geo.attributes.uv;
|
var uvAttribute = geo.attributes.uv;
|
||||||
for (var i = 0; i < uvAttribute.count; i++) {
|
for (var i = 0; i < uvAttribute.count; i++) {
|
||||||
|
|||||||
@@ -8,37 +8,58 @@ import { PuzzleGame4 } from "./PuzzleGame4";
|
|||||||
import { TextObject } from "./TextObject";
|
import { TextObject } from "./TextObject";
|
||||||
import { ImageObject } from "./ImageObject";
|
import { ImageObject } from "./ImageObject";
|
||||||
import { VideoPlayer } from "./VideoPlayer";
|
import { VideoPlayer } from "./VideoPlayer";
|
||||||
|
import { MazeQuizGame } from "./MazeQuizGame/MazeQuizGame";
|
||||||
|
import { assignMaterial, assignParams } from "@/lib/MeshUtils";
|
||||||
|
|
||||||
const games = {PuzzleGame1, PuzzleGame2, PuzzleGame4};
|
const games = {PuzzleGame1, PuzzleGame2, PuzzleGame4};
|
||||||
|
|
||||||
class InteractiveObject {
|
class InteractiveObject {
|
||||||
constructor(obj, context) {
|
constructor(obj, gameEngine, params) {
|
||||||
this.name = obj.name;
|
this.name = obj.name;
|
||||||
this.ready = new Promise((resolve, reject) => {
|
this.ready = new Promise(async (resolve, reject) => {
|
||||||
let mesh;
|
|
||||||
switch (obj.type || 'GenericObject') {
|
switch (obj.type || 'GenericObject') {
|
||||||
case 'Group':
|
case 'Group':
|
||||||
mesh = new Group();
|
this.object = new Group();
|
||||||
obj.group.forEach(g => {
|
for (let g of obj.group){
|
||||||
let go = new InteractiveObject(g, context);
|
let go = new InteractiveObject(g, gameEngine);
|
||||||
go.ready.then((gameMesh) => {
|
let gameMesh = await go.ready;
|
||||||
mesh.add(gameMesh);
|
this.object.add(gameMesh.object);
|
||||||
});
|
}
|
||||||
});
|
|
||||||
resolve(mesh);
|
|
||||||
break;
|
break;
|
||||||
case 'Text':
|
case 'Text':
|
||||||
let text = new TextObject(obj, context);
|
this.source = new TextObject(obj, gameEngine, params);
|
||||||
resolve(text.object);
|
this.object = this.source.object;
|
||||||
break;
|
break;
|
||||||
case 'Image':
|
case 'Image':
|
||||||
let imo = new ImageObject(obj, context);
|
this.source = new ImageObject(obj, gameEngine, params);
|
||||||
mesh = imo.object;
|
this.object = this.source.object;
|
||||||
resolve(mesh);
|
break;
|
||||||
|
case 'Gltf':
|
||||||
|
let gltf = await gameEngine.load(obj.value);
|
||||||
|
let gltfObj = gltf.scene;
|
||||||
|
gltfObj.traverse(function (object) {
|
||||||
|
object.frustumCulled = false;
|
||||||
|
if (obj.name && obj.name == object.name) {
|
||||||
|
gltfObj = object;
|
||||||
|
}
|
||||||
|
// object.castShadow = true;
|
||||||
|
// object.receiveShadow = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
assignMaterial(gltfObj, obj, params);
|
||||||
|
if (gltf.animations && gltf.animations.length) {
|
||||||
|
let mixer = new AnimationMixer(gltfObj);
|
||||||
|
gameEngine.mixers.push(mixer);
|
||||||
|
let action = mixer.clipAction(gltf.animations[0]);
|
||||||
|
action.setLoop(LoopPingPong);
|
||||||
|
action.play();
|
||||||
|
}
|
||||||
|
this.object = gltfObj;
|
||||||
|
this.source = gltf;
|
||||||
break;
|
break;
|
||||||
case 'GenericObject':
|
case 'GenericObject':
|
||||||
let promise = context.load(`/asset/default/${obj.$go.asset.name}`);
|
this.source = await gameEngine.load(`/asset/default/${obj.$go.asset.name}`);
|
||||||
promise.then(resolve);
|
this.object = this.source.scene;
|
||||||
break;
|
break;
|
||||||
case 'PuzzleGame1':
|
case 'PuzzleGame1':
|
||||||
case 'PuzzleGame2':
|
case 'PuzzleGame2':
|
||||||
@@ -46,19 +67,23 @@ class InteractiveObject {
|
|||||||
case 'PuzzleGame4':
|
case 'PuzzleGame4':
|
||||||
case 'PuzzleGame5':
|
case 'PuzzleGame5':
|
||||||
case 'PuzzleGame6':
|
case 'PuzzleGame6':
|
||||||
let game = new games[obj.type](context, obj.args[0], obj.args[1], obj.args[2]);
|
let game = new games[obj.type](gameEngine, obj.args[0], obj.args[1], obj.args[2]);
|
||||||
mesh = game.object;
|
this.object = game.object;
|
||||||
mesh.game = game;
|
this.object.game = game;
|
||||||
resolve(mesh);
|
|
||||||
break;
|
break;
|
||||||
case 'VideoPlayer':
|
case 'VideoPlayer':
|
||||||
let vp = new VideoPlayer(context, `/asset/default/${obj.$go.asset.name}`);
|
let vp = new VideoPlayer(gameEngine, `/asset/default/${obj.$go.asset.name}`);
|
||||||
vp.ready.then(resolve);
|
this.source = await vp.ready;
|
||||||
|
this.object = vp.object;
|
||||||
|
break;
|
||||||
|
case 'MazeQuizGame':
|
||||||
|
this.source = new MazeQuizGame(gameEngine, obj);
|
||||||
|
await this.source.load();
|
||||||
|
this.object = this.source.object;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
assignParams(this.object, obj);
|
||||||
this.ready.then((mesh) => {
|
resolve(this);
|
||||||
this.object = mesh;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide, CanvasTexture, SRGBColorSpace } from 'three';
|
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide} from 'three';
|
||||||
import { InteractiveObject } from '../InteractiveObject';
|
import { InteractiveObject } from '../InteractiveObject';
|
||||||
import Utils from '@/lib/Utils';
|
|
||||||
|
|
||||||
class MazeObject {
|
class MazeObject {
|
||||||
constructor(engine, def, params = {}){
|
constructor(engine, def, params = {}){
|
||||||
@@ -39,7 +38,7 @@ class MazeObject {
|
|||||||
if (typeof size == 'number'){
|
if (typeof size == 'number'){
|
||||||
size = { width: 0.1, height:1, depth:size/2 }
|
size = { width: 0.1, height:1, depth:size/2 }
|
||||||
}
|
}
|
||||||
let po = engine.phy.add(
|
let po = engine.physics.add(
|
||||||
{position: v}, 'fixed', false, undefined, 'cuboid',
|
{position: v}, 'fixed', false, undefined, 'cuboid',
|
||||||
{ ...size, isSensor, userData }
|
{ ...size, isSensor, userData }
|
||||||
)
|
)
|
||||||
@@ -51,13 +50,6 @@ class MazeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addRoom(elements, def, offsetZ){
|
function addRoom(elements, def, offsetZ){
|
||||||
// e = [
|
|
||||||
// o.floor.clone(),
|
|
||||||
// o.door.clone(),
|
|
||||||
// o[def.r ? 'door' : 'wall'].clone(),
|
|
||||||
// o[def.f ? 'door' : 'wall'].clone(),
|
|
||||||
// o[def.l ? 'door' : 'wall'].clone()
|
|
||||||
// ];
|
|
||||||
|
|
||||||
let e = elements.map(e=>o[e].clone())
|
let e = elements.map(e=>o[e].clone())
|
||||||
|
|
||||||
@@ -106,9 +98,6 @@ class MazeObject {
|
|||||||
let offsetZ = 0, e;
|
let offsetZ = 0, e;
|
||||||
def.len = def.len || 0;
|
def.len = def.len || 0;
|
||||||
if (step == 0) {
|
if (step == 0) {
|
||||||
// e = o.door.clone();
|
|
||||||
// e.rotateY(_tf.rotation.f);
|
|
||||||
// mazeMeshes.push(e);
|
|
||||||
addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -context.wallSize)
|
addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -context.wallSize)
|
||||||
}
|
}
|
||||||
if (def.userData?.answer !== undefined){
|
if (def.userData?.answer !== undefined){
|
||||||
@@ -121,15 +110,6 @@ class MazeObject {
|
|||||||
root.add(t);
|
root.add(t);
|
||||||
}
|
}
|
||||||
offsetZ = def.len * context.tubeSize;
|
offsetZ = def.len * context.tubeSize;
|
||||||
//if (!def.len) offsetZ = -.275;
|
|
||||||
//room.getWorldQuaternion(quat);
|
|
||||||
// const geometry = new BoxGeometry(2, 2, offsetZ);
|
|
||||||
// const cube = new Mesh(geometry, o.tunnel.material)
|
|
||||||
// cube.position.set(context.tubeSize / 2, 0.6, offsetZ/2)
|
|
||||||
// root.add(cube);
|
|
||||||
|
|
||||||
// console.log(offsetZ, room.localToWorld(new Vector3(context.tubeSize / 2, 0.6, offsetZ/2)))
|
|
||||||
|
|
||||||
addPhysics(def.matrix, [context.tubeSize / 2, 0.6, offsetZ/2], offsetZ)
|
addPhysics(def.matrix, [context.tubeSize / 2, 0.6, offsetZ/2], offsetZ)
|
||||||
addPhysics(def.matrix, [-context.tubeSize / 2, 0.6, offsetZ/2], offsetZ)
|
addPhysics(def.matrix, [-context.tubeSize / 2, 0.6, offsetZ/2], offsetZ)
|
||||||
|
|
||||||
@@ -140,16 +120,12 @@ class MazeObject {
|
|||||||
//console.log('loadingggg', def.objects)
|
//console.log('loadingggg', def.objects)
|
||||||
def.objects?.forEach(async obj => {
|
def.objects?.forEach(async obj => {
|
||||||
obj.room = room;
|
obj.room = room;
|
||||||
// let go = new GameObject(obj, context);
|
let go = new InteractiveObject(obj, engine, context)
|
||||||
let go = new InteractiveObject(obj, context)
|
|
||||||
await go.ready;
|
await go.ready;
|
||||||
go.object.scale.multiplyScalar(context.wallSize)
|
go.object.scale.multiplyScalar(context.wallSize)
|
||||||
go.object.position.multiplyScalar(context.wallSize)
|
go.object.position.multiplyScalar(context.wallSize)
|
||||||
go.object.applyMatrix4(def.matrix);
|
go.object.applyMatrix4(def.matrix);
|
||||||
root.add(go.object);
|
root.add(go.object);
|
||||||
// go.ready.then(mesh => {
|
|
||||||
// room.add(mesh);
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
def.room = room;
|
def.room = room;
|
||||||
@@ -173,17 +149,8 @@ class MazeObject {
|
|||||||
o[e] = mazeAsset.scene.getObjectByName(e);
|
o[e] = mazeAsset.scene.getObjectByName(e);
|
||||||
//o[e].frustumCulled = false;
|
//o[e].frustumCulled = false;
|
||||||
o[e].scale.set(scale, scale, scale)
|
o[e].scale.set(scale, scale, scale)
|
||||||
// o[e].geometry.computeBoundingBox();
|
|
||||||
// console.log(e, o[e].geometry.boundingBox)
|
|
||||||
});
|
});
|
||||||
this.mazeObject(def, room);
|
this.mazeObject(def, room);
|
||||||
// mazeMeshes.forEach(mesh=>{
|
|
||||||
// //let mesh = new Mesh(mg, o.tunnel.material)
|
|
||||||
// root.add(mesh);
|
|
||||||
// //engine.phy.add(mesh, 'fixed')
|
|
||||||
// })
|
|
||||||
|
|
||||||
//console.log(bbox, 'bbox')
|
|
||||||
const floorGeometry = new PlaneGeometry(bbox.r - bbox.l + 10*scale, bbox.f + 10*scale);
|
const floorGeometry = new PlaneGeometry(bbox.r - bbox.l + 10*scale, bbox.f + 10*scale);
|
||||||
const floor = new Mesh(floorGeometry,new MeshStandardMaterial({
|
const floor = new Mesh(floorGeometry,new MeshStandardMaterial({
|
||||||
roughness: 0, metalness:1, color: 0x00ffff, side: DoubleSide
|
roughness: 0, metalness:1, color: 0x00ffff, side: DoubleSide
|
||||||
@@ -191,8 +158,6 @@ class MazeObject {
|
|||||||
floor.rotation.set(Math.PI/2, 0, 0)
|
floor.rotation.set(Math.PI/2, 0, 0)
|
||||||
floor.position.set((bbox.l + bbox.r)/2, 0.3, bbox.f/2);
|
floor.position.set((bbox.l + bbox.r)/2, 0.3, bbox.f/2);
|
||||||
root.add(floor);
|
root.add(floor);
|
||||||
//scene.add(new Mesh(BufferGeometryUtils.mergeGeometries(mazeGeometries, false), o.tunnel.material));
|
|
||||||
//console.log(room);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,28 @@ import Utils from "@/lib/Utils";
|
|||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
arrows:{
|
arrows:{
|
||||||
r: len => ({ type: 'image', value: '/static/textures/arrow.png', position:[-.5,.44,len+.96], rotation:[0,Math.PI, 0], scale: [0.03, 0.03, 0.03] }),
|
r: len => ({ type: 'Image', value: '/static/textures/arrow.png', position:[-.5,.44,len+.96], rotation:[0,Math.PI, 0], scale: [0.03, 0.03, 0.03] }),
|
||||||
l: len => ({ type: 'image', value: '/static/textures/arrow.png', position:[.5,.44,len+.96], rotation:[0,Math.PI, Math.PI], scale: [0.03, 0.03, 0.03] }),
|
l: len => ({ type: 'Image', value: '/static/textures/arrow.png', position:[.5,.44,len+.96], rotation:[0,Math.PI, Math.PI], scale: [0.03, 0.03, 0.03] }),
|
||||||
f: len => ({ type: 'image', value: '/static/textures/arrow.png', position:[0,.7,len+.96], rotation:[0,Math.PI, Math.PI/2], scale: [0.03, 0.03, 0.03] })
|
f: len => ({ type: 'Image', value: '/static/textures/arrow.png', position:[0,.7,len+.96], rotation:[0,Math.PI, Math.PI/2], scale: [0.03, 0.03, 0.03] })
|
||||||
},
|
},
|
||||||
answers:{
|
answers:{
|
||||||
r: (len, text) => ({ type: 'text', width:0.5, text, fontSize:0.025, position:[-.5,.3,len+.9], rotation:[0,Math.PI, 0] }),
|
r: (len, text) => ({ type: 'Text', width:0.5, text, fontSize:0.025, position:[-.5,.3,len+.9], rotation:[0,Math.PI, 0] }),
|
||||||
l: (len, text) => ({ type: 'text', width:0.5, text, fontSize:0.025, position:[.5,.3,len+.9], rotation:[0,Math.PI, 0] }),
|
l: (len, text) => ({ type: 'Text', width:0.5, text, fontSize:0.025, position:[.5,.3,len+.9], rotation:[0,Math.PI, 0] }),
|
||||||
f: (len, text) => ({ type: 'text', width:0.5, text, fontSize:0.025, position:[0,.55,len+.9], rotation:[0,Math.PI, 0] })
|
f: (len, text) => ({ type: 'Text', width:0.5, text, fontSize:0.025, position:[0,.55,len+.9], rotation:[0,Math.PI, 0] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tl = 4;
|
const tl = 4;
|
||||||
|
|
||||||
class MazeQuizGame {
|
class MazeQuizGame {
|
||||||
constructor(engine, context, questions) {
|
constructor(engine, data) {
|
||||||
|
let questions = data.shuffle ? Utils.shuffleArray(data.questions) : data.questions;
|
||||||
let def = this.generate(questions);
|
let def = this.generate(questions);
|
||||||
this.mazeObject = new MazeObject(engine, def)
|
this.mazeObject = new MazeObject(engine, def)
|
||||||
engine.addEventListener('collision', async e=>{
|
engine.addEventListener('collision', async e=>{
|
||||||
let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData;
|
let ud1 = engine.physics.world.getCollider(e.handle1)?.parent()?.userData,
|
||||||
|
ud2 = engine.physics.world.getCollider(e.handle2)?.parent()?.userData;
|
||||||
|
let ud = {...ud1, ...ud2}
|
||||||
if (ud?.finish){
|
if (ud?.finish){
|
||||||
if (e.started){
|
if (e.started){
|
||||||
engine.dashboard.updateProgress(1)
|
engine.dashboard.updateProgress(1)
|
||||||
@@ -53,7 +56,7 @@ class MazeQuizGame {
|
|||||||
async load(){
|
async load(){
|
||||||
await this.mazeObject.load();
|
await this.mazeObject.load();
|
||||||
this.object = this.mazeObject.object;
|
this.object = this.mazeObject.object;
|
||||||
return this.object;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(questions, qid = 0, len){
|
generate(questions, qid = 0, len){
|
||||||
@@ -63,7 +66,7 @@ class MazeQuizGame {
|
|||||||
userData: { finish: true },
|
userData: { finish: true },
|
||||||
objects:[
|
objects:[
|
||||||
{
|
{
|
||||||
type: 'gltf',
|
type: 'Gltf',
|
||||||
position:[0,.25,len + .52], scale: [0.037, 0.037, 0.037], rotation: [0, Math.PI/4, 0],
|
position:[0,.25,len + .52], scale: [0.037, 0.037, 0.037], rotation: [0, Math.PI/4, 0],
|
||||||
value: '/static/meshes/award.glb'
|
value: '/static/meshes/award.glb'
|
||||||
}
|
}
|
||||||
@@ -83,12 +86,12 @@ class MazeQuizGame {
|
|||||||
len, userData: { question, qid },
|
len, userData: { question, qid },
|
||||||
objects:[
|
objects:[
|
||||||
{
|
{
|
||||||
type: 'text', text: question.q, fontSize:0.033, width:0.5, position:[0,.33,len + .96], rotation:[0,Math.PI, 0]
|
type: 'Text', text: question.q, fontSize:0.033, width:0.5, position:[0,.33,len + .96], rotation:[0,Math.PI, 0]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
question.a.forEach((a, i)=>{
|
question.a.filter(a=>!!a).forEach((a, i)=>{
|
||||||
let d = directions[i];
|
let d = directions[i];
|
||||||
mo.objects.push(
|
mo.objects.push(
|
||||||
defaults.arrows[d](len),
|
defaults.arrows[d](len),
|
||||||
@@ -114,7 +117,7 @@ class MazeQuizGame {
|
|||||||
len: 2,
|
len: 2,
|
||||||
objects:[
|
objects:[
|
||||||
{
|
{
|
||||||
type: 'text', width:0.5, color:0xff0000, text: question.h, fontSize:0.033, position:[0,.44,2+.96], rotation:[0,Math.PI, 0]
|
type: 'Text', width:0.5, color:0xff0000, text: question.h, fontSize:0.033, position:[0,.44,2+.96], rotation:[0,Math.PI, 0]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<v-checkbox v-model="modelValue.shuffle" label="Shuffle questions"></v-checkbox>
|
||||||
MAZE
|
<v-dialog max-width="1400" scrollable>
|
||||||
</div>
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
|
<v-btn v-bind="activatorProps">Manage Questions</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:default="{ isActive }">
|
||||||
|
<v-card>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex flex-wrap w-100">
|
||||||
|
<v-card v-for="(n, ni) in modelValue.questions" class="v-col-6" variant="outlined" border="0">
|
||||||
|
<v-card-item>
|
||||||
|
<v-text-field density="compact" hide-details :label="`Question #${ni+1}`" v-model="n.q" class="py-2">
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-btn variant="plain" color="error" @click="deleteQuestion(ni)">
|
||||||
|
<v-icon icon="mdi-delete-forever"></v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
<v-text-field hide-details density="compact" v-model="n.a[0]" class="pb-2"
|
||||||
|
label="Correct answer" icon-color="success" prepend-icon="mdi-check"></v-text-field>
|
||||||
|
<v-text-field hide-details density="compact" v-model="n.a[1]" class="pb-2"
|
||||||
|
label="Wrong answer #1" icon-color="error" prepend-icon="mdi-close"></v-text-field>
|
||||||
|
<v-text-field hide-details density="compact" v-model="n.a[2]" class="pb-2"
|
||||||
|
label="Wrong answer #2" v-if="n.a[1]" icon-color="error" prepend-icon="mdi-close"></v-text-field>
|
||||||
|
<v-text-field density="compact" hide-details label="Wrong answer hint" v-model="n.h" class="pb-2"></v-text-field>
|
||||||
|
</v-card-item>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-btn @click="addQuestion" block>Add question</v-btn>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props:['modelValue'],
|
||||||
|
mounted(){
|
||||||
|
this.modelValue.questions ??= [];
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
addQuestion(){
|
||||||
|
this.modelValue.questions.push({
|
||||||
|
q:'', a:['', ''], h:''
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteQuestion(idx){
|
||||||
|
this.modelValue.questions.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -3,7 +3,7 @@ import { Text } from "troika-three-text";
|
|||||||
import { assignParams } from "@/lib/MeshUtils";
|
import { assignParams } from "@/lib/MeshUtils";
|
||||||
|
|
||||||
class TextObject {
|
class TextObject {
|
||||||
constructor(obj, params) {
|
constructor(obj, engine, params) {
|
||||||
const txt = new Text();
|
const txt = new Text();
|
||||||
// Set properties to configure:
|
// Set properties to configure:
|
||||||
txt.text = obj.text;
|
txt.text = obj.text;
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default {
|
|||||||
if (e.type == 'InteractiveObject'){
|
if (e.type == 'InteractiveObject'){
|
||||||
this.modelValue.type = e.id;
|
this.modelValue.type = e.id;
|
||||||
}else{
|
}else{
|
||||||
this.modelValue.type = 'Object3D'
|
this.modelValue.type = 'GenericObject'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ export class CharacterControls {
|
|||||||
this.currentAction = currentAction
|
this.currentAction = currentAction
|
||||||
this.animationsMap[currentAction].play()
|
this.animationsMap[currentAction].play()
|
||||||
|
|
||||||
this.characterController = engine.phy.world.createCharacterController(0.1);
|
this.characterController = engine.physics.world.createCharacterController(0.1);
|
||||||
this.characterController.setUp({x:0, y:1, z:0});
|
this.characterController.setUp({x:0, y:1, z:0});
|
||||||
po.rigidBody.setTranslation(this.model.position)
|
po.rigidBody.setTranslation(this.model.position)
|
||||||
|
po.characterController = this.characterController;
|
||||||
// this.characterController.enableSnapToGround(0.5);
|
// this.characterController.enableSnapToGround(0.5);
|
||||||
// // Don’t allow climbing slopes larger than 45 degrees.
|
// // Don’t allow climbing slopes larger than 45 degrees.
|
||||||
// this.characterController.setMaxSlopeClimbAngle(45 * Math.PI / 180);
|
// this.characterController.setMaxSlopeClimbAngle(45 * Math.PI / 180);
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ class DashBoard {
|
|||||||
dash.visible = false;
|
dash.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.reset = ()=>{
|
||||||
|
this.updateProgress(0);
|
||||||
|
this.update({
|
||||||
|
hint: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.createProgressBar();
|
this.createProgressBar();
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,8 +262,8 @@ class GameEngine extends THREE.EventDispatcher{
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initPhysics() {
|
async initPhysics() {
|
||||||
this.phy = new Physics(this);
|
this.physics = new Physics(this);
|
||||||
await this.phy.init();
|
await this.physics.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleXrAction(gameEngine, delta) {
|
handleXrAction(gameEngine, delta) {
|
||||||
|
|||||||
+7
-124
@@ -9,7 +9,6 @@ class Hero{
|
|||||||
this.object = object;
|
this.object = object;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.model = object.scene
|
this.model = object.scene
|
||||||
//console.log(object)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(gameEngine){
|
init(gameEngine){
|
||||||
@@ -17,10 +16,6 @@ class Hero{
|
|||||||
gameEngine.camera.position.set(0,17,-30)
|
gameEngine.camera.position.set(0,17,-30)
|
||||||
gameEngine.camera.lookAt(new THREE.Vector3(this.model.position.x, 5, this.model.position.z))
|
gameEngine.camera.lookAt(new THREE.Vector3(this.model.position.x, 5, this.model.position.z))
|
||||||
|
|
||||||
// this.heroCamera = new THREE.Object3D()
|
|
||||||
// this.model.add(this.heroCamera)
|
|
||||||
// this.heroCamera.applyMatrix4(gameEngine.camera.matrix)
|
|
||||||
|
|
||||||
this.mixer = new AnimationMixer(this.model);
|
this.mixer = new AnimationMixer(this.model);
|
||||||
gameEngine.mixers.push( this.mixer );
|
gameEngine.mixers.push( this.mixer );
|
||||||
|
|
||||||
@@ -32,7 +27,7 @@ class Hero{
|
|||||||
this.pointerControls = new PointerControls(gameEngine.camera, this.model, gameEngine.renderer.domElement);
|
this.pointerControls = new PointerControls(gameEngine.camera, this.model, gameEngine.renderer.domElement);
|
||||||
gameEngine.hero = this;
|
gameEngine.hero = this;
|
||||||
|
|
||||||
let po = gameEngine.phy.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: 0.5, halfHeight: 1})
|
let po = gameEngine.physics.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: 0.5, halfHeight: 1})
|
||||||
po.collider.setTranslationWrtParent({ x: 0, y: 2.0, z: 0 });
|
po.collider.setTranslationWrtParent({ x: 0, y: 2.0, z: 0 });
|
||||||
//po.collider.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
|
//po.collider.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
|
||||||
|
|
||||||
@@ -59,128 +54,16 @@ class Hero{
|
|||||||
let dlt = this.clock.getDelta();
|
let dlt = this.clock.getDelta();
|
||||||
this.delta += dlt;
|
this.delta += dlt;
|
||||||
//if (this.delta > 0.00001){
|
//if (this.delta > 0.00001){
|
||||||
this.characterControls.update(this.gameEngine.phy.world, this.delta, pc)
|
this.characterControls.update(this.gameEngine.physics.world, this.delta, pc)
|
||||||
this.gameEngine.phy.step()
|
this.gameEngine.physics.step()
|
||||||
this.delta = 0;
|
this.delta = 0;
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(){
|
||||||
return;
|
delete this.gameEngine.hero;
|
||||||
let { inputVelocity, velocity, quat, v, targetQuaternion, clock, rotation } = this;
|
this.gameEngine.mixers.splice(this.gameEngine.mixers.indexOf(this.mixer), 1);
|
||||||
// let pc = this.pointerControls;
|
|
||||||
// pc.update();
|
|
||||||
if (this.ready) {
|
|
||||||
if (this.canJump) {
|
|
||||||
//walking
|
|
||||||
this.mixer.update(this.distance / 100)
|
|
||||||
} else {
|
|
||||||
//were in the air
|
|
||||||
this.mixer.update(this.delta)
|
|
||||||
}
|
|
||||||
const p = this.characterCollider.position
|
|
||||||
p.y -= 1
|
|
||||||
this.model.position.y = this.characterCollider.position.y
|
|
||||||
this.distance = this.model.position.distanceTo(p)
|
|
||||||
|
|
||||||
// const rotationMatrix = new THREE.Matrix4()
|
|
||||||
// rotationMatrix.lookAt(p, this.model.position, this.model.up)
|
|
||||||
// targetQuaternion.setFromRotationMatrix(rotationMatrix)
|
|
||||||
|
|
||||||
// if (!this.model.quaternion.equals(targetQuaternion)) {
|
|
||||||
// this.model.quaternion.rotateTowards(targetQuaternion, this.delta * 10)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this.canJump) {
|
|
||||||
inputVelocity.set(0, 0, 0)
|
|
||||||
|
|
||||||
if (pc.moveForward) {
|
|
||||||
inputVelocity.z = 1
|
|
||||||
}
|
|
||||||
if (pc.moveBackward) {
|
|
||||||
inputVelocity.z = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pc.moveLeft) {
|
|
||||||
//inputVelocity.x = -1
|
|
||||||
rotation.y+=2*this.delta
|
|
||||||
}
|
|
||||||
if (pc.moveRight) {
|
|
||||||
//inputVelocity.x = 1
|
|
||||||
rotation.y-=2*this.delta
|
|
||||||
}
|
|
||||||
|
|
||||||
//let d = this.gameEngine.camera.position.distanceTo(v);
|
|
||||||
|
|
||||||
inputVelocity.setLength(this.delta * 10)
|
|
||||||
|
|
||||||
// apply camera rotation to inputVelocity
|
|
||||||
//euler.y = this.gameEngine.camera.rotation.y;
|
|
||||||
quat.setFromEuler(rotation)
|
|
||||||
inputVelocity.applyQuaternion(quat)
|
|
||||||
//this.gameEngine.camera.updateProjectionMatrix()
|
|
||||||
//this.gameEngine.orbitControls.update()
|
|
||||||
// if (this.gameEngine.orbitControls){
|
|
||||||
//this.gameEngine.orbitControls.target = v//this.model.position;
|
|
||||||
// //this.gameEngine.orbitControls.update()
|
|
||||||
// }
|
|
||||||
|
|
||||||
//this.gameEngine.camera.rotateOnAxis(v, rotation.y)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(inputVelocity.x || inputVelocity.z){
|
|
||||||
this.setAction(this.actionWalk, true)
|
|
||||||
}else{
|
|
||||||
this.setAction(this.actionIdle, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this.gameEngine.camera.position.lerp(this.characterCollider.position, 1)
|
|
||||||
// this.gameEngine.camera.rotation.y = rotation.y;
|
|
||||||
|
|
||||||
this.model.position.lerp(this.characterCollider.position, 1)
|
|
||||||
this.model.rotation.y = rotation.y;
|
|
||||||
|
|
||||||
}
|
|
||||||
velocity.set(inputVelocity.x, inputVelocity.y, inputVelocity.z)
|
|
||||||
this.colliderBody.applyImpulse(velocity)
|
|
||||||
|
|
||||||
this.delta = Math.min(clock.getDelta(), 0.1)
|
|
||||||
this.gameEngine.phy.world.step(this.delta)
|
|
||||||
|
|
||||||
//cannonDebugRenderer.update()
|
|
||||||
|
|
||||||
this.characterCollider.position.set(this.colliderBody.position.x, this.colliderBody.position.y, this.colliderBody.position.z)
|
|
||||||
// boxes.forEach((b, i) => {
|
|
||||||
// boxMeshes[i].position.set(b.position.x, b.position.y, b.position.z)
|
|
||||||
// boxMeshes[i].quaternion.set(b.quaternion.x, b.quaternion.y, b.quaternion.z, b.quaternion.w)
|
|
||||||
// })
|
|
||||||
|
|
||||||
this.characterCollider.getWorldPosition(v)
|
|
||||||
//this.gameEngine.cameraPivot.position.lerp(v, 0.1)
|
|
||||||
|
|
||||||
// if (this.gameEngine.camera.position.distanceTo(v) > 2){
|
|
||||||
// this.gameEngine.camera.position.set(v.x+1, 3, v.z + 3);
|
|
||||||
// }
|
|
||||||
//this.gameEngine.camera.lookAt(v.x, 3, v.z);
|
|
||||||
//this.gameEngine.camera.rotation.y = this.model.rotation.y;
|
|
||||||
|
|
||||||
// this.gameEngine.camera.updateMatrix();
|
|
||||||
// this.model.updateMatrix();
|
|
||||||
let cam = this.gameEngine.camera;
|
|
||||||
let vv = new THREE.Vector3(), qq = new THREE.Quaternion;
|
|
||||||
this.heroCamera.getWorldPosition(vv)
|
|
||||||
//this.heroCamera.getWorldQuaternion(qq);
|
|
||||||
cam.position.copy(vv)
|
|
||||||
//cam.setRotationFromQuaternion(qq)
|
|
||||||
cam.lookAt(new THREE.Vector3(this.model.position.x, 2.8, this.model.position.z))
|
|
||||||
//console.log(vv)
|
|
||||||
// cam.matrixAutoUpdate = false;
|
|
||||||
// cam.matrix.multiplyMatrices(this.model.matrix, cam.matrix)
|
|
||||||
// //cam.matrix.decompose(cam.position, cam.quaternion, cam.scale)
|
|
||||||
// //this.gameEngine.camera.updateMatrix();
|
|
||||||
// cam.matrixAutoUpdate = true;
|
|
||||||
// this.gameEngine.orbitControls.update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -31,29 +31,25 @@ class Physics{
|
|||||||
let colliderDesc
|
let colliderDesc
|
||||||
|
|
||||||
switch (colliderType) {
|
switch (colliderType) {
|
||||||
case 'cuboid':
|
case 'cuboid':{
|
||||||
{
|
|
||||||
const { width, height, depth } = colliderSettings
|
const { width, height, depth } = colliderSettings
|
||||||
colliderDesc = RAPIER.ColliderDesc.cuboid(width, height, depth)
|
colliderDesc = RAPIER.ColliderDesc.cuboid(width, height, depth)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'ball':
|
case 'ball':{
|
||||||
{
|
|
||||||
const { radius } = colliderSettings
|
const { radius } = colliderSettings
|
||||||
colliderDesc = RAPIER.ColliderDesc.ball(radius)
|
colliderDesc = RAPIER.ColliderDesc.ball(radius)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'capsule':
|
case 'capsule':{
|
||||||
{
|
|
||||||
const { halfHeight, radius } = colliderSettings
|
const { halfHeight, radius } = colliderSettings
|
||||||
colliderDesc = RAPIER.ColliderDesc.capsule(halfHeight, radius)
|
colliderDesc = RAPIER.ColliderDesc.capsule(halfHeight, radius)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:{
|
||||||
{
|
|
||||||
colliderDesc = RAPIER.ColliderDesc.trimesh(
|
colliderDesc = RAPIER.ColliderDesc.trimesh(
|
||||||
mesh.geometry.attributes.position.array,
|
mesh.geometry.attributes.position.array,
|
||||||
mesh.geometry.index?.array
|
mesh.geometry.index?.array
|
||||||
@@ -74,14 +70,21 @@ class Physics{
|
|||||||
|
|
||||||
// * Responsible for collision detection
|
// * Responsible for collision detection
|
||||||
const collider = this.world.createCollider(colliderDesc, rigidBody)
|
const collider = this.world.createCollider(colliderDesc, rigidBody)
|
||||||
|
|
||||||
const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate }
|
const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate }
|
||||||
|
|
||||||
this.physicsObjects.push(physicsObject)
|
this.physicsObjects.push(physicsObject)
|
||||||
|
|
||||||
return physicsObject
|
return physicsObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear(){
|
||||||
|
this.physicsObjects.forEach(po=>{
|
||||||
|
po.characterController && this.world.removeCharacterController(po.characterController)
|
||||||
|
this.world.removeCollider(po.collider);
|
||||||
|
this.world.removeRigidBody(po.rigidBody);
|
||||||
|
})
|
||||||
|
this.physicsObjects = [];
|
||||||
|
}
|
||||||
|
|
||||||
step(){
|
step(){
|
||||||
this.world.step(this.eventQueue)
|
this.world.step(this.eventQueue)
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,10 @@ export default {
|
|||||||
*/
|
*/
|
||||||
async loadEnvironment(scene, target){
|
async loadEnvironment(scene, target){
|
||||||
//await gameEngine.loadPanorama(`/asset/default/43.webp`);
|
//await gameEngine.loadPanorama(`/asset/default/43.webp`);
|
||||||
|
gameEngine.hero?.destroy();
|
||||||
|
gameEngine.dashboard?.reset();
|
||||||
gameEngine.activeObjects.clear();
|
gameEngine.activeObjects.clear();
|
||||||
|
gameEngine.physics.clear();
|
||||||
await this.expandScenarioData(scene);
|
await this.expandScenarioData(scene);
|
||||||
target.objects = target.objects || {};
|
target.objects = target.objects || {};
|
||||||
let l = target.objects;
|
let l = target.objects;
|
||||||
@@ -129,7 +132,8 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.scene.data.$scene){
|
if (this.scene.data.$scene){
|
||||||
let env = await gameEngine.load(`/asset/default/${this.scene.data.$scene.asset.name}`);
|
let env = await gameEngine.load(`/asset/default/${this.scene.data.$scene.asset.name}`);
|
||||||
this.setObjectAttributes(l, this.scene.data, env, 100);
|
//console.log('ENV', env)
|
||||||
|
this.setObjectAttributes(l, this.scene.data, env.scene, env, 100);
|
||||||
gameEngine.activeObjects.add(env.scene);
|
gameEngine.activeObjects.add(env.scene);
|
||||||
}
|
}
|
||||||
for (let i of this.scene.data.items || []) {
|
for (let i of this.scene.data.items || []) {
|
||||||
@@ -137,17 +141,16 @@ export default {
|
|||||||
await io.ready;
|
await io.ready;
|
||||||
|
|
||||||
//let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
|
//let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
|
||||||
let objKey = (i.data.type == 'GenericObject' || !i.data.type) ? 'scene' : 'object'
|
//console.log(i.data, io.object)
|
||||||
console.log(i.data, io.object)
|
this.setObjectAttributes(l, i.data, io.object, io.source, 1);
|
||||||
this.setObjectAttributes(l, i.data, io.object, 1, objKey);
|
gameEngine.activeObjects.add(io.object);
|
||||||
gameEngine.activeObjects.add(io.object[objKey]);
|
|
||||||
if (this.env == 'GamePlaying'){
|
if (this.env == 'GamePlaying'){
|
||||||
if (i.data.$go.type == 'player3d'){
|
if (i.data.$go?.type == 'player3d'){
|
||||||
let hero = new Hero(io.object, i.data.$go);
|
let hero = new Hero(io.source, i.data.$go);
|
||||||
hero.init(gameEngine);
|
hero.init(gameEngine);
|
||||||
}else{
|
}else{
|
||||||
if (io.object.animations?.length){
|
if (io.source?.animations?.length){
|
||||||
gameEngine.playAnimation(gameEngine.scene, io.object.animations[0]);
|
gameEngine.playAnimation(gameEngine.scene, io.source.animations[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,22 +188,22 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setObjectAttributes(l, data, o, autoScaleFactor = 1, objectKey = 'scene'){
|
setObjectAttributes(l, data, object, source, autoScaleFactor = 1){
|
||||||
if (l[data.id]){
|
if (l[data.id]){
|
||||||
['position', 'scale', 'rotation'].forEach(p=>{
|
['position', 'scale', 'rotation'].forEach(p=>{
|
||||||
o[objectKey][p].copy(l[data.id][p])
|
object[p].copy(l[data.id][p])
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
gameEngine.autoScale(o[objectKey], autoScaleFactor);
|
gameEngine.autoScale(object, autoScaleFactor);
|
||||||
}
|
}
|
||||||
l[data.id] = l[data.id] || {};
|
l[data.id] = l[data.id] || {};
|
||||||
['position', 'scale', 'rotation', 'visible'].forEach(p=>{
|
['position', 'scale', 'rotation', 'visible'].forEach(p=>{
|
||||||
l[data.id][p] = o[objectKey][p];
|
l[data.id][p] = object[p];
|
||||||
})
|
})
|
||||||
l[data.id].__o = o[objectKey];
|
l[data.id].__o = object;
|
||||||
l[data.id].__g = o;
|
l[data.id].__g = source;
|
||||||
l[data.id].__title = data.title;
|
l[data.id].__title = data.title;
|
||||||
o[objectKey].__pn_id = data.id;
|
object.__pn_id = data.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
async toggleAnimation(animation){
|
async toggleAnimation(animation){
|
||||||
|
|||||||
Reference in New Issue
Block a user