uiSetup.js

import { BoxGeometry, Color, Mesh, MeshBasicMaterial } from 'three';
import { C3DTilesLayer, C3DTILES_LAYER_EVENTS, PlanarView } from 'itowns';
import { createLocalStorageSlider } from '@ud-viz/utils_browser';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

/**
 * Sets up a loading UI for 3D tiles layers within a specified DOM element and iTowns view.
 *
 * @param {HTMLElement} domElement - The container for the loading indicators.
 * @param {C3DTilesLayer[]} layers - Array of 3D tiles layers to monitor for loading status.
 * @param {PlanarView} itownsView - The iTowns view with scene and camera.
 * @param {object} options - Additional options.
 * @param {string[]} options.c3DTilesLoadingDomElementClasses - CSS classes for styling the loading UI.
 * @returns {HTMLElement} The created loading UI element.
 */
export function setupLoadingUI(domElement, layers, itownsView, options) {
  const c3DTilesLoadingDomElement = document.createElement('div');
  if (options.c3DTilesLoadingDomElementClasses) {
    options.c3DTilesLoadingDomElementClasses.forEach((cssClass) => {
      c3DTilesLoadingDomElement.classList.add(cssClass);
    });
  }
  domElement.appendChild(c3DTilesLoadingDomElement);

  const camera3D = itownsView.camera.camera3D;
  c3DTilesLoadingDomElement.hidden = true;

  /** @type {Map<string, Mesh>} */
  const currentLoadingBox = new Map();

  const loadingBoxId = (layer, tileId) => layer.id + tileId;

  layers.forEach((c3dTilesLayer) => {
    c3dTilesLayer.addEventListener(
      C3DTILES_LAYER_EVENTS.ON_TILE_REQUESTED,
      ({ metadata }) => {
        if (metadata.tileId == undefined) throw new Error('no tile id');

        const worldBox3 = metadata.boundingVolume.box.clone();

        if (metadata.transform) {
          worldBox3.applyMatrix4(metadata.transform);
        } else if (metadata._worldFromLocalTransform) {
          worldBox3.applyMatrix4(metadata._worldFromLocalTransform);
        }

        const box3Object = new Mesh(
          new BoxGeometry(),
          new MeshBasicMaterial({
            wireframe: true,
            wireframeLinewidth: 2,
            color: new Color(Math.random(), Math.random(), Math.random()),
          })
        );
        box3Object.scale.copy(worldBox3.max.clone().sub(worldBox3.min));
        worldBox3.getCenter(box3Object.position);
        box3Object.updateMatrixWorld();
        itownsView.scene.add(box3Object);
        itownsView.notifyChange(camera3D);

        // bufferize
        currentLoadingBox.set(
          loadingBoxId(c3dTilesLayer, metadata.tileId),
          box3Object
        );
        c3DTilesLoadingDomElement.hidden = false;
      }
    );

    c3dTilesLayer.addEventListener(
      C3DTILES_LAYER_EVENTS.ON_TILE_CONTENT_LOADED,
      ({ tileContent }) => {
        if (
          currentLoadingBox.has(loadingBoxId(c3dTilesLayer, tileContent.tileId))
        ) {
          itownsView.scene.remove(
            currentLoadingBox.get(
              loadingBoxId(c3dTilesLayer, tileContent.tileId)
            )
          );
          currentLoadingBox.delete(
            loadingBoxId(c3dTilesLayer, tileContent.tileId)
          );
          c3DTilesLoadingDomElement.hidden = currentLoadingBox.size == 0; // nothing more is loaded
          itownsView.notifyChange(camera3D);
        }
      }
    );
  });
  return c3DTilesLoadingDomElement;
}

/**
 * The function setUpSpeedControls creates a set of speed controls for an OrbitControls object in a 3D
 * scene.
 *
 * @param {OrbitControls} orbitControls - Object that likely contains properties and methods
 * related to controlling the orbit of a camera in a 3D scene.
 * @returns {HTMLElement} returns the `domElementSpeedControls` which is a div
 * element containing the speed controls slider for the OrbitControls.
 */
export function setUpSpeedControls(orbitControls) {
  const domElementSpeedControls = document.createElement('div');
  const sliderSpeedControls = createLocalStorageSlider(
    'speed_orbit_controls',
    'Speed controls',
    domElementSpeedControls,
    {
      min: 0.01,
      max: 0.75,
      step: 'any',
      defaultValue: 0.3,
    }
  );

  const updateSpeedControls = () => {
    const speed = sliderSpeedControls.valueAsNumber;
    orbitControls.rotateSpeed = speed;
    orbitControls.zoomSpeed = speed;
    orbitControls.panSpeed = speed;
  };

  sliderSpeedControls.oninput = updateSpeedControls;
  updateSpeedControls();

  return domElementSpeedControls;
}