UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

497 lines (423 loc) 14.7 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type { TypedArray } from 'three'; import { MathUtils, Vector2, Vector3, Vector4 } from 'three'; import { nonNull } from '../utils/tsutils'; export type Dimension = 2 | 3 | 4; const X = 0; const Y = 1; const Z = 2; const W = 3; export type Vector = Vector2 | Vector3 | Vector4; /** * A typed array of three.js {@link Vector}s. * * @param V - The underlying {@link Vector} type. * @param Buffer - The underlying {@link TypedArray} type. */ export abstract class VectorArray< V extends Vector = Vector, Buffer extends TypedArray = TypedArray, > { private readonly _dimension: Dimension; private _capacity: number; protected _array: Buffer; private _length: number; private _lastExpansion = 0; /** * The length in bytes of the array. */ public get byteLength(): number { return this.array.byteLength; } public get capacity(): number { return this.array.length / this._dimension; } /** * Gets the underlying {@link Buffer}. */ public get array(): Buffer { return this._array; } /** * Returns the {@link Float32Array} equivalent of this vector array. * Note: if the underlying array is already a Float32Array, this array is returned. * Otherwise, a new array is constructed. */ public toFloat32Array(): Float32Array { if (this._array instanceof Float32Array) { if (this._array.length === this._length * this._dimension) { // Return the same array as it is already in the correct format and size return this._array; } else { // Return a slice of the same array as it is already in the correct format return this._array.slice(0, this._length * this._dimension); } } // Create an intermediate array in the original format // TODO might be more efficient to allocate the array directly with the correct size, // then copy elements individually ? return new Float32Array(this._array.slice(0, this._length * this._dimension)); } protected constructor(buffer: Buffer, dimension: Dimension) { this._dimension = dimension; if (buffer.length % this._dimension !== 0) { throw new Error( `invalid size, expected a multiple of ${this._dimension}, got ${buffer.length}`, ); } this._array = buffer; this._capacity = this._array.length / this._dimension; this._length = this._capacity; } /** * Returns the number of vectors in this array. */ public get length(): number { return this._length; } public set length(v: number) { this._length = v; } /** * Gets the vector at the specified index. */ public abstract get(index: number, target?: V): V; public setX(index: number, value: number): void { const idx = index * this._dimension; this._array[idx + X] = value; } /** * Gets the first component of the vector at the specified index. */ public getX(index: number): number { const idx = index * this._dimension; return this._array[idx + X]; } public setY(index: number, value: number): void { const idx = index * this._dimension; this._array[idx + Y] = value; } /** * Gets the second component of the vector at the specified index. */ public getY(index: number): number { const idx = index * this._dimension; return this._array[idx + Y]; } public setZ(index: number, value: number): void { if (this._dimension >= 3) { const idx = index * this._dimension; this._array[idx + Z] = value; } } /** * Gets the third component of the vector at the specified index, or `null` if the dimension * of this array is less than 3. */ public getZ(index: number): number | null { if (this._dimension >= 3) { const idx = index * this._dimension; return this._array[idx + Z]; } return null; } public setW(index: number, value: number): void { if (this._dimension >= 4) { const idx = index * this._dimension; this._array[idx + W] = value; } } /** * Gets the fourth component of the vector at the specified index, or `null` if the dimension * of this array is less than 4. */ public getW(index: number): number | null { if (this._dimension >= 4) { const idx = index * this._dimension; return this._array[idx + W]; } return null; } /** * Sets the vector at the specified index. */ public setVector(index: number, v: V): void { const idx = index * this._dimension; this._array[idx + X] = v.getComponent(X); this._array[idx + Y] = v.getComponent(Y); if (this._dimension >= 3) { this._array[idx + Z] = v.getComponent(Z); } if (this._dimension >= 4) { this._array[idx + W] = v.getComponent(W); } } /** * Sets the component of the array at the specified index. */ public set(index: number, x: number, y: number, z?: number, w?: number): void { const idx = index * this._dimension; if (idx >= this._array.length) { throw new Error('index out of bounds'); } this._array[idx + X] = x; this._array[idx + Y] = y; if (this._dimension >= Z && z != null) { this._array[idx + Z] = z; } if (this._dimension >= W && w != null) { this._array[idx + W] = w; } } /** * Copies an element from one location to another in the array. */ public copyItem(from: number, to: number): void { const dim = this._dimension; const toIdx = to * dim; const fromIdx = from * dim; this._array[toIdx + X] = this._array[fromIdx + X]; this._array[toIdx + Y] = this._array[fromIdx + Y]; if (dim >= 3) { this._array[toIdx + Z] = this._array[fromIdx + Z]; } if (dim >= 4) { this._array[toIdx + W] = this._array[fromIdx + W]; } } private computeExpansionSize(): number { if (this._lastExpansion === 0) { this._lastExpansion = 32; } else { this._lastExpansion *= 2; this._lastExpansion = MathUtils.clamp(this._lastExpansion, 32, 65536); } return this._lastExpansion; } /** * Removes unused capacity. */ public trim(): void { if (this._capacity > this._length) { this._capacity = this._length; this._array = this._array.slice(0, this._length * this._dimension) as Buffer; } } /** * Adds capacity at the end of the array using a growing expansion size (i.e the first time * this method is called, a small amount is added, and the amount grows every time this method * is called up to a certain point). * Contrary to {@link expand}, the length is left untouched (the expanded area is not considered * used.) */ private allocateIfFull(): void { if (this._capacity === this._length) { const currentLength = this._length; this.expand(this._length + this.computeExpansionSize()); this._capacity = this._array.length / this._dimension; this._length = currentLength; } } /** * Pushes a vector at the end of the array, allocating memory if necessary. */ public push(x: number, y: number, z?: number, w?: number): void { if (this._capacity === this._length) { this.allocateIfFull(); } this.setX(this._length, x); this.setY(this._length, y); if (z != null) { this.setZ(this._length, z); } if (w != null) { this.setW(this._length, w); } this._length++; } /** * Pushes a vector at the end of the array, allocating memory if necessary. */ public pushVector(v: V): void { if (this._capacity === this._length) { this.allocateIfFull(); } this.assignVector(this._length * this._dimension, v); this._length++; } /** * Allocates a new underlying array to match the new size, then copy the content * of the previous array at the beginning of the new array. * @param newSize - The new size, in number of vectors. */ public expand(newSize: number): this { // @ts-expect-error "this expression is not constructable" const newArray = new this.array.constructor(newSize * this._dimension); newArray.set(this._array); this._array = newArray; this._capacity = this._array.length / this._dimension; this._length = this._capacity; return this; } /** @internal */ protected abstract getTempVector(): V; /** @internal */ protected abstract readVector(rawIndex: number, tempVector: V): void; /** @internal */ protected abstract assignVector(rawIndex: number, v: Readonly<V>): void; /** * Performs the specified action for each element in an array. * * Note that mutating the callback value will **not** mutate the underlying array. To mutate the * underlying array, use the index provided as second argument, then mutate the array with a * mutating method, such as {@link setVector}: * ```ts * const array = new Vector3Array(...); * * // Add one to each Y value of the array * array.forEach((v, index) => { * // This has no effect on the Vector3Array: * v.setY(v.y + 1); * * // Use this pattern instead: * array.setVector(index, new Vector3(v.x, v.y + 1, v.z)); * * // Or this one * array.setY(index, v.y + 1); * }) * ``` * @param callbackfn - A function that accepts up to three arguments. forEach calls the * callbackfn function one time for each element in the array. */ public forEach(callbackfn: (value: Readonly<V>, index: number, array: this) => void): void { const value = this.getTempVector(); const stride = this._dimension; // Raw index is the index to the first component of each vector, not the vector itself for (let rawIndex = 0; rawIndex < this._array.length; rawIndex += stride) { this.readVector(rawIndex, value); const vectorIndex = rawIndex / stride; callbackfn(value, vectorIndex, this); } } /** * Clones this array. */ public abstract clone(): ThisType<this>; } /** * A typed array of three.js {@link Vector2}s. * * @param Buffer - The underlying {@link TypedArray} type. */ export class Vector2Array<Buffer extends TypedArray = TypedArray> extends VectorArray< Vector2, Buffer > { public readonly dimension = 2 as const; public constructor(buffer: Buffer) { super(buffer, 2); } public override get(index: number, target?: Vector2): Vector2 { target = target ?? new Vector2(); return target.set(this.getX(index), this.getY(index)); } public clone(): Vector2Array { return new Vector2Array(this._array.slice(0)); } protected getTempVector(): Vector2 { return new Vector2(); } protected readVector(rawIndex: number, tempVector: Vector2): void { const arr = this._array; tempVector.set(arr[rawIndex + X], arr[rawIndex + Y]); } protected assignVector(rawIndex: number, v: Readonly<Vector2>): void { this._array[rawIndex + X] = v.x; this._array[rawIndex + Y] = v.y; } } /** * A typed array of three.js {@link Vector3}s. * * @param Buffer - The underlying {@link TypedArray} type. */ export class Vector3Array<Buffer extends TypedArray = TypedArray> extends VectorArray< Vector3, Buffer > { public readonly dimension = 3 as const; public constructor(buffer: Buffer) { super(buffer, 3); } public override get(index: number, target?: Vector3): Vector3 { target = target ?? new Vector3(); return target.set(this.getX(index), this.getY(index), nonNull(this.getZ(index))); } public clone(): Vector3Array { return new Vector3Array(this._array.slice(0)); } public override getZ(index: number): number { return super.getZ(index) as number; } protected getTempVector(): Vector3 { return new Vector3(); } protected readVector(rawIndex: number, tempVector: Vector3): void { const arr = this._array; tempVector.set(arr[rawIndex + X], arr[rawIndex + Y], arr[rawIndex + Z]); } protected assignVector(rawIndex: number, v: Readonly<Vector3>): void { this._array[rawIndex + X] = v.x; this._array[rawIndex + Y] = v.y; this._array[rawIndex + Z] = v.z; } } /** * A typed array of three.js {@link Vector4}s. * * @param Buffer - The underlying {@link TypedArray} type. */ export class Vector4Array<Buffer extends TypedArray = TypedArray> extends VectorArray< Vector4, Buffer > { public readonly dimension = 4 as const; public constructor(buffer: Buffer) { super(buffer, 4); } public override get(index: number, target?: Vector4): Vector4 { target = target ?? new Vector4(); return target.set( this.getX(index), this.getY(index), nonNull(this.getZ(index)), nonNull(this.getW(index)), ); } public override getZ(index: number): number { return super.getZ(index) as number; } public override getW(index: number): number { return super.getW(index) as number; } public clone(): Vector4Array { return new Vector4Array(this._array.slice(0)); } protected getTempVector(): Vector4 { return new Vector4(); } protected readVector(rawIndex: number, tempVector: Vector4): void { const arr = this._array; tempVector.set(arr[rawIndex + X], arr[rawIndex + Y], arr[rawIndex + Z], arr[rawIndex + W]); } protected assignVector(rawIndex: number, v: Readonly<Vector4>): void { this._array[rawIndex + X] = v.x; this._array[rawIndex + Y] = v.y; this._array[rawIndex + Z] = v.z; this._array[rawIndex + W] = v.w; } }