UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

649 lines (589 loc) 21.2 kB
import MatrixEngineWGPU from "./src/world.js"; import {downloadMeshes} from './src/engine/loader-obj.js'; import {byId, LOG_FUNNY, LOG_INFO, LOG_MATRIX, mb, randomFloatFromTo, randomIntFromTo} from "./src/engine/utils.js"; import {dices, myDom} from "./examples/games/jamb/jamb.js"; import {addRaycastsAABBListener, addRaycastsListener, touchCoordinate, rayIntersectsSphere, getRayFromMouse} from "./src/engine/raycast.js"; export let application = new MatrixEngineWGPU({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 } }, () => { application.addLight(); console.log('light added.') application.lightContainer[0].outerCutoff = 0.5; application.lightContainer[0].position[2] = -10; application.lightContainer[0].intensity = 2; application.lightContainer[0].target[2] = -25; application.lightContainer[0].position[1] = 9; application.globalAmbient[0] = 0.7; application.globalAmbient[1] = 0.7; application.globalAmbient[2] = 0.7; const diceTexturePath = './res/meshes/jamb/dice.png'; // Dom operations application.userState = { name: 'Guest', points: 0 }; application.myDom = myDom; myDom.createJamb(); myDom.addDraggerForTable(); myDom.createBlocker(); application.dices = dices; application.activateDiceClickListener = null; // ------------------------- // TEST application.matrixAmmo.detectTopFaceFromQuat = (q) => { // Define based on *visual face* → object-space normal mapping const faces = [ {face: 1, vec: [0, 1, 0]}, // top {face: 2, vec: [0, -1, 0]}, // bottom {face: 3, vec: [0, 0, 1]}, // front {face: 4, vec: [0, 0, -1]}, // back {face: 5, vec: [1, 0, 0]}, // right {face: 6, vec: [-1, 0, 0]} // left ]; let maxDot = -Infinity; let topFace = null; for(const f of faces) { const v = application.matrixAmmo.applyQuatToVec(q, f.vec); const dot = v.y; // Compare with world up (0, 1, 0) if(dot > maxDot) { maxDot = dot; topFace = f.face; } } return topFace; }; application.matrixAmmo.applyQuatToVec = (q, vec) => { const [x, y, z] = vec; const qx = q.x(), qy = q.y(), qz = q.z(), qw = q.w(); // Quaternion * vector * inverse(quaternion) const ix = qw * x + qy * z - qz * y; const iy = qw * y + qz * x - qx * z; const iz = qw * z + qx * y - qy * x; const iw = -qx * x - qy * y - qz * z; return { x: ix * qw + iw * -qx + iy * -qz - iz * -qy, y: iy * qw + iw * -qy + iz * -qx - ix * -qz, z: iz * qw + iw * -qz + ix * -qy - iy * -qx }; } // ------------------------- // This code must be on top (Physics) application.matrixAmmo.detectCollision = function() { this.lastRoll = ''; this.presentScore = ''; let dispatcher = this.dynamicsWorld.getDispatcher(); let numManifolds = dispatcher.getNumManifolds(); for(let i = 0;i < numManifolds;i++) { let contactManifold = dispatcher.getManifoldByIndexInternal(i); // let numContacts = contactManifold.getNumContacts(); if(this.ground.kB == contactManifold.getBody0().kB || this.ground.kB == contactManifold.getBody1().kB) { // console.log(this.ground ,'GROUND IS IN CONTACT WHO IS BODY1 ', contactManifold.getBody1()) // CHECK ROTATION BEST WAY - VISAL PART IS NOT INTEREST NOW if(this.ground.kB == contactManifold.getBody0().kB) { var MY_DICE_NAME = this.getNameByBody(contactManifold.getBody1()); var testR = contactManifold.getBody1().getWorldTransform().getRotation(); } if(this.ground.kB == contactManifold.getBody1().kB) { var MY_DICE_NAME = this.getNameByBody(contactManifold.getBody0()); var testR = contactManifold.getBody0().getWorldTransform().getRotation(); } var passed = false; const face = application.matrixAmmo.detectTopFaceFromQuat(testR); if(face) { this.lastRoll = face.toString(); // Update score logic dispatchEvent(new CustomEvent(`dice-${face}`, {detail: {result: `dice-${face}`, cubeId: MY_DICE_NAME}})); } // if(Math.abs(testR.y()) < 0.00001) { // this.lastRoll = "3"; // this.presentScore += 4; // passed = true; // } // if(Math.abs(testR.x()) < 0.00001) { // this.lastRoll = "5"; // this.presentScore += 3; // passed = true; // } // if(testR.x().toString().substring(0, 5) == testR.y().toString().substring(1, 6)) { // this.lastRoll = "6"; // this.presentScore += 2; // passed = true; // } // if(testR.x().toString().substring(0, 5) == testR.y().toString().substring(0, 5)) { // this.lastRoll = "2"; // this.presentScore += 1; // passed = true; // } // if(testR.z().toString().substring(0, 5) == testR.y().toString().substring(1, 6)) { // this.lastRoll = "4"; // this.presentScore += 6; // passed = true; // } // if(testR.z().toString().substring(0, 5) == testR.y().toString().substring(0, 5)) { // this.lastRoll = "1"; // this.presentScore += 5; // passed = true; // } // if(passed == true) dispatchEvent(new CustomEvent(`dice-${this.lastRoll}`, { // detail: { // result: `dice-${this.lastRoll}`, // cubeId: MY_DICE_NAME // } // })) } } } addRaycastsListener(); // addRaycastsAABBListener(); application.canvas.addEventListener("ray.hit.event", (e) => { console.log('ray.hit.event @@@@@@@@@@@@ detected'); if(byId('topTitleDOM') && byId('topTitleDOM').getAttribute('data-gamestatus') != 'FREE' && byId('topTitleDOM').getAttribute('data-gamestatus') != 'status-select') { console.log('no hit in middle of game ...'); return; } if(application.dices.STATUS == "FREE_TO_PLAY") { console.log("hit cube status free to play prevent pick. ", e.detail.hitObject.name) } else if(application.dices.STATUS == "SELECT_DICES_1" || application.dices.STATUS == "SELECT_DICES_2" || application.dices.STATUS == "FINISHED") { if(Object.keys(application.dices.SAVED_DICES).length >= 5) { console.log("PREVENTED SELECT1/2 pick.", e.detail.hitObject.name) return; } console.log("hit cube status SELECT1/2 pick.", e.detail.hitObject.name) application.dices.pickDice(e.detail.hitObject.name) } }); addEventListener('mousemove', (e) => { // console.log('only on click') }) // Sounds application.matrixSounds.createAudio('start', 'res/audios/start.mp3', 1) application.matrixSounds.createAudio('block', 'res/audios/block.mp3', 6) application.matrixSounds.createAudio('dice1', 'res/audios/dice1.mp3', 6) application.matrixSounds.createAudio('dice2', 'res/audios/dice2.mp3', 6) application.matrixSounds.createAudio('hover', 'res/audios/toggle_002.mp3', 3) application.matrixSounds.createAudio('roll', 'res/audios/dice-roll.mp3', 2) addEventListener('AmmoReady', () => { app.matrixAmmo.speedUpSimulation = 2; downloadMeshes({ cube: "./res/meshes/jamb/dice.obj", }, onLoadObj, {scale: [1, 1, 1], swap: [null]}) // downloadMeshes({ // star1: "./res/meshes/shapes/star1.obj", // }, (m) => { // let o = { // scale: 2, // position: {x: 3, y: 0, z: -10}, // rotation: {x: 0, y: 0, z: 0}, // rotationSpeed: {x: 10, y: 0, z: 0}, // texturesPaths: ['./res/textures/default.png'] // }; // }, {scale: [11, 11, 11], swap: [null]}) downloadMeshes({ bg: "./res/meshes/jamb/bg.obj", }, onLoadObjFloor, {scale: [3, 1, 3], swap: [null]}) downloadMeshes({ mainTitle: "./res/meshes/jamb/jamb-title.obj", }, onLoadObjOther, {scale: [3, 2, 3], swap: [null]}) downloadMeshes({ cube: "./res/meshes/jamb/dice.obj", }, onLoadObjWallCenter, {scale: [50, 10, 10], swap: [null]}) downloadMeshes({ cube: "./res/meshes/jamb/dice.obj", }, (m) => { for(var key in m) { // console.log(`%c Loaded objs -> : ${key} `, LOG_MATRIX); } // right application.addMeshObj({ position: {x: 25, y: 5.5, z: -25}, rotation: {x: 0, y: -22, z: 0}, scale: [25, 10, 4], texturesPaths: ['./res/meshes/jamb/text.png'], name: 'wallRight', mesh: m.cube, physics: { mass: 0, enabled: true, geometry: "Cube" }, raycast: {enabled: false, radius: 2}, }) application.addMeshObj({ position: {x: -25, y: 5.5, z: -25}, rotation: {x: 0, y: 22, z: 0}, scale: [25, 10, 4], texturesPaths: ['./res/meshes/jamb/text.png'], name: 'wallLeft', mesh: m.cube, physics: { mass: 0, enabled: true, geometry: "Cube" }, raycast: {enabled: false, radius: 2}, }) }, {scale: [25, 10, 4], swap: [null]}) }) function onLoadObjWallCenter(m) { application.myLoadedMeshesWalls = m; for(var key in m) { // console.log(`%c Loaded objs -> : ${key} `, LOG_MATRIX); } // WALLS Center application.addMeshObj({ position: {x: 0, y: 5, z: -45}, rotation: {x: 0, y: 0, z: 0}, scale: [50, 10, 10], texturesPaths: ['./res/meshes/jamb/text.png'], name: 'wallCenter', mesh: m.cube, physics: { mass: 0, enabled: true, geometry: "Cube" }, raycast: {enabled: false, radius: 2}, }) } function onLoadObjOther(m) { application.myLoadedMeshes = m; // Add logo text top application.addMeshObj({ position: {x: 0, y: 6, z: -15}, rotation: {x: 0, y: 0, z: 0}, texturesPaths: ['./res/meshes/jamb/text.png'], name: 'mainTitle', mesh: m.mainTitle, physics: { mass: 0, enabled: true, geometry: "Cube" }, raycast: {enabled: false, radius: 2}, }) // application.cameras.WASD.pitch = 0.2 setTimeout(() => { // app.cameras.WASD.velocity[1] = 18 console.log('set camera position with timeout...') app.cameras.WASD.yaw = -6.21; app.cameras.WASD.pitch = -0.32; app.cameras.WASD.position[2] = 0; app.cameras.WASD.position[1] = 3.76; // BODY , x, y, z, rotX, rotY, RotZ app.matrixAmmo.setKinematicTransform( app.matrixAmmo.getBodyByName('mainTitle'), 0, 0, 0, 1) app.matrixAmmo.setKinematicTransform( app.matrixAmmo.getBodyByName('bg'), 0, -10, 0, 0, 0, 0) // Better access getBodyByName // console.log(' app.matrixAmmo. ', app.matrixAmmo.getBodyByName('CubePhysics1')) }, 1200) } function onLoadObjFloor(m) { application.myLoadedMeshes = m; application.addMeshObj({ scale: [10, 0.1, 0.1], position: {x: 0, y: 6, z: -10}, rotation: {x: 0, y: 0, z: 0}, texturesPaths: ['./res/meshes/jamb/bg.png'], name: 'bg', mesh: m.bg, physics: { collide: false, mass: 0, enabled: true, geometry: "Cube" }, raycast: {enabled: false, radius: 2}, }) } function onLoadObj(m) { application.myLoadedMeshes = m; // Add dices application.addMeshObj({ position: {x: 0, y: 6, z: -10}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics1', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.addMeshObj({ position: {x: -5, y: 4, z: -14}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics2', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.addMeshObj({ position: {x: 4, y: 8, z: -10}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics3', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.addMeshObj({ position: {x: 3, y: 4, z: -10}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics4', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.addMeshObj({ position: {x: -2, y: 4, z: -13}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics5', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.addMeshObj({ position: {x: -4, y: 6, z: -9}, rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, texturesPaths: [diceTexturePath], useUVShema4x2: true, name: 'CubePhysics6', mesh: m.cube, raycast: {enabled: true, radius: 2}, physics: { enabled: true, geometry: "Cube" } }) application.TOLERANCE = 0; let allDiceDoneProcedure = () => { console.log("ALL DONE", application.TOLERANCE) application.TOLERANCE++; if(application.TOLERANCE >= 1) { removeEventListener('dice-1', dice1Click) removeEventListener('dice-2', dice2Click) removeEventListener('dice-3', dice3Click) removeEventListener('dice-4', dice4Click) removeEventListener('dice-5', dice5Click) removeEventListener('dice-6', dice6Click) console.log(`%cFINAL<preliminar> ${dices.R}`, LOG_FUNNY) application.TOLERANCE = 0; console.log('se camera position 2') app.cameras.WASD.yaw = 0.01; app.cameras.WASD.pitch = -1.26; app.cameras.WASD.position[2] = -18; app.cameras.WASD.position[1] = 19; // ?? ? if(dices.STATUS == "FREE_TO_PLAY" || dices.STATUS == "IN_PLAY") { dices.STATUS = "SELECT_DICES_1"; console.log(`%cStatus<SELECT_DICES_1>`, LOG_FUNNY) setTimeout(() => { dispatchEvent(new CustomEvent('updateTitle', { detail: { text: app.label.get.freetoroll, status: 'FREE' } })); }, 500); } else if(dices.STATUS == "SELECT_DICES_1") { dices.STATUS = "SELECT_DICES_2"; setTimeout(() => { dispatchEvent(new CustomEvent('updateTitle', { detail: { text: app.label.get.freetoroll, status: 'FREE' } })); }, 500); console.log(`%cStatus<SELECT_DICES_2>`, LOG_FUNNY) } else if(dices.STATUS == "SELECT_DICES_2") { dices.STATUS = "FINISHED"; console.log(`%cStatus<FINISHED>`, LOG_FUNNY) dispatchEvent(new CustomEvent('updateTitle', { detail: { text: app.label.get.pick5, status: 'status-select' } })); } } }; addEventListener('all-done', allDiceDoneProcedure); addEventListener('FREE_TO_PLAY', () => { // Big reset console.log(`%c<Big reset needed ...>`, LOG_FUNNY) app.dices.SAVED_DICES = {}; app.dices.setStartUpPosition(); setTimeout(() => { app.dices.activateAllDicesPhysics(); }, 1000); console.log('se camera position 3') app.cameras.WASD.yaw = 0; app.cameras.WASD.pitch = 0; app.cameras.WASD.position[2] = 0; app.cameras.WASD.position[1] = 3.76; dispatchEvent(new CustomEvent('updateTitle', { detail: { text: app.label.get.hand1, status: 'FREE' } })); }) // ACTIONS let dice1Click = (e) => { // console.info('DICE 1 click ?????????', e.detail) dices.R[e.detail.cubeId] = '1'; dices.checkAll() }; let dice2Click = (e) => { // console.info('DICE 2', e.detail) dices.R[e.detail.cubeId] = '2'; dices.checkAll() }; let dice3Click = (e) => { // console.info('DICE 3', e.detail) dices.R[e.detail.cubeId] = '3'; dices.checkAll() }; let dice4Click = (e) => { // console.info('DICE 4', e.detail) dices.R[e.detail.cubeId] = '4'; dices.checkAll() } let dice5Click = (e) => { // console.info('DICE 5', e.detail) dices.R[e.detail.cubeId] = '5'; dices.checkAll() } let dice6Click = (e) => { // console.info('DICE 6', e.detail) dices.R[e.detail.cubeId] = '6'; dices.checkAll() } function shootDice(x) { setTimeout(() => { app.matrixAmmo.getBodyByName(`CubePhysics${x}`).setAngularVelocity(new Ammo.btVector3( randomFloatFromTo(3, 12), 9, 9 )) app.matrixAmmo.getBodyByName(`CubePhysics${x}`).setLinearVelocity(new Ammo.btVector3( randomFloatFromTo(-5, 5), 15, -20 )) setTimeout(() => app.matrixSounds.play('roll'), 1500) }, 200 * x) } application.activateDiceClickListener = (index) => { index = parseInt(index); switch(index) { case 1: addEventListener('dice-1', dice1Click) case 2: addEventListener('dice-2', dice2Click) case 3: addEventListener('dice-3', dice3Click) case 4: addEventListener('dice-4', dice4Click) case 5: addEventListener('dice-5', dice5Click) case 6: addEventListener('dice-6', dice6Click) } }; let rollProcedure = () => { if(topTitleDOM.getAttribute('data-gamestatus') != 'FREE') { console.log('validation fails...'); return; } if(dices.STATUS == "FREE_TO_PLAY") { app.matrixSounds.play('start') dices.STATUS = "IN_PLAY"; dispatchEvent(new CustomEvent('updateTitle', { detail: { text: app.label.get.hand1, status: 'inplay' } })); addEventListener('dice-1', dice1Click) addEventListener('dice-2', dice2Click) addEventListener('dice-3', dice3Click) addEventListener('dice-4', dice4Click) addEventListener('dice-5', dice5Click) addEventListener('dice-6', dice6Click) for(var x = 1;x < 7;x++) { shootDice(x) } } else if(dices.STATUS == "SELECT_DICES_1" || dices.STATUS == "SELECT_DICES_2") { // Now no selected dices still rolling for(let i = 1;i <= 6;i++) { const key = "CubePhysics" + i; if(!(key in app.dices.SAVED_DICES)) { console.log("Still in game last char is id : ", key[key.length - 1]); application.activateDiceClickListener(parseInt(key[key.length - 1])) shootDice(key[key.length - 1]) } else { console.log("??????????Still in game last char is id : ", key[key.length - 1]); application.activateDiceClickListener(parseInt(key[key.length - 1])) } } // ???? // application.activateDiceClickListener(1); dispatchEvent(new CustomEvent('updateTitle', { detail: { text: dices.STATUS == "SELECT_DICES_1" ? app.label.get.hand1 : app.label.get.hand2, status: 'inplay' } })); } else if(dices.STATUS == "FINISHED") { mb.error('No more roll...'); mb.show('Pick up 5 dices'); } } addEventListener('DICE.ROLL', rollProcedure) app.ROLL = () => { dispatchEvent(new CustomEvent('DICE.ROLL', {})) } } }) window.app = application