lume
Version:
208 lines • 7.53 kB
JavaScript
// based on THREE.CSS3DRenderer from https://github.com/mrdoob/three.js/blob/51ac0084709d4d3795ccb7119ee24e6a808618df/examples/js/renderers/CSS3DRenderer.js
import { Matrix4 } from 'three/src/math/Matrix4.js';
import { Object3DWithPivot } from '../core/Object3DWithPivot.js';
import { isPerspectiveCamera, isOrthographicCamera } from '../utils/three.js';
export class CSS3DObjectNested extends Object3DWithPivot {
element;
type = 'CSS3DObjectNested';
#initialFrame = requestAnimationFrame(() => {
// delay to the next frame because attributes are not allowed be set
// inside Custom Element (i.e. Web Component) constructors, otherwise
// this can throw an error if called inside a Custom Element
// constructor.
this.element.style.position = 'absolute';
});
constructor(element) {
super();
this.element = element;
}
dispose() {
cancelAnimationFrame(this.#initialFrame);
}
}
// TODO Sprite is still untested in this new nested renderer
export class CSS3DNestedSprite extends CSS3DObjectNested {
}
//
export class CSS3DRendererNested {
domElement;
#matrix = new Matrix4();
#cache = {
camera: { fov: 0, style: '' },
objects: new WeakMap(),
};
#width = 0;
#height = 0;
#widthHalf = 0;
#heightHalf = 0;
#cameraElement;
constructor() {
const domElement = document.createElement('div');
domElement.classList.add('CSS3DRendererNested');
domElement.style.overflow = 'hidden';
this.domElement = domElement;
const cameraElement = document.createElement('div');
cameraElement.classList.add('cameraElement');
cameraElement.appendChild(document.createElement('slot'));
cameraElement.style.transformStyle = 'preserve-3d';
domElement.appendChild(cameraElement);
this.#cameraElement = cameraElement;
}
getSize() {
return {
width: this.#width,
height: this.#height,
};
}
setSize(width, height) {
this.#width = width;
this.#height = height;
this.#widthHalf = width / 2;
this.#heightHalf = height / 2;
this.domElement.style.width = width + 'px';
this.domElement.style.height = height + 'px';
this.#cameraElement.style.width = width + 'px';
this.#cameraElement.style.height = height + 'px';
}
#renderObject(object, camera) {
if (object instanceof CSS3DObjectNested) {
let style = '';
if (object instanceof CSS3DNestedSprite) {
// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
this.#matrix.copy(camera.matrixWorldInverse);
this.#matrix.transpose();
this.#matrix.copyPosition(object.matrixWorld);
this.#matrix.scale(object.scale);
this.#matrix.elements[3] = 0;
this.#matrix.elements[7] = 0;
this.#matrix.elements[11] = 0;
this.#matrix.elements[15] = 1;
style = getObjectCSSMatrix(object, this.#matrix);
}
else {
style = getObjectCSSMatrix(object, object.matrix);
}
const element = object.element;
const cachedStyle = this.#cache.objects.get(object);
// if ( cachedStyle === undefined || cachedStyle !== style ) { // BUG, https://github.com/mrdoob/three.js/pull/15470
if (cachedStyle === undefined || cachedStyle.style !== style) {
element.style.transform = style;
const objectData = { style: style };
this.#cache.objects.set(object, objectData);
}
}
for (let i = 0, l = object.children.length; i < l; i++) {
this.#renderObject(object.children[i], camera);
}
}
render(scene, camera) {
const fov = camera.projectionMatrix.elements[5] * this.#heightHalf;
if (this.#cache.camera.fov !== fov) {
if (isPerspectiveCamera(camera)) {
this.domElement.style.perspective = fov + 'px';
}
this.#cache.camera.fov = fov;
}
scene.updateMatrixWorld();
if (camera.parent === null)
camera.updateMatrixWorld((void 0));
let tx = 0;
let ty = 0;
if (isOrthographicCamera(camera)) {
tx = -(camera.right + camera.left) / 2;
ty = (camera.top + camera.bottom) / 2;
}
// prettier-ignore
const cameraCSSMatrix = isOrthographicCamera(camera)
? 'scale(' + fov + ')' + 'translate(' + epsilon(tx) + 'px,' + epsilon(ty) + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse)
: 'translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse);
const style = cameraCSSMatrix + 'translate(' + this.#widthHalf + 'px,' + this.#heightHalf + 'px)';
if (this.#cache.camera.style !== style) {
this.#cameraElement.style.transform = style;
this.#cache.camera.style = style;
}
this.#renderObject(scene, camera);
}
}
function getCameraCSSMatrix(matrix) {
const elements = matrix.elements;
return ('matrix3d(' +
epsilon(elements[0]) +
',' +
epsilon(-elements[1]) +
',' +
epsilon(elements[2]) +
',' +
epsilon(elements[3]) +
',' +
epsilon(elements[4]) +
',' +
epsilon(-elements[5]) +
',' +
epsilon(elements[6]) +
',' +
epsilon(elements[7]) +
',' +
epsilon(elements[8]) +
',' +
epsilon(-elements[9]) +
',' +
epsilon(elements[10]) +
',' +
epsilon(elements[11]) +
',' +
epsilon(elements[12]) +
',' +
epsilon(-elements[13]) +
',' +
epsilon(elements[14]) +
',' +
epsilon(elements[15]) +
')');
}
function getObjectCSSMatrix(object, matrix) {
const parent = object.parent;
const childOfScene = parent && parent.type === 'Scene';
// TODO I don't remember why we negate values based on childOfScene below.
// It's been a while and I forgot. This should be documented.
const elements = matrix.elements;
const matrix3d = 'matrix3d(' +
epsilon(elements[0]) +
',' +
epsilon(elements[1]) +
',' +
epsilon(elements[2]) +
',' +
epsilon(elements[3]) +
',' +
epsilon((childOfScene ? -1 : 1) * elements[4]) +
',' +
epsilon((childOfScene ? -1 : 1) * elements[5]) +
',' +
epsilon((childOfScene ? -1 : 1) * elements[6]) +
',' +
epsilon((childOfScene ? -1 : 1) * elements[7]) +
',' +
epsilon(elements[8]) +
',' +
epsilon(elements[9]) +
',' +
epsilon(elements[10]) +
',' +
epsilon(elements[11]) +
',' +
epsilon(elements[12]) +
',' /* X position */ +
epsilon((childOfScene ? 1 : -1) * elements[13]) +
',' /* Y position */ +
epsilon(elements[14]) +
',' /* Z position */ +
epsilon(elements[15]) +
')';
// similar to mountPoint
return `${childOfScene ? 'translate(-50%, -50%)' : ''} ${matrix3d}`;
}
function epsilon(value) {
return Math.abs(value) < 1e-10 ? 0 : value;
}
//# sourceMappingURL=CSS3DRendererNested.js.map