@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
302 lines (238 loc) • 8.17 kB
JavaScript
import { Frustum } from 'three';
import { assert } from "../../../../core/assert.js";
import { plane3_compute_ray_intersection } from "../../../../core/geom/3d/plane/plane3_compute_ray_intersection.js";
import { v3_distance_above_plane } from "../../../../core/geom/vec3/v3_distance_above_plane.js";
import Vector1 from "../../../../core/geom/Vector1.js";
import Vector3 from "../../../../core/geom/Vector3.js";
import ObservedBoolean from "../../../../core/model/ObservedBoolean.js";
import ObservedEnum from "../../../../core/model/ObservedEnum.js";
import { frustum_from_camera } from "./frustum_from_camera.js";
import { ProjectionType } from "./ProjectionType.js";
import { quaternion_invert_orientation } from "./quaternion_invert_orientation.js";
import { unprojectPoint } from "./unprojectPoint.js";
/**
* @class
*/
export class Camera {
constructor() {
/**
* Whether clipping planes should be automatically calculated or not
* @type {boolean}
*/
this.autoClip = false;
/**
* Tunable parameter to prevent clipping planes from thrashing. Clipping planes must move by a certain portion of the current span before frustum gets shrunken
* @type {number}
*/
this.autoClipHysteresis = 0.33;
this.object = null;
/**
*
* @type {Vector1}
*/
this.fov = new Vector1(45);
/**
*
* @type {ObservedEnum<ProjectionType>}
*/
this.projectionType = new ObservedEnum(ProjectionType.Perspective, ProjectionType);
/**
*
* @type {ObservedBoolean}
*/
this.active = new ObservedBoolean(true);
/**
* Near clipping plane
* @type {number}
* @private
*/
this.__clip_far = 100;
/**
* Far clipping plane
* @type {number}
* @private
*/
this.__clip_near = 0.1;
}
/**
*
* @param {Quaternion} output
* @param {Quaternion} input
*/
getReciprocalRotation(output, input) {
quaternion_invert_orientation(output, input);
}
get clip_far() {
return this.__clip_far;
}
set clip_far(v) {
this.__clip_far = v;
if (this.object !== null) {
this.object.far = v;
}
}
get clip_near() {
return this.__clip_near;
}
set clip_near(v) {
this.__clip_near = v;
if (this.object !== null) {
this.object.near = v;
}
}
updateMatrices() {
const c = this.object;
if (c === null) {
return;
}
c.updateProjectionMatrix();
c.updateMatrixWorld(true);
//update world inverse matrix
c.matrixWorldInverse.copy(c.matrixWorld);
c.matrixWorldInverse.invert();
}
projectRay(x, y, source, target) {
Camera.projectRay(this.object, x, y, source, target);
}
/**
*
* @param {Camera} other
*/
copy(other) {
this.active.copy(other.active);
this.projectionType.copy(other.projectionType);
this.autoClip = other.autoClip;
}
/**
*
* @returns {Camera}
*/
clone() {
const clone = new Camera();
clone.copy(this);
return clone;
}
toJSON() {
return {
autoClip: this.autoClip,
active: this.active.toJSON()
};
}
fromJSON(json) {
this.autoClip = json.autoClip;
if (typeof json.active === "boolean") {
this.active.fromJSON(json.active);
} else {
this.active.set(false);
}
}
/**
*
* @param {Camera|THREE.PerspectiveCamera|THREE.OrthographicCamera} camera
* @param {number} x
* @param {number} y
* @param {Vector3} out_source
* @param {Vector3} out_direction
*/
static projectRay(
camera,
x, y,
out_source, out_direction
) {
assert.defined(camera, "camera");
assert.defined(camera.position, "Camera.position");
assert.isNumber(x, 'x');
assert.isNumber(y, "y");
// assert.ok(x >= -1, `X(=${x}) must be greater than or equal to -1.0, not a clip-space coordinate`);
// assert.ok(x <= 1, `X(=${x}) must be less than or equal to 1.0, not a clip-space coordinate`);
// assert.ok(y >= -1, `Y(=${y}) must be greater than or equal to -1.0, not a clip-space coordinate`);
// assert.ok(y <= 1, `Y(=${y}) must be less than or equal to 1.0, not a clip-space coordinate`);
if (camera.isPerspectiveCamera || camera.isOrthographicCamera) {
const m4_world = camera.matrixWorld.elements;
const m4_projection_inverse = camera.projectionMatrixInverse.elements;
scratch_v3_1.setFromMatrixPosition(m4_world);
scratch_v3_0.set(x, y, 0.5);
// NOTE: projection inverse and world matrices are used in "unproject" operation, we update those to make things work as intended. This is handled by the camera system
unprojectPoint(
scratch_v3_0,
scratch_v3_0,
m4_projection_inverse,
m4_world
);
//get direction
scratch_v3_0.sub(scratch_v3_1);
scratch_v3_0.normalize();
out_source.copy(scratch_v3_1);
out_direction.copy(scratch_v3_0);
} else {
throw new Error('Unsupported camera type');
}
}
/**
*
* @param {Matrix4} result
*/
computeProjectionMatrix(result) {
const camera = this.object;
if (camera === null) {
return false;
}
result.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
return true;
}
/**
*
* @param {Vector3} out
* @param {number} origin_x
* @param {number} origin_y
* @param {number} origin_z
* @param {number} direction_x
* @param {number} direction_y
* @param {number} direction_z
* @param {number} plane_index
*/
rayPlaneIntersection(
out,
origin_x, origin_y, origin_z,
direction_x, direction_y, direction_z,
plane_index
) {
assert.isNonNegativeInteger(plane_index, 'plane_index');
assert.lessThanOrEqual(plane_index, 5, `plane_index must be <= 5, was ${plane_index}`)
frustum_from_camera(this.object, scratch_frustum);
const plane = scratch_frustum.planes[plane_index];
assert.defined(plane, 'plane');
plane3_compute_ray_intersection(
out,
origin_x, origin_y, origin_z,
direction_x, direction_y, direction_z,
plane.normal.x, plane.normal.y, plane.normal.z,
plane.constant
);
}
/**
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {Vector3} result
*/
projectWorldPointOntoNearPlane(x, y, z, result) {
frustum_from_camera(this.object, scratch_frustum);
const near = scratch_frustum.planes[0];
scratch_v3_0.set(x, y, z);
const normal = near.normal;
const distance_to_plane = v3_distance_above_plane(x, y, z, normal.x, normal.y, normal.z, near.constant);
const negative_distance_to_plane = -distance_to_plane;
result.set(
normal.x * negative_distance_to_plane + x,
normal.y * negative_distance_to_plane + y,
normal.z * negative_distance_to_plane + z,
);
}
}
Camera.typeName = "Camera";
Camera.ProjectionType = ProjectionType;
const scratch_v3_0 = new Vector3();
const scratch_v3_1 = new Vector3();
const scratch_frustum = new Frustum();