UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

371 lines (282 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lowclass = _interopRequireDefault(require("lowclass")); var _documentReady = _interopRequireDefault(require("@awaitbox/document-ready")); var _Mixin = _interopRequireDefault(require("./Mixin")); var _Motor = _interopRequireDefault(require("./Motor")); var _ImperativeBase = _interopRequireWildcard(require("./ImperativeBase")); var _XYZSizeModeValues = _interopRequireDefault(require("./XYZSizeModeValues")); var _XYZNonNegativeValues = _interopRequireDefault(require("./XYZNonNegativeValues")); var _HTMLScene = _interopRequireDefault(require("../html/HTMLScene")); var _props = require("./props"); var _three = require("three"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // TODO: write a test that imports public interfaces in every possible // permutation to detect circular dependency errors. // See: https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem (0, _ImperativeBase.initImperativeBase)(); let Scene = (0, _Mixin.default)(Base => { const Parent = _ImperativeBase.default.mixin(Base); return (0, _lowclass.default)('Scene').extends(Parent, ({ Super }) => ({ static: { defaultElementName: 'i-scene', props: _objectSpread({}, Parent.props, { backgroundColor: _props.props.THREE.Color, backgroundOpacity: _props.props.number, shadowmapType: _props.props.string, vr: _props.props.boolean, experimentalWebgl: _props.props.boolean }) }, constructor(options = {}) { const self = Super(this).constructor(options); // Used by the `scene` getter in ImperativeBase // Motor's loop checks _scene on Nodes and Scenes when determining // modified scenes. self._scene = self; self._mounted = false; // TODO get default camera values from somewhere. self._perspective = 1000; // size of the element where the Scene is mounted // NOTE: z size is always 0, since native DOM elements are always flat. self._elementParentSize = { x: 0, y: 0, z: 0 }; self._calcSize(); self._needsToBeRendered(); return self; }, _onElementParentSizeChange(newSize) { this._elementParentSize = newSize; this._calcSize(); this._needsToBeRendered(); }, // For now, use the same program (with shaders) for all objects. // Basically it has position, frag colors, point light, directional // light, and ambient light. // TODO: maybe call this in `init()`, and destroy webgl stuff in // `deinit()`. // TODO: The user might enable this by setting the attribute later, so // we can't simply rely on having it in constructor, we need a // getter/setter like node properties. // TODO: we need to deinit webgl too. initWebGl() { // THREE // maybe keep this in sceneState in WebGLRendererThree Super(this).initWebGl(); // We don't let Three update any matrices, we supply our own world // matrices. this.threeObject3d.autoUpdate = false; // this.threeCamera holds the active camera. There can be many // cameras in the scene tree, but the last one with active="true" // will be the one referenced here. // If there are no cameras in the tree, a virtual default camera is // referenced here, who's perspective is that of the scene's // perspective attribute. this.threeCamera = null; this._createDefaultCamera(); // TODO: default ambient light when no AmbientLight elements are // present in the Scene. //const ambientLight = new AmbientLight( 0x353535 ) //this.threeObject3d.add( ambientLight ) // holds the renderer for this scene, renderers have scene-specific // settings so having this reference is okay. this._renderer = null; // a default orange background color. Use the backgroundColor and // backgroundOpacity attributes to customize. this._glBackgroundColor = new _three.Color(0xff6600); this._glBackgroundOpacity = 0; // holds active cameras found in the DOM tree (if this is empty, it // means no camera elements are in the DOM, but this.threeCamera // will still have a reference to the default camera that scenes // are rendered with when no camera elements exist). this._activeCameras = new Set(); this._renderer = _Motor.default.getWebGLRenderer(this, 'three'); // set default colors this._renderer.setClearColor(this, this._glBackgroundColor, this._glBackgroundOpacity); }, makeThreeObject3d() { return new _three.Scene(); }, // TODO ability to init and destroy webgl for the whole scene. destroyWebGl() {}, // TODO PERFORMANCE: make this static for better performance. _setDefaultProperties() { Super(this)._setDefaultProperties(); Object.assign(this._properties, { sizeMode: new _XYZSizeModeValues.default('proportional', 'proportional', 'proportional'), size: new _XYZNonNegativeValues.default(1, 1, 1) }); }, // TODO FIXME: manual camera doesn't work after we've added the // default-camera feature. _setCamera(camera) { if (!camera) { this._createDefaultCamera(); } else { // TODO?: implement an changecamera event/method and emit/call // that here, then move this logic to the renderer // handler/method? this.threeCamera = camera.threeObject3d; this._updateCameraAspect(); this._updateCameraProjection(); this._needsToBeRendered(); } }, _createDefaultCamera() { const size = this._calculatedSize; // THREE-COORDS-TO-DOM-COORDS // We apply Three perspective the same way as CSS3D perspective here. // TODO CAMERA-DEFAULTS, get defaults from somewhere common. // TODO the "far" arg will be auto-calculated to encompass the furthest objects (like CSS3D). this.threeCamera = new _three.PerspectiveCamera(45, size.x / size.y || 1, 0.1, 10000); this.perspective = 1000; }, // TODO can this be moved to a render task like _calcSize? It depends // on size values. _updateCameraPerspective() { const perspective = this._perspective; this.threeCamera.fov = 180 * (2 * Math.atan(this._calculatedSize.y / 2 / perspective)) / Math.PI; this.threeCamera.position.z = perspective; }, // TODO perspective SkateJS prop set perspective(value) { this._perspective = value; this._updateCameraPerspective(); this._updateCameraProjection(); this._needsToBeRendered(); }, get perspective() { return this._perspective; }, _updateCameraAspect() { this.threeCamera.aspect = this._calculatedSize.x / this._calculatedSize.y || 1; }, _updateCameraProjection() { this.threeCamera.updateProjectionMatrix(); }, _addCamera(camera) { this._activeCameras.add(camera); this._setCamera(camera); }, _removeCamera(camera) { this._activeCameras.delete(camera); if (this._activeCameras.size) { // get the last camera in the Set this._activeCameras.forEach(c => camera = c); } else camera = null; this._setCamera(camera); }, /** @override */ _getParentSize() { return this.parent ? this.parent._calculatedSize : this._elementParentSize; }, /** * Mount the scene into the given target. * Resolves the Scene's mountPromise, which can be use to do something once * the scene is mounted. * * @param {string|HTMLElement} [mountPoint=document.body] If a string selector is provided, * the mount point will be selected from the DOM. If an HTMLElement is * provided, that will be the mount point. If no mount point is provided, * the scene will be mounted into document.body. */ async mount(mountPoint) { // if no mountPoint was provided, just mount onto the <body> element. if (mountPoint === undefined) { if (!document.body) await (0, _documentReady.default)(); mountPoint = document.body; } // if the user supplied a selector, mount there. else if (typeof mountPoint === 'string') { mountPoint = document.querySelector(mountPoint); if (!mountPoint && document.readyState === 'loading') { // maybe the element wasn't parsed yet, check again when the // document is ready. await (0, _documentReady.default)(); mountPoint = document.querySelector(mountPoint); } } // if we have an actual mount point (the user may have supplied one) if (!(mountPoint instanceof HTMLElement)) { throw new Error(` Invalid mount point specified in Scene.mount() call. Pass a selector, an actual HTMLElement, or don\'t pass anything to mount to <body>. `); } // The user can mount to a new location without calling unmount // first. Call it automatically in that case. if (this._mounted) this.unmount(); if (mountPoint !== this.parentNode) mountPoint.appendChild(this); this._mounted = true; this._startOrStopSizePolling(); }, /** * Unmount the scene from it's mount point. Resets the Scene's * mountPromise. */ unmount() { if (!this._mounted) return; this._stopSizePolling(); if (this.parentNode) this.parentNode.removeChild(this); this._mounted = false; }, updated(oldProps, oldState, moddedProps) { Super(this).updated(oldProps, oldState, moddedProps); if (!this.isConnected) return; if (moddedProps.experimentalWebgl) { if (this.experimentalWebgl) this.initWebGl();else this.disposeWebGL(); } if (this.experimentalWebgl) { if (moddedProps.backgroundColor) { this._renderer.setClearColor(this, this.backgroundColor, this.backgroundOpacity); this._needsToBeRendered(); } if (moddedProps.backgroundOpacity) { this._renderer.setClearAlpha(this, this.backgroundOpacity); this._needsToBeRendered(); } if (moddedProps.shadowmapType) { this._renderer.setShadowMapType(this, this.shadowmapType); this._needsToBeRendered(); } if (moddedProps.vr) { this._renderer.enableVR(this, this.vr); if (this.vr) { _Motor.default.setFrameRequester(fn => this._renderer.requestFrame(this, fn)); this._renderer.createDefaultWebVREntryUI(this); } else {// TODO else return back to normal requestAnimationFrame } } } if (moddedProps.sizeMode) { this._startOrStopSizePolling(); } } })); }); // TODO for now, hard-mixin the HTMLInterface class. We'll do this automatically later. exports.default = Scene; exports.default = Scene = Scene.mixin(_HTMLScene.default);