Home Reference Source

viewer/abstractviewer.js

import * as mat4 from "./glmatrix/mat4.js";
import * as vec3 from "./glmatrix/vec3.js";

import {GeometryLoader} from "./geometryloader.js";
import {BimserverGeometryLoader} from "./bimservergeometryloader.js";
import {Viewer} from "./viewer.js";
import {DefaultRenderLayer} from "./defaultrenderlayer.js";
import {TilingRenderLayer} from "./tilingrenderlayer.js";
import {VertexQuantization} from "./vertexquantization.js";
import {Executor} from "./executor.js";
import {Stats} from "./stats.js";
import {DefaultSettings} from "./defaultsettings.js";
import {Utils} from "./utils.js";
import {DataInputStream} from "./datainputstream.js";
import { GLTFLoader } from "./gltfloader.js"


export class AbstractViewer {
	constructor(settings, canvas, width, height, stats) {
		if (stats == null) {
			stats = new Stats(false);
		}
		
		// Necessary for settings
		window.bimServerViewer = this;

		this.canvas = canvas;

		this.settings = settings;
		this.stats = stats;
		
		this.width = width || canvas.clientWidth;
		this.height = height || canvas.clientHeight;
		this.layers = new Map();
		
		this.settings = DefaultSettings.create(settings);

		this.viewer = new Viewer(canvas, settings, stats, this.width, this.height);
		
		stats.setParameter("Renderer settings", "Object colors", this.settings.useObjectColors);
		stats.setParameter("Renderer settings", "Small indices if possible", this.settings.useSmallIndicesIfPossible);
		stats.setParameter("Renderer settings", "Quantize normals", this.settings.quantizeNormals);
		stats.setParameter("Renderer settings", "Quantize vertices", this.settings.quantizeVertices);

		stats.setParameter("Loader settings", "Object colors", this.settings.loaderSettings.useObjectColors);
		stats.setParameter("Loader settings", "Quantize normals", this.settings.loaderSettings.quantizeNormals);
		stats.setParameter("Loader settings", "Quantize vertices", this.settings.loaderSettings.quantizeVertices);

		// Autoresize automatically resizes the viewer to the full width/height of the screen
        if ("OffscreenCanvas" in window && canvas instanceof OffscreenCanvas) {
			
		} else {
			if (this.settings.resizing == "manual") {
				// Do nothing, let the embedder deal with resizing
			} else {
				if (this.settings.autoResize) {
					this.autoResizeCanvas();
					this.resizeHandler = () => {
						this.autoResizeCanvas();
					};
					window.addEventListener("resize", this.resizeHandler, false);
				} else {
					this.canvas.width = this.width;
					this.canvas.height = this.height;
					this.resizeHandler = () => {
						this.autoResizeCanvas();
					};
					window.addEventListener("resize", this.resizeHandler, false);
				}
				this.viewer.setDimensions(this.width, this.height);
			}
		}
	}

	loadGltf(params) {
		let load = (buffer) => {
			var gltfLoader = new GLTFLoader(this.viewer, buffer, params);
			gltfLoader.processGLTFBuffer();
		};
		if (params.url) {
			fetch(params.url).then(function (response) {
				return response.arrayBuffer();
			}).then(load);
		} else if (params.buffer) {
			load(params.buffer);
		} else {
			throw new Error("Expected buffer or url");
		}
	}

	loadAnnotationsFromPreparedBufferUrl(url) {
		return Utils.request({url: url, binary: true}).then((buffer)=>{
			let stream = new DataInputStream(buffer);
			const layer = new DefaultRenderLayer(this.viewer);
			
			if (this.viewer.renderLayers.size === 0) {
				var defaultRenderLayer = new DefaultRenderLayer(this.viewer, null);
				this.viewer.renderLayers.add(defaultRenderLayer);
			}

			const proceed = () => {
				const gpuBufferManager = Array.from(this.viewer.renderLayers)[0].gpuBufferManager;
				let loader = new GeometryLoader(null, layer, {quantizeVertices: false}, this.viewer.vertexQuantization, null, this.viewer.settings, null, gpuBufferManager);
				this.viewer.renderLayers.add(layer);
				loader.processPreparedBufferInit(stream, false);
				return loader.processPreparedBuffer(stream, false);
			}

			if (!this.viewer.quad2) {
				return this.viewer.init().then(proceed);
			} else {
				return proceed();
			}			
		})
	}

	autoResizeCanvas() {
		this.canvas.width = this.canvas.clientWidth;
		this.canvas.height = this.canvas.clientHeight;
		this.viewer.setDimensions(this.canvas.width, this.canvas.height);
	}

	init() {
		return this.viewer.init();
	}
	
	/*
	 * Private method
	 */
	internalLoadRevision(api, revision, nrPrimitivesBelow, nrPrimitivesAbove) {
		return new Promise((resolve, reject) => {

			this.revisionId = revision.oid;

			this.viewer.stats.setParameter("Models", "Models to load", 1);

	//		console.log("Total triangles", nrPrimitivesBelow + nrPrimitivesAbove);
			
			var requests = [
				["ServiceInterface", "getTotalBounds", {
					roids: [revision.oid]
				}],
				["ServiceInterface", "getTotalUntransformedBounds", {
					roids: [revision.oid]
				}]
			];
			
			if (this.settings.gpuReuse) {
				requests.push(["ServiceInterface", "getGeometryDataToReuse", {
					roids: [revision.oid],
					excludedTypes: this.settings.excludedTypes,
					trianglesToSave: 0
				}]);
			}
			
			for (var croid of revision.concreteRevisions) {
				requests.push(["ServiceInterface", "getModelBoundsUntransformedForConcreteRevision", {
					croid: croid
				}]);
			}
			for (var croid of revision.concreteRevisions) {
				requests.push(["ServiceInterface", "getModelBoundsForConcreteRevision", {
					croid: croid
				}]);
			}

			api.multiCall(requests, (responses) => {
				var totalBounds = responses[0].result;
				var totalBoundsUntransformed = responses[1].result;
//				console.log(totalBounds, totalBoundsUntransformed);
				if (this.settings.gpuReuse) {
					this.geometryDataIdsToReuse = new Set(responses[2].result);
				} else {
					this.geometryDataIdsToReuse = null;
				}
	//			console.log("Geometry Data IDs to reuse", this.geometryDataIdsToReuse);

				var add = this.settings.gpuReuse ? 3 : 2;
				var modelBoundsUntransformed = new Map();
				for (var i=0; i<(responses.length - add) / 2; i++) {
					modelBoundsUntransformed.set(revision.concreteRevisions[i], responses[i + add].result);
				}
				var modelBoundsTransformed = new Map();
				for (var i=0; i<(responses.length - add) / 2; i++) {
					modelBoundsTransformed.set(revision.concreteRevisions[i], responses[(responses.length - add) / 2 + i + add].result);
				}
				
				var bounds = [
					totalBounds.min.x,
					totalBounds.min.y,
					totalBounds.min.z,
					totalBounds.max.x,
					totalBounds.max.y,
					totalBounds.max.z,
				];
				
				// globalTranslationVector is a translation vector that puts the complete model close to 0, 0, 0
				if (this.viewer.globalTranslationVector == null) {
					this.viewer.globalTranslationVector = vec3.fromValues(
						-(bounds[0] + (bounds[3] - bounds[0]) / 2), 
						-(bounds[1] + (bounds[4] - bounds[1]) / 2), 
						-(bounds[2] + (bounds[5] - bounds[2]) / 2));
				}

				if (this.settings.quantizeVertices || this.settings.loaderSettings.quantizeVertices) {
					if (this.viewer.vertexQuantization == null) {
						this.viewer.vertexQuantization = new VertexQuantization(this.settings);
					}
					for (var croid of modelBoundsUntransformed.keys()) {
						this.viewer.vertexQuantization.generateUntransformedMatrices(croid, modelBoundsUntransformed.get(croid));
					}
					this.viewer.vertexQuantization.generateMatrices(totalBounds, totalBoundsUntransformed, this.viewer.globalTranslationVector);
				}
				
				this.viewer.stats.inc("Primitives", "Primitives to load (L1)", nrPrimitivesBelow);
				this.viewer.stats.inc("Primitives", "Primitives to load (L2)", nrPrimitivesAbove);

				var min = vec3.fromValues(bounds[0], bounds[1], bounds[2]);
				var max = vec3.fromValues(bounds[3], bounds[4], bounds[5]);
				vec3.add(min, min, this.viewer.globalTranslationVector);
				vec3.add(max, max, this.viewer.globalTranslationVector);
				this.viewer.setModelBounds([min[0], min[1], min[2], max[0], max[1], max[2]]);
				
				// TODO This is very BIMserver specific, clutters the code, should move somewhere else (maybe BimserverGeometryLoader)
				var fieldsToInclude = ["indices"];
				fieldsToInclude.push("colorPack");
				if (this.settings.loaderSettings.quantizeNormals) {
					if (this.settings.loaderSettings.prepareBuffers) {
						fieldsToInclude.push("normals");
						fieldsToInclude.push("normalsQuantized");
					} else {
						fieldsToInclude.push("normalsQuantized");
					}
				} else {
					fieldsToInclude.push("normals");
				}
				if (this.settings.loaderSettings.quantizeVertices) {
					if (this.settings.loaderSettings.prepareBuffers) {
						fieldsToInclude.push("vertices");
						fieldsToInclude.push("verticesQuantized");
					} else {
						fieldsToInclude.push("verticesQuantized");
					}
				} else {
					fieldsToInclude.push("vertices");
				}
				if (!this.settings.loaderSettings.useObjectColors) {
					fieldsToInclude.push("colorsQuantized");
				}
				
				var promise = Promise.resolve();
				
				const layerSet = new Set();
				this.layers.set(revision.oid, layerSet);
				
				if (this.viewer.settings.defaultLayerEnabled && nrPrimitivesBelow) {
					var defaultRenderLayer = new DefaultRenderLayer(this.viewer, this.geometryDataIdsToReuse);
					layerSet.add(defaultRenderLayer);
					this.viewer.renderLayers.add(defaultRenderLayer);

					defaultRenderLayer.setProgressListener((nrPrimitivesLoaded) => {
						var percentage = 100 * nrPrimitivesLoaded / nrPrimitivesBelow;
						this.updateProgress(percentage);
					});

					promise = this.loadDefaultLayer(api, defaultRenderLayer, revision.oid, fieldsToInclude);
				}

				promise.then(() => {
					this.viewer.dirty = 2;
					var tilingPromise = Promise.resolve();
					if (this.viewer.settings.tilingLayerEnabled && nrPrimitivesAbove > 0) {
						var tilingRenderLayer = new TilingRenderLayer(this.viewer, this.geometryDataIdsToReuse, bounds);
						layerSet.add(tilingRenderLayer);
						this.viewer.renderLayers.add(tilingRenderLayer);
						
						tilingPromise = this.loadTilingLayer(api, tilingRenderLayer, revision, bounds, fieldsToInclude);
					}
					tilingPromise.then(() => {
						this.viewer.stats.setParameter("Loading time", "Total", performance.now() - this.totalStart);
						if (this.viewer.bufferSetPool != null) {
							this.viewer.bufferSetPool.cleanup();
						}
						this.viewer.dirty = 2;

						resolve();
					});
				});
			});
		});
	}

	findElement(api, globalId) {
		api.call("ServiceInterface", "getOidByGuid", {
			roid: this.revisionId,
			guid: globalId
		}, (oid) => {
			// @todo: This does not work, leaving this for Ruben
			var buffer, desc;
			this.layers.forEach((layer, index) => {
				if ((buffer = layer.uniqueIdToBufferSet.get(oid))) {
					if ((desc = buffer.uniqueIdToIndex.get(oid))) {
						console.log(buffer, desc);
					}
				}
			});
		});
	}
	
	loadTilingLayer(api, tilingLayer, revision, totalBounds, fieldsToInclude) {
		var startLayer2 = performance.now();

		var layer2Start = performance.now();
		
		var p = tilingLayer.load(api, this.densityThreshold, [revision.oid], fieldsToInclude, (percentage) => {
//			document.getElementById("progress").style.width = percentage + "%";
		});
		this.viewer.dirty = 2;
		p.then(() => {
			this.viewer.stats.setParameter("Loading time", "Layer 2", performance.now() - layer2Start);
			this.viewer.stats.setParameter("Loading time", "Total", performance.now() - this.totalStart);
//			document.getElementById("progress").style.display = "none";

			if (this.viewer.bufferSetPool != null) {
				this.viewer.bufferSetPool.cleanup();
			}

//			tilingLayer.octree.traverse((node) => {
//				if (node.liveBuffers.length > 0) {
//					console.log(node.getBounds(), node.liveBuffers.length);
//				}
//			}, true);
		});
		return p;
	}
	
	cleanup() {
		window.removeEventListener("resize", this.resizeHandler, false);
		if (this.stats) {
			this.stats.cleanup();
		}
		this.viewer.cleanup();
	}
	
	updateProgress(percentage) {
		if (this.progressListener) {
			this.progressListener(percentage);
		}
	}
	
	setProgressListener(progressListener) {
		this.progressListener = progressListener;
	}
	
	addSelectionListener(selectionListener) {
		this.viewer.addSelectionListener(selectionListener);
	}

}