start working on physics, all over the place right now
This commit is contained in:
parent
f1509a3a64
commit
9c980bc725
|
@ -29,6 +29,8 @@
|
|||
"@freeblox/engine": "workspace:^",
|
||||
"three": "^0.153.0",
|
||||
"vite-plugin-dts": "^2.3.0",
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
"vite-plugin-wasm": "^3.2.2",
|
||||
"vue": "^3.2.47"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
LevelComponent,
|
||||
Engine,
|
||||
assetManager,
|
||||
PhysicsWorldComponent,
|
||||
} from '@freeblox/engine';
|
||||
import { GameEvents } from '../types/events';
|
||||
import { GameplayComponent } from './gameplay';
|
||||
|
@ -21,6 +22,7 @@ export class Game extends Engine {
|
|||
this.use(LevelComponent);
|
||||
this.use(GameplayComponent);
|
||||
this.use(MouseComponent);
|
||||
this.use(PhysicsWorldComponent);
|
||||
|
||||
this.getComponent(ViewportComponent).setSizeFromViewport();
|
||||
this.start();
|
||||
|
|
|
@ -28,6 +28,7 @@ export class GameplayComponent extends EngineComponent {
|
|||
left: 0,
|
||||
right: 0,
|
||||
};
|
||||
public jump = false;
|
||||
|
||||
private move = new Vector3();
|
||||
private look = new Vector3();
|
||||
|
@ -60,6 +61,10 @@ export class GameplayComponent extends EngineComponent {
|
|||
if (this.move.length()) {
|
||||
this.character?.setLook(this.move.clone().normalize());
|
||||
}
|
||||
if (this.jump) {
|
||||
this.jump = false;
|
||||
this.character?.jump();
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
@ -98,6 +103,9 @@ export class GameplayComponent extends EngineComponent {
|
|||
case 'KeyD':
|
||||
this.movement.right = this.movementSpeed;
|
||||
break;
|
||||
case 'Space':
|
||||
this.jump = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
const keyUpEvent = (event: KeyboardEvent) => {
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import dts from 'vite-plugin-dts'
|
||||
import dts from 'vite-plugin-dts';
|
||||
import wasm from 'vite-plugin-wasm';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), dts()],
|
||||
plugins: [vue(), dts(), wasm(), topLevelAwait()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
name: 'Client',
|
||||
fileName: 'client',
|
||||
formats: ['es', 'cjs', 'umd']
|
||||
formats: ['es', 'cjs', 'umd'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue'],
|
||||
|
@ -20,5 +22,5 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d": "^0.11.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"three": "^0.153.0"
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './environment';
|
|||
export * from './viewport';
|
||||
export * from './mouse';
|
||||
export * from './level';
|
||||
export * from './physicsworld';
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
import { SkinnedMesh, Vector3 } from 'three';
|
||||
import { EngineComponent } from '../types/engine-component';
|
||||
import { World } from '../gameobjects/world.object';
|
||||
import {
|
||||
PhysicsObjectAssociation,
|
||||
getRapier,
|
||||
ColliderFactory,
|
||||
HumanoidPhysicsProxy,
|
||||
} from '../physics';
|
||||
import { Brick } from '../gameobjects/brick.object';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { Humanoid } from '../gameobjects/humanoid.object';
|
||||
|
||||
export class PhysicsWorldComponent extends EngineComponent {
|
||||
public name = PhysicsWorldComponent.name;
|
||||
private world!: World;
|
||||
private physicsWorld!: Rapier.World;
|
||||
private physicsEngine!: typeof Rapier;
|
||||
private characterPhysics!: Rapier.KinematicCharacterController;
|
||||
private characterRay!: Rapier.Ray;
|
||||
private initEventFiredBeforeEngineLoad = false;
|
||||
private cleanUpEvents?: Function;
|
||||
private sceneInitialized = false;
|
||||
private trackedObjects: PhysicsObjectAssociation[] = [];
|
||||
|
||||
initialize(): void {
|
||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||
this.cleanUpEvents = this.bindEvents();
|
||||
this.createRapier();
|
||||
}
|
||||
|
||||
update(delta: number): void {
|
||||
// FIXME: physics is tied to the FPS
|
||||
this.physicsWorld?.step();
|
||||
for (const object of this.trackedObjects) object.tick(delta);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cleanUpEvents?.call(this);
|
||||
this.physicsWorld.removeCharacterController(this.characterPhysics);
|
||||
}
|
||||
|
||||
private createRapier() {
|
||||
getRapier().then((R) => {
|
||||
const gravity = new Vector3(0, this.world.gravity, 0);
|
||||
const world = new R.World(gravity);
|
||||
this.physicsWorld = world;
|
||||
this.physicsEngine = R;
|
||||
|
||||
if (this.initEventFiredBeforeEngineLoad) {
|
||||
this.initEventFiredBeforeEngineLoad = false;
|
||||
setTimeout(() => this.initializePhysicsScene(), 500); // FIXME
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
const initializeEvent = () => {
|
||||
if (!this.physicsWorld) {
|
||||
this.initEventFiredBeforeEngineLoad = true;
|
||||
return;
|
||||
}
|
||||
setTimeout(() => this.initializePhysicsScene(), 500); // FIXME
|
||||
};
|
||||
|
||||
this.events.addListener('initialized', initializeEvent);
|
||||
|
||||
return () => {
|
||||
this.events.removeEventListener('initialized', initializeEvent);
|
||||
};
|
||||
}
|
||||
|
||||
private applyPhysics(object: Brick) {
|
||||
// This object has effecively no physics:
|
||||
// doesn't move, doesn't collide
|
||||
if (object.anchored && !object.canCollide) return;
|
||||
|
||||
let bodyDesc: Rapier.RigidBodyDesc;
|
||||
if (object.anchored) bodyDesc = this.physicsEngine.RigidBodyDesc.fixed();
|
||||
else bodyDesc = this.physicsEngine.RigidBodyDesc.dynamic();
|
||||
|
||||
bodyDesc
|
||||
.setTranslation(...object.position.toArray())
|
||||
.setRotation(object.quaternion);
|
||||
|
||||
const body = this.physicsWorld.createRigidBody(bodyDesc);
|
||||
|
||||
let collider: Rapier.Collider | undefined;
|
||||
if (object.canCollide) {
|
||||
collider = ColliderFactory.createCollider(
|
||||
object,
|
||||
this.physicsEngine,
|
||||
this.physicsWorld,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
const tracker = new PhysicsObjectAssociation(object, collider, body);
|
||||
console.log(tracker);
|
||||
this.trackedObjects.push(tracker);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
private applyHumanoidPhysics(humanoid: Humanoid) {
|
||||
const halfVec = new Vector3(0, humanoid.characterHalfHeight, 0);
|
||||
const colliderDesc = this.physicsEngine.ColliderDesc.cuboid(
|
||||
1,
|
||||
humanoid.characterHalfHeight,
|
||||
0.5
|
||||
);
|
||||
const rigidBodyDesc =
|
||||
this.physicsEngine.RigidBodyDesc.kinematicPositionBased();
|
||||
const rigidBody = this.physicsWorld.createRigidBody(rigidBodyDesc);
|
||||
const collider = this.physicsWorld.createCollider(colliderDesc, rigidBody);
|
||||
rigidBody.setTranslation(humanoid.parent!.position.clone(), true);
|
||||
rigidBody.setRotation(humanoid.parent!.quaternion, true);
|
||||
collider.setTranslationWrtParent(halfVec);
|
||||
const proxy = new HumanoidPhysicsProxy(
|
||||
humanoid,
|
||||
this.characterPhysics,
|
||||
this.characterRay,
|
||||
this.physicsWorld,
|
||||
collider,
|
||||
rigidBody
|
||||
);
|
||||
this.trackedObjects.push(proxy);
|
||||
humanoid.setPhysics(proxy);
|
||||
console.log('set physics', proxy);
|
||||
}
|
||||
|
||||
private async initializePhysicsScene() {
|
||||
if (this.sceneInitialized) return;
|
||||
console.log('init scene');
|
||||
|
||||
this.characterRay = new this.physicsEngine.Ray(
|
||||
{ x: 0, y: 0, z: 0 },
|
||||
{ x: 0, y: -0.5, z: 0 }
|
||||
);
|
||||
this.characterPhysics = this.physicsWorld.createCharacterController(0.01);
|
||||
this.characterPhysics.setApplyImpulsesToDynamicBodies(true);
|
||||
this.characterPhysics.setMaxSlopeClimbAngle((75 * Math.PI) / 180);
|
||||
// Automatically slide down on slopes smaller than 30 degrees.
|
||||
// this.characterPhysics.setMinSlopeSlideAngle((30 * Math.PI) / 180);
|
||||
|
||||
this.world.traverse((object) => {
|
||||
if (object instanceof Humanoid) return this.applyHumanoidPhysics(object);
|
||||
if (!(object instanceof Brick)) return;
|
||||
// Do not apply physics to animated objects
|
||||
if ((object.getMesh() as SkinnedMesh).skeleton) return;
|
||||
this.applyPhysics(object);
|
||||
});
|
||||
this.sceneInitialized = true;
|
||||
}
|
||||
}
|
|
@ -74,4 +74,12 @@ export class Brick extends GameObject3D {
|
|||
this.name = this.objectType;
|
||||
this.add(this.mesh);
|
||||
}
|
||||
|
||||
getGeometry() {
|
||||
return this.geometry;
|
||||
}
|
||||
|
||||
getMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,11 @@ import { GameObject } from '../types/game-object';
|
|||
import { Ticking } from '../types/ticking';
|
||||
import { EditorProperty } from '../decorators/property';
|
||||
import { MeshPart } from './mesh.object';
|
||||
import { clamp } from 'three/src/math/MathUtils.js';
|
||||
import { clamp, lerp } from 'three/src/math/MathUtils.js';
|
||||
import { NameTag } from './nametag.object';
|
||||
import { CanvasUtils } from '../canvas/utils';
|
||||
import { Disposable } from '../types/disposable';
|
||||
import { HumanoidPhysicsProxy } from '../physics';
|
||||
|
||||
export type HumanoidBodyPart =
|
||||
| 'Head'
|
||||
|
@ -23,7 +25,7 @@ export type HumanoidBodyPart =
|
|||
| 'LegRight'
|
||||
| 'LegLeft';
|
||||
|
||||
export class Humanoid extends GameObject implements Ticking {
|
||||
export class Humanoid extends GameObject implements Ticking, Disposable {
|
||||
public isTickingObject = true;
|
||||
public objectType = 'Humanoid';
|
||||
public name = 'Humanoid';
|
||||
|
@ -32,9 +34,12 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
private _health = 100;
|
||||
private _maxHealth = 100;
|
||||
private _velocity = new Vector3(0, 0, 0);
|
||||
private _appliedGravity = new Vector3(0, 0, 0);
|
||||
private _grounded = true;
|
||||
private _lookAt = new Vector3(0, 0, 1);
|
||||
private _currentLookAt = new Vector3(0, 0, 1);
|
||||
private _animState = 0;
|
||||
private _jumpEnergy = 0;
|
||||
public static bodyPartNames = [
|
||||
'Head',
|
||||
'Torso',
|
||||
|
@ -63,11 +68,18 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
textBorderSize: 1,
|
||||
});
|
||||
|
||||
public characterHeight = 5.5;
|
||||
public characterHalfHeight = this.characterHeight / 2;
|
||||
|
||||
public jumpPower = 18;
|
||||
|
||||
private mixer!: AnimationMixer;
|
||||
private idleAction!: AnimationAction;
|
||||
private walkAction!: AnimationAction;
|
||||
private jumpAction!: AnimationAction;
|
||||
|
||||
private nameTag?: NameTag;
|
||||
private physics?: HumanoidPhysicsProxy;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
get health() {
|
||||
|
@ -95,6 +107,16 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
return this.health > 0;
|
||||
}
|
||||
|
||||
get grounded() {
|
||||
return this._grounded;
|
||||
}
|
||||
set grounded(value: boolean) {
|
||||
if (value && this._jumpEnergy < this.jumpPower / 2) {
|
||||
this._jumpEnergy = 0;
|
||||
}
|
||||
this._grounded = value;
|
||||
}
|
||||
|
||||
private get bodyParts() {
|
||||
return Humanoid.bodyPartNames.map((key) => this.getBodyPartByName(key));
|
||||
}
|
||||
|
@ -123,8 +145,10 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
|
||||
const idleClip = AnimationClip.findByName(this.parent.animations, 'Idle');
|
||||
const walkClip = AnimationClip.findByName(this.parent.animations, 'Walk');
|
||||
const jumpClip = AnimationClip.findByName(this.parent.animations, 'Jump');
|
||||
this.idleAction = this.mixer.clipAction(idleClip);
|
||||
this.walkAction = this.mixer.clipAction(walkClip);
|
||||
this.jumpAction = this.mixer.clipAction(jumpClip);
|
||||
this.idleAction.play();
|
||||
|
||||
this.createNameTag();
|
||||
|
@ -140,14 +164,40 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
this._lookAt.lerp(vector, 0.15);
|
||||
}
|
||||
|
||||
jump() {
|
||||
if (!this.grounded) return;
|
||||
this.grounded = false;
|
||||
this._jumpEnergy = this.jumpPower;
|
||||
}
|
||||
|
||||
tick(dt: number): void {
|
||||
if (!this.ready) return;
|
||||
this.mixer.update(dt);
|
||||
this.parent?.position.add(this._velocity.clone().multiplyScalar(dt));
|
||||
|
||||
if (!this.grounded) {
|
||||
this._appliedGravity.y = -9.81;
|
||||
} else {
|
||||
this._appliedGravity.y = 0;
|
||||
}
|
||||
if (this._jumpEnergy > 0) {
|
||||
this._appliedGravity.y += this._jumpEnergy;
|
||||
this._jumpEnergy -= 10 * dt;
|
||||
}
|
||||
|
||||
if (this.physics)
|
||||
this.physics.applyMovement(
|
||||
this.parent!.position,
|
||||
this._velocity.clone().add(this._appliedGravity).multiplyScalar(dt)
|
||||
);
|
||||
else this.parent?.position.add(this._velocity.clone().multiplyScalar(dt));
|
||||
|
||||
this._currentLookAt.copy(this.parent!.position);
|
||||
this._currentLookAt.add(this._lookAt);
|
||||
this.parent?.lookAt(this._currentLookAt);
|
||||
|
||||
if (this.physics) {
|
||||
this.physics.applyRotation(this.parent!.quaternion);
|
||||
}
|
||||
}
|
||||
|
||||
setWalkAnimationState(index: number) {
|
||||
|
@ -197,4 +247,12 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
this.nameTag.position.set(0, 1.5, 0);
|
||||
this.add(this.nameTag);
|
||||
}
|
||||
|
||||
setPhysics(physics?: HumanoidPhysicsProxy) {
|
||||
this.physics = physics;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.nameTag?.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,6 @@ export class MeshPart extends Brick {
|
|||
super(geometry, material, mesh);
|
||||
}
|
||||
|
||||
getMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
|
||||
/** Do some surgery to convert a loaded SkinnedMesh into a game object */
|
||||
static fromLoaded(loaded: SkinnedMesh) {
|
||||
const newObject = new MeshPart(loaded.geometry, undefined, loaded);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Sprite, CanvasTexture, SpriteMaterial } from 'three';
|
||||
import { CanvasUtils } from '../canvas';
|
||||
import { GameObject } from '..';
|
||||
import { Disposable } from '../types/disposable';
|
||||
import { GameObject } from '../types/game-object';
|
||||
|
||||
export class NameTag extends GameObject {
|
||||
export class NameTag extends GameObject implements Disposable {
|
||||
public objectType = NameTag.name;
|
||||
public virtual = true;
|
||||
public archivable = false;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { EditorProperty } from '..';
|
||||
import { GameObject } from '../types/game-object';
|
||||
|
||||
export class World extends GameObject {
|
||||
|
@ -5,8 +6,12 @@ export class World extends GameObject {
|
|||
public name = 'World';
|
||||
public virtual = true;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
public gravity = -9.81;
|
||||
|
||||
override get properties() {
|
||||
return [];
|
||||
const properties = super.properties;
|
||||
return properties.filter((prop) => ['gravity'].includes(prop.name));
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
import {
|
||||
Brick,
|
||||
Cylinder,
|
||||
Sphere,
|
||||
Torus,
|
||||
Capsule,
|
||||
Wedge,
|
||||
WedgeCorner,
|
||||
WedgeInnerCorner,
|
||||
} from '../gameobjects';
|
||||
import type RAPIER from '@dimforge/rapier3d';
|
||||
import { Instancable } from '../types/instancable';
|
||||
import { Matrix4 } from 'three';
|
||||
|
||||
export class ColliderFactory {
|
||||
static gameObjectColliders = [
|
||||
{
|
||||
instance: Cylinder,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const height = obj.scale.y / 2;
|
||||
const radius = (obj.scale.x / 2 + obj.scale.z / 2) / 2;
|
||||
const collider = factory.ColliderDesc.cylinder(height, radius);
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: Sphere,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const radius = Math.max(obj.scale.x, obj.scale.y, obj.scale.z) / 2;
|
||||
const collider = factory.ColliderDesc.ball(radius);
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: Torus,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const height = obj.scale.y / 2;
|
||||
const radius = (obj.scale.x / 2 + obj.scale.z / 2) / 2;
|
||||
const collider = factory.ColliderDesc.roundCylinder(
|
||||
height,
|
||||
radius,
|
||||
height
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: Capsule,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const height = obj.scale.y / 2;
|
||||
const radius = (obj.scale.x / 2 + obj.scale.z / 2) / 2;
|
||||
const collider = factory.ColliderDesc.capsule(height, radius);
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: Wedge,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...obj.scale.toArray());
|
||||
const points = obj
|
||||
.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: WedgeCorner,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...obj.scale.toArray());
|
||||
const points = obj
|
||||
.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: WedgeInnerCorner,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...obj.scale.toArray());
|
||||
const points = obj
|
||||
.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
{
|
||||
instance: Brick,
|
||||
createCollisionShape: (
|
||||
obj: Brick,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) => {
|
||||
const scale = obj.scale.clone().divideScalar(2);
|
||||
const collider = factory.ColliderDesc.cuboid(scale.x, scale.y, scale.z);
|
||||
return world.createCollider(collider, body);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
static createCollider<T extends Brick>(
|
||||
obj: T,
|
||||
factory: typeof RAPIER,
|
||||
world: RAPIER.World,
|
||||
body?: RAPIER.RigidBody
|
||||
) {
|
||||
const collider = ColliderFactory.gameObjectColliders.find(
|
||||
(entry) => obj instanceof entry.instance
|
||||
);
|
||||
if (!collider) return undefined;
|
||||
return collider.createCollisionShape(obj, factory, world, body);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './rapier';
|
||||
export * from './physics-object';
|
||||
export * from './colliders';
|
|
@ -0,0 +1,73 @@
|
|||
import { Quaternion, Vector3 } from 'three';
|
||||
import { GameObject3D } from '../types/game-object';
|
||||
import { Ticking } from '../types/ticking';
|
||||
import type { Humanoid } from '../gameobjects/humanoid.object';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class PhysicsObjectAssociation implements Ticking {
|
||||
uuid: string;
|
||||
isTickingObject = true;
|
||||
|
||||
constructor(
|
||||
public object: GameObject3D,
|
||||
public collider?: Rapier.Collider,
|
||||
public body?: Rapier.RigidBody
|
||||
) {
|
||||
this.uuid = object.uuid;
|
||||
}
|
||||
initialize(): void {}
|
||||
|
||||
tick(dt: number): void {
|
||||
if (!this.body) return;
|
||||
this.object.position.copy(this.body.translation() as any);
|
||||
this.object.quaternion.copy(this.body.rotation() as any);
|
||||
}
|
||||
}
|
||||
|
||||
export class HumanoidPhysicsProxy extends PhysicsObjectAssociation {
|
||||
constructor(
|
||||
private humanoid: Humanoid,
|
||||
private controller: Rapier.KinematicCharacterController,
|
||||
private ray: Rapier.Ray,
|
||||
private world: Rapier.World,
|
||||
collider: Rapier.Collider,
|
||||
body: Rapier.RigidBody
|
||||
) {
|
||||
super(humanoid.parent! as GameObject3D, collider, body);
|
||||
}
|
||||
|
||||
applyMovement(position: Vector3, velocity: Vector3) {
|
||||
const vec3 = position.clone();
|
||||
this.controller.computeColliderMovement(this.collider!, velocity);
|
||||
const computed = this.controller.computedMovement();
|
||||
vec3.copy(computed as Vector3);
|
||||
// console.log(computed, position);
|
||||
this.body?.setNextKinematicTranslation(vec3.add(position));
|
||||
// After the collider movement calculation is done, we can read the
|
||||
// collision events.
|
||||
// for (let i = 0; i < this.controller.numComputedCollisions(); i++) {
|
||||
// let collision = this.controller.computedCollision(i);
|
||||
// // Do something with that collision information.
|
||||
// console.log(collision);
|
||||
// }
|
||||
// this.collider?.setTranslation(colliderHalf);
|
||||
// this.humanoid.parent!.position.copy(computed as Vector3).sub(halfVec);
|
||||
|
||||
vec3.copy(this.humanoid.parent!.position);
|
||||
this.ray.origin = vec3;
|
||||
this.ray.origin.y -= 0.01;
|
||||
const hit = this.world.castRay(this.ray, 0.5, false);
|
||||
this.humanoid.grounded = false;
|
||||
if (hit) {
|
||||
const point = this.ray.pointAt(hit.toi);
|
||||
const diff = vec3.y - (point.y + 0.15);
|
||||
if (diff < 0) {
|
||||
this.humanoid.grounded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRotation(quat: Quaternion) {
|
||||
this.body?.setNextKinematicRotation(quat);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export type Rapier = typeof import('@dimforge/rapier3d');
|
||||
|
||||
export function getRapier() {
|
||||
return import('@dimforge/rapier3d');
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { Disposable } from './disposable';
|
||||
import { Renderer } from '../core/renderer';
|
||||
import { EventEmitter } from '../utils/events';
|
||||
import { EngineEvents } from './events';
|
||||
|
||||
export abstract class EngineComponent {
|
||||
export abstract class EngineComponent implements Disposable {
|
||||
public abstract name: string;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -80,6 +80,7 @@ export type EngineEvents = {
|
|||
instance: (event: InstanceEvent) => void;
|
||||
sceneJoin: (event: Object3D) => void;
|
||||
sceneLeave: (event: Object3D) => void;
|
||||
queueFree: (event: Object3D) => void;
|
||||
loadComplete: () => void;
|
||||
initialized: () => void;
|
||||
reset: () => void;
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from './game-object';
|
|||
export * from './instancable';
|
||||
export * from './world-file';
|
||||
export * from './asset';
|
||||
export * from './disposable';
|
||||
|
|
|
@ -80,6 +80,7 @@ export const instanceCharacterObject = async (name: string) => {
|
|||
controller.position.set(0, 4.75, 0);
|
||||
controller.archivable = false;
|
||||
baseObject.add(controller);
|
||||
baseObject.position.set(0, 0.1, 0);
|
||||
|
||||
return baseObject;
|
||||
};
|
||||
|
|
162
pnpm-lock.yaml
162
pnpm-lock.yaml
|
@ -98,6 +98,12 @@ importers:
|
|||
vite-plugin-dts:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0(vite@4.3.9)
|
||||
vite-plugin-top-level-await:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(vite@4.3.9)
|
||||
vite-plugin-wasm:
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(vite@4.3.9)
|
||||
vue:
|
||||
specifier: ^3.2.47
|
||||
version: 3.2.47
|
||||
|
@ -160,6 +166,9 @@ importers:
|
|||
|
||||
packages/engine:
|
||||
dependencies:
|
||||
'@dimforge/rapier3d':
|
||||
specifier: ^0.11.2
|
||||
version: 0.11.2
|
||||
reflect-metadata:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.13
|
||||
|
@ -529,6 +538,10 @@ packages:
|
|||
mime: 3.0.0
|
||||
dev: true
|
||||
|
||||
/@dimforge/rapier3d@0.11.2:
|
||||
resolution: {integrity: sha512-B+AKkPmtJxED3goMTGU8v0ju8hUAUQGLgghzCos4G4OeN9X+mJ5lfN2xtNA0n8tJRJk2YfsMk9BOj/6AN89Acg==}
|
||||
dev: false
|
||||
|
||||
/@esbuild-kit/cjs-loader@2.4.2:
|
||||
resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==}
|
||||
dependencies:
|
||||
|
@ -1322,6 +1335,16 @@ packages:
|
|||
terser: 5.18.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-virtual@3.0.1:
|
||||
resolution: {integrity: sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-wasm@6.1.3(rollup@3.23.0):
|
||||
resolution: {integrity: sha512-7ItTTeyauE6lwdDtQWceEHZ9+txbi4RRy0mYPFn9BW7rD7YdgBDu7HTHsLtHrRzJc313RM/1m6GKgV3np/aEaw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
@ -1419,6 +1442,118 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@swc/core-darwin-arm64@1.3.65:
|
||||
resolution: {integrity: sha512-fQIXZgr7CD/+1ADqrVbz/gHvSoIMmggHvPzguQjV8FggBuS9Efm1D1ZrdUSqptggKvuLLHMZf+49tENq8NWWcg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-darwin-x64@1.3.65:
|
||||
resolution: {integrity: sha512-kGuWP7OP9mwOiIcJpEVa+ydC3Wxf0fPQ1MK0hUIPFcR6tAUEdOvdAuCzP6U20RX/JbbgwfI/Qq6ugT7VL6omgg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm-gnueabihf@1.3.65:
|
||||
resolution: {integrity: sha512-Bjbzldp8n4mWSdAvBt4VuLiHlfFM5pyftjJvJnmSY4H1IzbxkByyT60OHOedcIPRiZveD8NJzUJqutqrgTmtLg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm64-gnu@1.3.65:
|
||||
resolution: {integrity: sha512-GmxtcCymeQqEqT9n5mo857koRsUbEwmuijrBA4OeD5KOPW9gqAmUxr+ZgwgYHwyJ3CiN+UbK8uEqPsL6UVQmLg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-arm64-musl@1.3.65:
|
||||
resolution: {integrity: sha512-yv9jP3gbfMsYrqswT2MwK5Q1+avSwRXAKo+LYUknTeoLQNNlukDfqSLHajNq23XrVDRP4B3Pjn7kaqjxRcihbg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-x64-gnu@1.3.65:
|
||||
resolution: {integrity: sha512-GQkwysEPTlAOQ3jiTiedObzh6pBaf9RLaQqpGdCp+iKze9+BR+STBP0IIKhZDMPG/nWWNhrYFD/VMQxRoYPjfw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-linux-x64-musl@1.3.65:
|
||||
resolution: {integrity: sha512-ETzhOhtDluYFK4x73OTM9gVTMyzGd2WeWGlCu3WoT1EPPUwCqQpcAqI3TfEcP1ljFDG0pPkpYzVpwNf8yjQElg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-arm64-msvc@1.3.65:
|
||||
resolution: {integrity: sha512-3weD0I6F8bggN0KOnbZkvYC1PBrT5wrvohpvtgijRsODxjoWwztozjawJxF3rqgVqlSI/+nA+JkrN48e2cxJjQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-ia32-msvc@1.3.65:
|
||||
resolution: {integrity: sha512-i6c3D7E9Ca41HteW3+hn1OKQfjIabc2P0p1mJRXBkn+igwb+Ba6gXJc7NqhrlF8uZsDhhcGZTsAqBBtfcfTuHQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core-win32-x64-msvc@1.3.65:
|
||||
resolution: {integrity: sha512-tQ9hEDtwPZxQ2sYb2n8ypfmdMjobKAf6VSnChteLMktofU7o562op5pLS6D6QCP2AtL3lcwe1piTCgIhk4vmjA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@swc/core@1.3.65:
|
||||
resolution: {integrity: sha512-d5iDiKWf12FBo6h9Fro2pcnLK6HSPbyZ7A1U5iFNpRRx8XEd4uGdKtf5NoXJ3GDLQDLXnNSLA82Cl6SfrJ1lyw==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
'@swc/helpers': ^0.5.0
|
||||
peerDependenciesMeta:
|
||||
'@swc/helpers':
|
||||
optional: true
|
||||
optionalDependencies:
|
||||
'@swc/core-darwin-arm64': 1.3.65
|
||||
'@swc/core-darwin-x64': 1.3.65
|
||||
'@swc/core-linux-arm-gnueabihf': 1.3.65
|
||||
'@swc/core-linux-arm64-gnu': 1.3.65
|
||||
'@swc/core-linux-arm64-musl': 1.3.65
|
||||
'@swc/core-linux-x64-gnu': 1.3.65
|
||||
'@swc/core-linux-x64-musl': 1.3.65
|
||||
'@swc/core-win32-arm64-msvc': 1.3.65
|
||||
'@swc/core-win32-ia32-msvc': 1.3.65
|
||||
'@swc/core-win32-x64-msvc': 1.3.65
|
||||
dev: false
|
||||
|
||||
/@tootallnate/once@2.0.0:
|
||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
@ -6655,6 +6790,11 @@ packages:
|
|||
which-typed-array: 1.1.9
|
||||
dev: true
|
||||
|
||||
/uuid@9.0.0:
|
||||
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
dependencies:
|
||||
|
@ -6790,6 +6930,20 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-top-level-await@1.3.1(vite@4.3.9):
|
||||
resolution: {integrity: sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==}
|
||||
peerDependencies:
|
||||
vite: '>=2.8'
|
||||
dependencies:
|
||||
'@rollup/plugin-virtual': 3.0.1
|
||||
'@swc/core': 1.3.65
|
||||
uuid: 9.0.0
|
||||
vite: 4.3.9(@types/node@18.0.0)(sass@1.62.1)
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
- rollup
|
||||
dev: false
|
||||
|
||||
/vite-plugin-vue-inspector@3.4.2(vite@4.3.9):
|
||||
resolution: {integrity: sha512-q5OTkcZJqL78bwGJl1Zk8CNqtxZ9wP2udJYqyFIZzL1lTax0/oq7DhNkLrnPTxkJuf0QPZKdunb1vDyCByn4dQ==}
|
||||
peerDependencies:
|
||||
|
@ -6809,6 +6963,14 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-wasm@3.2.2(vite@4.3.9):
|
||||
resolution: {integrity: sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==}
|
||||
peerDependencies:
|
||||
vite: ^2 || ^3 || ^4
|
||||
dependencies:
|
||||
vite: 4.3.9(@types/node@18.0.0)(sass@1.62.1)
|
||||
dev: false
|
||||
|
||||
/vite@4.3.9(@types/node@18.0.0)(sass@1.62.1):
|
||||
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
|
Loading…
Reference in New Issue