import { MAIN_LOOP_EVENTS } from 'itowns';
import { Scene, PointsMaterial, Box3, Raycaster } from 'three';
import {
computeNearFarCamera,
RequestAnimationFrameProcess,
} from '@ud-viz/utils_browser';
import { ClippingPlane } from './ClippingPlane';
import { TargetOrbitControlMesh } from './TargetOrbitControlMesh';
import { ViewManager } from './ViewManager';
import { LayerManager } from './LayerManager';
import { setUpCameraDefaults } from './cameraSetup';
import { setupLoadingUI, setUpSpeedControls } from './uiSetup';
import { Measure } from './Measure';
/**
* @typedef {object} LayerConfig
* @property {string} name - Name of the C3DTilesLayer.
* @property {object} source - Source of the C3DTilesLayer.
* @property {string} source.url - URL to the tileset.json.
*/
/**
* Visualizer class for rendering 3D Tiles and managing interactions.
*
* @param {import("itowns").Extent} extent - The extent of the area being visualized in the itowns framework.
* @param {Array<LayerConfig>} layerConfigs - Configuration parameters for 3DTiles layers.
* @param {object} options - Configuration options for the visualizer.
* @param {HTMLElement} options.parentDomElement - The DOM element where the planar view will be appended.
* @param {string} options.domElementClass - CSS class to apply to the main DOM element.
* @param {Array<string>} options.c3DTilesLoadingDomElementClasses - CSS classes for loading indicators.
* @param {object} options.camera - Camera configuration options.
* @param {object} options.camera.default - Default camera settings.
* @param {object} options.camera.default.position - Default position for the camera.
* @param {number} options.maxSubdivisionLevel - Maximum level of detail for base map texture.
* @param {number} options.defaultPointCloudSize - Default size for point cloud points.
* @param {number} [options.raycasterPointsThreshold=Visualizer.RAYCASTER_POINTS_THRESHOLD] - Threshold for raycaster points.
* @param {number} [options.measure] - If true, initializes the measure tool.
*
* The constructor sets up the visualization environment, including initializing scenes,
* camera settings, layer management, and UI components. It also handles the rendering loop
* and event listeners for user interactions.
*
* Note: options.camera.default.quaternion is not available option since the camera
* is initialized as pointing towards the center of the bounding box of the
* observed 3DTiles.
*/
export class Visualizer {
constructor(extent, layerConfigs, options = {}) {
/** @type {Raycaster} */
this.raycaster = new Raycaster();
/** @type {ViewManager} */
this.viewManager = new ViewManager(extent, options);
/** @type {Scene} */
this.topScene = new Scene(); // Scene for additional rendering layers
this.itownsView.mainLoop.gfxEngine.renderer.autoClear = false; // Prevent automatic clearing of the renderer
// Custom render function to manage the rendering order of scenes
this.itownsView.render = () => {
this.itownsView.mainLoop.gfxEngine.renderer.clear(); // Clear buffers before rendering
this.itownsView.mainLoop.gfxEngine.renderer.render(
this.itownsView.scene,
this.itownsView.camera.camera3D
); // Render the main scene
this.itownsView.mainLoop.gfxEngine.renderer.clearDepth(); // Clear depth buffer
this.itownsView.mainLoop.gfxEngine.renderer.render(
this.topScene,
this.itownsView.camera.camera3D
); // Render the top scene
};
/** @type {ClippingPlane} */
this.clippingPlane = new ClippingPlane(this.itownsView);
// Init layer manager
this.layerManager = new LayerManager(
layerConfigs,
this.itownsView,
new PointsMaterial({
size: options.defaultPointCloudSize || Visualizer.DEFAULT_POINT_SIZE,
vertexColors: true,
clippingPlanes: [this.clippingPlane.plane], // Set clipping planes for the material
})
);
// Create mesh for target orbit controls
this.targetOrbitControlsMesh = new TargetOrbitControlMesh(
this.viewManager.orbitControls,
this.itownsView.camera.camera3D,
this.layerManager
);
this.topScene.add(this.targetOrbitControlsMesh.mesh); // Add mesh to the top scene
// Set up default camera settings
setUpCameraDefaults(
this.viewManager.itownsView,
this.viewManager.orbitControls,
this.layerManager.layers,
options.camera
);
// Set up loading UI for C3DTiles
this.c3DTilesLoadingDomElement = setupLoadingUI(
this.viewManager.domElement,
this.layerManager.layers,
this.viewManager.itownsView,
options
);
// Set up controls for adjusting zoom speed
this.domElementSpeedControls = setUpSpeedControls(
this.viewManager.orbitControls
);
// Add global bounding box mesh to the scene which embed all 3Dtiles layers
this.viewManager.itownsView.scene.add(this.layerManager.globalBBMesh);
/** @type {Measure} */
this.measure = null;
if (options.measure) {
this.measure = new Measure(
this.viewManager.itownsView,
this.layerManager,
this.viewManager.itownsView.mainLoop.gfxEngine.label2dRenderer.domElement
);
this.topScene.add(this.measure.group);
window.addEventListener('keyup', (event) => {
if (event.key == 'Escape') {
this.measure.leaveMeasureMode();
}
});
this.measure.update(this.itownsView);
}
// Add clipping plane to the scene
this.viewManager.itownsView.scene.add(this.clippingPlane.quad);
// Set position and scale for the clipping plane
this.clippingPlane.quad.position.set(
extent.center().x,
extent.center().y,
300
);
this.clippingPlane.quad.scale.set(1000, 1000, 1000);
const transformControlsProcess = new RequestAnimationFrameProcess(30); // Process to manage transform control of the clipping plane
// Event listener for dragging changes on the clipping plane
this.clippingPlane.transformControls.addEventListener(
'dragging-changed',
(event) => {
this.viewManager.orbitControls.enabled = !event.value;
}
);
// Start rendering updates for the clipping plane when it's visible
transformControlsProcess.start(() => {
if (!this.clippingPlane.quad.visible) return;
this.clippingPlane.transformControls.updateMatrixWorld();
this.viewManager.itownsView.render();
});
// Add a frame requester to dynamically compute near and far planes for the camera
this.viewManager.itownsView.addFrameRequester(
MAIN_LOOP_EVENTS.AFTER_CAMERA_UPDATE,
() => {
const bb = new Box3().setFromObject(this.viewManager.itownsView.scene);
computeNearFarCamera(
this.viewManager.itownsView.camera.camera3D,
bb.min,
bb.max
);
}
);
// Notify changes to the view manager to trigger a redraw
this.viewManager.itownsView.notifyChange(
this.viewManager.itownsView.camera.camera3D
);
}
/**
* Getter for the itowns view instance.
*
* @returns {object} The current itowns view.
*/
get itownsView() {
return this.viewManager.itownsView;
}
/**
* Getter for orbit controls.
*
* @returns {object} The current orbit controls.
*/
get orbitControls() {
return this.viewManager.orbitControls;
}
/**
* Getter for the layer manager's layers.
*
* @returns {Array<object>} The layers managed by the layer manager.
*/
get layers() {
return this.layerManager.layers;
}
/**
* Getter for the DOM element used in target dragging.
*
* @returns {HTMLElement} The target drag element.
*/
get domElementTargetDragElement() {
return this.targetOrbitControlsMesh.domElement;
}
/**
* Getter for the DOM element associated with the measure tool.
*
* @returns {HTMLElement} The measure tool's DOM element.
*/
get measureDomElement() {
return this.measure.domElement;
}
/**
* Getter for the clipping plane details.
*
* @returns {object} The details of the clipping plane.
*/
get clippingPlaneDetails() {
return this.clippingPlane.details;
}
/**
* Default size for points in the point cloud visualization.
*
* @returns {number} The default point size.
*/
static get DEFAULT_POINT_SIZE() {
return 0.03;
}
/**
* Key for storing target information in local storage.
*
* @returns {string} The key for target storage.
*/
static get TARGET_LOCAL_STORAGE_KEY() {
return 'target_local_storage_key_point_cloud_visualizer';
}
/**
* Key for storing camera settings in local storage.
*
* @returns {string} The key for camera storage.
*/
static get CAMERA_LOCAL_STORAGE_KEY() {
return 'camera_local_storage_key_point_cloud_visualizer';
}
/**
* Key for storing clipping plane settings in local storage.
*
* @returns {string} The key for clipping plane storage.
*/
static get CLIPPING_PLANE_LOCAL_STORAGE_KEY() {
return 'clipping_plane_local_storage_key_point_cloud_visualizer';
}
/**
* Threshold for the raycaster points.
*
* @returns {number} The threshold value.
*/
static get RAYCASTER_POINTS_THRESHOLD() {
return 0.01;
}
}