From a6827f4ddbcb3ca46fed6f4c7edf096358611f85 Mon Sep 17 00:00:00 2001 From: goynov Date: Fri, 29 Aug 2025 16:03:48 +0300 Subject: [PATCH] new physics engine - rapier instead of cannon --- package-lock.json | 309 ++++++++++++++++++++++++++++++++++- package.json | 4 +- src/lib/CharacterControls.js | 171 +++++++++++++++++++ src/lib/Hero.js | 172 +++++++++++-------- src/lib/PointerControls.js | 7 + src/lib/gameEngine.js | 54 +++--- vite.config.mjs | 5 +- 7 files changed, 625 insertions(+), 97 deletions(-) create mode 100644 src/lib/CharacterControls.js diff --git a/package-lock.json b/package-lock.json index eb75609..cf4ea74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,8 +31,8 @@ "vuetify": "^3.7.16" }, "devDependencies": { + "@dimforge/rapier3d": "^0.18.2", "@vitejs/plugin-vue": "^5.0.5", - "cannon-es": "^0.20.0", "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-config-vuetify": "^1.0.0", @@ -48,8 +48,10 @@ "unplugin-vue-components": "^0.27.2", "unplugin-vue-router": "^0.10.0", "vite": "^5.3.3", + "vite-plugin-top-level-await": "^1.6.0", "vite-plugin-vue-layouts": "^0.11.0", "vite-plugin-vuetify": "^2.0.3", + "vite-plugin-wasm": "^3.5.0", "vitest": "^3.2.2", "vue-router": "^4.4.0" } @@ -279,6 +281,13 @@ "node": ">=6.9.0" } }, + "node_modules/@dimforge/rapier3d": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d/-/rapier3d-0.18.2.tgz", + "integrity": "sha512-VM6f6L/1BVhvglTM67GlHmlI6Bw9NWcXzlLGoiZ7H1dOb86c6HJ9vIa6WDHThAfgN5B0c2oLeTAQ592BDPDzgA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -1221,6 +1230,24 @@ "node": ">= 8" } }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", @@ -1459,6 +1486,239 @@ "dev": true, "license": "MIT" }, + "node_modules/@swc/core": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.24" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", + "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", + "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.24.tgz", + "integrity": "sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@swc/wasm": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.13.5.tgz", + "integrity": "sha512-ZBZcxieydxNwgEU9eFAXGMaDb1Xoh+ZkZcUQ27LNJzc2lPSByoL6CSVqnYiaVo+n9JgqbYyHlMq+i7z0wRNTfA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -2432,13 +2692,6 @@ "node": ">=6" } }, - "node_modules/cannon-es": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/cannon-es/-/cannon-es-0.20.0.tgz", - "integrity": "sha512-eZhWTZIkFOnMAJOgfXJa9+b3kVlvG+FX4mdkpePev/w/rP5V8NRquGyEozcjPfEoXUlb+p7d9SUcmDSn14prOA==", - "dev": true, - "license": "MIT" - }, "node_modules/chai": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", @@ -7646,6 +7899,36 @@ "dev": true, "license": "MIT" }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz", + "integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.12.14", + "@swc/wasm": "^1.12.14", + "uuid": "10.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-top-level-await/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite-plugin-vue-layouts": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/vite-plugin-vue-layouts/-/vite-plugin-vue-layouts-0.11.0.tgz", @@ -7682,6 +7965,16 @@ "vuetify": "^3.0.0" } }, + "node_modules/vite-plugin-wasm": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", + "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" + } + }, "node_modules/vitest": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.2.tgz", diff --git a/package.json b/package.json index 5b92b90..f7e0ae6 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "vuetify": "^3.7.16" }, "devDependencies": { + "@dimforge/rapier3d": "^0.18.2", "@vitejs/plugin-vue": "^5.0.5", - "cannon-es": "^0.20.0", "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-config-vuetify": "^1.0.0", @@ -50,8 +50,10 @@ "unplugin-vue-components": "^0.27.2", "unplugin-vue-router": "^0.10.0", "vite": "^5.3.3", + "vite-plugin-top-level-await": "^1.6.0", "vite-plugin-vue-layouts": "^0.11.0", "vite-plugin-vuetify": "^2.0.3", + "vite-plugin-wasm": "^3.5.0", "vitest": "^3.2.2", "vue-router": "^4.4.0" } diff --git a/src/lib/CharacterControls.js b/src/lib/CharacterControls.js new file mode 100644 index 0000000..d71df03 --- /dev/null +++ b/src/lib/CharacterControls.js @@ -0,0 +1,171 @@ +import * as THREE from 'three' + +export const CONTROLLER_BODY_RADIUS = 0.28; + +export class CharacterControls { + + // temporary data + walkDirection = new THREE.Vector3() + rotateAngle = new THREE.Vector3(0, 1, 0) + rotateQuarternion = new THREE.Quaternion() + cameraTarget = new THREE.Vector3() + storedFall = 0 + + // constants + fadeDuration = 0.2 + runVelocity = 7 + walkVelocity = 3 + lerp = (x, y, a) => x * (1 - a) + y * a; + + constructor(model, mixer, animationsMap, orbitControl, camera, currentAction, ray, rigidBody, pointerControls) { + this.model = model + this.mixer = mixer + this.animationsMap = animationsMap + this.currentAction = currentAction + this.animationsMap[currentAction].play() + + this.ray = ray + this.rigidBody = rigidBody + this.pointerControls = pointerControls + + this.orbitControl = orbitControl + this.camera = camera + this.updateCameraTarget(new THREE.Vector3(0,1,5)) + } + + switchRunToggle() { + this.toggleRun = !this.toggleRun + } + + update(world, delta, pointerControls) { + const directionPressed = pointerControls.moving() + + var play = ''; + if (directionPressed && this.toggleRun) { + play = 'run' + } else if (directionPressed) { + play = 'walk' + } else { + play = 'idle' + } + + if (this.currentAction != play) { + const toPlay = this.animationsMap[play] + const current = this.animationsMap[this.currentAction] + + current.fadeOut(this.fadeDuration) + toPlay.reset().fadeIn(this.fadeDuration).play(); + + this.currentAction = play + } + + this.mixer.update(delta) + + this.walkDirection.x = this.walkDirection.y = this.walkDirection.z = 0 + + let velocity = 0 + if (this.currentAction == 'run' || this.currentAction == 'walk') { + // calculate towards camera direction + var angleYCameraDirection = Math.atan2( + (this.camera.position.x - this.model.position.x), + (this.camera.position.z - this.model.position.z)) + // diagonal movement angle offset + var directionOffset = this.directionOffset(pointerControls) + + // rotate model + this.rotateQuarternion.setFromAxisAngle(this.rotateAngle, Math.PI + angleYCameraDirection + directionOffset) + this.model.quaternion.rotateTowards(this.rotateQuarternion, 0.2) + + // calculate direction + this.camera.getWorldDirection(this.walkDirection) + this.walkDirection.y = 0 + this.walkDirection.normalize() + this.walkDirection.applyAxisAngle(this.rotateAngle, directionOffset) + + // run/walk velocity + velocity = this.currentAction == 'run' ? this.runVelocity : this.walkVelocity + } + + const translation = this.rigidBody.translation(); + if (translation.y < -1) { + // don't fall below ground + this.rigidBody.setNextKinematicTranslation( { + x: 0, + y: 10, + z: 0 + }); + } else { + const cameraPositionOffset = this.camera.position.sub(this.model.position); + // update model and camera + this.model.position.x = translation.x + this.model.position.y = translation.y + this.model.position.z = translation.z + this.updateCameraTarget(cameraPositionOffset) + + this.walkDirection.y += this.lerp(this.storedFall, -9.81 * delta, 0.10) + this.storedFall = this.walkDirection.y + this.ray.origin.x = translation.x + this.ray.origin.y = translation.y + this.ray.origin.z = translation.z + let hit = world.castRay(this.ray, 0.5, false, 0xfffffffff); + if (hit) { + const point = this.ray.pointAt(hit.timeOfImpact); + let diff = translation.y - ( point.y + CONTROLLER_BODY_RADIUS); + if (diff < 0.0) { + this.storedFall = 0 + this.walkDirection.y = this.lerp(0, -diff, 0.5) + } + } + + this.walkDirection.x = this.walkDirection.x * velocity * delta + this.walkDirection.z = this.walkDirection.z * velocity * delta + + this.rigidBody.setNextKinematicTranslation( { + x: translation.x + this.walkDirection.x, + y: translation.y + this.walkDirection.y, + z: translation.z + this.walkDirection.z + }); + } + } + + updateCameraTarget(offset) { + // move camera + const rigidTranslation = this.rigidBody.translation(); + this.camera.position.x = rigidTranslation.x + offset.x + this.camera.position.y = rigidTranslation.y + offset.y + this.camera.position.z = rigidTranslation.z + offset.z + + // update camera target + this.cameraTarget.x = rigidTranslation.x + this.cameraTarget.y = rigidTranslation.y + 1 + this.cameraTarget.z = rigidTranslation.z + this.orbitControl.target = this.cameraTarget + } + + directionOffset(pointerControls) { + var directionOffset = 0 // w + + if (pointerControls.moveForward) { + if (pointerControls.moveLeft) { + directionOffset = Math.PI / 4 // w+a + } else if (pointerControls.moveRight) { + directionOffset = - Math.PI / 4 // w+d + } + } else if (pointerControls.moveBackward) { + if (pointerControls.moveLeft) { + directionOffset = Math.PI / 4 + Math.PI / 2 // s+a + } else if (pointerControls.moveRight) { + directionOffset = -Math.PI / 4 - Math.PI / 2 // s+d + } else { + directionOffset = Math.PI // s + } + } else if (pointerControls.moveLeft) { + directionOffset = Math.PI / 2 // a + } else if (pointerControls.moveRight) { + directionOffset = - Math.PI / 2 // d + } + + return directionOffset + } + +} \ No newline at end of file diff --git a/src/lib/Hero.js b/src/lib/Hero.js index c763d26..99b9c07 100644 --- a/src/lib/Hero.js +++ b/src/lib/Hero.js @@ -1,7 +1,8 @@ import { AnimationMixer } from 'three'; import { PointerControls } from './PointerControls'; +import { CharacterControls } from './CharacterControls'; import * as THREE from 'three'; -import * as CANNON from 'cannon-es'; +import * as RAPIER from '@dimforge/rapier3d' class Hero{ @@ -16,7 +17,8 @@ class Hero{ this.gameEngine = gameEngine; //this.gameEngine.orbitControls.object = this.model; //this.gameEngine.orbitControls.target = this.model.position; - this.gameEngine.orbitControls.enabled = false; + + //this.gameEngine.orbitControls.enabled = false; //this.model.add(gameEngine.camera) gameEngine.camera.position.set(0,17,-30) @@ -24,66 +26,91 @@ class Hero{ this.heroCamera = new THREE.Object3D() this.model.add(this.heroCamera) - this.heroCamera.applyMatrix4(gameEngine.camera.matrix) + this.heroCamera.applyMatrix4(gameEngine.camera.matrix) this.mixer = new AnimationMixer(this.model); gameEngine.mixers.push( this.mixer ); - this.actionWalk = this.mixer.clipAction( this.object.animations.find(a=>a.name=='walk') ); - this.actionIdle = this.mixer.clipAction( this.object.animations.find(a=>a.name=='idle') ); - this.actionIdle.play(); - this.activeAction = this.actionIdle; + + this.animationsMap = {}; + this.object.animations.forEach(a=>{ + this.animationsMap[a.name] = this.mixer.clipAction(a); + }) + + // this.actionWalk = this.mixer.clipAction( this.object.animations.find(a=>a.name=='walk') ); + // this.actionIdle = this.mixer.clipAction( this.object.animations.find(a=>a.name=='idle') ); + // this.actionIdle.play(); + // this.activeAction = this.actionIdle; this.pointerControls = new PointerControls(gameEngine.camera, this.model, gameEngine.renderer.domElement); gameEngine.hero = this; // Character Collider - const characterCollider = new THREE.Object3D() - characterCollider.position.y = 3 - gameEngine.activeObjects.add(characterCollider) - const colliderShape = new CANNON.Sphere(0.5) - const colliderBody = new CANNON.Body({ mass: 1, material: gameEngine.phy.slipperyMaterial }) - colliderBody.addShape(colliderShape, new CANNON.Vec3(0, 0.5, 0)) - colliderBody.addShape(colliderShape, new CANNON.Vec3(0, -0.5, 0)) - colliderBody.position.set( - characterCollider.position.x, - characterCollider.position.y, - characterCollider.position.z - ) - colliderBody.linearDamping = 0.95 - colliderBody.angularFactor.set(0, 1, 0) // prevents rotation X,Z axis - //colliderBody.sleepSpeedLimit = 1.0; - gameEngine.phy.world.addBody(colliderBody) + // const characterCollider = new THREE.Object3D() + // characterCollider.position.y = 3 + // gameEngine.activeObjects.add(characterCollider) - this.characterCollider = characterCollider; - this.colliderBody = colliderBody; + + // const colliderShape = new CANNON.Sphere(0.5) + // const colliderBody = new CANNON.Body({ mass: 1, material: gameEngine.phy.slipperyMaterial }) + // colliderBody.addShape(colliderShape, new CANNON.Vec3(0, 0.5, 0)) + // colliderBody.addShape(colliderShape, new CANNON.Vec3(0, -0.5, 0)) + // colliderBody.position.set( + // characterCollider.position.x, + // characterCollider.position.y, + // characterCollider.position.z + // ) + // colliderBody.linearDamping = 0.95 + // colliderBody.angularFactor.set(0, 1, 0) // prevents rotation X,Z axis + // //colliderBody.sleepSpeedLimit = 1.0; + // gameEngine.phy.world.addBody(colliderBody) + + let bodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation( + // characterCollider.position.x, + // characterCollider.position.y, + // characterCollider.position.z + -1, 3, 1 + ) + let rigidBody = gameEngine.phy.world.createRigidBody(bodyDesc); + let dynamicCollider = RAPIER.ColliderDesc.ball(0.28); + gameEngine.phy.world.createCollider(dynamicCollider, rigidBody.handle); + + this.characterControls = new CharacterControls(this.model, this.mixer, + this.animationsMap, gameEngine.orbitControls, gameEngine.camera, 'idle', + new RAPIER.Ray( + { x: 0, y: 0, z: 0 }, + { x: 0, y: -1, z: 0} + ), rigidBody, this.pointerControls) + + // this.characterCollider = characterCollider; + // this.colliderBody = colliderBody; this.clock = new THREE.Clock() this.delta = 0 - this.inputVelocity = new THREE.Vector3() - this.velocity = new CANNON.Vec3() - this.quat = new THREE.Quaternion() - this.v = new THREE.Vector3() - this.rotation = new THREE.Euler() - this.targetQuaternion = new THREE.Quaternion() - this.distance = 0 - this.canJump = true; + // this.inputVelocity = new THREE.Vector3() + // this.velocity = new CANNON.Vec3() + // this.quat = new THREE.Quaternion() + // this.v = new THREE.Vector3() + // this.rotation = new THREE.Euler() + // this.targetQuaternion = new THREE.Quaternion() + // this.distance = 0 + // this.canJump = true; - this.contactNormal = new CANNON.Vec3() - this.upAxis = new CANNON.Vec3(0, 1, 0) + // this.contactNormal = new CANNON.Vec3() + // this.upAxis = new CANNON.Vec3(0, 1, 0) - colliderBody.addEventListener('collide', function (e) { - const contact = e.contact - if (contact.bi.id == this.colliderBody.id) { - contact.ni.negate(this.contactNormal) - } else { - this.contactNormal.copy(contact.ni) - } - if (this.contactNormal.dot(this.upAxis) > 0.5) { - if (!this.canJump) { - setAction(animationActions[1], true) - } - this.canJump = true - } - }.bind(this)) + // colliderBody.addEventListener('collide', function (e) { + // const contact = e.contact + // if (contact.bi.id == this.colliderBody.id) { + // contact.ni.negate(this.contactNormal) + // } else { + // this.contactNormal.copy(contact.ni) + // } + // if (this.contactNormal.dot(this.upAxis) > 0.5) { + // if (!this.canJump) { + // setAction(animationActions[1], true) + // } + // this.canJump = true + // } + // }.bind(this)) this.ready = true; @@ -93,27 +120,42 @@ class Hero{ this.pointerControls.controls.lock(); } - setAction(toAction, loop){ - if (toAction != this.activeAction) { - let lastAction = this.activeAction; - this.activeAction = toAction - lastAction.fadeOut(0.1) - this.activeAction.reset() - this.activeAction.fadeIn(0.1) - this.activeAction.play() - if (!loop) { - this.activeAction.clampWhenFinished = true - this.activeAction.loop = THREE.LoopOnce - } - } - } + // setAction(toAction, loop){ + // if (toAction != this.activeAction) { + // let lastAction = this.activeAction; + // this.activeAction = toAction + // lastAction.fadeOut(0.1) + // this.activeAction.reset() + // this.activeAction.fadeIn(0.1) + // this.activeAction.play() + // if (!loop) { + // this.activeAction.clampWhenFinished = true + // this.activeAction.loop = THREE.LoopOnce + // } + // } + // } update(){ //return if (this.gameEngine.renderer.xr.isPresenting) return; + + if (this.ready) { + let pc = this.pointerControls; + pc.update(); + let dlt = this.clock.getDelta(); + this.delta += dlt; + if (this.delta > 0.016){ + this.characterControls.update(this.gameEngine.phy.world, this.delta, pc) + this.gameEngine.phy.world.step() + this.delta = 0; + } + } + + + return; let { inputVelocity, velocity, quat, v, targetQuaternion, clock, rotation } = this; - let pc = this.pointerControls; - pc.update(); + // let pc = this.pointerControls; + // pc.update(); if (this.ready) { if (this.canJump) { //walking diff --git a/src/lib/PointerControls.js b/src/lib/PointerControls.js index 2d8f54c..76b566e 100644 --- a/src/lib/PointerControls.js +++ b/src/lib/PointerControls.js @@ -11,10 +11,13 @@ class PointerControls { this.moveBackward = false; this.moveLeft = false; this.moveRight = false; + this.moveUp = false; this.moveDown = false; + this.rotateLeft = false; this.rotateRight = false; + this.canJump = false; this.velocity = new Vector3(); this.direction = new Vector3(); @@ -163,6 +166,10 @@ class PointerControls { }; } + + moving(){ + return this.moveForward || this.moveBackward || this.moveLeft || this.moveRight; + } } export { PointerControls }; \ No newline at end of file diff --git a/src/lib/gameEngine.js b/src/lib/gameEngine.js index 8621e89..4d98db7 100644 --- a/src/lib/gameEngine.js +++ b/src/lib/gameEngine.js @@ -13,7 +13,7 @@ import { TransformControls } from 'three/addons/controls/TransformControls.js'; import { ARButton } from 'three/addons/webxr/ARButton.js'; import { XRButton } from 'three/addons/webxr/XRButton.js'; import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js'; -import * as CANNON from 'cannon-es'; +import * as RAPIER from '@dimforge/rapier3d' import { Clickable } from './Clickable.js'; class GameEngine { @@ -247,28 +247,38 @@ class GameEngine { } initPhysics() { - const world = new CANNON.World() - world.gravity.set(0, -9.82, 0) - 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) + //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 = { - world, slipperyMaterial + world, + //slipperyMaterial } } diff --git a/vite.config.mjs b/vite.config.mjs index b1b78d7..82987c3 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -7,6 +7,8 @@ import Vue from '@vitejs/plugin-vue' import VueRouter from 'unplugin-vue-router/vite' import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' import basicSsl from '@vitejs/plugin-basic-ssl' +import wasm from 'vite-plugin-wasm' +import topLevelAwait from 'vite-plugin-top-level-await' // Utilities import { defineConfig } from 'vite' @@ -46,7 +48,8 @@ export default defineConfig({ }, vueTemplate: true, }), - basicSsl() + basicSsl(), + wasm(), topLevelAwait() ], define: { 'process.env': {} }, resolve: {