import * as itownsWidget from 'itowns/widgets';
import * as itowns from 'itowns';
import { createLabelInput } from '@ud-viz/utils_browser';
import * as THREE from 'three';
const DEFAULT_OPTIONS = {
position: 'top-right', // should be deprecated https://github.com/iTowns/itowns/issues/2005
};
/**
* Within a list of already existing layers, add a UI entry for a newly
* appended layer (together with a button for its removal).
*
* @param {itowns.View} view - The view to which the layer will be added
* @param {itowns.C3DTilesLayer} layer - layer to be added to dom element
* @param {HTMLDivElement} layersContainer - HTML division holding the listed layers
* @param {string} [layerContainerClassName] - Class name of the layer container
*/
function addLayerToDomElement(
view,
layer,
layersContainer,
layerContainerClassName
) {
const layerContainerDomElement = document.createElement('div');
if (layerContainerClassName)
layerContainerDomElement.classList.add(layerContainerClassName);
layersContainer.appendChild(layerContainerDomElement);
// visibility checkbox
const { input, parent } = createLabelInput(layer.name, 'checkbox');
layerContainerDomElement.appendChild(parent);
input.checked = layer.visible;
input.onchange = () => {
layer.visible = input.checked;
view.notifyChange();
};
// remove button
const removeButton = document.createElement('button');
removeButton.innerText = 'Remove';
layerContainerDomElement.appendChild(removeButton);
removeButton.onclick = () => {
view.removeLayer(layer.id, true); // clear cache
layerContainerDomElement.remove();
};
}
export class C3DTiles extends itownsWidget.Widget {
/**
*
* @param {itowns.View} view - itowns view
* @param {object} options - options
* @param {HTMLElement} options.overrideStyle - style applied to 3DTilesLayer add
* @param {HTMLElement} options.parentElement - parent element of the widget
* @param {string} options.layerContainerClassName - class name of the layer container div
* @param {string} options.urlContainerClassName - class name of the layer container div
* @param {string} options.c3DTFeatureInfoContainerClassName - class name of the c3DTFeatureInfo container div
* @param {boolean} options.displayExistingLayers - whether the existing layers should be listed in the UI or not (default is True)
*/
constructor(view, options = {}) {
super(view, options, DEFAULT_OPTIONS);
// Available layers are optionnaly listed in the UI. Inhibiting this display
// allows for an alternative usage of other widgets with a similar purpose
// but different feature e.g. @ud-viz/widget_layer_choice.
if (options.displayExistingLayers == undefined) {
options.displayExistingLayers = true;
}
/** @type {THREE.Box3Helper} */
this.displayedBBFeature = new THREE.Box3Helper(new THREE.Box3());
this.displayedBBFeature.visible = false;
view.scene.add(this.displayedBBFeature);
// Inhibit click selection "through" the widget
this.domElement.onclick = (event) => event.stopImmediatePropagation();
// Display a UI section allowing the addition of a C3DTilesLayer out of
// an url and a (local) tagname.
const urlObject = createLabelInput('url', 'text');
if (options.urlContainerClassName)
urlObject.parent.classList.add(options.urlContainerClassName);
this.domElement.appendChild(urlObject.parent);
// name of the 3DTiles requested
const name3DTilesObject = createLabelInput('name', 'text');
urlObject.parent.appendChild(name3DTilesObject.parent);
// request tileset.json button
const requestButton = document.createElement('button');
requestButton.innerText = 'Add 3DTiles From URL';
urlObject.parent.appendChild(requestButton);
requestButton.onclick = () => {
// add layer
const url = urlObject.input.value;
try {
const c3DTilesLayer = new itowns.C3DTilesLayer(
THREE.MathUtils.generateUUID(),
{
style: options.overrideStyle || null,
name: name3DTilesObject.input.value,
source: new itowns.C3DTilesSource({
url: url,
}),
},
view
);
itowns.View.prototype.addLayer.call(view, c3DTilesLayer);
if (options.displayExistingLayers)
addLayerToDomElement(
view,
c3DTilesLayer,
this.layersContainer,
options.layerContainerClassName
);
} catch (error) {
// do not catch error when a wrong url have been entered
alert(error);
}
};
if (options.displayExistingLayers) {
this.layersContainer = document.createElement('div');
this.layersContainer.innerText = 'Layers:';
this.domElement.appendChild(this.layersContainer);
// Initialize the list of existing layer
view
.getLayers()
.filter((el) => el.isC3DTilesLayer)
.forEach((layer) => {
addLayerToDomElement(
view,
layer,
this.layersContainer,
options.layerContainerClassName
);
});
/**
* c3DTfeature display container
*
@type {HTMLDivElement} */
this.c3DTFeatureInfoContainer = document.createElement('div');
if (options.c3DTFeatureInfoContainerClassName)
this.c3DTFeatureInfoContainer.classList.add(
options.c3DTFeatureInfoContainerClassName
);
this.c3DTFeatureInfoContainer.hidden = true; // hidden by default
this.domElement.appendChild(this.c3DTFeatureInfoContainer);
} // if (options.displayExistingLayers)
} // Constructor()
/**
*
* @param {itowns.C3DTFeature|null} c3DTFeature - feature to display info if null nothing is display
* @param {itowns.C3DTilesLayer|null} layer - layer of the feature
* @param {number} [stepPadding=20] - number of pixels to pad left each indent of the info object displayed
*/
displayC3DTFeatureInfo(c3DTFeature = null, layer = null, stepPadding = 20) {
// clear
while (this.c3DTFeatureInfoContainer.firstChild)
this.c3DTFeatureInfoContainer.firstChild.remove();
this.displayedBBFeature.visible = !!c3DTFeature;
this.c3DTFeatureInfoContainer.hidden = !c3DTFeature;
if (!c3DTFeature) return;
const createObjectDomElement = (object, label, indent = 0) => {
const result = document.createElement('div');
const labelDomElement = document.createElement('div');
labelDomElement.innerText = label;
labelDomElement.style.paddingLeft = indent * stepPadding + 'px';
result.appendChild(labelDomElement);
if (!object) return result;
for (const key in object) {
const value = object[key];
if (value instanceof Object) {
result.appendChild(createObjectDomElement(value, key, indent + 1));
} else {
const content = document.createElement('div');
content.innerText = key + ': ' + value;
content.style.paddingLeft = (indent + 1) * stepPadding + 'px';
result.appendChild(content);
}
}
return result;
};
// label layer
const labelLayerDomElement = document.createElement('div');
labelLayerDomElement.innerText = layer.name;
this.c3DTFeatureInfoContainer.appendChild(labelLayerDomElement);
c3DTFeature.computeWorldBox3(this.displayedBBFeature.box);
this.displayedBBFeature.updateMatrixWorld();
// feature info
this.c3DTFeatureInfoContainer.appendChild(
createObjectDomElement(
{
info: c3DTFeature.getInfo(),
boundingBox: this.displayedBBFeature.box,
},
'Feature'
)
);
}
}