gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
155 lines (154 loc) • 6.23 kB
JavaScript
import { Vec3 } from "../../math/Vec3.mjs";
import { Mat4 } from "../../math/Mat4.mjs";
import { Box3 } from "../../math/Box3.mjs";
//#region src/core/DOM/DOMFrustum.ts
/** @constant {RectCoords} - default {@link DOMFrustum#DOMFrustumMargins | DOMFrustumMargins} */
const defaultDOMFrustumMargins = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
/**
* Used to check if a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D} is currently contained inside a DOM bounding rectangle.
*
* Uses a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#modelViewProjectionMatrix | model view projection matrix} that contains both useful {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#transforms | Object3D transforms} and {@link core/cameras/Camera.Camera | Camera} projection information.
* The DOM bounding rectangle to check against usually is the {@link core/renderers/GPURenderer.GPURenderer | renderer}'s {@link core/DOM/DOMElement.DOMElement | DOMElement} bounding rectangle, unless frustum margins are specified.
*/
var DOMFrustum = class {
/**
* DOMFrustum constructor
* @param {DOMFrustumParams} parameters - {@link DOMFrustumParams | parameters} used to create our {@link DOMFrustum}
*/
constructor({ boundingBox = new Box3(), modelViewProjectionMatrix = new Mat4(), containerBoundingRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0,
x: 0,
y: 0
}, DOMFrustumMargins = defaultDOMFrustumMargins, onReEnterView = () => {}, onLeaveView = () => {} }) {
this.boundingBox = boundingBox;
this.clipSpaceOBB = new Box3();
this.modelViewProjectionMatrix = modelViewProjectionMatrix;
this.containerBoundingRect = containerBoundingRect;
this.DOMFrustumMargins = {
...defaultDOMFrustumMargins,
...DOMFrustumMargins
};
this.clipSpaceBoundingRect = {
top: 0,
left: 0,
width: 0,
height: 0
};
this.projectedBoundingRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0,
x: 0,
y: 0
};
this.onReEnterView = onReEnterView;
this.onLeaveView = onLeaveView;
this.isIntersecting = false;
}
/**
* Set our {@link containerBoundingRect} (called on resize)
* @param boundingRect - new bounding rectangle
*/
setContainerBoundingRect(boundingRect) {
this.containerBoundingRect = boundingRect;
}
/**
* Get our DOM frustum bounding rectangle, i.e. our {@link containerBoundingRect} with the {@link DOMFrustumMargins} applied
* @readonly
*/
get DOMFrustumBoundingRect() {
return {
top: this.projectedBoundingRect.top - this.DOMFrustumMargins.top,
right: this.projectedBoundingRect.right + this.DOMFrustumMargins.right,
bottom: this.projectedBoundingRect.bottom + this.DOMFrustumMargins.bottom,
left: this.projectedBoundingRect.left - this.DOMFrustumMargins.left
};
}
/**
* Compute the oriented bounding box in clip space.
*/
computeClipSpaceOBB() {
this.clipSpaceOBB.set();
this.boundingBox.applyMat4(this.modelViewProjectionMatrix, this.clipSpaceOBB);
}
/**
* Applies all {@link modelViewProjectionMatrix} transformations to our {@link boundingBox}, i.e. apply OBB to document coordinates and set {@link projectedBoundingRect}.
*/
setDocumentCoordsFromClipSpaceOBB() {
this.computeClipSpaceOBB();
this.clipSpaceBoundingRect = {
top: this.clipSpaceOBB.max.y,
left: this.clipSpaceOBB.min.x,
width: this.clipSpaceOBB.max.x - this.clipSpaceOBB.min.x,
height: this.clipSpaceOBB.max.y - this.clipSpaceOBB.min.y
};
const minX = (this.clipSpaceOBB.min.x + 1) * .5;
const maxX = (this.clipSpaceOBB.max.x + 1) * .5;
const minY = 1 - (this.clipSpaceOBB.min.y + 1) * .5;
const maxY = 1 - (this.clipSpaceOBB.max.y + 1) * .5;
const { width, height, top, left } = this.containerBoundingRect;
this.projectedBoundingRect = {
left: minX * width + left,
x: minX * width + left,
top: maxY * height + top,
y: maxY * height + top,
right: maxX * width + left,
bottom: minY * height + top,
width: maxX * width + left - (minX * width + left),
height: minY * height + top - (maxY * height + top)
};
}
/**
* Apply the bounding sphere in clip space to document coordinates and set {@link projectedBoundingRect}.
* @param boundingSphere - bounding sphere in clip space.
*/
setDocumentCoordsFromClipSpaceSphere(boundingSphere = {
center: new Vec3(),
radius: 0
}) {
this.clipSpaceBoundingRect = {
top: boundingSphere.center.y + boundingSphere.radius,
left: boundingSphere.center.x - boundingSphere.radius,
width: boundingSphere.radius * 2,
height: boundingSphere.radius * 2
};
const centerX = (boundingSphere.center.x + 1) * .5;
const centerY = 1 - (boundingSphere.center.y + 1) * .5;
const { width, height, top, left } = this.containerBoundingRect;
this.projectedBoundingRect.width = boundingSphere.radius * height;
this.projectedBoundingRect.height = boundingSphere.radius * height;
this.projectedBoundingRect.left = centerX * width + left - this.projectedBoundingRect.width * .5;
this.projectedBoundingRect.x = this.projectedBoundingRect.left;
this.projectedBoundingRect.top = centerY * height + top - this.projectedBoundingRect.height * .5;
this.projectedBoundingRect.y = this.projectedBoundingRect.top;
this.projectedBoundingRect.right = this.projectedBoundingRect.left + this.projectedBoundingRect.width;
this.projectedBoundingRect.bottom = this.projectedBoundingRect.top + this.projectedBoundingRect.height;
}
/**
* Check whether our {@link projectedBoundingRect} intersects with our {@link DOMFrustumBoundingRect}.
*/
intersectsContainer() {
if (Math.round(this.DOMFrustumBoundingRect.right) <= this.containerBoundingRect.left || Math.round(this.DOMFrustumBoundingRect.left) >= this.containerBoundingRect.left + this.containerBoundingRect.width || Math.round(this.DOMFrustumBoundingRect.bottom) <= this.containerBoundingRect.top || Math.round(this.DOMFrustumBoundingRect.top) >= this.containerBoundingRect.top + this.containerBoundingRect.height) {
if (this.isIntersecting) this.onLeaveView();
this.isIntersecting = false;
} else {
if (!this.isIntersecting) this.onReEnterView();
this.isIntersecting = true;
}
}
};
//#endregion
export { DOMFrustum };