UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

343 lines (342 loc) 11.1 kB
"use strict"; import { WebGLRenderer } from "three"; import { TypedViewer } from "./_Base"; import { CoreCameraRendererController } from "../../core/camera/CoreCameraRendererController"; import { CoreCameraPostProcessController } from "../../core/camera/CoreCameraPostProcessController"; import { CoreCameraCSSRendererController } from "../../core/camera/CoreCameraCSSRendererController"; import { CoreCameraViewerCodeController } from "../../core/camera/CoreCameraViewerCodeController"; import { CoreCameraControlsController } from "../../core/camera/CoreCameraControlsController"; import { CoreCameraRenderSceneController } from "../../core/camera/CoreCameraRenderSceneController"; import { CoreCameraWebXRController } from "../../core/camera/webXR/CoreCameraWebXR"; import { CoreCameraMarkerTrackingController } from "../../core/camera/webXR/CoreCameraMarkerTracking"; import { CoreCameraViewerFPSController, isDeltaValid } from "../../core/camera/CoreCameraFPS"; const CSS_CLASS = "CoreThreejsViewer"; export class ThreejsViewer extends TypedViewer { constructor(options) { super(options); this._animateWebBound = this._animateWeb.bind(this); this._accumulatedDelta = 0; // // // resize // // this._onResizeBound = this.onResize.bind(this); this._setupFunctions(options); } static _canvasIdPrefix() { return "ThreejsViewer"; } rendererConfig() { return this._rendererConfig; } _setupFunctions(options) { var _a; const camera = this.camera(); const scene = this.scene(); const canvas = this.canvas(); const threejsScene = scene.threejsScene(); this._errorMessage = void 0; this._renderer = options.renderer; if (!this._renderer) { this._rendererConfig = CoreCameraRendererController.rendererConfig({ camera, scene, canvas }); if (this._rendererConfig) { this._renderer = this._rendererConfig.renderer; } } const renderer = this._renderer; if (!renderer) { console.error("no renderer"); } if (renderer) { const rendererScene = CoreCameraRenderSceneController.renderScene({ camera, scene }); const renderScene = rendererScene || threejsScene; this._effectComposer = CoreCameraPostProcessController.createComposer({ camera, scene, renderScene, renderer, viewer: this }); const effectComposer = this._effectComposer; if (renderer instanceof WebGLRenderer) { this._webXRConfig = CoreCameraWebXRController.process({ camera, scene, renderer, canvas: this.canvas() }); this._markerTrackingConfig = CoreCameraMarkerTrackingController.process({ canvas, camera, scene, onError: (errorMessage) => { this._errorMessage = errorMessage; } }); } this._codeConfig = CoreCameraViewerCodeController.viewerCodeConfig({ camera }); this._cssRendererConfig = CoreCameraCSSRendererController.cssRendererConfig({ scene, camera, canvas }); const cssRenderer = (_a = this._cssRendererConfig) == null ? void 0 : _a.cssRenderer; this._renderCSSFunc = cssRenderer ? () => cssRenderer.render(renderScene, camera) : void 0; this._controlsNode = CoreCameraControlsController.controlsNode({ camera, scene }); this._FPSConfig = CoreCameraViewerFPSController.viewerFPSConfig({ camera }); if (effectComposer) { this._renderFunc = (delta) => effectComposer.render(delta); } else { this._renderFunc = () => renderer.render(renderScene, camera); } return { renderer, renderScene, camera }; } } /** * mounts the viewer onto an element * * */ mount(element, options) { var _a, _b, _c; super.mount(element, options); const canvas = this.canvas(); const _appendCanvasWithViewerCodeConfig = (config) => { if (!this._domElement) { return; } return config.createViewerElement({ domElement: this._domElement, canvas, CSSClass: CSS_CLASS }); }; const _appendCanvasWithoutViewerCodeConfig = () => { var _a2, _b2; (_a2 = this._domElement) == null ? void 0 : _a2.appendChild(canvas); (_b2 = this._domElement) == null ? void 0 : _b2.classList.add(CSS_CLASS); return this._domElement; }; const viewerContainerElement = this._codeConfig ? _appendCanvasWithViewerCodeConfig(this._codeConfig) : _appendCanvasWithoutViewerCodeConfig(); const cssRendererNode = (_a = this._cssRendererConfig) == null ? void 0 : _a.cssRendererNode; if (cssRendererNode) { cssRendererNode.mountRenderer(canvas); } (_b = this._webXRConfig) == null ? void 0 : _b.mountFunction(); (_c = this._markerTrackingConfig) == null ? void 0 : _c.mountFunction(); this._build(); this._setEvents(); const canvasParent = canvas.parentElement; if (canvasParent) { this._createResizeObserver(canvasParent); } this.onResize(); if (this._errorMessage) { const errorElement = document.createElement("div"); errorElement.style.position = "absolute"; errorElement.style.top = "0px"; errorElement.style.width = "100%"; errorElement.style.color = "red"; errorElement.style.backgroundColor = "white"; errorElement.style.padding = "20px"; errorElement.style.textAlign = "center"; errorElement.style.opacity = "90%"; errorElement.innerText = this._errorMessage; viewerContainerElement == null ? void 0 : viewerContainerElement.append(errorElement); } } _build() { this._initDisplay(); this.activate(); } /** * disposes the viewer * * */ dispose() { var _a, _b, _c; const canvas = this.canvas(); const cssRendererNode = (_a = this._cssRendererConfig) == null ? void 0 : _a.cssRendererNode; if (cssRendererNode) { cssRendererNode.unmountRenderer(canvas); } this._cssRendererConfig = void 0; (_b = this._webXRConfig) == null ? void 0 : _b.unmountFunction(); (_c = this._markerTrackingConfig) == null ? void 0 : _c.unmountFunction(); this._effectComposer = void 0; this.setAutoRender(false); this._cancelAnimate(); this._unlistenToWindowResize(); this._disposeResizeObserver(); super.dispose(); } _setEvents() { this.eventsController().init(); this.webglController().init(); this._unlistenToWindowResize(); this._listenToWindowResize(); } _initDisplay() { if (!this._canvas) { console.warn("no canvas found for viewer"); return; } if (!this._renderer) { return; } const pixelRatio = this._renderer.getPixelRatio(); this.camerasController().computeSizeAndAspect(pixelRatio); this.audioController().update(); this._startAnimate(); } /** * setAutoRender to false will stop the rendering. This can be useful if you know that nothing has changed in the scene, or if the renderer is currently not visible. * * */ setAutoRender(state = true, options) { super.setAutoRender(state); if (this._doRender && this._requestAnimationFrameId == null) { this._startAnimate(); } if (!this._doRender) { if (options && options.cancelAnimate == true) { this._cancelAnimate(); } } } isXR() { if (!this._renderer) { return false; } return this._renderer instanceof WebGLRenderer && this._renderer.xr.enabled; } _startAnimate() { if (this.isXR()) { const renderer = this._renderer; if (!renderer) { return; } const webXRController = this.scene().webXR; const xrCallback = (timestamp, frame) => { var _a; (_a = webXRController.activeXRController()) == null ? void 0 : _a.process(frame); this._animateWebXR(); }; renderer.setAnimationLoop(xrCallback); } else { this._animateWeb(); } } _cancelAnimate() { var _a; if (this.isXR()) { (_a = this._renderer) == null ? void 0 : _a.setAnimationLoop(null); } else { this._cancelAnimateCommon(); } } _animateWeb() { this._requestAnimationFrameId = requestAnimationFrame(this._animateWebBound); this.__animateCommon__(); } _animateWebXR() { this.__animateCommon__(); } __animateCommon__() { const delta = this._scene.timeController.updateClockDelta(); if (this._FPSConfig) { this._accumulatedDelta += delta; if (!isDeltaValid(this._accumulatedDelta, this._FPSConfig)) { return; } this._accumulatedDelta = 0; } this._tickAndRender(delta); } _cancelAnimateCommon() { this._doRender = false; if (this._requestAnimationFrameId != null) { cancelAnimationFrame(this._requestAnimationFrameId); this._requestAnimationFrameId = void 0; } if (this._canvas) { } } _tick(delta) { var _a; super._tick(delta); (_a = this._markerTrackingConfig) == null ? void 0 : _a.renderFunction(); } /** * returns the current renderer * * */ renderer() { return this._renderer; } effectComposer() { return this._effectComposer; } preCompile() { if (!this._renderer) { return; } this._renderer.compile(this._scene.threejsScene(), this._camera); } markAsReady() { this.preCompile(); this.setAutoRender(true); } onResize() { const renderUpdateRequired = this.updateSize(); if (!renderUpdateRequired) { return; } this._updateRendererSize(); if (this._renderFunc) { this._renderFunc(this.scene().timeController.delta()); } this._runOnResizeCallbacks(); } updateSize() { const renderer = this._renderer; if (!renderer) { return false; } const pixelRatio = renderer.getPixelRatio(); this.camerasController().computeSizeAndAspect(pixelRatio); const size = this.camerasController().size; if (this._size.equals(size)) { return false; } this._size.copy(size); this._scene.viewersRegister.markViewerAsSizeUpdated(this); return true; } _updateRendererSize() { var _a, _b; const canvas = this._canvas; if (!canvas) { return; } CoreCameraRendererController.setRendererSize(canvas, this._size); (_a = this._cssRendererConfig) == null ? void 0 : _a.cssRenderer.setSize(this._size.x, this._size.y); (_b = this._effectComposer) == null ? void 0 : _b.setSize(this._size.x, this._size.y); this.camerasController().updateCameraAspect(); } _listenToWindowResize() { } _unlistenToWindowResize() { } _createResizeObserver(canvasElementParent) { this._disposeResizeObserver(); this._resizeObserver = new ResizeObserver(this._onResizeBound); this._resizeObserver.observe(canvasElementParent, { box: "border-box" }); } _disposeResizeObserver() { if (this._resizeObserver) { this._resizeObserver.disconnect(); this._resizeObserver = void 0; } } }