add physics to interactive objects
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { GameEngine } from '@/lib/GameEngine.js';
|
import { GameEngine } from '@/lib/GameEngine.js';
|
||||||
|
import { autoScale } from '@/lib/MeshUtils';
|
||||||
let gameEngine = null;
|
let gameEngine = null;
|
||||||
|
|
||||||
export default{
|
export default{
|
||||||
@@ -75,7 +76,7 @@ export default{
|
|||||||
this.animations = gltf.animations.map(a => ({
|
this.animations = gltf.animations.map(a => ({
|
||||||
name: a.name, id: a.uuid
|
name: a.name, id: a.uuid
|
||||||
}));
|
}));
|
||||||
gameEngine.autoScale(gltf.scene);
|
autoScale(gltf.scene);
|
||||||
let bb = new gameEngine.$.Box3().setFromObject(gltf.scene);
|
let bb = new gameEngine.$.Box3().setFromObject(gltf.scene);
|
||||||
gltf.scene.traverse(function (o) {
|
gltf.scene.traverse(function (o) {
|
||||||
o.frustumCulled = false;
|
o.frustumCulled = false;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxMaxLength } from "@/lib/MeshUtils";
|
import { getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxMaxLength, centerOrigin } from "@/lib/MeshUtils";
|
||||||
|
|
||||||
class GenericObject{
|
class GenericObject{
|
||||||
constructor(engine, data){
|
constructor(engine, data){
|
||||||
return new Promise(async(resolve, reject)=>{
|
return new Promise(async(resolve, reject)=>{
|
||||||
this.source = await engine.load(data.$go.asset.name);
|
this.source = await engine.load(data.$go.asset.name);
|
||||||
this.object = this.source.scene;
|
this.object = centerOrigin(this.source.scene)
|
||||||
|
|
||||||
if (!data.exclude){
|
if (!data.exclude){
|
||||||
engine.clickable.add(this.object, async e=>{
|
engine.clickable.add(this.object, async e=>{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea>
|
<v-textarea :label="l.description" v-model="modelValue.description"></v-textarea>
|
||||||
<v-checkbox density="compact" v-model="modelValue.hud" hide-details label="Observe in head-up display"></v-checkbox>
|
<v-checkbox density="compact" v-model="modelValue.hud" hide-details label="Observe in head-up display"></v-checkbox>
|
||||||
<v-checkbox density="compact" v-model="modelValue.exclude" hide-details label="Disable interactions"></v-checkbox>
|
<v-checkbox density="compact" v-model="modelValue.exclude" hide-details label="Disable interactions"></v-checkbox>
|
||||||
|
<v-checkbox density="compact" v-model="modelValue.noPhysics" hide-details label="Disable physics"></v-checkbox>
|
||||||
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
|
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
|
||||||
<div class="text-caption text-center">{{ modelValue.title }}</div>
|
<div class="text-caption text-center">{{ modelValue.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export default {
|
|||||||
props:['modelValue'],
|
props:['modelValue'],
|
||||||
mounted(){
|
mounted(){
|
||||||
this.modelValue.questions ??= [];
|
this.modelValue.questions ??= [];
|
||||||
|
this.modelValue.noPhysics = true;
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
addQuestion(){
|
addQuestion(){
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export default {
|
|||||||
this.modelValue.count = 1000;
|
this.modelValue.count = 1000;
|
||||||
this.modelValue.w = 50;
|
this.modelValue.w = 50;
|
||||||
this.modelValue.h = 50;
|
this.modelValue.h = 50;
|
||||||
|
this.modelValue.noPhysics = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
|
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
|
||||||
import { MotionEngine } from '../../lib/MotionEngine';
|
import { MotionEngine } from '../../lib/MotionEngine';
|
||||||
|
import { centerOrigin } from '@/lib/MeshUtils';
|
||||||
|
|
||||||
class PuzzleGame1 {
|
class PuzzleGame1 {
|
||||||
constructor(engine, data) {
|
constructor(engine, data) {
|
||||||
return new Promise(async (resolve, reject)=>{
|
return new Promise(async (resolve, reject)=>{
|
||||||
let w = data.w, h = data.h, wh = w*h;
|
let w = data.w, h = data.h, wh = w*h;
|
||||||
this.object = new Group();
|
let container = new Group();
|
||||||
const aq = new MotionEngine();
|
const aq = new MotionEngine();
|
||||||
const pr = [[0, 2], [0, -1], [0, 1], [1, 0], [-1, 0], [0, 0]];
|
const pr = [[0, 2], [0, -1], [0, 1], [1, 0], [-1, 0], [0, 0]];
|
||||||
let d = 1.2;
|
let d = 1.2;
|
||||||
@@ -50,16 +51,16 @@ class PuzzleGame1 {
|
|||||||
if (idxs[4] == idxs[5]){
|
if (idxs[4] == idxs[5]){
|
||||||
mesh._dd = true;
|
mesh._dd = true;
|
||||||
}
|
}
|
||||||
this.object.add(mesh);
|
container.add(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.object.children[0].onBeforeRender = () => {
|
container.children[0].onBeforeRender = () => {
|
||||||
this.update();
|
this.update();
|
||||||
};
|
};
|
||||||
|
|
||||||
var check = () => {
|
var check = () => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
this.object.children.forEach(o=>{
|
container.children.forEach(o=>{
|
||||||
if (o._ri == 5 || o._dd && o._ri == 0) i++;
|
if (o._ri == 5 || o._dd && o._ri == 0) i++;
|
||||||
})
|
})
|
||||||
return i == wh;
|
return i == wh;
|
||||||
@@ -76,7 +77,7 @@ class PuzzleGame1 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.object.children.forEach(c => {
|
container.children.forEach(c => {
|
||||||
engine.clickable.add(c, clickFn);
|
engine.clickable.add(c, clickFn);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,18 +85,20 @@ class PuzzleGame1 {
|
|||||||
aq.update();
|
aq.update();
|
||||||
if (aq.isIdle() && !this.done && check()) {
|
if (aq.isIdle() && !this.done && check()) {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
this.object.children.forEach((c, i) => {
|
container.children.forEach((c, i) => {
|
||||||
aq.add({
|
aq.add({
|
||||||
o: c,
|
o: c,
|
||||||
a: { position: { x: i % w, y: Math.trunc(i/w)} },
|
a: { position: { x: i % w, y: Math.trunc(i/w)} },
|
||||||
t: 1,
|
t: 1,
|
||||||
f: i == 0 && this.onfinish
|
f: i == 0 && this.onfinish || undefined
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//engine.dashboard.addPoints(10);
|
//engine.dashboard.addPoints(10);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.object = centerOrigin(container);
|
||||||
|
|
||||||
resolve(this);
|
resolve(this);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
|
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three';
|
||||||
import { MotionEngine } from '../../lib/MotionEngine';
|
import { MotionEngine } from '../../lib/MotionEngine';
|
||||||
|
import { centerOrigin } from '@/lib/MeshUtils';
|
||||||
|
|
||||||
class PuzzleGame2 {
|
class PuzzleGame2 {
|
||||||
constructor(engine, data) {
|
constructor(engine, data) {
|
||||||
@@ -17,7 +18,7 @@ class PuzzleGame2 {
|
|||||||
});
|
});
|
||||||
let last, lidx = w - 1;
|
let last, lidx = w - 1;
|
||||||
|
|
||||||
this.object = new Group();
|
let container = new Group();
|
||||||
let d = 1.2, p = [];
|
let d = 1.2, p = [];
|
||||||
|
|
||||||
function check() {
|
function check() {
|
||||||
@@ -60,7 +61,7 @@ class PuzzleGame2 {
|
|||||||
// }
|
// }
|
||||||
p.forEach((e, i) => {
|
p.forEach((e, i) => {
|
||||||
let x = e % w, y = ~~(e / h);
|
let x = e % w, y = ~~(e / h);
|
||||||
this.object.children[i].position.set(x * d, y * d, 0);
|
container.children[i].position.set(x * d, y * d, 0);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,11 +90,11 @@ class PuzzleGame2 {
|
|||||||
}
|
}
|
||||||
let mesh = new Mesh(bg, i != lidx ? material : m2);
|
let mesh = new Mesh(bg, i != lidx ? material : m2);
|
||||||
mesh.position.set(x * d, y * d, 0);
|
mesh.position.set(x * d, y * d, 0);
|
||||||
this.object.add(mesh);
|
container.add(mesh);
|
||||||
}
|
}
|
||||||
last = this.object.children[lidx];
|
last = container.children[lidx];
|
||||||
|
|
||||||
this.object.children[0].onBeforeRender = () => {
|
container.children[0].onBeforeRender = () => {
|
||||||
this.update();
|
this.update();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ class PuzzleGame2 {
|
|||||||
|
|
||||||
let clickFn = (i) => {
|
let clickFn = (i) => {
|
||||||
if (!this.done && !aq.isActive(i.object)) {
|
if (!this.done && !aq.isActive(i.object)) {
|
||||||
let idx = this.object.children.indexOf(i.object);
|
let idx = container.children.indexOf(i.object);
|
||||||
if (idx == lidx) return; //we ignore the empty cell
|
if (idx == lidx) return; //we ignore the empty cell
|
||||||
let xc = p[idx] % w, yc = ~~(p[idx] / h);
|
let xc = p[idx] % w, yc = ~~(p[idx] / h);
|
||||||
let xl = p[lidx] % w, yl = ~~(p[lidx] / h);
|
let xl = p[lidx] % w, yl = ~~(p[lidx] / h);
|
||||||
@@ -122,7 +123,7 @@ class PuzzleGame2 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.object.children.forEach(c => {
|
container.children.forEach(c => {
|
||||||
engine.clickable.add(c, clickFn);
|
engine.clickable.add(c, clickFn);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ class PuzzleGame2 {
|
|||||||
aq.update();
|
aq.update();
|
||||||
if (aq.isIdle() && !this.done && check()) {
|
if (aq.isIdle() && !this.done && check()) {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
this.object.children.forEach((c, i) => {
|
container.children.forEach((c, i) => {
|
||||||
last.material = material;
|
last.material = material;
|
||||||
aq.add({
|
aq.add({
|
||||||
o: c,
|
o: c,
|
||||||
@@ -142,6 +143,7 @@ class PuzzleGame2 {
|
|||||||
//engine.dashboard.addPoints(10);
|
//engine.dashboard.addPoints(10);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.object = centerOrigin(container)
|
||||||
resolve(this)
|
resolve(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-6
@@ -1,4 +1,4 @@
|
|||||||
import { TextureLoader, Box3, Vector3 } from "three";
|
import { TextureLoader, Box3, Vector3, Group } from "three";
|
||||||
|
|
||||||
function assignParams(mesh, params){
|
function assignParams(mesh, params){
|
||||||
['scale', 'rotation', 'position'].forEach(p=>params[p] && mesh[p].fromArray(params[p]));
|
['scale', 'rotation', 'position'].forEach(p=>params[p] && mesh[p].fromArray(params[p]));
|
||||||
@@ -30,16 +30,22 @@ function getBoundingBox(object){
|
|||||||
return new Box3().setFromObject(object);
|
return new Box3().setFromObject(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBoundingBoxSize(bb){
|
||||||
|
return new Vector3(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z);
|
||||||
|
}
|
||||||
|
|
||||||
function getBoundingBoxMaxLength(bb){
|
function getBoundingBoxMaxLength(bb){
|
||||||
return Math.max(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z)
|
let size = getBoundingBoxSize(bb)
|
||||||
|
return Math.max(size.x, size.y, size.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBoundingBoxCenterPoint(bb, relativeTo){
|
function getBoundingBoxCenterPoint(bb, relativeTo){
|
||||||
relativeTo = relativeTo || new Vector3(0,0,0)
|
relativeTo = relativeTo || new Vector3(0,0,0)
|
||||||
|
let size = getBoundingBoxSize(bb)
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
bb.min.x + (bb.max.x - bb.min.x)/2 - relativeTo.x,
|
bb.min.x + (size.x)/2 - relativeTo.x,
|
||||||
bb.min.y + (bb.max.y - bb.min.y)/2 - relativeTo.y,
|
bb.min.y + (size.y)/2 - relativeTo.y,
|
||||||
bb.min.z + (bb.max.z - bb.min.z)/2 - relativeTo.z
|
bb.min.z + (size.z)/2 - relativeTo.z
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,4 +55,16 @@ function autoScale(object, mk = 1) {
|
|||||||
object.scale.multiplyScalar(mk / k);
|
object.scale.multiplyScalar(mk / k);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { assignParams, assignMaterial, autoScale, getBoundingBox, getBoundingBoxMaxLength, getBoundingBoxCenterPoint }
|
function centerOrigin(object){
|
||||||
|
let result = new Group();
|
||||||
|
let bb = getBoundingBox(object);
|
||||||
|
let position = getBoundingBoxCenterPoint(bb, object.position).negate();
|
||||||
|
object.position.copy(position)
|
||||||
|
result.add(object);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
assignParams, assignMaterial, autoScale, centerOrigin,
|
||||||
|
getBoundingBox, getBoundingBoxSize, getBoundingBoxMaxLength, getBoundingBoxCenterPoint
|
||||||
|
}
|
||||||
+7
-11
@@ -20,9 +20,10 @@ class Physics{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
add = (mesh, rigidBodyType, autoAnimate = true, postPhysicsFn, colliderType, colliderSettings) => {
|
add = (mesh, rigidBodyType, autoAnimate = true, postPhysicsFn, colliderType, colliderSettings = {}) => {
|
||||||
const rigidBodyDesc = RAPIER.RigidBodyDesc[rigidBodyType]()
|
const rigidBodyDesc = RAPIER.RigidBodyDesc[rigidBodyType]()
|
||||||
rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z)
|
rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z)
|
||||||
|
mesh.quaternion && rigidBodyDesc.setRotation(mesh.quaternion)
|
||||||
|
|
||||||
// * Responsible for collision response
|
// * Responsible for collision response
|
||||||
const rigidBody = this.world.createRigidBody(rigidBodyDesc)
|
const rigidBody = this.world.createRigidBody(rigidBodyDesc)
|
||||||
@@ -70,6 +71,7 @@ class Physics{
|
|||||||
|
|
||||||
// * Responsible for collision detection
|
// * Responsible for collision detection
|
||||||
const collider = this.world.createCollider(colliderDesc, rigidBody)
|
const collider = this.world.createCollider(colliderDesc, rigidBody)
|
||||||
|
mesh.quaternion && collider.setRotationWrtParent(mesh.quaternion)
|
||||||
const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate }
|
const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate }
|
||||||
this.physicsObjects.push(physicsObject)
|
this.physicsObjects.push(physicsObject)
|
||||||
|
|
||||||
@@ -89,17 +91,11 @@ class Physics{
|
|||||||
this.world.step(this.eventQueue)
|
this.world.step(this.eventQueue)
|
||||||
|
|
||||||
for (let po of this.physicsObjects) {
|
for (let po of this.physicsObjects) {
|
||||||
const autoAnimate = po.autoAnimate
|
if (po.autoAnimate) {
|
||||||
|
po.mesh.position.copy(po.rigidBody.translation())
|
||||||
if (autoAnimate) {
|
po.mesh.quaternion.copy(po.rigidBody.rotation() )
|
||||||
const mesh = po.mesh
|
|
||||||
const collider = po.collider
|
|
||||||
mesh.position.copy(collider.translation())
|
|
||||||
mesh.quaternion.copy(collider.rotation() )
|
|
||||||
}
|
}
|
||||||
|
po.fn?.()
|
||||||
const fn = po.fn
|
|
||||||
fn && fn()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
|
this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { InteractiveObject } from '@/components/InteractiveObjects/InteractiveOb
|
|||||||
import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer';
|
import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer';
|
||||||
import { GameEngine } from '@/lib/GameEngine';
|
import { GameEngine } from '@/lib/GameEngine';
|
||||||
import { Hero } from '@/lib/Hero';
|
import { Hero } from '@/lib/Hero';
|
||||||
import { autoScale } from '@/lib/MeshUtils';
|
import { autoScale, getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxSize } from '@/lib/MeshUtils';
|
||||||
let gameEngine = null;
|
let gameEngine = null;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -151,6 +151,14 @@ export default {
|
|||||||
if (io.source?.animations?.length){
|
if (io.source?.animations?.length){
|
||||||
gameEngine.playAnimation(gameEngine.scene, io.source.animations[0]);
|
gameEngine.playAnimation(gameEngine.scene, io.source.animations[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!i.data.noPhysics){
|
||||||
|
let bb = getBoundingBox(io.object);
|
||||||
|
let bbs = getBoundingBoxSize(bb);
|
||||||
|
gameEngine.physics.add(io.object, 'fixed', false, undefined, 'capsule', {
|
||||||
|
radius: Math.max(bbs.x, bbs.z)/2, halfHeight: bbs.y/2
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user