UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

192 lines (182 loc) 5.38 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { ArrowHelper, AxesHelper, Box3, Box3Helper, Color, GridHelper, Mesh, Vector3 } from 'three'; import { isMaterial, isObject } from '../utils/predicates'; import { nonNull } from '../utils/tsutils'; export class SphereHelper extends Mesh { isHelper = true; } export class BoundingBoxHelper extends Box3Helper { isHelper = true; isvolumeHelper = true; } function hasBoundingBoxHelper(obj) { return isObject(obj) && obj.volumeHelper != null; } export function hasBoundingVolumeHelper(obj) { return obj?.boundingVolumeHelper !== undefined; } const _vector = new Vector3(); let _axisSize = 500; /** * @param colorDesc - A THREE color or hex string. * @returns The THREE color. */ function getColor(colorDesc) { if (typeof colorDesc === 'string' || colorDesc instanceof String) { return new Color(colorDesc); } return colorDesc; } /** * This function creates a Box3 by matching the object's bounding box, * without including its children. * * @param object - The object to expand. * @param precise - If true, the computation uses the vertices from the geometry. * @returns The expanded box. */ function makeLocalBbox(object, precise = false) { // The object provides a specific bounding box if (object.boundingBox != null) { return object.boundingBox; } const box = new Box3(); const geometry = object.geometry; if (geometry !== undefined) { if (precise && geometry.attributes !== undefined && geometry.attributes.position !== undefined) { const position = geometry.attributes.position; for (let i = 0, l = position.count; i < l; i++) { _vector.fromBufferAttribute(position, i); box.expandByPoint(_vector); } } else { if (geometry.boundingBox === null) { geometry.computeBoundingBox(); } box.copy(nonNull(geometry.boundingBox)); } } return box; } /** * Provides utility functions to create scene helpers, such as bounding boxes, grids, axes... * */ class Helpers { /** * Adds a bounding box helper to the object. * If a bounding box is already present, it is updated instead. * * @param obj - The object to decorate. * @param color - The color. * @example * // add a bounding box to 'obj' * Helpers.addBoundingBox(obj, 'green'); */ static addBoundingBox(obj, color) { // Don't add a bounding box helper to a bounding box helper ! if (obj.isvolumeHelper) { return; } const helper = Helpers.createBoxHelper(makeLocalBbox(obj), getColor(color)); obj.add(helper); obj.volumeHelper = helper; helper.updateMatrixWorld(true); } static createBoxHelper(box, color) { const helper = new BoundingBoxHelper(box, color); helper.name = 'bounding box'; if (isMaterial(helper.material)) { helper.material.transparent = true; helper.material.needsUpdate = true; } return helper; } static set axisSize(v) { _axisSize = v; } static get axisSize() { return _axisSize; } /** * Creates a selection bounding box helper around the specified object. * * @param obj - The object to decorate. * @param color - The color. * @returns the created box helper. * @example * // add a bounding box to 'obj' * Helpers.createSelectionBox(obj, 'green'); */ static createSelectionBox(obj, color) { const helper = Helpers.createBoxHelper(makeLocalBbox(obj), getColor(color)); obj.selectionHelper = helper; obj.add(helper); obj.updateMatrixWorld(true); return helper; } /** * Create a grid on the XZ plane. * * @param origin - The grid origin. * @param size - The size of the grid. * @param subdivs - The number of grid subdivisions. */ static createGrid(origin, size, subdivs) { const grid = new GridHelper(size, subdivs); grid.name = 'grid'; // Rotate the grid to be in the XZ plane. grid.rotateX(Math.PI / 2); grid.position.copy(origin); grid.updateMatrixWorld(); return grid; } /** * Create an axis helper. * * @param size - The size of the helper. */ static createAxes(size) { const axes = new AxesHelper(size); // We want the axes to be always visible, // and rendered on top of any other object in the scene. axes.renderOrder = 9999; axes.material.depthTest = false; return axes; } /** * Creates an arrow between the two points. * * @param start - The starting point. * @param end - The end point. */ static createArrow(start, end) { const length = start.distanceTo(end); const dir = end.sub(start).normalize(); const arrow = new ArrowHelper(dir, start, length); return arrow; } /** * Removes an existing bounding box from the object, if any. * * @param obj - The object to update. * @example * Helpers.removeBoundingBox(obj); */ static removeBoundingBox(obj) { if (hasBoundingBoxHelper(obj)) { const volumeHelper = obj.volumeHelper; obj.remove(volumeHelper); if ('dispose' in volumeHelper && typeof volumeHelper.dispose === 'function') { volumeHelper.dispose(); } // @ts-expect-error cannot remove "mandatory" property delete obj.volumeHelper; } } } export default Helpers;