@threejs-addons/threejs-addons
Version:
free & opensource threejs addons to boost productivity
233 lines (184 loc) • 8.57 kB
text/typescript
import { World, KinematicCharacterController, Collider, CharacterCollision, QueryFilterFlags } from '@dimforge/rapier3d';
import { BaseController } from './Base';
import { Vector3 } from 'three/src/math/Vector3';
import { Quaternion } from 'three/src/math/Quaternion';
import { CameraControllerType } from '../../types';
import { CameraController } from './camera-controller';
interface RapierColliderControllerAPI {
world: World;
offset: number;
controller: KinematicCharacterController;
collider: Collider | null;
toi: number;
}
export class RapierColliderController extends BaseController implements RapierColliderControllerAPI {
world: World;
offset: number;
controller: KinematicCharacterController;
collider: Collider | null = null;
toi: number = 0;
constructor({ world, offset, options, CCC } : { world: World, offset: number, options: object, CCC?: CameraController }){
super({ target: null, options, CCC });
this.world = world;
this.offset = offset;
this.controller = this.initCharacterController();
}
/**
* @description sets up the character controller
* @returns CharacterController
*/
private initCharacterController(){
return this.world.createCharacterController(this.offset) as KinematicCharacterController;
}
/**
*
* @description compute movements of collider
* @returns number of collisions
*/
private computeMovement({ collider, desiredMovementVector, filterFlags, filterGroups, filterPredicate } : {
collider: Collider | null,
desiredMovementVector: Vector3,
filterFlags?: QueryFilterFlags | undefined,
filterGroups?: number | undefined,
filterPredicate?: (collider: Collider) => boolean
}): null | number {
let c = collider || this.collider;
if(!c) return null;
if(!desiredMovementVector) return null;
this.controller.computeColliderMovement(c, desiredMovementVector, filterFlags, filterGroups, filterPredicate);
return this.controller.numComputedCollisions();
}
/**
* @description get computed collisions
* @returns Array of collisions
*/
private getComputedCollisions(): CharacterCollision[] {
let collisions: CharacterCollision[] = [];
for (let i = 0; i < this.controller.numComputedCollisions(); i++) {
let collision = this.controller.computedCollision(i);
collisions.push(collision as CharacterCollision);
}
return collisions;
}
/**
* @description wether or not collision is imminent
*/
private isCollisionImminent(): boolean {
let is_collision_imminent = false;
for (let i = 0; i < this.controller.numComputedCollisions(); i++) {
let collision = this.controller.computedCollision(i) as CharacterCollision;
if(collision.toi <= this.toi) is_collision_imminent = true;
}
return is_collision_imminent;
}
/**
* @description get corrected movement
*/
private getCorrectedMovement(): Vector3 {
const { x, y, z } = this.controller.computedMovement();
return new Vector3(x, y, z);
}
/**
* @param collider
* @param updateTargetPosition Wether or not to update the target position. This forces the controller to try and update target position even if the collider is null
* @description sets the collider
*/
setCollider(collider: Collider, updateTargetPosition?: boolean): void {
this.collider = collider;
if(!this.target && !updateTargetPosition)return;
const { x, y, z} = this.collider.translation();
this.desiredMovementVector = new Vector3(x,y,z);
this.updateTargetPosition();
}
/** @description handle XYZ movements */
handleTranslateXYZDirection(): void {
if(!this.collider) return;
this.setColliderQuaternionFromCameraController();
const { x: cx, y: cy, z: cz } = this.collider.translation();
const computedTranslation = BaseController.computeTranslationXYZDirection({
speedFactor: this.speedFactor,
SPEED_UP_CONSTANT: this.SPEED_UP_CONSTANT,
delta: this.delta,
direction: this.direction,
translation: new Vector3(cx, cy, cz),
cameraController: this.CCC?.activeController as CameraControllerType
});
if(!computedTranslation) return;
const { position: desiredMovementVector, velocity: desiredVelocityVector } = computedTranslation;
const shape = this.collider?.shape
const shapePosition = this.collider?.translation();
const shapeRotation = this.collider?.rotation();
const shapeVelocity = desiredVelocityVector.clone()
let collisions = [];
let hit = this.world.castShape(shapePosition, shapeRotation, shapeVelocity, shape, this.toi, false, undefined, this.options?.filterGroups, this.collider);
if (hit != null) {
collisions.push(hit);
}
let is_collision_imminent = false
let collision_imminent = null
for(const collision of collisions){
if(collision.toi <= this.toi) is_collision_imminent = true;
if(is_collision_imminent) collision_imminent = collision
if(is_collision_imminent) break;
}
if(is_collision_imminent && collision_imminent && collision_imminent?.toi <= this.toi){
this.desiredVelocityVector = new Vector3(0, 0, 0);
}else{
this.desiredMovementVector = desiredMovementVector;
/** carry out corrections based on max an min translation options */
// this.desiredMovementVector.y = Math.min(Math.max(this.desiredMovementVector.y, this.options?.translation?.max?.y), this.options?.translation?.max?.y);
// this.desiredVelocityVector = desiredVelocityVector;
// this.desiredVelocityVector.y = Math.min(Math.max(this.desiredMovementVector.y, this.options?.translation?.max?.y), this.options?.translation?.max?.y);
this.desiredMovementVector.y = shapePosition.y
this.desiredVelocityVector = desiredVelocityVector;
this.desiredVelocityVector.y = 0
}
/** update computational values */
this.updateColliderPosition(); // this current colliders position
this.updateTargetPosition(); //if target is attached
this.updateTargetQuaternion(); // if target is attached
/** update internal values */
this._updateColliderPosition();
this._updateColliderQuaternion();
this._updateColliderVelocity();
}
/**
* @description set max time to impact for collider
*/
setToi(value: number){
this.toi = value;
}
/** set collider quartenion */
setColliderQuaternionFromCameraController(){
if(!this.collider) return;
if(!this.CCC?.activeController) return;
this.collider.setRotation({ x: this.CCC.activeController?.camera.quaternion.x as number, y: this.CCC.activeController?.camera.quaternion.y as number, z: this.CCC.activeController?.camera.quaternion.z as number, w: this.CCC.activeController?.camera.quaternion.w as number});
}
updateColliderPosition(): void {
if(this.collider) this.collider.setTranslation(this.desiredMovementVector);
}
/** update internal this.position value */
private _updateColliderPosition(): void {
if(!this.collider) return;
this.position.copy(this.desiredMovementVector);
}
/** update internal this.rotation value */
private _updateColliderQuaternion(): void {
if(!this.collider) return;
const { x, y, z, w } = this.collider.rotation();
this.rotation.copy(new Quaternion(x, y, z, w));
}
/** update internal this.velocity */
private _updateColliderVelocity(): void {
if(!this.collider) return;
this.velocity.copy(this.desiredVelocityVector);
}
/** update filter groups on options */
updateFilterGroups(groups: number){
this.options.filterGroups = groups;
}
/** update options */
updateOptions(options: object){
this.options = options;
}
}