UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

214 lines (210 loc) 7.23 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { ArrowHelper, AxesHelper, BufferAttribute, BufferGeometry, Color, LineSegments, MeshBasicMaterial, Object3D, Vector3 } from 'three'; import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import Ellipsoid from '../core/geographic/Ellipsoid'; import { Vector3Array } from '../core/VectorArray'; const tmp = { a: new Vector3(), b: new Vector3() }; function createParallel(ellipsoid, latitude, segments, positions, colors, color) { const step = 360 / segments; let longitude = 0; const r = Math.round(color.r * 255); const g = Math.round(color.g * 255); const b = Math.round(color.b * 255); for (let i = 0; i <= segments; i++) { const v0 = ellipsoid.toCartesian(latitude, longitude, 0, tmp.a); const v1 = ellipsoid.toCartesian(latitude, longitude + step, 0, tmp.b); longitude += step; positions.pushVector(v0); positions.pushVector(v1); colors.push(r, g, b); colors.push(r, g, b); } } function createMeridian(ellipsoid, longitude, segments, target, colors, color) { const step = 360 / segments; let latitude = -90; const r = Math.round(color.r * 255); const g = Math.round(color.g * 255); const b = Math.round(color.b * 255); for (let i = 0; i <= segments / 2; i++) { const v0 = ellipsoid.toCartesian(latitude, longitude, 0, tmp.a); const v1 = ellipsoid.toCartesian(latitude + step, longitude, 0, tmp.b); latitude += step; target.pushVector(v0); target.pushVector(v1); colors.push(r, g, b); colors.push(r, g, b); } } function createLabel(text, color) { const div = document.createElement('div'); div.style.textAlign = 'center'; div.style.verticalAlign = 'middle'; div.style.textShadow = 'black 0 0 3px'; div.style.fontWeight = 'bold'; div.style.color = '#' + new Color(color).getHexString(); div.innerText = text; const label = new CSS2DObject(div); label.name = text; return label; } /** * Displays an ellipsoid along with its axes. */ export class EllipsoidHelper extends Object3D { isEllipsoidHelper = true; type = 'EllipsoidHelper'; _arrows = []; _showNormals = false; _disposed = false; /** * The color of the lines. */ get color() { return this._mesh.material.color; } set color(c) { this._mesh.material.color = c; } get showLines() { return this._mesh.visible; } set showLines(show) { this._mesh.visible = show; } get showAxes() { return this._axes.visible; } set showAxes(show) { this._axes.visible = show; } get showNormals() { return this._showNormals; } set showNormals(show) { if (this._showNormals !== show) { this._showNormals = show; if (show) { this.createNormalArrows(); } else { this.deleteNormalArrows(); } } } get showLabels() { return this._labels[0].visible; } set showLabels(show) { this._labels.forEach(l => l.visible = show); } constructor(params) { super(); this.ellipsoid = params?.ellipsoid ?? Ellipsoid.WGS84; const meridianCount = params?.meridians ?? 24; const segments = params?.segments ?? 32; const parallelCount = params?.parallels ?? 5; const mainColor = params?.lineColor != null ? new Color(params?.lineColor) : new Color('grey'); const primeMeridianColor = params?.primeMeridianColor != null ? new Color(params.primeMeridianColor) : new Color('#75b1c7'); if (parallelCount % 2 === 0) { throw new Error(`parallels must be an odd number, got: ${parallelCount}`); } const vectors = new Vector3Array(new Float32Array(3000)); vectors.length = 0; const colors = new Vector3Array(new Uint8ClampedArray(3000)); colors.length = 0; // equator if (parallelCount > 0) { const equatorColor = params?.equatorColor != null ? new Color(params.equatorColor) : new Color('#ff4f93'); createParallel(this.ellipsoid, 0, segments, vectors, colors, equatorColor); } const parallelsPerHemisphere = (parallelCount - 1) / 2; const latitudeStep = 90 / (parallelsPerHemisphere + 1); let latitude = latitudeStep; for (let index = 0; index < parallelsPerHemisphere; index++) { createParallel(this.ellipsoid, +latitude, segments, vectors, colors, mainColor); createParallel(this.ellipsoid, -latitude, segments, vectors, colors, mainColor); latitude += latitudeStep; } let longitude = 0; for (let index = 0; index < meridianCount; index++) { const color = index === 0 ? primeMeridianColor : mainColor; createMeridian(this.ellipsoid, longitude, segments, vectors, colors, color); longitude += 360 / meridianCount; } const positionBuffer = new BufferAttribute(vectors.toFloat32Array(), 3); colors.trim(); const colorBuffer = new BufferAttribute(colors.array, 3, true); const geometry = new BufferGeometry(); geometry.setAttribute('position', positionBuffer); geometry.setAttribute('color', colorBuffer); this._mesh = new LineSegments(geometry, new MeshBasicMaterial({ vertexColors: true })); this._mesh.name = 'lines'; this._axes = new AxesHelper(this.ellipsoid.semiMajorAxis * 1.3); this._axes.name = 'axes'; this._axes.scale.set(1, 1, this.ellipsoid.compressionFactor); const xLabel = createLabel('+X', new Color(1, 0.2, 0)); const yLabel = createLabel('+Y', new Color(0.2, 1, 0)); const zLabel = createLabel('+Z', new Color(0, 0.2, 1)); zLabel.position.copy(this.ellipsoid.toCartesian(+90, 0, this.ellipsoid.semiMinorAxis * 0.4)); yLabel.position.copy(this.ellipsoid.toCartesian(0, +90, this.ellipsoid.semiMajorAxis * 0.4)); xLabel.position.copy(this.ellipsoid.toCartesian(0, 0, this.ellipsoid.semiMajorAxis * 0.4)); this.add(xLabel); this.add(yLabel); this.add(zLabel); this._labels = [xLabel, yLabel, zLabel]; this.add(this._mesh); this.add(this._axes); this.updateMatrixWorld(true); } deleteNormalArrows() { if (this._arrows.length > 0) { this._arrows.forEach(arrow => { arrow.dispose(); arrow.removeFromParent(); }); this._arrows.length = 0; } } createNormalArrows() { const normal = new Vector3(); for (let i = 0; i < 10; i++) { for (let j = 0; j < 20; j++) { const lat = i * 18 - 90; const lon = j * 18 - 180; const origin = this.ellipsoid.toCartesian(lat, lon, 0); this.ellipsoid.getNormal(lat, lon, normal); const arrow = new ArrowHelper(normal, origin, this.ellipsoid.semiMajorAxis * 0.3, 'yellow'); this.add(arrow); arrow.updateMatrixWorld(true); this._arrows.push(arrow); } } } dispose() { if (this._disposed) { return; } this._disposed = true; this._mesh.geometry.dispose(); this._mesh.material.dispose(); this._axes.dispose(); this._labels.forEach(l => l.element.remove); this._labels.length = 0; this._arrows.forEach(a => { a.dispose(); a.removeFromParent(); }); this._arrows.length = 0; this.clear(); } } export default EllipsoidHelper;