@itwin/core-common
Version:
iTwin.js components common to frontend and backend
295 lines • 15.1 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Views
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Frustum = exports.NpcCenter = exports.NpcCorners = exports.Npc = void 0;
const core_geometry_1 = require("@itwin/core-geometry");
/** The 8 corners of the [Normalized Plane Coordinate]($docs/learning/glossary.md#npc) cube.
* @public
* @extensions
*/
var Npc;
(function (Npc) {
/** Left bottom rear */
Npc[Npc["_000"] = 0] = "_000";
/** Right bottom rear */
Npc[Npc["_100"] = 1] = "_100";
/** Left top rear */
Npc[Npc["_010"] = 2] = "_010";
/** Right top rear */
Npc[Npc["_110"] = 3] = "_110";
/** Left bottom front */
Npc[Npc["_001"] = 4] = "_001";
/** Right bottom front */
Npc[Npc["_101"] = 5] = "_101";
/** Left top front */
Npc[Npc["_011"] = 6] = "_011";
/** Right top front */
Npc[Npc["_111"] = 7] = "_111";
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
Npc[Npc["LeftBottomRear"] = 0] = "LeftBottomRear";
Npc[Npc["RightBottomRear"] = 1] = "RightBottomRear";
Npc[Npc["LeftTopRear"] = 2] = "LeftTopRear";
Npc[Npc["RightTopRear"] = 3] = "RightTopRear";
Npc[Npc["LeftBottomFront"] = 4] = "LeftBottomFront";
Npc[Npc["RightBottomFront"] = 5] = "RightBottomFront";
Npc[Npc["LeftTopFront"] = 6] = "LeftTopFront";
Npc[Npc["RightTopFront"] = 7] = "RightTopFront";
/* eslint-enable @typescript-eslint/no-duplicate-enum-values */
/** useful for sizing arrays */
Npc[Npc["CORNER_COUNT"] = 8] = "CORNER_COUNT";
})(Npc || (exports.Npc = Npc = {}));
/** The 8 corners of an [[Npc]] Frustum.
* @public
*/
exports.NpcCorners = [
new core_geometry_1.Point3d(0.0, 0.0, 0.0),
new core_geometry_1.Point3d(1.0, 0.0, 0.0),
new core_geometry_1.Point3d(0.0, 1.0, 0.0),
new core_geometry_1.Point3d(1.0, 1.0, 0.0),
new core_geometry_1.Point3d(0.0, 0.0, 1.0),
new core_geometry_1.Point3d(1.0, 0.0, 1.0),
new core_geometry_1.Point3d(0.0, 1.0, 1.0),
new core_geometry_1.Point3d(1.0, 1.0, 1.0),
];
exports.NpcCorners.forEach((corner) => Object.freeze(corner));
Object.freeze(exports.NpcCorners);
/** The center point of the [Normalized Plane Coordinate]($docs/learning/glossary.md#npc) cube.
* @public
*/
exports.NpcCenter = new core_geometry_1.Point3d(.5, .5, .5); // eslint-disable-line @typescript-eslint/naming-convention
Object.freeze(exports.NpcCenter);
/** The region of physical (3d) space that appears in a view. It forms the field-of-view of a camera.
* It is stored as 8 points, in [[Npc]] order, that must define a truncated pyramid.
* @public
*/
class Frustum {
/** Array of the 8 points of this Frustum. */
points = [];
/** Constructor for Frustum. Members are initialized to the Npc cube. */
constructor() {
for (let i = 0; i < 8; ++i)
this.points[i] = exports.NpcCorners[i].clone();
}
/** Initialize this Frustum to the 8 corners of the NPC cube. */
initNpc() {
for (let i = 0; i < 8; ++i)
core_geometry_1.Point3d.createFrom(exports.NpcCorners[i], this.points[i]);
return this;
}
/** Get a corner Point from this Frustum. */
getCorner(i) { return this.points[i]; }
/** Get the point at the center of this Frustum (halfway between RightTopFront and LeftBottomRear. */
getCenter() {
return this.getCorner(Npc.RightTopFront).interpolate(0.5, this.getCorner(Npc.LeftBottomRear));
}
/** Get the distance between two corners of this Frustum. */
distance(corner1, corner2) {
return this.getCorner(corner1).distance(this.getCorner(corner2));
}
/** Get the ratio of the length of the diagonal of the front plane to the diagonal of the back plane. */
getFraction() {
return core_geometry_1.Geometry.safeDivideFraction(this.distance(Npc.LeftTopFront, Npc.RightBottomFront), this.distance(Npc.LeftTopRear, Npc.RightBottomRear), 0);
}
/** Multiply all the points of this Frustum by a Transform, in place. */
multiply(trans) {
trans.multiplyPoint3dArrayInPlace(this.points);
}
/** Offset all of the points of this Frustum by a vector. */
translate(offset) {
for (const pt of this.points)
pt.plus(offset, pt);
}
/** Transform all the points of this Frustum and return the result in another Frustum. */
transformBy(trans, result) {
result = result ? result : new Frustum();
trans.multiplyPoint3dArray(this.points, result.points);
return result;
}
/** Calculate a bounding range from the 8 points in this Frustum. */
toRange(range) {
return core_geometry_1.Range3d.createArray(this.points, range);
}
/** Make a copy of this Frustum.
* @param result Optional Frustum for copy. If undefined allocate a new Frustum.
*/
clone(result) {
result = result ? result : new Frustum();
result.setFrom(this);
return result;
}
/** Set the points of this Frustum to be copies of the points in another Frustum. */
setFrom(other) { this.setFromCorners(other.points); }
/** Set the points of this frustum from array of corner points in NPC order. */
setFromCorners(corners) {
for (let i = 0; i < 8; ++i)
this.points[i].setFrom(corners[i]);
}
/** Scale this Frustum, in place, about its center by a scale factor. */
scaleAboutCenter(scale) {
const orig = this.clone();
const f = 0.5 * (1.0 + scale);
orig.points[Npc._111].interpolate(f, orig.points[Npc._000], this.points[Npc._000]);
orig.points[Npc._011].interpolate(f, orig.points[Npc._100], this.points[Npc._100]);
orig.points[Npc._101].interpolate(f, orig.points[Npc._010], this.points[Npc._010]);
orig.points[Npc._001].interpolate(f, orig.points[Npc._110], this.points[Npc._110]);
orig.points[Npc._110].interpolate(f, orig.points[Npc._001], this.points[Npc._001]);
orig.points[Npc._010].interpolate(f, orig.points[Npc._101], this.points[Npc._101]);
orig.points[Npc._100].interpolate(f, orig.points[Npc._011], this.points[Npc._011]);
orig.points[Npc._000].interpolate(f, orig.points[Npc._111], this.points[Npc._111]);
}
/** The point at the center of the front face of this frustum */
get frontCenter() {
return this.getCorner(Npc.LeftBottomFront).interpolate(.5, this.getCorner(Npc.RightTopFront));
}
/** The point at the center of the rear face of this frustum */
get rearCenter() {
return this.getCorner(Npc.LeftBottomRear).interpolate(.5, this.getCorner(Npc.RightTopRear));
}
/** Scale this frustum's XY (viewing) plane about its center */
scaleXYAboutCenter(scale) {
const frontCenter = this.frontCenter, rearCenter = this.rearCenter;
frontCenter.interpolate(scale, this.points[Npc.LeftTopFront], this.points[Npc.LeftTopFront]);
frontCenter.interpolate(scale, this.points[Npc.RightTopFront], this.points[Npc.RightTopFront]);
frontCenter.interpolate(scale, this.points[Npc.LeftBottomFront], this.points[Npc.LeftBottomFront]);
frontCenter.interpolate(scale, this.points[Npc.RightBottomFront], this.points[Npc.RightBottomFront]);
rearCenter.interpolate(scale, this.points[Npc.LeftTopRear], this.points[Npc.LeftTopRear]);
rearCenter.interpolate(scale, this.points[Npc.RightTopRear], this.points[Npc.RightTopRear]);
rearCenter.interpolate(scale, this.points[Npc.LeftBottomRear], this.points[Npc.LeftBottomRear]);
rearCenter.interpolate(scale, this.points[Npc.RightBottomRear], this.points[Npc.RightBottomRear]);
}
/** Create a Map4d that converts world coordinates to/from [[Npc]] coordinates of this Frustum. */
toMap4d() {
const org = this.getCorner(Npc.LeftBottomRear);
const xVec = org.vectorTo(this.getCorner(Npc.RightBottomRear));
const yVec = org.vectorTo(this.getCorner(Npc.LeftTopRear));
const zVec = org.vectorTo(this.getCorner(Npc.LeftBottomFront));
return core_geometry_1.Map4d.createVectorFrustum(org, xVec, yVec, zVec, this.getFraction());
}
/** Get the rotation matrix to the frame of this frustum. This is equivalent to the view rotation matrix. */
getRotation(result) {
const org = this.getCorner(Npc.LeftBottomRear);
const xVec = org.vectorTo(this.getCorner(Npc.RightBottomRear));
const yVec = org.vectorTo(this.getCorner(Npc.LeftTopRear));
const matrix = core_geometry_1.Matrix3d.createRigidFromColumns(xVec, yVec, core_geometry_1.AxisOrder.XYZ, result);
if (matrix)
matrix.transposeInPlace();
return matrix;
}
/** Get the eye point - undefined if parallel projection */
getEyePoint(result) {
const fraction = this.getFraction();
if (Math.abs(fraction - 1) < 1E-8)
return undefined; // Parallel.
const org = this.getCorner(Npc.LeftBottomRear);
const zVec = org.vectorTo(this.getCorner(Npc.LeftBottomFront));
return org.plusScaled(zVec, 1 / (1 - fraction), result);
}
/** Invalidate this Frustum by setting all 8 points to zero. */
invalidate() {
for (let i = 0; i < 8; ++i)
this.points[i].set(0, 0, 0);
}
/** Return true if this Frustum is equal to another Frustum */
equals(rhs) {
for (let i = 0; i < 8; ++i) {
if (!this.points[i].isExactEqual(rhs.points[i]))
return false;
}
return true;
}
/** Return true if all of the points in this Frustum are *almost* the same as the points in another Frustum.
* @see [[equals]], [XYZ.isAlmostEqual]($geometry)
*/
isSame(other) {
for (let i = 0; i < 8; ++i) {
if (!this.points[i].isAlmostEqual(other.points[i]))
return false;
}
return true;
}
/** Initialize this Frustum from a Range */
initFromRange(range) {
const getZ = (arg) => arg.z !== undefined ? arg.z : 0;
const pts = this.points;
pts[0].x = pts[2].x = pts[4].x = pts[6].x = range.low.x;
pts[1].x = pts[3].x = pts[5].x = pts[7].x = range.high.x;
pts[0].y = pts[1].y = pts[4].y = pts[5].y = range.low.y;
pts[2].y = pts[3].y = pts[6].y = pts[7].y = range.high.y;
pts[0].z = pts[1].z = pts[2].z = pts[3].z = getZ(range.low);
pts[4].z = pts[5].z = pts[6].z = pts[7].z = getZ(range.high);
}
/** Create a new Frustum from a Range3d */
static fromRange(range, out) {
const frustum = undefined !== out ? out : new Frustum();
frustum.initFromRange(range);
return frustum;
}
/** Return true if this Frustum has a mirror (is not in the correct order.) */
get hasMirror() {
const pts = this.points;
const u = pts[Npc._000].vectorTo(pts[Npc._001]);
const v = pts[Npc._000].vectorTo(pts[Npc._010]);
const w = pts[Npc._000].vectorTo(pts[Npc._100]);
return (u.tripleProduct(v, w) > 0);
}
/** Make sure the frustum point order does not include mirroring. If so, reverse the order. */
fixPointOrder() {
if (!this.hasMirror)
return;
// frustum has mirroring, reverse points
const pts = this.points;
for (let i = 0; i < 8; i += 2) {
const tmpPoint = pts[i];
pts[i] = pts[i + 1];
pts[i + 1] = tmpPoint;
}
}
/** Get a convex set of clipping planes bounding the region contained by this Frustum. */
getRangePlanes(clipFront, clipBack, expandPlaneDistance) {
const convexSet = core_geometry_1.ConvexClipPlaneSet.createEmpty();
const scratchNormal = core_geometry_1.Vector3d.create();
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[5], this.points[3], this.points[1], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[1]) - expandPlaneDistance));
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[2], this.points[4], this.points[0], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[0]) - expandPlaneDistance));
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[3], this.points[6], this.points[2], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[2]) - expandPlaneDistance));
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[4], this.points[1], this.points[0], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[0]) - expandPlaneDistance));
if (clipBack) {
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[1], this.points[2], this.points[0], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[0]) - expandPlaneDistance));
}
if (clipFront) {
core_geometry_1.Vector3d.createCrossProductToPoints(this.points[6], this.points[5], this.points[4], scratchNormal);
if (scratchNormal.normalizeInPlace())
convexSet.addPlaneToConvexSet(core_geometry_1.ClipPlane.createNormalAndDistance(scratchNormal, scratchNormal.dotProduct(this.points[4]) - expandPlaneDistance));
}
return convexSet;
}
/** Get a (convex) polygon that represents the intersection of this frustum with a plane, or undefined if no intersection exists */
getIntersectionWithPlane(plane) {
const clipPlane = core_geometry_1.ClipPlane.createPlane(plane);
const loopPoints = clipPlane.intersectRange(this.toRange(), true);
if (undefined === loopPoints)
return undefined;
const convexSet = this.getRangePlanes(false, false, 0);
const workPoints = new core_geometry_1.GrowableXYZArray();
const outPoints = new core_geometry_1.GrowableXYZArray();
convexSet.polygonClip(loopPoints, outPoints, workPoints);
return outPoints.length < 4 ? undefined : outPoints.getPoint3dArray();
}
}
exports.Frustum = Frustum;
//# sourceMappingURL=Frustum.js.map