/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type { ColorRepresentation, Object3D } from 'three'; import { Box3, BufferGeometry, Color, Float32BufferAttribute, Group, Line3, LineBasicMaterial, LineSegments, MathUtils, Sphere, Vector2, Vector3, type Camera, } from 'three'; import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import type Context from '../core/Context'; import type Extent from '../core/geographic/Extent'; import type View from '../renderer/View'; import type { EntityUserData } from './Entity'; import type { Entity3DOptions, Entity3DEventMap } from './Entity3D'; import { getGeometryMemoryUsage, type GetMemoryUsageContext } from '../core/MemoryUsage'; import Helpers from '../helpers/Helpers'; import { isBufferGeometry, isCSS2DObject } from '../utils/predicates'; import { nonNull } from '../utils/tsutils'; import Entity3D from './Entity3D'; type Axis = 'X' | 'Y' | 'Z'; interface Line3WithLabel extends Line3 { labelValue: number; axis: Axis; } const mod = MathUtils.euclideanModulo; const UP = new Vector2(0, 1); const RIGHT = new Vector2(1, 0); const tmpVec2 = new Vector2(); const tmpVec3 = new Vector3(); const tmpBl = new Vector2(); const tmpBr = new Vector2(); const tmpTl = new Vector2(); const tmpTr = new Vector2(); const tmp = { position: new Vector3(), planeNormal: new Vector3(), edgeCenter: new Vector3(), sideCenter: new Vector3(), v2: new Vector2(), sphere: new Sphere(), }; /** * The grid step values. */ export interface Ticks { /** The tick distance on the x axis. */ x: number; /** The tick distance on the y axis. */ y: number; /** The tick distance on the z (vertical) axis. */ z: number; } /** * The grid volume. */ export interface Volume { /** The grid volume extent. */ extent: Extent; /** The elevation of the grid floor. */ floor: number; /** The elevation of the grid ceiling. */ ceiling: number; } /** * The grid formatting options. */ export interface Style { /** The grid line and label colors. */ color: ColorRepresentation; /** The fontsize, in points (pt). */ fontSize: number; /** The number format for the labels. */ numberFormat: Intl.NumberFormat; } export const DEFAULT_STYLE: Style = { color: new Color('white'), fontSize: 10, numberFormat: new Intl.NumberFormat(), }; /** * Describes the starting point of the ticks. */ export enum TickOrigin { /** * Tick values represent distances to the grid's lower left corner */ Relative = 0, /** * Tick values represent coordinates in the CRS of the scene. */ Absolute = 1, } /** * Returns the padding to apply to a label that is located at the edge of the viewport, * according to its normalized device coordinates (NDC), to ensure that the label is fully * visible and not partially outside of the viewport. */ function getPaddingForAdaptiveLabel(ndc: Vector3, fontSize: number, text: string): string { const { x, y } = ndc; const yMargin = fontSize * 2; const xMargin = fontSize * 0.7; // per character // top right bottom left const top = y > 0.95 ? yMargin : 0; const bottom = y < -0.95 ? yMargin : 0; const charCount = text.length; const right = x > 0.95 ? xMargin * charCount : 0; const left = x < -0.95 ? xMargin * charCount : 0; return `${top}pt ${right}pt ${bottom}pt ${left}pt`; } class Side extends LineSegments { public readonly lines: Line3WithLabel[]; public logicalVisibility = false; public constructor( geometry: BufferGeometry, material: LineBasicMaterial, lines: Line3WithLabel[], ) { super(geometry, material); this.lines = lines; } } class Edge extends Group { public readonly isEdge = true as const; public readonly side1: Side; public readonly side2: Side; public constructor(side1: Side, side2: Side) { super(); this.side1 = side1; this.side2 = side2; } } function getCssColor(color: ColorRepresentation): string { return `#${new Color(color).getHexString()}`; } function createLabelElement( text: string, color: string, opacity: number, fontSize: number, ): { container: HTMLDivElement; label: HTMLSpanElement } { const container = document.createElement('div'); // Static properties container.style.textAlign = 'center'; // Dynamic properties const label = document.createElement('span'); label.innerText = text; label.style.paddingLeft = '5pt'; label.style.paddingRight = '5pt'; container.appendChild(label); // API exposed properties container.style.opacity = `${opacity}`; container.style.color = color; container.style.fontSize = `${fontSize}pt`; return { container, label }; } export interface AxisGridEventMap extends Entity3DEventMap { /** * Raised when a new label is created. */ 'label-created': { /** * The label DOM element. */ label: HTMLSpanElement; }; } /** * Constructor options for the {@link AxisGrid} entity. */ export interface AxisGridOptions extends Entity3DOptions { /** * The grid volume */ volume: Volume; /** * The origin of the ticks volume * @defaultValue {@link TickOrigin.Relative} */ origin?: TickOrigin; /** * The distance between grid lines. * @defaultValue 100 on each axis. */ ticks?: Ticks; /** * The style to apply to lines and labels. */ style?: Partial