UNPKG

cione-comp-lib

Version:

`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`

377 lines (376 loc) 13.4 kB
import { EventDispatcher, WebGLRenderer, sRGBEncoding, ACESFilmicToneMapping, Vector2 } from "../../../../_three@0.150.1@three/build/three.module.mjs"; import { $updateEnvironment } from "../features/environment.mjs"; import { $canvas, $updateSize, $tick } from "../model-viewer-base.mjs"; import { resolveDpr, isDebugMode, clamp } from "../utilities.mjs"; import { ARRenderer } from "./ARRenderer.mjs"; import { CachingGLTFLoader } from "./CachingGLTFLoader.mjs"; import { Debugger } from "./Debugger.mjs"; import { ModelViewerGLTFInstance } from "./gltf-instance/ModelViewerGLTFInstance.mjs"; import TextureUtils from "./TextureUtils.mjs"; /* @license * Copyright 2019 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const DURATION_DECAY = 0.2; const LOW_FRAME_DURATION_MS = 40; const HIGH_FRAME_DURATION_MS = 60; const MAX_AVG_CHANGE_MS = 5; const SCALE_STEPS = [1, 0.79, 0.62, 0.5, 0.4, 0.31, 0.25]; const DEFAULT_LAST_STEP = 3; const DEFAULT_POWER_PREFERENCE = "high-performance"; class Renderer extends EventDispatcher { constructor(options) { super(); this.loader = new CachingGLTFLoader(ModelViewerGLTFInstance); this.width = 0; this.height = 0; this.dpr = 1; this.debugger = null; this.scenes = /* @__PURE__ */ new Set(); this.multipleScenesVisible = false; this.lastTick = performance.now(); this.renderedLastFrame = false; this.scaleStep = 0; this.lastStep = DEFAULT_LAST_STEP; this.avgFrameDuration = (HIGH_FRAME_DURATION_MS + LOW_FRAME_DURATION_MS) / 2; this.onWebGLContextLost = (event) => { this.dispatchEvent({ type: "contextlost", sourceEvent: event }); }; this.onWebGLContextRestored = () => { var _a; (_a = this.textureUtils) === null || _a === void 0 ? void 0 : _a.dispose(); this.textureUtils = new TextureUtils(this.threeRenderer); for (const scene of this.scenes) { scene.element[$updateEnvironment](); } }; this.dpr = resolveDpr(); this.canvas3D = document.createElement("canvas"); this.canvas3D.id = "webgl-canvas"; this.canvas3D.classList.add("show"); try { this.threeRenderer = new WebGLRenderer({ canvas: this.canvas3D, alpha: true, antialias: true, powerPreference: options.powerPreference, preserveDrawingBuffer: true }); this.threeRenderer.autoClear = true; this.threeRenderer.outputEncoding = sRGBEncoding; this.threeRenderer.physicallyCorrectLights = true; this.threeRenderer.setPixelRatio(1); this.debugger = !!options.debug ? new Debugger(this) : null; this.threeRenderer.debug = { checkShaderErrors: !!this.debugger }; this.threeRenderer.toneMapping = ACESFilmicToneMapping; } catch (error) { console.warn(error); } this.arRenderer = new ARRenderer(this); this.textureUtils = this.canRender ? new TextureUtils(this.threeRenderer) : null; CachingGLTFLoader.initializeKTX2Loader(this.threeRenderer); this.canvas3D.addEventListener("webglcontextlost", this.onWebGLContextLost); this.canvas3D.addEventListener("webglcontextrestored", this.onWebGLContextRestored); this.updateRendererSize(); } static get singleton() { return this._singleton; } static resetSingleton() { const elements = this._singleton.dispose(); for (const element of elements) { element.disconnectedCallback(); } this._singleton = new Renderer({ powerPreference: (self.ModelViewerElement || {}).powerPreference || DEFAULT_POWER_PREFERENCE, debug: isDebugMode() }); for (const element of elements) { element.connectedCallback(); } } get canRender() { return this.threeRenderer != null; } get scaleFactor() { return SCALE_STEPS[this.scaleStep]; } set minScale(scale) { let i = 1; while (i < SCALE_STEPS.length) { if (SCALE_STEPS[i] < scale) { break; } ++i; } this.lastStep = i - 1; } registerScene(scene) { this.scenes.add(scene); scene.forceRescale(); const size = new Vector2(); this.threeRenderer.getSize(size); scene.canvas.width = size.x; scene.canvas.height = size.y; if (this.canRender && this.scenes.size > 0) { this.threeRenderer.setAnimationLoop((time, frame) => this.render(time, frame)); } if (this.debugger != null) { this.debugger.addScene(scene); } } unregisterScene(scene) { this.scenes.delete(scene); if (this.canvas3D.parentElement === scene.canvas.parentElement) { scene.canvas.parentElement.removeChild(this.canvas3D); } if (this.canRender && this.scenes.size === 0) { this.threeRenderer.setAnimationLoop(null); } if (this.debugger != null) { this.debugger.removeScene(scene); } } displayCanvas(scene) { return this.multipleScenesVisible ? scene.element[$canvas] : this.canvas3D; } countVisibleScenes() { const { canvas3D } = this; let visibleScenes = 0; let canvas3DScene = null; for (const scene of this.scenes) { const { element } = scene; if (element.modelIsVisible && scene.externalRenderer == null) { ++visibleScenes; } if (canvas3D.parentElement === scene.canvas.parentElement) { canvas3DScene = scene; } } const multipleScenesVisible = visibleScenes > 1; if (canvas3DScene != null) { const newlyMultiple = multipleScenesVisible && !this.multipleScenesVisible; const disappearing = !canvas3DScene.element.modelIsVisible; if (newlyMultiple || disappearing) { const { width, height } = this.sceneSize(canvas3DScene); this.copyPixels(canvas3DScene, width, height); canvas3D.parentElement.removeChild(canvas3D); } } this.multipleScenesVisible = multipleScenesVisible; } updateRendererSize() { const dpr = resolveDpr(); if (dpr !== this.dpr) { for (const scene of this.scenes) { const { element } = scene; element[$updateSize](element.getBoundingClientRect()); } } let width = 0; let height = 0; for (const scene of this.scenes) { width = Math.max(width, scene.width); height = Math.max(height, scene.height); } if (width === this.width && height === this.height && dpr === this.dpr) { return; } this.width = width; this.height = height; this.dpr = dpr; width = Math.ceil(width * dpr); height = Math.ceil(height * dpr); if (this.canRender) { this.threeRenderer.setSize(width, height, false); } for (const scene of this.scenes) { const { canvas } = scene; canvas.width = width; canvas.height = height; scene.forceRescale(); } } updateRendererScale(delta) { const scaleStep = this.scaleStep; this.avgFrameDuration += clamp(DURATION_DECAY * (delta - this.avgFrameDuration), -MAX_AVG_CHANGE_MS, MAX_AVG_CHANGE_MS); if (this.avgFrameDuration > HIGH_FRAME_DURATION_MS) { ++this.scaleStep; } else if (this.avgFrameDuration < LOW_FRAME_DURATION_MS && this.scaleStep > 0) { --this.scaleStep; } this.scaleStep = Math.min(this.scaleStep, this.lastStep); if (scaleStep !== this.scaleStep) { this.avgFrameDuration = (HIGH_FRAME_DURATION_MS + LOW_FRAME_DURATION_MS) / 2; } } shouldRender(scene) { if (!scene.shouldRender()) { if (scene.scaleStep != 0) { scene.scaleStep = 0; this.rescaleCanvas(scene); } else { return false; } } else if (scene.scaleStep != this.scaleStep) { scene.scaleStep = this.scaleStep; this.rescaleCanvas(scene); } return true; } rescaleCanvas(scene) { const scale = SCALE_STEPS[scene.scaleStep]; const width = Math.ceil(this.width / scale); const height = Math.ceil(this.height / scale); const { style } = scene.canvas; style.width = `${width}px`; style.height = `${height}px`; this.canvas3D.style.width = `${width}px`; this.canvas3D.style.height = `${height}px`; const renderedDpr = this.dpr * scale; const reason = scale < 1 ? "GPU throttling" : this.dpr !== window.devicePixelRatio ? "No meta viewport tag" : ""; scene.element.dispatchEvent(new CustomEvent("render-scale", { detail: { reportedDpr: window.devicePixelRatio, renderedDpr, minimumDpr: this.dpr * SCALE_STEPS[this.lastStep], pixelWidth: Math.ceil(scene.width * renderedDpr), pixelHeight: Math.ceil(scene.height * renderedDpr), reason } })); } sceneSize(scene) { const { dpr } = this; const scaleFactor = SCALE_STEPS[scene.scaleStep]; const width = Math.min(Math.ceil(scene.width * scaleFactor * dpr), this.canvas3D.width); const height = Math.min(Math.ceil(scene.height * scaleFactor * dpr), this.canvas3D.height); return { width, height }; } copyPixels(scene, width, height) { const context2D = scene.context; if (context2D == null) { console.log("could not acquire 2d context"); return; } context2D.clearRect(0, 0, width, height); context2D.drawImage(this.canvas3D, 0, 0, width, height, 0, 0, width, height); scene.canvas.classList.add("show"); } orderedScenes() { const scenes = []; for (const visible of [false, true]) { for (const scene of this.scenes) { if (scene.element.modelIsVisible === visible) { scenes.push(scene); } } } return scenes; } get isPresenting() { return this.arRenderer.isPresenting; } preRender(scene, t, delta) { const { element, exposure } = scene; element[$tick](t, delta); const exposureIsNumber = typeof exposure === "number" && !Number.isNaN(exposure); this.threeRenderer.toneMappingExposure = exposureIsNumber ? exposure : 1; } render(t, frame) { if (frame != null) { this.arRenderer.onWebXRFrame(t, frame); return; } const delta = t - this.lastTick; this.lastTick = t; if (!this.canRender || this.isPresenting) { return; } this.countVisibleScenes(); this.updateRendererSize(); if (this.renderedLastFrame) { this.updateRendererScale(delta); this.renderedLastFrame = false; } const { canvas3D } = this; for (const scene of this.orderedScenes()) { const { element } = scene; if (!element.loaded || !element.modelIsVisible && scene.renderCount > 0) { continue; } this.preRender(scene, t, delta); if (!this.shouldRender(scene)) { continue; } if (scene.externalRenderer != null) { const camera = scene.getCamera(); camera.updateMatrix(); const { matrix, projectionMatrix } = camera; const viewMatrix = matrix.elements.slice(); const target = scene.getTarget(); viewMatrix[12] += target.x; viewMatrix[13] += target.y; viewMatrix[14] += target.z; scene.externalRenderer.render({ viewMatrix, projectionMatrix: projectionMatrix.elements }); continue; } if (!element.modelIsVisible && !this.multipleScenesVisible) { for (const visibleScene of this.scenes) { if (visibleScene.element.modelIsVisible) { visibleScene.queueRender(); } } } const { width, height } = this.sceneSize(scene); scene.renderShadow(this.threeRenderer); this.threeRenderer.setRenderTarget(null); this.threeRenderer.setViewport(0, Math.ceil(this.height * this.dpr) - height, width, height); this.threeRenderer.render(scene, scene.camera); if (this.multipleScenesVisible || !scene.element.modelIsVisible && scene.renderCount === 0) { this.copyPixels(scene, width, height); } else if (canvas3D.parentElement !== scene.canvas.parentElement) { scene.canvas.parentElement.appendChild(canvas3D); scene.canvas.classList.remove("show"); } scene.hasRendered(); ++scene.renderCount; this.renderedLastFrame = true; } } dispose() { if (this.textureUtils != null) { this.textureUtils.dispose(); } if (this.threeRenderer != null) { this.threeRenderer.dispose(); } this.textureUtils = null; this.threeRenderer = null; const elements = []; for (const scene of this.scenes) { elements.push(scene.element); } this.canvas3D.removeEventListener("webglcontextlost", this.onWebGLContextLost); this.canvas3D.removeEventListener("webglcontextrestored", this.onWebGLContextRestored); return elements; } } Renderer._singleton = new Renderer({ powerPreference: (self.ModelViewerElement || {}).powerPreference || DEFAULT_POWER_PREFERENCE, debug: isDebugMode() }); export { DEFAULT_POWER_PREFERENCE, Renderer };