web-ifc-viewer
Version:
260 lines • 10.8 kB
JavaScript
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { Color, DepthTexture, MeshLambertMaterial, Vector2, WebGLRenderTarget } from 'three';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { SAOPass } from 'three/examples/jsm/postprocessing/SAOPass';
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { CustomOutlinePass } from './custom-outline-pass';
// source: https://discourse.threejs.org/t/how-to-render-full-outlines-as-a-post-process-tutorial/22674
export class Postproduction {
constructor(context, renderer) {
this.context = context;
this.renderer = renderer;
this.htmlOverlay = document.createElement('img');
this.excludedItems = new Set();
this.initialized = false;
this.visibilityField = 'ifcjsPostproductionVisible';
this.isUserControllingCamera = false;
this.isControlSleeping = true;
this.lastWheelUsed = 0;
this.lastResized = 0;
this.resizeDelay = 500;
this.isActive = false;
this.isVisible = false;
this.white = new Color(255, 255, 255);
this.tempMaterial = new MeshLambertMaterial({
colorWrite: false,
opacity: 0,
transparent: true
});
this.outlineParams = {
mode: { Mode: 0 },
FXAA: true,
outlineColor: 0x777777,
depthBias: 16,
depthMult: 83,
normalBias: 5,
normalMult: 1.0
};
this.onControlStart = () => (this.isUserControllingCamera = true);
this.onWake = () => (this.isControlSleeping = false);
this.onResize = () => {
this.lastResized = performance.now();
this.visible = false;
setTimeout(() => {
if (performance.now() - this.lastResized >= this.resizeDelay) {
this.visible = true;
}
}, this.resizeDelay);
};
this.onControl = () => {
this.visible = false;
};
this.onControlEnd = () => {
this.isUserControllingCamera = false;
if (!this.isUserControllingCamera && this.isControlSleeping) {
this.visible = true;
}
};
this.onWheel = () => {
this.lastWheelUsed = performance.now();
};
this.onSleep = () => {
// This prevents that this gets triggered a million times when zooming with the wheel
this.isControlSleeping = true;
const currentWheel = performance.now();
setTimeout(() => {
if (this.lastWheelUsed > currentWheel)
return;
if (!this.isUserControllingCamera && this.isControlSleeping) {
this.visible = true;
}
}, 200);
};
this.onChangeProjection = (camera) => {
this.composer.passes.forEach((pass) => {
// @ts-ignore
pass.camera = camera;
});
this.update();
};
this.renderTarget = this.newRenderTarget();
this.composer = new EffectComposer(renderer, this.renderTarget);
this.composer.setSize(window.innerWidth, window.innerHeight);
}
get active() {
return this.isActive;
}
set active(active) {
if (this.isActive === active)
return;
if (!this.initialized)
this.tryToInitialize();
this.visible = active;
this.isActive = active;
}
get visible() {
return this.isVisible;
}
set visible(visible) {
if (!this.isActive)
return;
this.isVisible = visible;
if (visible)
this.update();
this.htmlOverlay.style.visibility = visible ? 'visible' : 'collapse';
}
get outlineColor() {
return this.outlineParams.outlineColor;
}
set outlineColor(color) {
this.outlineParams.outlineColor = color;
this.outlineUniforms.outlineColor.value.set(color);
}
get sao() {
var _a;
return (_a = this.saoPass) === null || _a === void 0 ? void 0 : _a.params;
}
dispose() {
var _a, _b;
this.active = false;
window.removeEventListener('resize', this.onResize);
this.renderTarget.dispose();
this.renderTarget = null;
(_a = this.depthTexture) === null || _a === void 0 ? void 0 : _a.dispose();
this.depthTexture = null;
(_b = this.customOutline) === null || _b === void 0 ? void 0 : _b.dispose();
this.customOutline = null;
this.composer = null;
this.excludedItems.clear();
this.excludedItems = null;
this.composer = null;
this.htmlOverlay.remove();
this.htmlOverlay = null;
this.outlineParams = null;
this.context = null;
this.renderer = null;
this.saoPass = null;
this.outlineUniforms = null;
this.scene = null;
}
setSize(width, height) {
this.composer.setSize(width, height);
}
update() {
var _a, _b, _c;
if (!this.initialized || !this.isActive)
return;
this.hideExcludedItems();
this.context.getScene().traverse((object) => {
// @ts-ignore
object.userData.prevMaterial = object.material;
// @ts-ignore
object.material = this.tempMaterial;
});
const background = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.background;
if (((_b = this.scene) === null || _b === void 0 ? void 0 : _b.background) && background)
this.scene.background = this.white;
this.composer.render();
if (((_c = this.scene) === null || _c === void 0 ? void 0 : _c.background) && background)
this.scene.background = background;
this.context.getScene().traverse((object) => {
// @ts-ignore
object.material = object.userData.prevMaterial;
delete object.userData.prevMaterial;
});
this.htmlOverlay.src = this.renderer.domElement.toDataURL();
this.showExcludedItems();
}
hideExcludedItems() {
for (const object of this.excludedItems) {
object.userData[this.visibilityField] = object.visible;
object.visible = false;
}
}
showExcludedItems() {
for (const object of this.excludedItems) {
if (object.userData[this.visibilityField] !== undefined) {
object.visible = object.userData[this.visibilityField];
}
}
}
tryToInitialize() {
const scene = this.context.getScene();
const camera = this.context.getCamera();
if (!scene || !camera)
return;
this.scene = scene;
this.renderer.clippingPlanes = this.context.getClippingPlanes();
this.setupEvents();
this.addBasePass(scene, camera);
this.addSaoPass(scene, camera);
this.addOutlinePass(scene, camera);
this.addAntialiasPass();
this.setupHtmlOverlay();
this.initialized = true;
}
setupEvents() {
const controls = this.context.ifcCamera.cameraControls;
const domElement = this.context.getDomElement();
controls.addEventListener('control', this.onControl);
controls.addEventListener('controlstart', this.onControlStart);
controls.addEventListener('wake', this.onWake);
controls.addEventListener('controlend', this.onControlEnd);
domElement.addEventListener('wheel', this.onWheel);
controls.addEventListener('sleep', this.onSleep);
window.addEventListener('resize', this.onResize);
this.context.ifcCamera.onChangeProjection.on(this.onChangeProjection);
}
setupHtmlOverlay() {
this.context.getContainerElement().appendChild(this.htmlOverlay);
// @ts-ignore
this.htmlOverlay.style.mixBlendMode = 'multiply';
this.htmlOverlay.style.position = 'absolute';
this.htmlOverlay.style.height = '100%';
this.htmlOverlay.style.userSelect = 'none';
this.htmlOverlay.style.pointerEvents = 'none';
this.htmlOverlay.style.top = '0';
this.htmlOverlay.style.left = '0';
}
addAntialiasPass() {
this.fxaaPass = new ShaderPass(FXAAShader);
this.fxaaPass.uniforms.resolution.value.set((1 / this.renderer.domElement.offsetWidth) * this.renderer.getPixelRatio(), (1 / this.renderer.domElement.offsetHeight) * this.renderer.getPixelRatio());
this.composer.addPass(this.fxaaPass);
}
addOutlinePass(scene, camera) {
this.customOutline = new CustomOutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera);
// Initial values
// @ts-ignore
this.outlineUniforms = this.customOutline.fsQuad.material.uniforms;
this.outlineUniforms.outlineColor.value.set(this.outlineParams.outlineColor);
this.outlineUniforms.multiplierParameters.value.x = this.outlineParams.depthBias;
this.outlineUniforms.multiplierParameters.value.y = this.outlineParams.depthMult;
this.outlineUniforms.multiplierParameters.value.z = this.outlineParams.normalBias;
this.outlineUniforms.multiplierParameters.value.w = this.outlineParams.normalMult;
this.composer.addPass(this.customOutline);
}
addSaoPass(scene, camera) {
this.saoPass = new SAOPass(scene, camera, false, true);
this.composer.addPass(this.saoPass);
this.saoPass.enabled = true;
this.saoPass.params.saoIntensity = 0.01;
this.saoPass.params.saoBias = 0.5;
this.saoPass.params.saoBlurRadius = 8;
this.saoPass.params.saoBlurDepthCutoff = 0.0015;
this.saoPass.params.saoScale = 30;
this.saoPass.params.saoKernelRadius = 30;
}
addBasePass(scene, camera) {
this.basePass = new RenderPass(scene, camera);
this.composer.addPass(this.basePass);
}
newRenderTarget() {
this.depthTexture = new DepthTexture(window.innerWidth, window.innerHeight);
return new WebGLRenderTarget(window.innerWidth, window.innerHeight, {
depthTexture: this.depthTexture,
depthBuffer: true
});
}
}
//# sourceMappingURL=postproduction.js.map