lume
Version:
285 lines (284 loc) • 13.3 kB
JavaScript
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
import { booleanAttribute, element } from '@lume/element';
import { SharedAPI } from './SharedAPI.js';
import { autoDefineElements } from '../LumeConfig.js';
/**
* @class Element3D -
*
* Element: `<lume-element3d>`
*
* `Element3D` is the backing class for `<lume-element3d>` elements, the most
* primitive of the LUME 3D elements.
*
* `Element3D` contains the basics that all elements in a 3D scene need, such as a
* transform (position, rotation, scale, align-point, mount-point, origin),
* size, and mechanisms of reactivity.
*
* All elements in a 3D scene are an instance of `Element3D`, including more advanced
* elements that render different types of visuals. For example, `<lume-sphere>`
* is an element that renders a sphere on the screen and is backed by the
* [`Sphere`](./Sphere) class which extends from `Element3D`.
*
* All Nodes must be a child of a [`Scene`](/api/core/Scene) node (`<lume-scene>`
* elements) or another `Element3D` (or anything that extends from `Element3D`).
* If a `<lume-element3d>` element is a child of anything else, it will not do
* anything currently.
*
* The `Element3D` class (backing `<lume-element3d>` elements) is useful for the following:
*
* - Transform a parent node in 3D space, and it will transform all its
* children and grandchildren along with it. For example, if you scale a
* parent `Element3D`, then all its children are scaled along too.
* - Transform child Nodes relative to their parent.
* - Render traditional HTML content by placing any regular HTML elements as
* children of a `<lume-element3d>` element. See the next example.
* - Extend the `Element3D` class to make new types of 3D elements relying on the basic
* features that `Element3D` provides. Other classes that extend from `Element3D` may, for
* example, create [layouts](/examples/autolayout-declarative), or render
* [WebGL content](/examples/hello-world/).
*
* ## Example
*
* The following example shows traditional HTML content inside a 3D scene, as
* well as the concept of a hierarchy of nodes called a "scene graph".
*
* Regular HTML content is places in each `<lume-element3d>` element. CSS is applied
* to the nodes to give them rounded borders. Standard
* [`<img />` elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
* are used display the sun images.
*
* We create a hierarchy of nodes. We give each node further down in the
* hiearchy a smaller size. We use `align` and `mount-point` attributes to
* align Nodes to one corner of their parent. `align` controls where a node is
* mounted relative to their parent, and `mount-point` specifies the point in
* the child that is aligned in the parent. See the [alignment guide](TODO)
* for how that works.
*
* Each node has the same amount of rotation directly applied to it. Due to the
* hiearchy, the rotations add up. The parent most node has the least
* amount of rotation, and the child-most nodes have the most rotation and also
* are more displaced due to rotation of their parents. See the [scene graph
* guide](TODO) for details on how the hierarchy works.
*
* Finally, we listen to mouse or finger movement events in order to apply a
* rotation to the root node based on the current mouse or finger position.
* See the [events guide](TODO) for how the event system works.
*
* <live-code src="../../examples/mini-galaxy/example.html"></live-code>
*
* @extends SharedAPI
*/
export { Element3D };
let Element3D = (() => {
let _classDecorators = [element('lume-element3d', autoDefineElements)];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
let _classSuper = SharedAPI;
let _visible_decorators;
let _visible_initializers = [];
let _visible_extraInitializers = [];
var Element3D = class extends _classSuper {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_visible_decorators = [booleanAttribute];
__esDecorate(null, null, _visible_decorators, { kind: "field", name: "visible", static: false, private: false, access: { has: obj => "visible" in obj, get: obj => obj.visible, set: (obj, value) => { obj.visible = value; } }, metadata: _metadata }, _visible_initializers, _visible_extraInitializers);
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
Element3D = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
hasShadow = false;
/**
* @property {true} isElement3D -
*
* *readonly*
*
* Always `true` for things that are or inherit from `Element3D`.
*/
isElement3D = true;
/**
* @property {boolean} visible -
*
* *attribute*
*
* Default: `true`
*
* Whether or not the node will be
* visible (if it renders anything). For `<lume-element3d>` elements, this
* only applies if the element has CSS styling or traditional HTML
* content inside of it (children), otherwise `<lume-element3d>`
* element's don't have any visual output by default. Other nodes that
* have default visual output like `<lume-box>` or `<lume-sphere>` will
* not be visible if this is false, and their rendering mechanics will
* be skipped.
*
* If an `Element3D` is not visible, its children are also not visible.
*/
visible = __runInitializers(this, _visible_initializers, true
/**
* *reactive*
*/
);
/**
* *reactive*
*/
get parentSize() {
const composedLumeParent = this.composedLumeParent;
if (this.scene && this.scene === this.parentElement)
return this.scene.calculatedSize;
return composedLumeParent?.calculatedSize ?? { x: 0, y: 0, z: 0 };
}
/**
* @constructor - Create a `Element3D` instance.
*
* The following examples calls `.set()` to set initial properties. Any
* properties passed into `.set()` are applied to the instance. For
* example, writing
*
* ```js
* var node = new Element3D().set({
* size: {x:100, y:100, z:100},
* rotation: {x:30, y:20, z:25}
* })
* ```
*
* is the same as writing
*
* ```js
* var node = new Element3D()
* node.size = {x:100, y:100, z:100}
* node.rotation = {x:30, y:20, z:25}
* ```
*
* @example TODO
*/
// TODO @example jsdoc tag
constructor() {
super();
__runInitializers(this, _visible_extraInitializers);
// The `parent` property can already be set if this instance is
// already in the DOM and wwhile being upgraded into a custom
// element.
// TODO Remove this after we make _calcSize lazy and deferred to a
// render task.
if (this.composedLumeParent) {
this._calcSize();
// No harm deferring, plus we need to because this may end up
// calling a super method of a super class that relies on a private
// field that is not initialized yet, causing an error. Welcome to
// TC39's vision of inheritance for JavaScript.
queueMicrotask(() => this.needsUpdate());
}
}
/**
* @method traverseSceneGraph - This traverses the composed tree of
* LUME 3D elements (the scene graph) including this element, in pre-order. It skips non-LUME elements.
*
* This is similar to
* [`Scene#traverseSceneGraph`](./Scene.md#traversescenegraph) but traversal
* includes the `Element3D` that this is called on.
*
* Example:
*
* ```js
* node.traverseSceneGraph(n => {
* console.log(node === n) // true for the first call only
* console.log(n instanceof LUME.Element3D) // true
* })
* ```
*
* @param {(node: Element3D) => void} visitor - A function called for each
* LUME node in the scene graph (the composed tree).
* @param {boolean} waitForUpgrade - Defaults to `false`. If `true`,
* the traversal will wait for custom elements to be defined (with
* customElements.whenDefined) before traversing to them.
* @returns {void | Promise<void>} - If `waitForUpgrade` is `false`,
* the traversal will complete synchronously, and the return value will be
* `undefined`. If `waitForUpgrade` is `true`, then traversal completes
* asynchronously as soon as all custom elements are defined, and a Promise is
* returned so that it is possible to wait for the traversal to complete.
*/
traverseSceneGraph(visitor, waitForUpgrade = false) {
visitor(this);
if (!waitForUpgrade) {
for (const child of this.composedLumeChildren)
child.traverseSceneGraph(visitor, waitForUpgrade);
return;
}
// if waitForUpgrade is true, we make a promise chain so that
// traversal order is still the same as when waitForUpgrade is false.
let promise = Promise.resolve();
for (const child of this.composedLumeChildren) {
const isUpgraded = child.matches(':defined');
if (isUpgraded) {
promise = promise.then(() => child.traverseSceneGraph(visitor, waitForUpgrade));
}
else {
promise = promise
.then(() => customElements.whenDefined(child.tagName.toLowerCase()))
.then(() => child.traverseSceneGraph(visitor, waitForUpgrade));
}
}
return promise;
}
connectedCallback() {
super.connectedCallback();
this.createEffect(() => {
this._elementOperations.shouldRender = this.visible;
this.three.visible = this.visible;
this.needsUpdate();
});
}
static css = /*css*/ `
${Reflect.get(_classSuper, "css", _classThis)}
:host {
position: absolute;
top: 0;
left: 0;
}
`;
static {
__runInitializers(_classThis, _classExtraInitializers);
}
};
return Element3D = _classThis;
})();
// Put initial value on the prototype to make it available during construction
// in a super() call.
// @ts-expect-error readonly
Element3D.prototype.isElement3D = true;
//# sourceMappingURL=Element3D.js.map