UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

302 lines (238 loc) • 8.17 kB
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();