@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
214 lines (210 loc) • 7.23 kB
JavaScript
/*
* 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;