physics cleanup
This commit is contained in:
parent
837be262a4
commit
f0b877f11b
|
@ -40,9 +40,6 @@ export class GameplayComponent extends EngineComponent {
|
|||
|
||||
override update(delta: number): void {
|
||||
this.controls?.update(delta);
|
||||
for (const character of this.characters) {
|
||||
character.tick(delta);
|
||||
}
|
||||
|
||||
this.move.set(0, 0, 0);
|
||||
this.look.setFromMatrixColumn(this.renderer.camera.matrix, 0);
|
||||
|
@ -77,7 +74,6 @@ export class GameplayComponent extends EngineComponent {
|
|||
const ctrl = getCharacterController(char);
|
||||
this.world.add(char);
|
||||
this.characters.push(ctrl);
|
||||
ctrl.initialize();
|
||||
this.character = ctrl;
|
||||
|
||||
this.controls = new ThirdPersonCamera(
|
||||
|
@ -86,6 +82,7 @@ export class GameplayComponent extends EngineComponent {
|
|||
this.renderer.renderer.domElement
|
||||
);
|
||||
this.controls.initialize();
|
||||
this.events.emit('sceneJoin', char);
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
|
|
|
@ -59,13 +59,13 @@ export class LevelComponent extends EngineComponent {
|
|||
};
|
||||
}
|
||||
|
||||
public createObject(object: string, setParent?: Object3D) {
|
||||
public createObject(object: string, setParent?: Object3D, skipJoin = false) {
|
||||
const parent = setParent || this.world;
|
||||
const ObjectType = instancableGameObjects[object];
|
||||
if (!ObjectType) return;
|
||||
const newObject = new ObjectType();
|
||||
parent.add(newObject);
|
||||
this.events.emit('sceneJoin', newObject);
|
||||
if (!skipJoin) this.events.emit('sceneJoin', newObject);
|
||||
return newObject;
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ export class LevelComponent extends EngineComponent {
|
|||
|
||||
private recursiveCreate(entry: SerializedObject, setParent?: Object3D) {
|
||||
const parent = setParent || this.world;
|
||||
const newObject = this.createObject(entry.objectType, parent);
|
||||
const newObject = this.createObject(entry.objectType, parent, true);
|
||||
newObject?.deserialize(entry);
|
||||
entry.children.forEach((child) => this.recursiveCreate(child, newObject));
|
||||
return newObject;
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import { SkinnedMesh, Vector3 } from 'three';
|
||||
import { Object3D, 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 { getRapier, PhysicsTicking } from '../physics';
|
||||
import { Humanoid } from '../gameobjects/humanoid.object';
|
||||
import { PhysicsObject } from '../gameobjects/physics.object';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { GameObject } from '..';
|
||||
|
||||
export class PhysicsWorldComponent extends EngineComponent {
|
||||
public name = PhysicsWorldComponent.name;
|
||||
|
@ -17,11 +13,10 @@ export class PhysicsWorldComponent extends EngineComponent {
|
|||
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[] = [];
|
||||
private trackedObjects: PhysicsTicking[] = [];
|
||||
|
||||
initialize(): void {
|
||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||
|
@ -38,18 +33,19 @@ export class PhysicsWorldComponent extends EngineComponent {
|
|||
dispose(): void {
|
||||
this.cleanUpEvents?.call(this);
|
||||
this.physicsWorld.removeCharacterController(this.characterPhysics);
|
||||
for (const object of this.trackedObjects) object.dispose();
|
||||
}
|
||||
|
||||
private createRapier() {
|
||||
getRapier().then((R) => {
|
||||
getRapier().then((physicsEngine) => {
|
||||
const gravity = new Vector3(0, this.world.gravity, 0);
|
||||
const world = new R.World(gravity);
|
||||
const world = new physicsEngine.World(gravity);
|
||||
this.physicsWorld = world;
|
||||
this.physicsEngine = R;
|
||||
this.physicsEngine = physicsEngine;
|
||||
|
||||
if (this.initEventFiredBeforeEngineLoad) {
|
||||
this.initEventFiredBeforeEngineLoad = false;
|
||||
setTimeout(() => this.initializePhysicsScene(), 500); // FIXME
|
||||
this.initializePhysicsScene();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -60,93 +56,87 @@ export class PhysicsWorldComponent extends EngineComponent {
|
|||
this.initEventFiredBeforeEngineLoad = true;
|
||||
return;
|
||||
}
|
||||
setTimeout(() => this.initializePhysicsScene(), 500); // FIXME
|
||||
this.initializePhysicsScene();
|
||||
};
|
||||
|
||||
const sceneJoinEvent = (object: Object3D) => {
|
||||
this.applyPhysics(object);
|
||||
};
|
||||
|
||||
const sceneLeaveEvent = (object: Object3D) => {
|
||||
this.removePhysics(object);
|
||||
};
|
||||
|
||||
this.events.addListener('initialized', initializeEvent);
|
||||
this.events.addListener('sceneJoin', sceneJoinEvent);
|
||||
this.events.addListener('sceneLeave', sceneLeaveEvent);
|
||||
|
||||
return () => {
|
||||
this.events.removeEventListener('initialized', initializeEvent);
|
||||
this.events.removeEventListener('sceneJoin', sceneJoinEvent);
|
||||
this.events.removeEventListener('sceneLeave', sceneLeaveEvent);
|
||||
};
|
||||
}
|
||||
|
||||
private applyPhysics(object: Brick) {
|
||||
// This object has effecively no physics:
|
||||
// doesn't move, doesn't collide
|
||||
if (object.anchored && !object.canCollide) return;
|
||||
private applyPhysics(root: Object3D) {
|
||||
root.traverse((object) => {
|
||||
// Prevent double-init
|
||||
if (this.trackedObjects.some((entry) => entry.uuid === object.uuid))
|
||||
return;
|
||||
|
||||
let bodyDesc: Rapier.RigidBodyDesc;
|
||||
if (object.anchored) bodyDesc = this.physicsEngine.RigidBodyDesc.fixed();
|
||||
else bodyDesc = this.physicsEngine.RigidBodyDesc.dynamic();
|
||||
// Initialize humanoids
|
||||
if (object instanceof Humanoid) {
|
||||
object.initialize(
|
||||
this.physicsEngine,
|
||||
this.physicsWorld,
|
||||
this.characterPhysics
|
||||
);
|
||||
this.trackedObjects.push(object);
|
||||
return;
|
||||
}
|
||||
|
||||
bodyDesc
|
||||
.setTranslation(...object.position.toArray())
|
||||
.setRotation(object.quaternion);
|
||||
// Only track physics object instances
|
||||
if (!(object instanceof PhysicsObject)) return;
|
||||
|
||||
const body = this.physicsWorld.createRigidBody(bodyDesc);
|
||||
// Do not add physics to virtual objects
|
||||
if ((object as GameObject).virtual) return;
|
||||
|
||||
let collider: Rapier.Collider | undefined;
|
||||
if (object.canCollide) {
|
||||
collider = ColliderFactory.createCollider(
|
||||
object,
|
||||
this.physicsEngine,
|
||||
this.physicsWorld,
|
||||
body
|
||||
);
|
||||
}
|
||||
// Do not apply physics to animated objects
|
||||
if ((object.getMesh() as SkinnedMesh).skeleton) return;
|
||||
|
||||
const tracker = new PhysicsObjectAssociation(object, collider, body);
|
||||
console.log(tracker);
|
||||
this.trackedObjects.push(tracker);
|
||||
return tracker;
|
||||
// Initialize object physics
|
||||
object.initialize(this.physicsEngine, this.physicsWorld);
|
||||
this.trackedObjects.push(object);
|
||||
});
|
||||
}
|
||||
|
||||
private applyHumanoidPhysics(humanoid: Humanoid) {
|
||||
const halfVec = new Vector3(0, humanoid.characterHalfHeight, 0);
|
||||
const colliderDesc = this.physicsEngine.ColliderDesc.cuboid(
|
||||
1,
|
||||
humanoid.characterHalfHeight,
|
||||
0.5
|
||||
private removePhysics(root: Object3D) {
|
||||
const trackedObjects = [...this.trackedObjects];
|
||||
const physicsLeaveUUIDs: string[] = [];
|
||||
|
||||
root.traverse((object) => {
|
||||
if (trackedObjects.some((item) => item.uuid === object.uuid)) {
|
||||
physicsLeaveUUIDs.push(object.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
this.trackedObjects = this.trackedObjects.filter((object) =>
|
||||
physicsLeaveUUIDs.includes(object.uuid)
|
||||
);
|
||||
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,
|
||||
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.1, z: 0 }
|
||||
);
|
||||
this.characterPhysics = this.physicsWorld.createCharacterController(0.01);
|
||||
this.characterPhysics.setApplyImpulsesToDynamicBodies(true);
|
||||
this.characterPhysics.setMaxSlopeClimbAngle((75 * Math.PI) / 180);
|
||||
this.characterPhysics.enableAutostep(1, 0.5, true);
|
||||
// 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.applyPhysics(this.world);
|
||||
|
||||
this.sceneInitialized = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,85 +1,31 @@
|
|||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
ColorRepresentation,
|
||||
Mesh,
|
||||
MeshPhongMaterial,
|
||||
} from 'three';
|
||||
import { GameObject3D } from '../types/game-object';
|
||||
import { PhysicsObject } from './physics.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import { assetManager } from '../assets/manager';
|
||||
import { AssetInfo } from '../types/asset';
|
||||
import { EditorProperty } from '../decorators/property';
|
||||
import { BufferGeometry, Mesh, MeshPhongMaterial } from 'three';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Brick extends GameObject3D {
|
||||
public objectType = Brick.name;
|
||||
private texturePath?: string;
|
||||
|
||||
@EditorProperty({ type: Color })
|
||||
get color() {
|
||||
return this.material.color;
|
||||
}
|
||||
set color(color: ColorRepresentation) {
|
||||
this.material.color = new Color(color);
|
||||
}
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
get transparency() {
|
||||
return 1 - this.material.opacity;
|
||||
}
|
||||
set transparency(value: number) {
|
||||
this.material.transparent = value != 0;
|
||||
this.material.opacity = 1 - value;
|
||||
this.material.needsUpdate = true;
|
||||
}
|
||||
|
||||
@EditorProperty({ type: AssetInfo })
|
||||
get texture() {
|
||||
return this.texturePath;
|
||||
}
|
||||
set texture(path: string | undefined) {
|
||||
if (!path) {
|
||||
this.material.map = null;
|
||||
this.texturePath = undefined;
|
||||
this.material.needsUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = assetManager.getAssetByPath(path);
|
||||
if (!asset || !asset.texture) {
|
||||
console.error(`Asset ${path} does not exist or is not loaded`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.texturePath = path;
|
||||
this.material.map = asset.texture;
|
||||
this.material.needsUpdate = true;
|
||||
}
|
||||
|
||||
@EditorProperty({ type: Boolean })
|
||||
public canCollide = true;
|
||||
|
||||
@EditorProperty({ type: Boolean })
|
||||
public anchored = true;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
public mass = 1;
|
||||
export class Brick extends PhysicsObject {
|
||||
public objectType = 'Brick';
|
||||
public name = 'Brick';
|
||||
|
||||
constructor(
|
||||
protected geometry: BufferGeometry = gameObjectGeometries.boxGeometry,
|
||||
protected material = new MeshPhongMaterial(),
|
||||
protected mesh: Mesh = new Mesh(geometry, material)
|
||||
geometry: BufferGeometry = gameObjectGeometries.boxGeometry,
|
||||
material?: MeshPhongMaterial,
|
||||
mesh?: Mesh
|
||||
) {
|
||||
super();
|
||||
this.name = this.objectType;
|
||||
this.add(this.mesh);
|
||||
super(geometry, material, mesh);
|
||||
}
|
||||
|
||||
getGeometry() {
|
||||
return this.geometry;
|
||||
}
|
||||
|
||||
getMesh() {
|
||||
return this.mesh;
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const scale = this.scale.clone().divideScalar(2);
|
||||
const collider = factory.ColliderDesc.cuboid(
|
||||
Math.abs(scale.x),
|
||||
Math.abs(scale.y),
|
||||
Math.abs(scale.z)
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Capsule extends Brick {
|
||||
public objectType = Capsule.name;
|
||||
public objectType = 'Capsule';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.capsuleGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const height = this.scale.y / 2;
|
||||
const radius = (this.scale.x / 2 + this.scale.z / 2) / 2;
|
||||
const collider = factory.ColliderDesc.capsule(
|
||||
Math.abs(height),
|
||||
Math.abs(radius)
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Cylinder extends Brick {
|
||||
public objectType = Cylinder.name;
|
||||
public objectType = 'Cylinder';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.cylinderGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const height = this.scale.y / 2;
|
||||
const radius = (this.scale.x / 2 + this.scale.z / 2) / 2;
|
||||
const collider = factory.ColliderDesc.cylinder(
|
||||
Math.abs(height),
|
||||
Math.abs(radius)
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { environmentDefaults } from '../defaults/environment';
|
|||
import { EditorProperty, EditorPropertyExclude } from '../decorators/property';
|
||||
|
||||
export class Environment extends GameObject {
|
||||
public objectType = Environment.name;
|
||||
public objectType = 'Environment';
|
||||
@EditorPropertyExclude()
|
||||
public name = 'Environment';
|
||||
public virtual = true;
|
||||
|
|
|
@ -3,20 +3,19 @@ import {
|
|||
AnimationClip,
|
||||
AnimationMixer,
|
||||
LoopOnce,
|
||||
Object3D,
|
||||
Skeleton,
|
||||
SkinnedMesh,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { GameObject } from '../types/game-object';
|
||||
import { Ticking } from '../types/ticking';
|
||||
import { EditorProperty } from '../decorators/property';
|
||||
import { MeshPart } from './mesh.object';
|
||||
import { clamp, lerp } from 'three/src/math/MathUtils.js';
|
||||
import { clamp } 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';
|
||||
import { PhysicsTicking } from '../physics';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { Quaternion } from '@dimforge/rapier3d';
|
||||
|
||||
export type HumanoidBodyPart =
|
||||
| 'Head'
|
||||
|
@ -26,7 +25,7 @@ export type HumanoidBodyPart =
|
|||
| 'LegRight'
|
||||
| 'LegLeft';
|
||||
|
||||
export class Humanoid extends GameObject implements Ticking, Disposable {
|
||||
export class Humanoid extends GameObject implements PhysicsTicking {
|
||||
public isTickingObject = true;
|
||||
public objectType = 'Humanoid';
|
||||
public name = 'Humanoid';
|
||||
|
@ -71,7 +70,6 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
public characterHeight = 5.5;
|
||||
public characterHalfHeight = this.characterHeight / 2;
|
||||
|
||||
private gravity = -12;
|
||||
private shouldJump = false;
|
||||
|
||||
private mixer!: AnimationMixer;
|
||||
|
@ -80,10 +78,17 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
private jumpAction!: AnimationAction;
|
||||
|
||||
private nameTag?: NameTag;
|
||||
private physics?: HumanoidPhysicsProxy;
|
||||
|
||||
protected collider?: Rapier.Collider;
|
||||
protected rigidBody?: Rapier.RigidBody;
|
||||
protected physicsWorldRef?: Rapier.World;
|
||||
protected characterControllerRef?: Rapier.KinematicCharacterController;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
public weight = -16;
|
||||
|
||||
@EditorProperty({ type: Boolean })
|
||||
public jumpPower = 6;
|
||||
public jumpPower = 8;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
get health() {
|
||||
|
@ -132,7 +137,11 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
);
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
initialize(
|
||||
physicsEngine: typeof Rapier,
|
||||
physicsWorld: Rapier.World,
|
||||
characterController: Rapier.KinematicCharacterController
|
||||
): void {
|
||||
if (!this.parent)
|
||||
throw new Error('Cannot initialize Humanoid to empty parent');
|
||||
|
||||
|
@ -141,6 +150,7 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
if (!this.skeleton) throw new Error('Could not find Skeleton for Humanoid');
|
||||
this.skeleton.pose();
|
||||
|
||||
// Clip actions
|
||||
this.mixer = new AnimationMixer(this.parent);
|
||||
this.ready = true;
|
||||
|
||||
|
@ -153,7 +163,29 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
this.jumpAction.setLoop(LoopOnce, 0);
|
||||
this.idleAction.play();
|
||||
|
||||
// Create name tag
|
||||
this.createNameTag();
|
||||
|
||||
if (this.virtual) return;
|
||||
this.physicsWorldRef = physicsWorld;
|
||||
this.characterControllerRef = characterController;
|
||||
|
||||
// Character Physics
|
||||
const halfVec = new Vector3(0, this.characterHalfHeight, 0);
|
||||
const colliderDesc = physicsEngine.ColliderDesc.cuboid(
|
||||
1,
|
||||
this.characterHalfHeight,
|
||||
0.5
|
||||
);
|
||||
const rigidBodyDesc = physicsEngine.RigidBodyDesc.kinematicPositionBased()
|
||||
.setTranslation(...this.parent!.position.toArray())
|
||||
.setRotation(this.parent!.quaternion);
|
||||
const rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
|
||||
const collider = physicsWorld.createCollider(colliderDesc, rigidBody);
|
||||
collider.setTranslationWrtParent(halfVec);
|
||||
|
||||
this.rigidBody = rigidBody;
|
||||
this.collider = collider;
|
||||
}
|
||||
|
||||
setVelocity(velocity: Vector3) {
|
||||
|
@ -173,31 +205,39 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
|
||||
tick(dt: number): void {
|
||||
if (!this.ready) return;
|
||||
|
||||
// Apply rigidbody transforms to object from last process tick
|
||||
if (this.rigidBody) {
|
||||
this.parent!.position.copy(this.rigidBody.translation() as any);
|
||||
this.parent!.quaternion.copy(this.rigidBody.rotation() as any);
|
||||
}
|
||||
|
||||
// Run animation
|
||||
this.mixer.update(dt);
|
||||
|
||||
// Apply jumping
|
||||
if (this.shouldJump) {
|
||||
this._appliedGravity.y = Math.sqrt(-2 * this.gravity * this.jumpPower);
|
||||
this._appliedGravity.y = Math.sqrt(-2 * this.weight * this.jumpPower);
|
||||
this.grounded = false;
|
||||
this.shouldJump = false;
|
||||
}
|
||||
|
||||
this._appliedGravity.y += this.gravity * dt;
|
||||
// Apply gravity
|
||||
this._appliedGravity.y += this.weight * 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));
|
||||
// Apply velocity
|
||||
this.applyVelocity(
|
||||
this._velocity.clone().add(this._appliedGravity).multiplyScalar(dt)
|
||||
);
|
||||
|
||||
// Apply look vector
|
||||
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);
|
||||
}
|
||||
this.applyRotation(this.parent!.quaternion);
|
||||
|
||||
// Stick to ground
|
||||
if (this.grounded) {
|
||||
this._appliedGravity.y = 0;
|
||||
}
|
||||
|
@ -251,11 +291,43 @@ export class Humanoid extends GameObject implements Ticking, Disposable {
|
|||
this.add(this.nameTag);
|
||||
}
|
||||
|
||||
setPhysics(physics?: HumanoidPhysicsProxy) {
|
||||
this.physics = physics;
|
||||
private applyVelocity(velocity: Vector3) {
|
||||
if (!this.characterControllerRef || !this.parent || !this.rigidBody) return;
|
||||
|
||||
const vec3 = this.parent.position.clone();
|
||||
this.characterControllerRef.computeColliderMovement(
|
||||
this.collider!,
|
||||
velocity
|
||||
);
|
||||
const computed = this.characterControllerRef.computedMovement();
|
||||
const grounded = this.characterControllerRef.computedGrounded();
|
||||
vec3.copy(computed as Vector3);
|
||||
this.rigidBody?.setNextKinematicTranslation(vec3.add(this.parent.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._grounded = grounded;
|
||||
}
|
||||
|
||||
private applyRotation(quat: Quaternion) {
|
||||
this.rigidBody?.setNextKinematicRotation(quat);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.nameTag?.dispose();
|
||||
|
||||
if (this.collider && !this.rigidBody) {
|
||||
this.physicsWorldRef?.removeCollider(this.collider, false);
|
||||
}
|
||||
|
||||
if (this.rigidBody) {
|
||||
this.physicsWorldRef?.removeRigidBody(this.rigidBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ export const instancableGameObjects: Record<string, Instancable<GameObject>> = {
|
|||
export * from './environment.object';
|
||||
export * from './world.object';
|
||||
export * from './nametag.object';
|
||||
export * from './physical.object';
|
||||
export * from './physics.object';
|
||||
export {
|
||||
Group,
|
||||
Cylinder,
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import {
|
||||
BufferGeometry,
|
||||
Material,
|
||||
Mesh,
|
||||
MeshPhongMaterial,
|
||||
SkinnedMesh,
|
||||
} from 'three';
|
||||
import { BufferGeometry, Mesh, MeshPhongMaterial, SkinnedMesh } from 'three';
|
||||
import { Brick } from '.';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class MeshPart extends Brick {
|
||||
public objectType = MeshPart.name;
|
||||
public objectType = 'MeshPart';
|
||||
|
||||
constructor(
|
||||
geometry: BufferGeometry,
|
||||
|
@ -25,4 +20,18 @@ export class MeshPart extends Brick {
|
|||
newObject.add(loaded);
|
||||
return newObject;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const scale = this.scale.clone().divideScalar(2);
|
||||
const collider = factory.ColliderDesc.cuboid(
|
||||
Math.abs(scale.x),
|
||||
Math.abs(scale.y),
|
||||
Math.abs(scale.z)
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Disposable } from '../types/disposable';
|
|||
import { GameObject } from '../types/game-object';
|
||||
|
||||
export class NameTag extends GameObject implements Disposable {
|
||||
public objectType = NameTag.name;
|
||||
public objectType = 'NameTag';
|
||||
public virtual = true;
|
||||
public archivable = false;
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { GameObject3D } from '../types/game-object';
|
||||
import {
|
||||
BufferGeometry,
|
||||
Color,
|
||||
ColorRepresentation,
|
||||
Mesh,
|
||||
MeshPhongMaterial,
|
||||
} from 'three';
|
||||
import { assetManager } from '../assets/manager';
|
||||
import { AssetInfo } from '../types/asset';
|
||||
import { EditorProperty } from '../decorators/property';
|
||||
|
||||
export class PhysicalObject extends GameObject3D {
|
||||
private texturePath?: string;
|
||||
|
||||
@EditorProperty({ type: Color })
|
||||
get color() {
|
||||
return this.material.color;
|
||||
}
|
||||
set color(color: ColorRepresentation) {
|
||||
this.material.color = new Color(color);
|
||||
}
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
get transparency() {
|
||||
return 1 - this.material.opacity;
|
||||
}
|
||||
set transparency(value: number) {
|
||||
this.material.transparent = value != 0;
|
||||
this.material.opacity = 1 - value;
|
||||
this.material.needsUpdate = true;
|
||||
}
|
||||
|
||||
@EditorProperty({ type: AssetInfo })
|
||||
get texture() {
|
||||
return this.texturePath;
|
||||
}
|
||||
set texture(path: string | undefined) {
|
||||
if (!path) {
|
||||
this.material.map = null;
|
||||
this.texturePath = undefined;
|
||||
this.material.needsUpdate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = assetManager.getAssetByPath(path);
|
||||
if (!asset || !asset.texture) {
|
||||
console.error(`Asset ${path} does not exist or is not loaded`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.texturePath = path;
|
||||
this.material.map = asset.texture;
|
||||
this.material.needsUpdate = true;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected geometry: BufferGeometry,
|
||||
protected material = new MeshPhongMaterial(),
|
||||
protected mesh: Mesh = new Mesh(geometry, material)
|
||||
) {
|
||||
super();
|
||||
this.name = this.objectType;
|
||||
this.add(this.mesh);
|
||||
}
|
||||
|
||||
getGeometry() {
|
||||
return this.geometry;
|
||||
}
|
||||
|
||||
getMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import { EditorProperty } from '..';
|
||||
import { PhysicsTicking } from '../physics/ticking';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { PhysicalObject } from './physical.object';
|
||||
|
||||
export class PhysicsObject extends PhysicalObject implements PhysicsTicking {
|
||||
public objectType = 'PhysicsObject';
|
||||
isTickingObject = true;
|
||||
|
||||
protected collider?: Rapier.Collider;
|
||||
protected rigidBody?: Rapier.RigidBody;
|
||||
protected physicsWorldRef?: Rapier.World;
|
||||
|
||||
@EditorProperty({ type: Boolean })
|
||||
public canCollide = true;
|
||||
|
||||
@EditorProperty({ type: Boolean })
|
||||
public anchored = true;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
public mass = 1;
|
||||
|
||||
@EditorProperty({ type: Number })
|
||||
public friction = 0.2;
|
||||
|
||||
initialize(physicsEngine: typeof Rapier, physicsWorld: Rapier.World): void {
|
||||
if (this.virtual) return;
|
||||
this.physicsWorldRef = physicsWorld;
|
||||
|
||||
// This object has effecively no physics:
|
||||
// doesn't move, doesn't collide
|
||||
if (this.anchored && !this.canCollide) return;
|
||||
|
||||
let bodyDesc: Rapier.RigidBodyDesc;
|
||||
if (this.anchored) bodyDesc = physicsEngine.RigidBodyDesc.fixed();
|
||||
else bodyDesc = physicsEngine.RigidBodyDesc.dynamic();
|
||||
|
||||
bodyDesc
|
||||
.setTranslation(...this.position.toArray())
|
||||
.setRotation(this.quaternion)
|
||||
.setAdditionalMass(this.mass)
|
||||
.setLinearDamping(this.friction)
|
||||
.setAngularDamping(this.friction);
|
||||
|
||||
const body = physicsWorld.createRigidBody(bodyDesc);
|
||||
|
||||
let collider: Rapier.Collider | undefined;
|
||||
if (this.canCollide) {
|
||||
collider = this.createCollider(physicsEngine, physicsWorld, body);
|
||||
}
|
||||
|
||||
this.collider = collider;
|
||||
this.rigidBody = body;
|
||||
}
|
||||
|
||||
tick(dt: number): void {
|
||||
if (!this.rigidBody || this.virtual) return;
|
||||
this.position.copy(this.rigidBody.translation() as any);
|
||||
this.quaternion.copy(this.rigidBody.rotation() as any);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.collider && !this.rigidBody) {
|
||||
this.physicsWorldRef?.removeCollider(this.collider, false);
|
||||
}
|
||||
|
||||
if (this.rigidBody) {
|
||||
this.physicsWorldRef?.removeRigidBody(this.rigidBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create collision shape for physics object.
|
||||
* @internal
|
||||
* @param factory Physics engine
|
||||
* @param world Physics world
|
||||
* @param body RigidBody
|
||||
*/
|
||||
protected createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
): Rapier.Collider | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,24 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Sphere extends Brick {
|
||||
public objectType = Sphere.name;
|
||||
public objectType = 'Sphere';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.sphereGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const radius = Math.max(this.scale.x, this.scale.y, this.scale.z) / 2;
|
||||
const collider = factory.ColliderDesc.ball(Math.abs(radius));
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Mesh, Quaternion, Vector3 } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Torus extends Brick {
|
||||
public objectType = Torus.name;
|
||||
public objectType = 'Torus';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.torusGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const height = this.scale.y * 0.4;
|
||||
const radius = (this.scale.x + this.scale.z) / 2;
|
||||
const collider = factory.ColliderDesc.cylinder(
|
||||
Math.abs(height),
|
||||
Math.abs(radius) * 1.4
|
||||
).setRotation(
|
||||
new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2)
|
||||
);
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Matrix4, Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class WedgeCorner extends Brick {
|
||||
public objectType = WedgeCorner.name;
|
||||
public objectType = 'WedgeCorner';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.wedgeCornerGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...this.scale.toArray());
|
||||
const points = this.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Matrix4, Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class WedgeInnerCorner extends Brick {
|
||||
public objectType = WedgeInnerCorner.name;
|
||||
public objectType = 'WedgeInnerCorner';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.wedgeInnerCornerGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...this.scale.toArray());
|
||||
const points = this.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
import { Mesh } from 'three';
|
||||
import { Matrix4, Mesh } from 'three';
|
||||
import { Brick } from './brick.object';
|
||||
import { gameObjectGeometries } from './geometries';
|
||||
import type Rapier from '@dimforge/rapier3d';
|
||||
|
||||
export class Wedge extends Brick {
|
||||
public objectType = Wedge.name;
|
||||
public objectType = 'Wedge';
|
||||
protected mesh = new Mesh(this.geometry, this.material);
|
||||
|
||||
constructor() {
|
||||
super(gameObjectGeometries.wedgeGeometry);
|
||||
this.name = this.objectType;
|
||||
}
|
||||
|
||||
protected override createCollider(
|
||||
factory: typeof Rapier,
|
||||
world: Rapier.World,
|
||||
body?: Rapier.RigidBody
|
||||
) {
|
||||
const mat = new Matrix4();
|
||||
mat.makeScale(...this.scale.toArray());
|
||||
const points = this.getGeometry()
|
||||
.getAttribute('position')
|
||||
.clone()
|
||||
.applyMatrix4(mat)?.array as Float32Array;
|
||||
const collider = factory.ColliderDesc.convexMesh(points)!;
|
||||
return world.createCollider(collider, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { EditorProperty } from '..';
|
|||
import { GameObject } from '../types/game-object';
|
||||
|
||||
export class World extends GameObject {
|
||||
public objectType = World.name;
|
||||
public objectType = 'World';
|
||||
public name = 'World';
|
||||
public virtual = true;
|
||||
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
export * from './rapier';
|
||||
export * from './physics-object';
|
||||
export * from './colliders';
|
||||
export * from './ticking';
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
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,
|
||||
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();
|
||||
const grounded = this.controller.computedGrounded();
|
||||
vec3.copy(computed as Vector3);
|
||||
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);
|
||||
|
||||
this.humanoid.grounded = grounded;
|
||||
}
|
||||
|
||||
applyRotation(quat: Quaternion) {
|
||||
this.body?.setNextKinematicRotation(quat);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import type Rapier from '@dimforge/rapier3d';
|
||||
import { Disposable } from '../types/disposable';
|
||||
|
||||
export interface PhysicsTicking extends Disposable {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isTickingObject: boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
uuid: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
initialize(physicsEngine: typeof Rapier, physicsWorld: Rapier.World, controller?: Rapier.KinematicCharacterController): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
tick(dt: number): void;
|
||||
}
|
|
@ -1,5 +1,16 @@
|
|||
export interface Ticking {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isTickingObject: boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
initialize(): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
tick(dt: number): void;
|
||||
}
|
||||
|
|
|
@ -80,7 +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.5, 0);
|
||||
baseObject.position.set(0, 1, 0);
|
||||
|
||||
return baseObject;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue