@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
206 lines (205 loc) • 6.64 kB
JavaScript
"use strict";
import { CapsuleSopOperation } from "./../../engine/operations/sop/Capsule";
import { Vector3 } from "three";
import { Box3 } from "three";
import { Line3 } from "three";
import { Matrix4 } from "three";
import { MathUtils } from "three";
const tmpGravity = new Vector3(0, 0, 0);
const upVector = new Vector3(0, 1, 0);
const tempVector1 = new Vector3();
const tempVector2 = new Vector3();
const tempVector3 = new Vector3();
const tempVector4 = new Vector3();
const tempVector5 = new Vector3();
const tempBox = new Box3();
const tempMat = new Matrix4();
const tempSegment = new Line3();
const startRotationRadians = new Vector3();
export class CorePlayer {
constructor(options) {
this._pressed = {
forward: false,
backward: false,
left: false,
right: false
};
this._onGround = false;
this._velocity = new Vector3();
this.capsuleInfo = {
radius: CapsuleSopOperation.DEFAULT_PARAMS.radius,
segment: new Line3(
new Vector3(0, 0, 0),
new Vector3(
0,
-(CapsuleSopOperation.DEFAULT_PARAMS.height - 2 * CapsuleSopOperation.DEFAULT_PARAMS.radius),
0
)
)
};
this.startPosition = new Vector3(0, 5, 0);
this.startRotation = new Vector3(0, 0, 0);
this.jumpAllowed = true;
this.jumpStrength = 10;
this.runAllowed = true;
this.runSpeedMult = 2;
this._running = false;
this.speed = 10;
this.physicsSteps = 5;
this.gravity = new Vector3(0, -30, 0);
this._azimuthalAngle = 0;
this._resetYMax = -25;
this._resetRequiredCallback = () => {
return this.object.position.y < this._resetYMax;
};
this.setOptions(options);
}
setOptions(options) {
this._setObject(options.object);
this.setCollider(options.collider);
}
_setObject(object) {
this.object = object;
this.object.matrixAutoUpdate = true;
}
setCollider(collider) {
this.collider = collider;
}
setCapsule(capsuleOptions) {
this.capsuleInfo.radius = capsuleOptions.radius;
this.capsuleInfo.segment.end.y = -(capsuleOptions.height - 2 * capsuleOptions.radius);
}
reset() {
this.stop();
this.object.position.copy(this.startPosition);
startRotationRadians.copy(this.startRotation).multiplyScalar(MathUtils.DEG2RAD);
this.object.rotation.setFromVector3(startRotationRadians);
this.object.updateMatrix();
this.object.updateWorldMatrix(true, true);
this.object.updateMatrixWorld(true);
}
stop() {
this._pressed.forward = false;
this._pressed.backward = false;
this._pressed.left = false;
this._pressed.right = false;
this._running = false;
this._velocity.set(0, 0, 0);
}
setResetRequiredCallback(callback) {
this._resetRequiredCallback = callback;
}
setAzimuthalAngle(angle) {
this._azimuthalAngle = angle;
}
update(delta) {
const deltaBounded = Math.min(delta, 0.1);
for (let i = 0; i < this.physicsSteps; i++) {
this._updateStep(deltaBounded / this.physicsSteps);
}
}
_updateStep(delta) {
if (!this._onGround) {
tmpGravity.copy(this.gravity).multiplyScalar(delta);
this._velocity.add(tmpGravity);
}
this.object.position.addScaledVector(this._velocity, delta);
const angle = this._azimuthalAngle;
const speed = this.speed * delta * (this._running ? this.runSpeedMult : 1);
tempVector2.set(0, 0, 0);
if (this._pressed.forward) {
tempVector1.set(0, 0, -1).applyAxisAngle(upVector, angle);
tempVector2.add(tempVector1);
}
if (this._pressed.backward) {
tempVector1.set(0, 0, 1).applyAxisAngle(upVector, angle);
tempVector2.add(tempVector1);
}
if (this._pressed.left) {
tempVector1.set(-1, 0, 0).applyAxisAngle(upVector, angle);
tempVector2.add(tempVector1);
}
if (this._pressed.right) {
tempVector1.set(1, 0, 0).applyAxisAngle(upVector, angle);
tempVector2.add(tempVector1);
}
tempVector2.normalize().multiplyScalar(speed);
this.object.position.add(tempVector2);
this.object.updateMatrixWorld();
const capsuleInfo = this.capsuleInfo;
tempBox.makeEmpty();
tempMat.copy(this.collider.matrixWorld).invert();
tempSegment.copy(capsuleInfo.segment);
tempSegment.start.applyMatrix4(this.object.matrixWorld).applyMatrix4(tempMat);
tempSegment.end.applyMatrix4(this.object.matrixWorld).applyMatrix4(tempMat);
tempBox.expandByPoint(tempSegment.start);
tempBox.expandByPoint(tempSegment.end);
tempBox.min.addScalar(-capsuleInfo.radius);
tempBox.max.addScalar(capsuleInfo.radius);
const intersectsBounds = (box, isLeaf, score, depth, nodeIndex) => {
return box.intersectsBox(tempBox);
};
const intersectsTriangle = (tri) => {
const triPoint = tempVector3;
const capsulePoint = tempVector4;
const distance = tri.closestPointToSegment(tempSegment, triPoint, capsulePoint);
if (distance < capsuleInfo.radius) {
const depth = capsuleInfo.radius - distance;
const direction = capsulePoint.sub(triPoint).normalize();
tempSegment.start.addScaledVector(direction, depth);
tempSegment.end.addScaledVector(direction, depth);
}
};
this.collider.geometry.boundsTree.shapecast({
intersectsBounds,
intersectsTriangle
});
const newPosition = tempVector5;
newPosition.copy(tempSegment.start);
newPosition.applyMatrix4(this.collider.matrixWorld);
const deltaVector = tempVector2;
deltaVector.subVectors(newPosition, this.object.position);
this._onGround = deltaVector.y > Math.abs(delta * this._velocity.y * 0.25);
const offset = Math.max(0, deltaVector.length() - 1e-5);
deltaVector.normalize().multiplyScalar(offset);
this.object.position.add(deltaVector);
if (!this._onGround) {
deltaVector.normalize();
this._velocity.addScaledVector(deltaVector, -deltaVector.dot(this._velocity));
} else {
this._velocity.set(0, 0, 0);
}
if (this._resetRequiredCallback()) {
this.reset();
}
}
setForward(state) {
this._pressed.forward = state;
}
setBackward(state) {
this._pressed.backward = state;
}
setLeft(state) {
this._pressed.left = state;
}
setRight(state) {
this._pressed.right = state;
}
jump() {
if (this._onGround && this.jumpAllowed) {
this._velocity.y = this.jumpStrength;
}
}
setRun(state) {
if (state) {
if (this._onGround && this.runAllowed) {
this._running = true;
}
} else {
this._running = false;
}
}
running() {
return this._running;
}
}