diff --git a/package-lock.json b/package-lock.json index ae7d3983..07f7318b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { + "@dimforge/rapier3d": "^0.12.0", "three": "^0.164.1" }, "devDependencies": { @@ -960,6 +961,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@dimforge/rapier3d": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d/-/rapier3d-0.12.0.tgz", + "integrity": "sha512-+31rbMJuhT5jrqNhXpLK8iaDsk9dg+8xiK+2QIOCAN++zDcLm2JvegQqsydzu6aL7OsAxBkGhWTfEmeLz+uAvw==" + }, "node_modules/@docsearch/css": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", diff --git a/package.json b/package.json index 8d07870a..6ae64141 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "vitest": "^1.5.0" }, "dependencies": { + "@dimforge/rapier3d": "^0.12.0", "three": "^0.164.1" } } diff --git a/playground/package-lock.json b/playground/package-lock.json index b8e5c641..d5c189bc 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -11,18 +11,26 @@ "devDependencies": { "@fibbojs/fibbo": "file:../", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-plugin-top-level-await": "^1.4.1", + "vite-plugin-wasm": "^3.3.0" } }, "..": { + "name": "@fibbojs/fibbo", "version": "0.0.1", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@dimforge/rapier3d": "^0.12.0", + "three": "^0.164.1" + }, "devDependencies": { "@antfu/eslint-config": "^2.14.0", "@antfu/ni": "^0.21.12", "@antfu/utils": "^0.7.7", "@types/node": "^20.12.7", + "@types/three": "^0.164.0", "eslint": "^9.0.0", "esno": "^4.7.0", "rimraf": "^5.0.5", @@ -408,6 +416,23 @@ "resolved": "..", "link": true }, + "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, + "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/rollup-android-arm-eabi": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", @@ -616,6 +641,219 @@ "win32" ] }, + "node_modules/@swc/core": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.17.tgz", + "integrity": "sha512-tq+mdWvodMBNBBZbwFIMTVGYHe9N7zvEaycVVjfvAx20k1XozHbHhRv+9pEVFJjwRxLdXmtvFZd3QZHRAOpoNQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.2", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.4.17", + "@swc/core-darwin-x64": "1.4.17", + "@swc/core-linux-arm-gnueabihf": "1.4.17", + "@swc/core-linux-arm64-gnu": "1.4.17", + "@swc/core-linux-arm64-musl": "1.4.17", + "@swc/core-linux-x64-gnu": "1.4.17", + "@swc/core-linux-x64-musl": "1.4.17", + "@swc/core-win32-arm64-msvc": "1.4.17", + "@swc/core-win32-ia32-msvc": "1.4.17", + "@swc/core-win32-x64-msvc": "1.4.17" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.17.tgz", + "integrity": "sha512-HVl+W4LezoqHBAYg2JCqR+s9ife9yPfgWSj37iIawLWzOmuuJ7jVdIB7Ee2B75bEisSEKyxRlTl6Y1Oq3owBgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.17.tgz", + "integrity": "sha512-WYRO9Fdzq4S/he8zjW5I95G1zcvyd9yyD3Tgi4/ic84P5XDlSMpBDpBLbr/dCPjmSg7aUXxNQqKqGkl6dQxYlA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.17.tgz", + "integrity": "sha512-cgbvpWOvtMH0XFjvwppUCR+Y+nf6QPaGu6AQ5hqCP+5Lv2zO5PG0RfasC4zBIjF53xgwEaaWmGP5/361P30X8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.17.tgz", + "integrity": "sha512-l7zHgaIY24cF9dyQ/FOWbmZDsEj2a9gRFbmgx2u19e3FzOPuOnaopFj0fRYXXKCmtdx+anD750iBIYnTR+pq/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.17.tgz", + "integrity": "sha512-qhH4gr9gAlVk8MBtzXbzTP3BJyqbAfUOATGkyUtohh85fPXQYuzVlbExix3FZXTwFHNidGHY8C+ocscI7uDaYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.17.tgz", + "integrity": "sha512-vRDFATL1oN5oZMImkwbgSHEkp8xG1ofEASBypze01W1Tqto8t+yo6gsp69wzCZBlxldsvPpvFZW55Jq0Rn+UnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.17.tgz", + "integrity": "sha512-zQNPXAXn3nmPqv54JVEN8k2JMEcMTQ6veVuU0p5O+A7KscJq+AGle/7ZQXzpXSfUCXlLMX4wvd+rwfGhh3J4cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.17.tgz", + "integrity": "sha512-z86n7EhOwyzxwm+DLE5NoLkxCTme2lq7QZlDjbQyfCxOt6isWz8rkW5QowTX8w9Rdmk34ncrjSLvnHOeLY17+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.17.tgz", + "integrity": "sha512-JBwuSTJIgiJJX6wtr4wmXbfvOswHFj223AumUrK544QV69k60FJ9q2adPW9Csk+a8wm1hLxq4HKa2K334UHJ/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.17.tgz", + "integrity": "sha512-jFkOnGQamtVDBm3MF5Kq1lgW8vx4Rm1UvJWRUfg+0gx7Uc3Jp3QMFeMNw/rDNQYRDYPG3yunCC+2463ycd5+dg==", + "cpu": [ + "x64" + ], + "dev": true, + "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 + }, + "node_modules/@swc/types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -783,6 +1021,19 @@ "node": ">=14.17" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "5.2.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", @@ -837,6 +1088,29 @@ "optional": true } } + }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.1.tgz", + "integrity": "sha512-hogbZ6yT7+AqBaV6lK9JRNvJDn4/IJvHLu6ET06arNfo0t2IsyCaon7el9Xa8OumH+ESuq//SDf8xscZFE0rWw==", + "dev": true, + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.3.100", + "uuid": "^9.0.1" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.3.0.tgz", + "integrity": "sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==", + "dev": true, + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5" + } } } } diff --git a/playground/package.json b/playground/package.json index f1a32453..c7add61e 100644 --- a/playground/package.json +++ b/playground/package.json @@ -19,6 +19,8 @@ "devDependencies": { "@fibbojs/fibbo": "file:../", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-plugin-top-level-await": "^1.4.1", + "vite-plugin-wasm": "^3.3.0" } } diff --git a/playground/src/classes/Duck.ts b/playground/src/classes/Duck.ts index 809e8851..e44dd07b 100644 --- a/playground/src/classes/Duck.ts +++ b/playground/src/classes/Duck.ts @@ -1,8 +1,9 @@ import { FibboGLTF } from '@fibbojs/fibbo' +import type { FibboScene } from '@fibbojs/fibbo' export default class Duck extends FibboGLTF { - constructor() { - super('Duck.glb') + constructor(scene: FibboScene) { + super(scene, 'Duck.glb') } onFrame(delta: number) { diff --git a/playground/src/classes/GltfCube.ts b/playground/src/classes/GltfCube.ts index b209870a..aa2a3efd 100644 --- a/playground/src/classes/GltfCube.ts +++ b/playground/src/classes/GltfCube.ts @@ -1,13 +1,12 @@ import { FibboGLTF } from '@fibbojs/fibbo' +import type { FibboScene } from '@fibbojs/fibbo' export default class GltfCube extends FibboGLTF { - constructor() { - super('Cube.gltf', 2, 0, -2) + constructor(scene: FibboScene) { + super(scene, 'Cube.gltf') } onFrame(delta: number) { super.onFrame(delta) - this.object3D.rotation.x += 0.2 * delta - this.object3D.rotation.y += 0.2 * delta } } diff --git a/playground/src/main.ts b/playground/src/main.ts index c4c4b308..42f7d2aa 100644 --- a/playground/src/main.ts +++ b/playground/src/main.ts @@ -5,8 +5,12 @@ import GltfCube from './classes/GltfCube' const scene = new FibboScene(true) -const duck = new Duck() +const duck = new Duck(scene) scene.addModel(duck) -const cube = new GltfCube() -scene.addModel(cube) +const gltfCube = new GltfCube(scene) +scene.addModel(gltfCube) + +setTimeout(() => { + gltfCube.rigidBody?.applyImpulse({ x: 0, y: 5, z: 0 }, true) +}, 2000) diff --git a/playground/vite.config.ts b/playground/vite.config.ts new file mode 100644 index 00000000..7e2bc751 --- /dev/null +++ b/playground/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import wasm from 'vite-plugin-wasm' +import topLevelAwait from 'vite-plugin-top-level-await' + +export default defineConfig({ + plugins: [ + wasm(), + topLevelAwait(), + ], +}) diff --git a/src/core/FibboScene.ts b/src/core/FibboScene.ts index f0d60700..534f1e16 100644 --- a/src/core/FibboScene.ts +++ b/src/core/FibboScene.ts @@ -1,13 +1,20 @@ import * as THREE from 'three' +import * as RAPIER from '@dimforge/rapier3d' import { OrbitControls } from 'three/addons/controls/OrbitControls.js' +import type { World } from '@dimforge/rapier3d' import type { FibboModel } from './model/FibboModel' +import { FibboCube } from './model/FibboCube' import { FibboGLTF } from './model/FibboGLTF' export class FibboScene { models: FibboModel[] + // Three.js scene: THREE.Scene camera: THREE.PerspectiveCamera controls: OrbitControls | undefined + // Rapier + gravity: { x: number, y: number, z: number } = { x: 0, y: -9.81, z: 0 } + world?: World constructor(debug = false) { // Initialize models array @@ -21,7 +28,7 @@ export class FibboScene { this.scene = new THREE.Scene() this.scene.background = new THREE.Color(0x121212) this.camera = new THREE.PerspectiveCamera(75, (window as any).innerWidth / (window as any).innerHeight, 0.1, 1000) - this.camera.position.set(2.5, 2.5, 2.5) + this.camera.position.set(5, 5, 5) const renderer = new THREE.WebGLRenderer() renderer.setSize((window as any).innerWidth, (window as any).innerHeight) @@ -57,6 +64,13 @@ export class FibboScene { let currentTime = 0 let delta = 0 + // Initialize Rapier world + this.world = new RAPIER.World(this.gravity) + + // Create the ground + const groundColliderDesc = RAPIER.ColliderDesc.cuboid(10.0, 0.1, 10.0) + this.world.createCollider(groundColliderDesc) + /** * Animation loop */ @@ -78,6 +92,23 @@ export class FibboScene { // Debug info this.debug() + + // Rapier debug + if (this.world) { + const { vertices, colors } = this.world.debugRender() + const geometry = new THREE.BufferGeometry() + geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)) + geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)) + const material = new THREE.LineBasicMaterial({ vertexColors: true }) + const lines = new THREE.LineSegments(geometry, material) + this.scene.add(lines) + } + } + + // Physics + if (this.world) { + this.world.timestep = delta + this.world.step() } renderer.render(this.scene, this.camera) diff --git a/src/core/model/FibboCube.ts b/src/core/model/FibboCube.ts index 59c7909b..69cc45cd 100644 --- a/src/core/model/FibboCube.ts +++ b/src/core/model/FibboCube.ts @@ -1,9 +1,10 @@ import * as THREE from 'three' +import type { FibboScene } from '../FibboScene' import { FibboModel } from './FibboModel' export class FibboCube extends FibboModel { - constructor(x: number = 0, y: number = 0, z: number = 0) { - super(x, y, z) + constructor(scene: FibboScene) { + super(scene) // Create a cube const geometry = new THREE.BoxGeometry(1, 1, 1) const material = new THREE.MeshBasicMaterial({ color: 0x00FF00 }) diff --git a/src/core/model/FibboGLTF.ts b/src/core/model/FibboGLTF.ts index cac75859..e4e9456d 100644 --- a/src/core/model/FibboGLTF.ts +++ b/src/core/model/FibboGLTF.ts @@ -1,4 +1,5 @@ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' +import type { FibboScene } from '../FibboScene' import { FibboModel } from './FibboModel' /* @@ -20,8 +21,8 @@ export class FibboGLTF extends FibboModel { public type: string = 'FibboGLTF' public onLoaded: () => void = () => {} - constructor(model: string, x: number = 0, y: number = 0, z: number = 0) { - super(x, y, z) + constructor(scene: FibboScene, model: string) { + super(scene) // Create GLTF Loader const loader = new GLTFLoader() /* @@ -39,7 +40,7 @@ export class FibboGLTF extends FibboModel { (gltf) => { this.object3D = gltf.scene this.object3D.scale.set(1, 1, 1) - this.object3D.position.set(x, y, z) + this.object3D.position.set(0, 5, 0) this.onLoaded() }, // Called while loading is progressing diff --git a/src/core/model/FibboModel.ts b/src/core/model/FibboModel.ts index f0ff7f29..dd48bb03 100644 --- a/src/core/model/FibboModel.ts +++ b/src/core/model/FibboModel.ts @@ -1,18 +1,41 @@ import * as THREE from 'three' +import * as RAPIER from '@dimforge/rapier3d' +import type { Collider, RigidBody } from '@dimforge/rapier3d' +import type { FibboScene } from '../FibboScene' export abstract class FibboModel { + // Object3D object3D: THREE.Object3D - physics: boolean = false + // Gravity + gravity: boolean = false + rigidBody?: RigidBody + // Collision collision: boolean = false + collider?: Collider + // Scene + scene: FibboScene - constructor(x: number = 0, y: number = 0, z: number = 0) { + constructor(scene: FibboScene) { + this.scene = scene this.object3D = new THREE.Object3D() - this.object3D.position.set(x, y, z) + this.object3D.position.set(0, 2, 0) + + // Create a dynamic rigid-body. + const rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic() + .setTranslation(0.0, 2.0, 0.0) + this.rigidBody = this.scene.world?.createRigidBody(rigidBodyDesc) + + // Create a cuboid collider attached to the dynamic rigidBody. + const colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5) + this.collider = this.scene.world?.createCollider(colliderDesc, this.rigidBody) } - onFrame(delta: number): void { - if (this.physics) - // TODO: Implement physics - console.log(`Physics enabled : ${delta}`) + onFrame(_delta: number): void { + if (this.rigidBody) { + const position = this.rigidBody.translation() + this.object3D.position.set(position.x, position.y, position.z) + const rotation = this.rigidBody.rotation() + this.object3D.rotation.set(rotation.x, rotation.y, rotation.z) + } } }