diff --git a/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js b/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js index 0b78cf6..4102778 100644 --- a/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js +++ b/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js @@ -1,5 +1,6 @@ import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide } from 'three'; import { InteractiveObject } from '../InteractiveObject'; +import { ActiveEvents } from '@dimforge/rapier3d'; class MazeObject { constructor(engine, def, params = {}){ @@ -32,16 +33,21 @@ class MazeObject { let o = {}; - function addPhysics(matrix, position, size, placement = 'side'){ + function addPhysics(matrix, position, size, placement = 'side', isSensor = false, userData){ let quat = new Quaternion().setFromRotationMatrix(matrix); let v = new Vector3(...position).applyMatrix4(matrix); + if (typeof size == 'number'){ + size = { width: 0.1, height:1, depth:size/2 } + } let po = engine.phy.add( - {position: v}, 'fixed', false, undefined, 'cuboid',{ width: 0.01, height:1, depth:size/2 } + {position: v}, 'fixed', false, undefined, 'cuboid', + { ...size, isSensor, userData } ) if (placement == 'front') { quat.multiply(new Quaternion(0, 0.7071068, 0, 0.7071068)) //rotate by 90deg } po.rigidBody.setRotation(quat, true) + return po; } function addRoom(elements, def, offsetZ){ @@ -105,6 +111,9 @@ class MazeObject { // mazeMeshes.push(e); addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -context.wallSize) } + if (def.userData?.answer !== undefined){ + addPhysics(def.matrix, [0,0,0], context.wallSize, 'front', true, def.userData) + } for (let i = 0; i < def.len; i++) { let t = o.tunnel.clone(); t.position.set(0, 0, i * context.tubeSize); @@ -125,8 +134,10 @@ class MazeObject { addPhysics(def.matrix, [-context.tubeSize / 2, 0.6, offsetZ/2], offsetZ) addRoom(['floor', 'door', def.r ? 'door' : 'wall', def.f ? 'door' : 'wall', def.l ? 'door' : 'wall'], def, offsetZ) - - console.log('loadingggg', def.objects) + if (def.userData?.qid !== undefined || def.userData?.finish){ + addPhysics(def.matrix, [0,0,offsetZ + context.wallSize/2], { width: context.wallSize/2, height: context.wallSize/2, depth: context.wallSize/2}, 'side', true, def.userData) + } + //console.log('loadingggg', def.objects) def.objects?.forEach(async obj => { obj.room = room; // let go = new GameObject(obj, context); @@ -172,7 +183,7 @@ class MazeObject { // //engine.phy.add(mesh, 'fixed') // }) - console.log(bbox, 'bbox') + //console.log(bbox, 'bbox') const floorGeometry = new PlaneGeometry(bbox.r - bbox.l + 10*scale, bbox.f + 10*scale); const floor = new Mesh(floorGeometry,new MeshStandardMaterial({ roughness: 0, metalness:1, color: 0x00ffff, side: DoubleSide diff --git a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js index 9954e5f..d03e54a 100644 --- a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js +++ b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js @@ -20,6 +20,24 @@ class MazeQuizGame { constructor(engine, context, questions) { let def = this.generate(questions); this.mazeObject = new MazeObject(engine, def) + engine.addEventListener('collision', async e=>{ + let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData; + if (ud?.finish && engine.hero?.animationsMap?.win){ + if (e.started){ + engine.hero.animationsMap._idle = engine.hero.animationsMap.idle + engine.hero.animationsMap.idle = engine.hero.animationsMap.win + engine.hero.characterControls.cameraDelta = Math.PI; + await Utils.wait(1000); + engine.hero.characterControls.direction += Math.PI; + await Utils.wait(10000); + this.onfinish?.() + }else{ + engine.hero.animationsMap.idle = engine.hero.animationsMap._idle + engine.hero.characterControls.cameraDelta = 0 + } + } + //console.log(e, ud, engine.hero?.animationsMap); + }) } async load(){ @@ -32,7 +50,7 @@ class MazeQuizGame { let question = questions[qid] if (!question) return { len:3, - finish: true, + userData: { finish: true }, objects:[ { type: 'gltf', @@ -52,7 +70,7 @@ class MazeQuizGame { let directions = Utils.shuffleArray( ['l', 'r', 'f'] ) let mo = { - len, question, qid, + len, userData: { question, qid }, objects:[ { type: 'text', text: question.q, fontSize:0.033, width:0.5, position:[0,.33,len + .96], rotation:[0,Math.PI, 0] @@ -74,11 +92,13 @@ class MazeQuizGame { } if (i == 0){ mo[d] = { - len: 3, + answer: i, + len: 4, [dd]: this.generate(questions, qid + 1, 3) } }else{ mo[d] = { + userData: { question, qid, answer: i }, len: 4, [dd]: { len: 2, diff --git a/src/lib/CharacterControls.js b/src/lib/CharacterControls.js index 74a786e..b05559e 100644 --- a/src/lib/CharacterControls.js +++ b/src/lib/CharacterControls.js @@ -1,6 +1,5 @@ import * as THREE from 'three' - -export const CONTROLLER_BODY_RADIUS = 0.28; +import { QueryFilterFlags } from '@dimforge/rapier3d'; export class CharacterControls { @@ -33,7 +32,7 @@ export class CharacterControls { // // Automatically slide down on slopes smaller than 30 degrees. // this.characterController.setMinSlopeSlideAngle(30 * Math.PI / 180); // this.characterController.enableAutostep(0.5, 0.2, true); - // this.characterController.setApplyImpulsesToDynamicBodies(true); + this.characterController.setApplyImpulsesToDynamicBodies(true); // this.characterController.setCharacterMass(50); this.po = po; this.pointerControls = pointerControls @@ -47,20 +46,14 @@ export class CharacterControls { this.actionStart = 0; this.cameraDelta = 0; this.cameraIdleDelta = 0; - //this.toggleRun = true - } - - switchRunToggle() { - this.toggleRun = !this.toggleRun } update(world, delta, pointerControls) { - const directionPressed = pointerControls.moving let input = this.getInput(pointerControls) let play = this.currentAction || 'idle', velocity = this.walkVelocity; this.fadeDuration = 0.2; - if (input[1] && this.toggleRun) { + if (input[1] && pointerControls.running) { play = 'run'; velocity = this.runVelocity } else if (input[1] > 0) { @@ -111,23 +104,13 @@ export class CharacterControls { this.walkDirection.x = this.walkDirection.y = this.walkDirection.z = 0 - if (directionPressed) { + if (pointerControls.motion) { this.directionVelocity = this.directionVelocity * 2.5 * Math.abs(input[0]) this.direction += input[0] * delta * 2.5 //this.directionVelocity; this.model.rotation.y = this.direction; this.walkDirection.set(0, 0, input[1]) this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction) this.walkDirection.normalize(); - - - //let dst = Math.sqrt(Math.pow(cameraPosition.x - this.model.position.x, 2) + Math.pow(cameraPosition.z - this.model.position.z, 2)); - //cameraPosition.y = 8 - dst; - //if (dst >0.52){ - //} - //this.camera.zoom = dst - //this.camera.updateProjectionMatrix(); - - // run/walk velocity } this.walkDirection.x = this.walkDirection.x * velocity * delta// + this.model.position.x @@ -138,8 +121,15 @@ export class CharacterControls { this.characterController.computeColliderMovement( this.po.collider, // The collider we would like to move. this.walkDirection, // The movement we would like to apply if there wasn’t any obstacle. + QueryFilterFlags['EXCLUDE_SENSORS'] ); + // for (let i = 0; i < this.characterController.numComputedCollisions(); i++) { + // let collision = this.characterController.computedCollision(i); + // //console.log(collision) + // // Do something with that collision information. + // } + let correctedMovement = this.characterController.computedMovement(); let v = new THREE.Vector3(); diff --git a/src/lib/GameEngine.js b/src/lib/GameEngine.js index adc37b0..2790a66 100644 --- a/src/lib/GameEngine.js +++ b/src/lib/GameEngine.js @@ -17,7 +17,7 @@ import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFa import { Physics } from './Physics.js'; import { Clickable } from './Clickable.js'; -class GameEngine { +class GameEngine extends THREE.EventDispatcher{ async init(domNode, opts = {}) { this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800; this.aspect = this.w / this.h @@ -251,37 +251,7 @@ class GameEngine { } async initPhysics() { - //const world = new CANNON.World() - //world.gravity.set(0, -9.82, 0) - // let gravity = { x: 0.0, y: -9.81, z: 0.0 }; - // let world = new RAPIER.World(gravity); - - // // Create Ground. - // let bodyDesc = RAPIER.RigidBodyDesc.fixed(); - // let body = world.createRigidBody(bodyDesc); - // let colliderDesc = RAPIER.ColliderDesc.cuboid(100.0, 0.1, 100.0); - // world.createCollider(colliderDesc, body.handle); - - // const groundMaterial = new CANNON.Material('groundMaterial') - // const slipperyMaterial = new CANNON.Material('slipperyMaterial') - // const slippery_ground_cm = new CANNON.ContactMaterial( - // groundMaterial, - // slipperyMaterial, - // { - // friction: 0, - // restitution: 0.3, - // contactEquationStiffness: 1e10, - // contactEquationRelaxation: 30, - // } - // ) - // world.addContactMaterial(slippery_ground_cm) - // const planeShape = new CANNON.Plane() - // const planeBody = new CANNON.Body({ mass: 0, material: groundMaterial }) - // planeBody.addShape(planeShape) - // planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2) - // world.addBody(planeBody) - - this.phy = new Physics(); + this.phy = new Physics(this); await this.phy.init(); } diff --git a/src/lib/Hero.js b/src/lib/Hero.js index b2ec335..0adb58b 100644 --- a/src/lib/Hero.js +++ b/src/lib/Hero.js @@ -2,7 +2,6 @@ import { AnimationMixer } from 'three'; import { PointerControls } from './PointerControls'; import { CharacterControls } from './CharacterControls'; import * as THREE from 'three'; -import * as RAPIER from '@dimforge/rapier3d' class Hero{ @@ -18,9 +17,9 @@ class Hero{ gameEngine.camera.position.set(0,17,-30) 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.heroCamera = new THREE.Object3D() + // this.model.add(this.heroCamera) + // this.heroCamera.applyMatrix4(gameEngine.camera.matrix) this.mixer = new AnimationMixer(this.model); gameEngine.mixers.push( this.mixer ); @@ -35,6 +34,7 @@ class Hero{ let po = gameEngine.phy.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: 0.5, halfHeight: 1}) po.collider.setTranslationWrtParent({ x: 0, y: 2.0, z: 0 }); + //po.collider.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS); this.characterControls = new CharacterControls(this.model, this.mixer, this.animationsMap, gameEngine, 'idle', po, this.pointerControls) @@ -53,16 +53,16 @@ class Hero{ //return if (this.gameEngine.renderer.xr.isPresenting) return; - if (this.ready) { + if (this.ready && !this.disableInput) { let pc = this.pointerControls; pc.update(); let dlt = this.clock.getDelta(); this.delta += dlt; - if (this.delta > 0.00001){ - this.gameEngine.phy.step() + //if (this.delta > 0.00001){ this.characterControls.update(this.gameEngine.phy.world, this.delta, pc) + this.gameEngine.phy.step() this.delta = 0; - } + //} } diff --git a/src/lib/Physics.js b/src/lib/Physics.js index 3fce5b1..e4e391c 100644 --- a/src/lib/Physics.js +++ b/src/lib/Physics.js @@ -1,16 +1,7 @@ import * as RAPIER from '@dimforge/rapier3d' - -// export type PhysicsObject = { -// mesh: THREE.Mesh -// collider: Rapier.Collider -// rigidBody: Rapier.RigidBody -// fn?: Function -// autoAnimate: boolean -// } - class Physics{ - constructor(){ - + constructor(engine){ + this.engine = engine } init = async ()=>{ @@ -25,6 +16,7 @@ class Physics{ world.createCollider(colliderDesc, body.handle); this.world = world; this.physicsObjects = []; + this.eventQueue = new RAPIER.EventQueue(true); return this; } @@ -34,6 +26,7 @@ class Physics{ // * Responsible for collision response const rigidBody = this.world.createRigidBody(rigidBodyDesc) + rigidBody.userData = colliderSettings.userData || {}; let colliderDesc @@ -73,6 +66,12 @@ class Physics{ console.error('Collider Mesh Error: convex mesh creation failed.') } + if (colliderSettings.isSensor){ + colliderDesc.setSensor(true); + colliderDesc.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS); + colliderDesc.setActiveCollisionTypes(RAPIER.ActiveCollisionTypes.KINEMATIC_FIXED); + } + // * Responsible for collision detection const collider = this.world.createCollider(colliderDesc, rigidBody) @@ -84,7 +83,7 @@ class Physics{ } step(){ - this.world.step() + this.world.step(this.eventQueue) for (let po of this.physicsObjects) { const autoAnimate = po.autoAnimate @@ -99,6 +98,14 @@ class Physics{ const fn = po.fn fn && fn() } + + this.eventQueue.drainCollisionEvents((handle1, handle2, started) => { + /* Handle the collision event. */ + //console.log(this.world.getCollider(handle2), handle1, started) + this.engine.dispatchEvent({ + type: 'collision', handle1, handle2, started + }) + }); } } diff --git a/src/lib/PointerControls.js b/src/lib/PointerControls.js index 2022516..53aa121 100644 --- a/src/lib/PointerControls.js +++ b/src/lib/PointerControls.js @@ -139,7 +139,19 @@ class PointerControls { } get moving(){ - return this.moveForward || this.moveBackward || this.moveLeft || this.moveRight; + return this.moveForward || this.moveBackward; + } + + get rotating(){ + return this.rotateLeft || this.rotateRight; + } + + get motion(){ + return this.moving || this.rotating; + } + + get running(){ + return this.moving && this.kb.ShiftLeft; } } diff --git a/src/lib/Utils.js b/src/lib/Utils.js index 79ce865..e6e9752 100644 --- a/src/lib/Utils.js +++ b/src/lib/Utils.js @@ -58,5 +58,17 @@ export default { shuffleArray(arr){ return arr.map(value => ({ value, sort: Math.random() })) .sort((a, b) => a.sort - b.sort).map(({ value }) => value) + }, + + async wait(ms){ + await new Promise((resolve, reject)=>{ + setTimeout(resolve, ms) + }) + }, + + async waitFor(expFn){ + while (!expFn()){ + await JsUtils.wait(200); + } } } \ No newline at end of file