cione-comp-lib
Version:
`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`
377 lines (376 loc) • 13.4 kB
JavaScript
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 };