UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

162 lines (159 loc) 6.21 kB
import { math } from '../../core/math/math.js'; import { Quat } from '../../core/math/quat.js'; import { Vec2 } from '../../core/math/vec2.js'; import { Vec3 } from '../../core/math/vec3.js'; const tmpV1 = new Vec3(); const rotation = new Quat(); /** * Represents a pose in 3D space, including position and rotation. * * @category Input * @alpha */ class Pose { /** * Creates a new Pose instance. * * @param {Vec3} [position] - The position of the pose. * @param {Vec3} [angles] - The angles of the pose in degrees. * @param {number} [distance] - The focus distance from the position to the pose. */ constructor(position = Vec3.ZERO, angles = Vec3.ZERO, distance = 0){ /** * The position of the pose. * * @type {Vec3} */ this.position = new Vec3(); /** * The angles of the pose in degrees calculated from the forward vector. * * @type {Vec3} */ this.angles = new Vec3(); /** * The focus distance from the position to the pose. * * @type {number} */ this.distance = 0; /** * @type {Vec2} */ this.pitchRange = new Vec2(-Infinity, Infinity); /** * @type {Vec2} */ this.yawRange = new Vec2(-Infinity, Infinity); /** * @type {Vec2} */ this.xRange = new Vec2(-Infinity, Infinity); /** * @type {Vec2} */ this.yRange = new Vec2(-Infinity, Infinity); /** * @type {Vec2} */ this.zRange = new Vec2(-Infinity, Infinity); this.set(position, angles, distance); } /** * Copies the position and rotation from another pose. * * @param {Pose} other - The pose to copy from. * @returns {Pose} The updated Pose instance. */ copy(other) { return this.set(other.position, other.angles, other.distance); } /** * Creates a clone of this pose. * * @returns {Pose} A new Pose instance with the same position, angles, and distance. */ clone() { return new Pose(this.position.clone(), this.angles.clone(), this.distance); } /** * Checks if this pose is approximately equal to another pose within a given epsilon. * * @param {Pose} other - The pose to compare with. * @param {number} [epsilon] - The tolerance for comparison. * @returns {boolean} True if the poses are approximately equal, false otherwise. */ equalsApprox(other, epsilon = 1e-6) { return this.position.equalsApprox(other.position, epsilon) && this.angles.equalsApprox(other.angles, epsilon) && Math.abs(this.distance - other.distance) < epsilon; } /** * Lerps between two poses based on the given alpha values. * * @param {Pose} lhs - The left-hand side pose. * @param {Pose} rhs - The right-hand side pose. * @param {number} alpha1 - The alpha value for position interpolation. * @param {number} [alpha2] - The alpha value for angles interpolation. * @param {number} [alpha3] - The alpha value for distance interpolation. * @returns {Pose} The updated Pose instance. */ lerp(lhs, rhs, alpha1, alpha2 = alpha1, alpha3 = alpha1) { this.position.lerp(lhs.position, rhs.position, alpha1); this.angles.x = math.lerpAngle(lhs.angles.x, rhs.angles.x, alpha2) % 360; this.angles.y = math.lerpAngle(lhs.angles.y, rhs.angles.y, alpha2) % 360; this.angles.z = math.lerpAngle(lhs.angles.z, rhs.angles.z, alpha2) % 360; this.distance = math.lerp(lhs.distance, rhs.distance, alpha3); return this; } /** * Moves the pose by the given vector. * * @param {Vec3} offset - The vector to move by. * @returns {Pose} The updated Pose instance. */ move(offset) { this.position.add(offset); // clamp position this.position.x = math.clamp(this.position.x, this.xRange.x, this.xRange.y); this.position.y = math.clamp(this.position.y, this.yRange.x, this.yRange.y); this.position.z = math.clamp(this.position.z, this.zRange.x, this.zRange.y); return this; } /** * Rotates the pose by the given angles in degrees. * * @param {Vec3} euler - The angles to rotate by. * @returns {Pose} The updated Pose instance. */ rotate(euler) { this.angles.add(euler); // wrap angles to [0, 360) this.angles.x %= 360; this.angles.y %= 360; this.angles.z %= 360; // clamp pitch and yaw this.angles.x = math.clamp(this.angles.x, this.pitchRange.x, this.pitchRange.y); this.angles.y = math.clamp(this.angles.y, this.yawRange.x, this.yawRange.y); return this; } /** * Sets the position and rotation of the pose. * * @param {Vec3} position - The new position. * @param {Vec3} angles - The new angles in degrees. * @param {number} distance - The new focus distance. * @returns {Pose} The updated Pose instance. */ set(position, angles, distance) { this.position.copy(position); this.angles.copy(angles); this.distance = distance; return this; } /** * Sets the pose to look in the direction of the given vector. * * @param {Vec3} from - The point from which to look. * @param {Vec3} to - The point to look at. * @returns {Pose} The updated Pose instance. */ look(from, to) { this.position.copy(from); this.distance = from.distance(to); const dir = tmpV1.sub2(to, from).normalize(); const elev = Math.atan2(-dir.y, Math.sqrt(dir.x * dir.x + dir.z * dir.z)) * math.RAD_TO_DEG; const azim = Math.atan2(-dir.x, -dir.z) * math.RAD_TO_DEG; this.angles.set(-elev, azim, 0); return this; } /** * Gets the focus point of the pose, which is the position plus the forward vector scaled by the distance. * * @param {Vec3} [out] - The output vector to store the focus point. * @returns {Vec3} The focus point of the pose. */ getFocus(out) { return rotation.setFromEulerAngles(this.angles).transformVector(Vec3.FORWARD, out).mulScalar(this.distance).add(this.position); } } export { Pose };