entity/polyline/PolylineBatchRenderer.tstypescript
import { Entity } from "../Entity";
import { Extent } from "../../Extent";
import { LonLat } from "../../LonLat";
import { Vec3 } from "../../math/Vec3";
import type { NumberArray3 } from "../../math/Vec3";
import type { NumberArray4 } from "../../math/Vec4";
import type { Planet } from "../../scene/Planet";
import { PolylineHandler } from "./PolylineHandler";
import { Scene } from "../../scene/Scene";
import type { WebGLBufferExt } from "../../webgl/Handler";
import {
cloneArray,
createVec3,
htmlColorToFloat32Array,
htmlColorToRgba,
makeArray,
makeArrayTyped,
insertTypedArray,
spliceTypedArray
} from "../../utils/shared";
import type { TypedArray } from "../../utils/shared";
import { srgbToLinear } from "../../utils/colorSpace";
import { Ellipsoid } from "../../ellipsoid/Ellipsoid";
import type { HTMLImageElementExt } from "../../utils/ImagesCacheManager";
import type {
Geodetic,
Cartesian,
SegmentPath3vExt,
SegmentPathLonLatExt,
SegmentPathColor,
SegmentPath3v,
SegmentPathLonLat
} from "./Polyline";
const VERTICES_BUFFER = 0;
const INDEX_BUFFER = 1;
const COLORS_BUFFER = 2;
const TEXCOORD_BUFFER = 3;
const THICKNESS_BUFFER = 4;
const TEXPARAM_BUFFER = 5;
const PICKINGCOLORS_BUFFER = 6;
const PATHPHASE_BUFFER = 7;
const BOUNDING_SPHERE_BUFFER = 8;
const ANIMATION_TIME_WRAP_SEC = 59.0;
const DEFAULT_COLOR = "#ffffff";
const DEFAULT_STROKE_TEXTURE_DATA_URL =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+W7Y4AAAAASUVORK5CYII=";
const F3V = 0;
const FLONLAT = 1;
const FDETECT = 2;
const R = 0;
const G = 1;
const B = 2;
const A = 3;
export interface TexParam {
texOffset: number;
strokeSize: number;
texOffsetSpeed: number;
}
type IndexFormatMode = typeof F3V | typeof FLONLAT | typeof FDETECT;
type LineSourceMode = typeof F3V | typeof FLONLAT;
type StrokeSource = string | HTMLImageElement | null;
const toVec3 = (point: Cartesian): Vec3 => {
if (point instanceof Array) {
return new Vec3(point[0], point[1], point[2]);
}
return point as Vec3;
};
const toLonLat = (point: Geodetic): LonLat => {
if (point instanceof Array) {
return new LonLat(point[0], point[1], point[2]);
}
return point as LonLat;
};
const createMirroredPoint = (p0: Vec3, p1: Vec3): Vec3 => {
return new Vec3(p0.x + p0.x - p1.x, p0.y + p0.y - p1.y, p0.z + p0.z - p1.z);
};
const writeQuadColor = (target: TypedArray | number[], offset: number, color: NumberArray4, opacity: number = 1.0) => {
const r = srgbToLinear(color[R]);
const g = srgbToLinear(color[G]);
const b = srgbToLinear(color[B]);
const a = (color[A] != undefined ? color[A] : 1.0) * opacity;
target[offset] = target[offset + 4] = target[offset + 8] = target[offset + 12] = r;
target[offset + 1] = target[offset + 5] = target[offset + 9] = target[offset + 13] = g;
target[offset + 2] = target[offset + 6] = target[offset + 10] = target[offset + 14] = b;
target[offset + 3] = target[offset + 7] = target[offset + 11] = target[offset + 15] = a;
};
const pushQuadColor = (outColors: number[], color: NumberArray4, opacity: number) => {
const r = srgbToLinear(color[R]);
const g = srgbToLinear(color[G]);
const b = srgbToLinear(color[B]);
const a = (color[A] != undefined ? color[A] : 1.0) * opacity;
outColors.push(r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a);
};
const pushQuadPicking = (outPickingColors: number[], pickingColor: NumberArray3 | NumberArray4) => {
const pr = pickingColor[R],
pg = pickingColor[G],
pb = pickingColor[B];
outPickingColors.push(pr, pg, pb, pr, pg, pb, pr, pg, pb, pr, pg, pb);
};
const pushQuadThickness = (outThickness: number[], thickness: number) => {
outThickness.push(thickness, thickness, thickness, thickness);
};
const pushQuadTexParams = (outTexParams: number[], texParam: TexParam) => {
outTexParams.push(
texParam.texOffset,
texParam.strokeSize,
texParam.texOffsetSpeed,
texParam.texOffset,
texParam.strokeSize,
texParam.texOffsetSpeed,
texParam.texOffset,
texParam.strokeSize,
texParam.texOffsetSpeed,
texParam.texOffset,
texParam.strokeSize,
texParam.texOffsetSpeed
);
};
const pushQuadBoundingSphere = (outBoundingSphere: number[], x: number, y: number, z: number, r: number) => {
outBoundingSphere.push(x, y, z, r, x, y, z, r, x, y, z, r, x, y, z, r);
};
const pushQuadOrders = (outOrders: number[]) => {
outOrders.push(1, -1, 2, -2);
};
const pushQuadTexCoords = (outTexCoords: number[], atlas: number[] | null, skipTexCoords: boolean = false) => {
if (atlas && atlas.length >= 10) {
const minY = atlas[1];
const imgHeight = atlas[3] - minY;
const t0x = atlas[4],
t0y = atlas[5];
const t1x = atlas[2],
t1y = atlas[3];
const t2x = atlas[8],
t2y = atlas[9];
const t3x = atlas[0],
t3y = atlas[1];
outTexCoords.push(
t0x,
t0y,
minY,
imgHeight,
t1x,
t1y,
minY,
imgHeight,
t2x,
t2y,
minY,
imgHeight,
t3x,
t3y,
minY,
imgHeight
);
return;
}
if (skipTexCoords) {
outTexCoords.push(0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0);
return;
}
outTexCoords.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
};
const initLineIndexes = (outIndexes: number[], indexFormat: IndexFormatMode): number => {
const is3vMode =
indexFormat === FDETECT
? outIndexes.length > 1 && outIndexes[0] === 0 && outIndexes[1] === 0
: indexFormat === F3V;
let index = 0;
if (outIndexes.length > 0) {
if (outIndexes.length < 5) {
if (is3vMode) {
outIndexes.length = 2;
outIndexes[0] = 0;
outIndexes[1] = 0;
} else {
outIndexes.length = 1;
outIndexes[0] = 0;
}
return 0;
}
index = outIndexes[outIndexes.length - 5] + 9;
outIndexes.push(index);
if (is3vMode) {
outIndexes.push(index);
}
} else {
outIndexes.push(0);
if (is3vMode) {
outIndexes.push(0);
}
}
return index;
};
const resetExtentBounds = (extent: Extent) => {
extent.southWest.set(180.0, 90.0);
extent.northEast.set(-180.0, -90.0);
};
const extendExtent = (extent: Extent, lonLat: LonLat) => {
if (lonLat.lon < extent.southWest.lon) {
extent.southWest.lon = lonLat.lon;
}
if (lonLat.lat < extent.southWest.lat) {
extent.southWest.lat = lonLat.lat;
}
if (lonLat.lon > extent.northEast.lon) {
extent.northEast.lon = lonLat.lon;
}
if (lonLat.lat > extent.northEast.lat) {
extent.northEast.lat = lonLat.lat;
}
};
export interface IPolylineBatchRendererParams {
altitude?: number;
thickness?: number;
opacity?: number;
color?: string[];
visibility?: boolean;
isTextured?: boolean;
isClosed?: boolean[];
pathColors?: SegmentPathColor[];
path3v?: SegmentPath3vExt[];
pathLonLat?: SegmentPathLonLatExt[];
visibleSpherePosition?: Cartesian;
visibleSphereRadius?: number;
src?: StrokeSource[];
texParams?: Partial<TexParam>[];
}
class PolylineBatchRenderer {
static __counter__: number = 0;
protected static _defaultStrokeImage: HTMLImageElementExt | null = null;
/**
* Object uniq identifier.
* @protected
* @type {number}
*/
protected __id: number;
public altitude: number;
/**
* Polyline thickness in screen pixels.
* @type {number}
*/
protected _thickness: number;
protected _opacity: number;
/**
* Polyline RGBA color.
* @protected
* @type {Float32Array}
*/
protected _defaultColor: Float32Array | NumberArray4;
protected _pickingColor: Float32Array | NumberArray4;
/**
* Polyline visibility.
* @public
* @type {boolean}
*/
public visibility: boolean;
public isTextured: boolean;
/**
* Polyline geometry ring type identification.
* @protected
* @type {Boolean}
*/
protected _pathClosed: boolean[];
/**
* Polyline cartesian coordinates.
* @public
* @type {Array.<Vec3>}
*/
public _path3v: SegmentPath3vExt[];
protected _pathLengths: number[];
/**
* Polyline geodetic degrees coordinates.
* @private
* @type {Array.<LonLat>}
*/
public _pathLonLat: SegmentPathLonLatExt[];
/**
* Polyline geodetic mercator coordinates.
* @public
* @type {Array.<LonLat>}
*/
public _pathLonLatMerc: LonLat[][];
protected _pathColors: SegmentPathColor[];
protected _segmentOpacity: number[];
protected _segmentThickness: number[];
protected _segmentTexParams: (TexParam | undefined)[];
protected _pathPickingColors: NumberArray3[][];
/**
* Polyline geodetic extent.
* @protected
* @type {Extent}
*/
public _extent: Extent;
protected _verticesHigh: TypedArray | number[];
protected _verticesLow: TypedArray | number[];
protected _orders: TypedArray | number[];
protected _indexes: TypedArray | number[];
protected _colors: TypedArray | number[];
protected _thicknessArr: TypedArray | number[];
protected _pathTexParamArr: TypedArray | number[];
protected _pathPhaseArr: TypedArray | number[];
protected _boundingSphereArr: TypedArray | number[];
protected _texCoordArr: TypedArray | number[];
protected _pickingColors: TypedArray | number[];
protected _verticesHighBuffer: WebGLBufferExt | null;
protected _verticesLowBuffer: WebGLBufferExt | null;
protected _ordersBuffer: WebGLBufferExt | null;
protected _indexesBuffer: WebGLBufferExt | null;
protected _colorsBuffer: WebGLBufferExt | null;
protected _texCoordBuffer: WebGLBufferExt | null;
protected _thicknessBuffer: WebGLBufferExt | null;
protected _pathTexParamBuffer: WebGLBufferExt | null;
protected _pathPhaseBuffer: WebGLBufferExt | null;
protected _boundingSphereBuffer: WebGLBufferExt | null;
protected _pickingColorsBuffer: WebGLBufferExt | null;
protected _scene: Scene | null;
/**
* Entity instance that holds this Polyline.
* @public
* @type {Entity}
*/
public _entity: Entity | null;
/**
* Handler that stores and renders this Polyline object.
* @public
* @type {PolylineHandler | null}
*/
public _handler: PolylineHandler | null;
//public _handlerIndex: number;
protected _buffersUpdateCallbacks: Function[];
protected _changedBuffers: boolean[];
protected _visibleSphere: Float32Array;
protected _visibleSpherePosition: Vec3;
public __doubleToTwoFloats: (pos: Vec3, highPos: Vec3, lowPos: Vec3) => void;
protected _src: StrokeSource[];
protected _image: ((HTMLImageElement & { __nodeIndex?: number }) | null)[];
protected _defaultTexParam: TexParam;
constructor(handler: PolylineHandler, options: IPolylineBatchRendererParams = {}) {
this.__id = PolylineBatchRenderer.__counter__++;
this.__doubleToTwoFloats = Vec3.doubleToTwoFloats;
this.altitude = options.altitude || 0.0;
this._thickness = options.thickness || 1.5;
this._opacity = options.opacity != undefined ? options.opacity : 1.0;
this._defaultColor = htmlColorToFloat32Array(options.color?.[0] || DEFAULT_COLOR, options.opacity);
this._pickingColor = new Float32Array([0, 0, 0]);
this.visibility = options.visibility != undefined ? options.visibility : true;
this.isTextured =
options.isTextured != undefined ? !!options.isTextured : (options.src ?? []).some((s) => s != null);
this._pathClosed = this._normalizePathClosedInput(options.isClosed);
this._path3v = [];
this._pathLengths = [];
this._pathLonLat = [];
this._pathLonLatMerc = [];
this._pathColors = options.pathColors
? cloneArray(options.pathColors)
: this._normalizePathColorInput(options.color);
this._segmentOpacity = [];
this._segmentThickness = [];
this._segmentTexParams =
this.isTextured && options.texParams
? options.texParams.map((p) => ({
texOffset: p?.texOffset ?? 0,
strokeSize: p?.strokeSize ?? 32,
texOffsetSpeed: p?.texOffsetSpeed ?? 0
}))
: [];
this._pathPickingColors = [];
this._extent = new Extent();
this._verticesHigh = [];
this._verticesLow = [];
this._orders = [];
this._indexes = [];
this._colors = [];
this._thicknessArr = [];
this._pathTexParamArr = [];
this._pathPhaseArr = [];
this._boundingSphereArr = [];
this._texCoordArr = [];
this._pickingColors = [];
this._verticesHighBuffer = null;
this._verticesLowBuffer = null;
this._ordersBuffer = null;
this._indexesBuffer = null;
this._colorsBuffer = null;
this._texCoordBuffer = null;
this._thicknessBuffer = null;
this._pathTexParamBuffer = null;
this._pathPhaseBuffer = null;
this._boundingSphereBuffer = null;
this._pickingColorsBuffer = null;
this._scene = null;
this._entity = null;
this._handler = handler;
//this._handlerIndex = -1;
this._image = [];
this._src = this.isTextured ? (options.src ?? []).slice() : [];
this._defaultTexParam = {
texOffset: 0,
strokeSize: 32,
texOffsetSpeed: 0
};
this._buffersUpdateCallbacks = [];
this._buffersUpdateCallbacks[VERTICES_BUFFER] = this._createVerticesBuffer;
this._buffersUpdateCallbacks[INDEX_BUFFER] = this._createIndexBuffer;
this._buffersUpdateCallbacks[COLORS_BUFFER] = this._createColorsBuffer;
this._buffersUpdateCallbacks[TEXCOORD_BUFFER] = this._createTexCoordBuffer;
this._buffersUpdateCallbacks[THICKNESS_BUFFER] = this._createThicknessBuffer;
this._buffersUpdateCallbacks[TEXPARAM_BUFFER] = this._createTexParamsBuffer;
this._buffersUpdateCallbacks[PICKINGCOLORS_BUFFER] = this._createPickingColorsBuffer;
this._buffersUpdateCallbacks[PATHPHASE_BUFFER] = this._createPathPhaseBuffer;
this._buffersUpdateCallbacks[BOUNDING_SPHERE_BUFFER] = this._createBoundingSphereBuffer;
this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);
this._visibleSpherePosition = createVec3(options.visibleSpherePosition);
const r = options.visibleSphereRadius || 0;
this._visibleSphere = new Float32Array([0, 0, 0, r]);
this._updateVisibleSphereRTC();
// create path
if (options.pathLonLat) {
this.setPathLonLat(options.pathLonLat);
} else if (options.path3v) {
this.setPath3v(options.path3v);
}
this._refresh();
}
protected _normalizePathClosedInput(isClosed?: boolean[]): boolean[] {
return (isClosed ?? []).map((v) => !!v);
}
protected _normalizePathColorInput(color?: string[]): SegmentPathColor[] {
return (color ?? []).map((htmlColor) => {
const rgba = htmlColorToRgba(htmlColor, this._opacity);
return [[rgba.x, rgba.y, rgba.z, rgba.w]];
});
}
protected _getDefaultStrokeSource(): StrokeSource {
if (PolylineBatchRenderer._defaultStrokeImage) {
return PolylineBatchRenderer._defaultStrokeImage;
}
if (typeof Image === "undefined") {
return DEFAULT_STROKE_TEXTURE_DATA_URL;
}
const img = new Image() as HTMLImageElementExt;
if (typeof document !== "undefined" && typeof document.createElement === "function") {
const canvas = document.createElement("canvas");
canvas.width = 2;
canvas.height = 2;
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, 2, 2);
img.src = canvas.toDataURL("image/png");
} else {
img.src = DEFAULT_STROKE_TEXTURE_DATA_URL;
}
} else {
img.src = DEFAULT_STROKE_TEXTURE_DATA_URL;
}
PolylineBatchRenderer._defaultStrokeImage = img;
return img;
}
protected _syncPathClosedLength(segCount: number) {
if (segCount < 0) {
segCount = 0;
}
if (this._pathClosed.length > segCount) {
this._pathClosed.length = segCount;
return;
}
while (this._pathClosed.length < segCount) {
this._pathClosed.push(false);
}
}
protected _syncSrcLength(segCount: number) {
if (!this.isTextured) {
this._src.length = 0;
this._image.length = 0;
return;
}
if (segCount < 0) {
segCount = 0;
}
if (this._src.length > segCount) {
this._src.length = segCount;
}
if (this._image.length > segCount) {
this._image.length = segCount;
}
const defaultSrc = this._getDefaultStrokeSource();
while (this._src.length < segCount) {
this._src.push(defaultSrc);
}
for (let i = 0; i < segCount; i++) {
if (this._src[i] == null) {
this._src[i] = defaultSrc;
}
}
while (this._image.length < segCount) {
this._image.push(null);
}
}
protected _isSegmentClosed(segmentIndex: number): boolean {
return this._pathClosed[segmentIndex];
}
protected _hasTexture(segmentIndex: number): boolean {
return this.isTextured && this._src[segmentIndex] != null;
}
protected _hasAnyTextureSegments(): boolean {
if (!this.isTextured) {
return false;
}
const segCount = Math.max(this._path3v.length, this._src.length);
for (let i = 0; i < segCount; i++) {
const path = this._path3v[i] as Vec3[] | undefined;
if (path && path.length > 0 && this._hasTexture(i)) {
return true;
}
}
return false;
}
public setImage(image: HTMLImageElement) {
this.setPathSrc(image, 0);
}
public getImage(): (HTMLImageElementExt | null)[] {
return this._image;
}
protected _setSrcPerSegment(src: StrokeSource[], segCount: number, scene: Scene, textureAtlas: any) {
const pending = new Map<Exclude<StrokeSource, null>, number[]>();
for (let j = 0; j < segCount; j++) {
const segmentSrc = src[j];
if (segmentSrc == null) continue;
if (!pending.has(segmentSrc)) {
pending.set(segmentSrc, []);
}
pending.get(segmentSrc)!.push(j);
}
const segTexCoords: (number[] | null)[] = new Array(segCount).fill(null);
const segImages: (HTMLImageElementExt | null)[] = new Array(segCount).fill(null);
let loaded = 0;
const needed = pending.size;
const onLoaded = () => {
loaded++;
if (loaded === needed) {
this._image = segImages;
this._setTexCoordArr(segTexCoords);
this._updateAllTextureMetrics();
scene.updateStrokeTexCoords();
}
};
pending.forEach((segIndices, source) => {
const onImageReady = (img: HTMLImageElementExt) => {
let atlasData = img.__nodeIndex != undefined ? textureAtlas.get(img.__nodeIndex) : undefined;
if (!atlasData) {
textureAtlas.addImage(img);
textureAtlas.createTexture();
atlasData = img.__nodeIndex != undefined ? textureAtlas.get(img.__nodeIndex) : undefined;
}
for (const j of segIndices) {
segTexCoords[j] = atlasData?.texCoords ?? null;
segImages[j] = img;
}
onLoaded();
};
if (typeof source === "string") {
textureAtlas.loadImage(source, onImageReady);
return;
}
const img = source as HTMLImageElementExt;
if (img.width && img.height) {
onImageReady(img);
} else {
img.addEventListener("load", () => onImageReady(img), { once: true });
}
});
}
protected _setSrcDisabled(segCount: number, scene: Scene) {
this._image = new Array(segCount).fill(null);
const empty: (number[] | null)[] = new Array(segCount).fill(null);
this._setTexCoordArr(empty);
this._updateAllTextureMetrics();
scene.updateStrokeTexCoords();
}
protected _setTextureEnabled(segCount: number) {
if (this._defaultTexParam.strokeSize <= 0) {
this._defaultTexParam.strokeSize = 32;
}
for (let i = 0; i < segCount; i++) {
const texParams = this._resolveSegmentTexParams(i);
if (texParams.strokeSize <= 0) {
this.setPathTexParams(undefined, this._defaultTexParam.strokeSize, i);
}
}
}
/**
* Sets stroke source per segment (null = color-only).
* @public
*/
public setSrc(src: StrokeSource[]) {
if (!this.isTextured) {
return;
}
const segCount = Math.max(this._path3v.length, this._pathLonLat.length, src.length);
const defaultSrc = this._getDefaultStrokeSource();
const normalizedSrc: StrokeSource[] = new Array(segCount).fill(null);
for (let i = 0; i < segCount; i++) {
normalizedSrc[i] = src[i] ?? defaultSrc;
}
this._src = normalizedSrc;
this._syncSrcLength(segCount);
const bh = this._handler;
if (!bh) {
return;
}
const rn = bh._entityCollection.scene;
if (!rn || !rn.renderer) {
return;
}
const ta = rn.renderer.strokeTextureAtlas;
const hasTexture = normalizedSrc.some((s) => s !== null);
if (!hasTexture) {
this._setSrcDisabled(segCount, rn);
return;
}
this._setTextureEnabled(segCount);
this._setSrcPerSegment(normalizedSrc, segCount, rn, ta);
}
/**
* Sets stroke source (string or `Image`) for a segment index.
* @public
*/
public setPathSrc(src: StrokeSource, segmentIndex: number = 0) {
if (!this.isTextured) {
return;
}
const segCount = Math.max(this._path3v.length, this._pathLonLat.length, segmentIndex + 1);
const perSegmentSrc: StrokeSource[] = new Array(segCount).fill(null);
for (let i = 0; i < segCount; i++) {
perSegmentSrc[i] = this._src[i] ?? null;
}
perSegmentSrc[segmentIndex] = src;
this.setSrc(perSegmentSrc);
}
/**
* Sets closed/open state for one path segment.
* @public
*/
public setPathClosed(isClosed: boolean, segmentIndex: number = 0) {
if (!Number.isInteger(segmentIndex) || segmentIndex < 0) {
return;
}
const segCount = Math.max(this._path3v.length, this._pathLonLat.length, segmentIndex + 1);
this._syncPathClosedLength(segCount);
if (this._pathClosed[segmentIndex] === isClosed) {
return;
}
this._pathClosed[segmentIndex] = isClosed;
if (!this._scene) {
return;
}
const path3v = this._path3v[segmentIndex] as SegmentPath3v | undefined;
if (!path3v || path3v.length === 0) {
return;
}
if (path3v.length >= 2) {
const ellipsoid = (this._scene as Planet).ellipsoid;
const pathLonLat = this._pathLonLat[segmentIndex];
if (ellipsoid && pathLonLat && pathLonLat.length === path3v.length) {
this._setSegmentEqualLonLat(pathLonLat, segmentIndex);
} else {
this._setSegmentEqualPath3v(path3v, segmentIndex);
}
this._setChangedBuffer(VERTICES_BUFFER);
}
this._rebuildIndexes();
if (this._hasTexture(segmentIndex)) {
this._updateTextureMetrics(segmentIndex);
this._setChangedBuffer(PATHPHASE_BUFFER);
this._setChangedBuffer(BOUNDING_SPHERE_BUFFER);
}
this._setChangedBuffer(INDEX_BUFFER);
}
public getSrc(): StrokeSource[] {
return this._src;
}
public _setTexCoordArr(tCoordArrs: (number[] | null)[]) {
if (!this.isTextured) {
return;
}
this._texCoordArr = [];
PolylineBatchRenderer.setPathTexCoords(this._path3v, tCoordArrs, this._texCoordArr);
this._setChangedBuffer(TEXCOORD_BUFFER);
}
public setTextureDisabled() {
if (!this.isTextured) {
return;
}
this._defaultTexParam.strokeSize = 0;
const segCount = Math.max(this._path3v.length, this._pathLonLat.length, this._segmentTexParams.length);
for (let i = 0; i < segCount; i++) {
this.setPathStrokeSize(0, i);
}
}
/** Get atlas tex coords for segment (null = color-only) */
protected _getAtlasTexCoordsForSegment(segIndex: number): number[] | null {
const m = this._image[segIndex];
if (m == null) return null;
const rn = this._handler?._entityCollection?.scene;
const d = rn?.renderer && m.__nodeIndex != null ? rn.renderer.strokeTextureAtlas.get(m.__nodeIndex) : null;
return d?.texCoords ?? null;
}
protected _pushQuadVertices(
position: Vec3,
vHigh: Vec3,
vLow: Vec3,
outVerticesHigh: number[],
outVerticesLow: number[]
) {
this.__doubleToTwoFloats(position, vHigh, vLow);
outVerticesHigh.push(
vHigh.x,
vHigh.y,
vHigh.z,
vHigh.x,
vHigh.y,
vHigh.z,
vHigh.x,
vHigh.y,
vHigh.z,
vHigh.x,
vHigh.y,
vHigh.z
);
outVerticesLow.push(
vLow.x,
vLow.y,
vLow.z,
vLow.x,
vLow.y,
vLow.z,
vLow.x,
vLow.y,
vLow.z,
vLow.x,
vLow.y,
vLow.z
);
}
protected _resolveSegmentThickness(segIndex: number): number {
let thickness = this._segmentThickness[segIndex];
if (thickness == undefined) {
thickness = this._thickness;
this._segmentThickness[segIndex] = thickness;
}
return thickness;
}
protected _resolveSegmentTexParams(segIndex: number): TexParam {
let texParam = this._segmentTexParams[segIndex];
if (!texParam) {
texParam = {
texOffset: this._defaultTexParam.texOffset,
strokeSize: this._defaultTexParam.strokeSize,
texOffsetSpeed: this._defaultTexParam.texOffsetSpeed
};
this._segmentTexParams[segIndex] = texParam;
}
return texParam;
}
protected _recalculateExtentFromLonLatPaths() {
resetExtentBounds(this._extent);
const paths = this._pathLonLat;
for (let i = 0; i < paths.length; i++) {
const segment = paths[i];
if (!segment) continue;
for (let j = 0; j < segment.length; j++) {
const p = segment[j];
const lonLat = p instanceof Array ? new LonLat(p[0], p[1], p[2]) : (p as LonLat);
extendExtent(this._extent, lonLat);
}
}
}
protected _normalizeSegmentPathColors(
segmentLength: number,
segColorsInput: SegmentPathColor | NumberArray4
): SegmentPathColor {
const hasUniformSegmentColor =
Array.isArray(segColorsInput) && segColorsInput.length > 0 && typeof segColorsInput[0] === "number";
const segmentColorCount = hasUniformSegmentColor ? 0 : (segColorsInput as SegmentPathColor).length;
const lastSegmentColor =
segmentColorCount > 0 ? (segColorsInput as SegmentPathColor)[segmentColorCount - 1] : undefined;
const outSegmentColors = new Array(segmentLength);
let color = this._defaultColor as NumberArray4;
for (let i = 0; i < segmentLength; i++) {
if (hasUniformSegmentColor) {
color = segColorsInput as NumberArray4;
} else if ((segColorsInput as SegmentPathColor)[i]) {
color = (segColorsInput as SegmentPathColor)[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
outSegmentColors[i] = color;
}
return outSegmentColors;
}
protected _getSegmentAttrGroupStart(segmentIndex: number): number {
return segmentIndex === 0 ? 0 : this._pathLengths[segmentIndex] + 2 * segmentIndex - 1;
}
protected _getSegmentAttrGroupCount(segmentIndex: number): number {
const path = this._path3v[segmentIndex] as Vec3[] | undefined;
if (!path || path.length === 0) return 0;
return path.length + 1 + (segmentIndex > 0 ? 1 : 0);
}
protected _setPhaseQuad(groupIndex: number, phase: number, arr?: TypedArray | number[]) {
const target = (arr ?? this._pathPhaseArr) as TypedArray | number[];
const base = groupIndex * 4;
target[base] = phase;
target[base + 1] = phase;
target[base + 2] = phase;
target[base + 3] = phase;
}
protected _setBoundingSphereQuad(groupIndex: number, sphere: NumberArray4, arr?: TypedArray | number[]) {
const target = (arr ?? this._boundingSphereArr) as TypedArray | number[];
const base = groupIndex * 16;
for (let i = 0; i < 4; i++) {
const k = base + i * 4;
target[k] = sphere[0];
target[k + 1] = sphere[1];
target[k + 2] = sphere[2];
target[k + 3] = sphere[3];
}
}
protected _getTotalAttrGroupCount(): number {
let groups = 0;
for (let segIndex = 0; segIndex < this._path3v.length; segIndex++) {
const path = this._path3v[segIndex] as Vec3[] | undefined;
if (!path || path.length === 0) {
continue;
}
groups += path.length + 1 + (segIndex > 0 ? 1 : 0);
}
return groups;
}
protected _cloneMetricArray(source: TypedArray | number[] | undefined, requiredLength: number): number[] {
const out = new Array(requiredLength).fill(0);
if (!source) {
return out;
}
const src = source as TypedArray | number[];
const copyLen = Math.min(requiredLength, src.length);
for (let i = 0; i < copyLen; i++) {
out[i] = src[i];
}
return out;
}
protected _rebuildPathPhaseArr() {
const outPhase = this._cloneMetricArray(
this._pathPhaseArr as TypedArray | number[],
this._getTotalAttrGroupCount() * 4
);
let cumulativeLength = 0.0;
for (let segIndex = 0; segIndex < this._path3v.length; segIndex++) {
const path = this._path3v[segIndex] as Vec3[];
if (!path || path.length === 0) continue;
if (!this._hasTexture(segIndex)) continue;
let groupIndex = this._getSegmentAttrGroupStart(segIndex);
if (segIndex > 0) {
this._setPhaseQuad(groupIndex++, cumulativeLength, outPhase);
}
let segLength = 0.0;
this._setPhaseQuad(groupIndex++, cumulativeLength, outPhase);
for (let i = 1; i < path.length; i++) {
segLength += path[i].distance(path[i - 1]);
this._setPhaseQuad(groupIndex++, cumulativeLength + segLength, outPhase);
}
let capPhase = cumulativeLength + segLength;
if (this._isSegmentClosed(segIndex) && path.length > 1) {
capPhase += path[path.length - 1].distance(path[0]);
}
this._setPhaseQuad(groupIndex, capPhase, outPhase);
cumulativeLength = capPhase;
}
this._pathPhaseArr = outPhase;
}
protected _calculateSegmentTextureScaleSphere(path: Vec3[], segmentIndex: number): NumberArray4 {
if (!this._hasTexture(segmentIndex) || !path || path.length === 0) {
return [0, 0, 0, 0];
}
let minX = Infinity,
minY = Infinity,
minZ = Infinity;
let maxX = -Infinity,
maxY = -Infinity,
maxZ = -Infinity;
for (let i = 0; i < path.length; i++) {
const p = path[i];
if (p.x < minX) minX = p.x;
if (p.y < minY) minY = p.y;
if (p.z < minZ) minZ = p.z;
if (p.x > maxX) maxX = p.x;
if (p.y > maxY) maxY = p.y;
if (p.z > maxZ) maxZ = p.z;
}
const centerX = (minX + maxX) * 0.5;
const centerY = (minY + maxY) * 0.5;
const centerZ = (minZ + maxZ) * 0.5;
let radius2 = 0.0;
for (let i = 0; i < path.length; i++) {
const p = path[i];
const dx = p.x - centerX;
const dy = p.y - centerY;
const dz = p.z - centerZ;
const d2 = dx * dx + dy * dy + dz * dz;
if (d2 > radius2) radius2 = d2;
}
let rtcCenterX = centerX;
let rtcCenterY = centerY;
let rtcCenterZ = centerZ;
if (this._handler) {
rtcCenterX -= this._handler._relativeCenter.x;
rtcCenterY -= this._handler._relativeCenter.y;
rtcCenterZ -= this._handler._relativeCenter.z;
}
return [rtcCenterX, rtcCenterY, rtcCenterZ, Math.sqrt(radius2)];
}
protected _rebuildBoundingSphereArr() {
const outBoundingSphere = this._cloneMetricArray(
this._boundingSphereArr as TypedArray | number[],
this._getTotalAttrGroupCount() * 16
);
for (let segIndex = 0; segIndex < this._path3v.length; segIndex++) {
const path = this._path3v[segIndex] as Vec3[];
if (!path || path.length === 0) continue;
if (!this._hasTexture(segIndex)) continue;
const sphere = this._calculateSegmentTextureScaleSphere(path, segIndex);
const startGroup = this._getSegmentAttrGroupStart(segIndex);
const groupsCount = this._getSegmentAttrGroupCount(segIndex);
for (let i = 0; i < groupsCount; i++) {
this._setBoundingSphereQuad(startGroup + i, sphere, outBoundingSphere);
}
}
this._boundingSphereArr = outBoundingSphere;
}
protected _updatePathPhaseSegment(segmentIndex: number): number {
const path = this._path3v[segmentIndex] as Vec3[] | undefined;
if (!path || path.length === 0) {
return 0;
}
if (!this._hasTexture(segmentIndex)) {
return 0;
}
const startGroup = this._getSegmentAttrGroupStart(segmentIndex);
const groupsCount = this._getSegmentAttrGroupCount(segmentIndex);
const capGroup = startGroup + groupsCount - 1;
const arr = this._pathPhaseArr as TypedArray | number[];
const segmentStartPhase = segmentIndex > 0 ? arr[startGroup * 4] : 0.0;
const oldCapPhase = arr[capGroup * 4];
let groupIndex = startGroup;
if (segmentIndex > 0) {
this._setPhaseQuad(groupIndex++, segmentStartPhase);
}
let segLength = 0.0;
this._setPhaseQuad(groupIndex++, segmentStartPhase);
for (let i = 1; i < path.length; i++) {
segLength += path[i].distance(path[i - 1]);
this._setPhaseQuad(groupIndex++, segmentStartPhase + segLength);
}
let capPhase = segmentStartPhase + segLength;
if (this._isSegmentClosed(segmentIndex) && path.length > 1) {
capPhase += path[path.length - 1].distance(path[0]);
}
this._setPhaseQuad(capGroup, capPhase);
return capPhase - oldCapPhase;
}
protected _shiftPathPhaseTailAfterSegment(segmentIndex: number, phaseDelta: number) {
if (phaseDelta === 0) {
return;
}
const arr = this._pathPhaseArr as TypedArray | number[];
for (let segIndex = segmentIndex + 1; segIndex < this._path3v.length; segIndex++) {
const path = this._path3v[segIndex] as Vec3[] | undefined;
if (!path || path.length === 0 || !this._hasTexture(segIndex)) {
continue;
}
const groupStart = this._getSegmentAttrGroupStart(segIndex);
const groupCount = this._getSegmentAttrGroupCount(segIndex);
for (let localGroup = 0; localGroup < groupCount; localGroup++) {
const base = (groupStart + localGroup) * 4;
const shifted = arr[base] + phaseDelta;
arr[base] = shifted;
arr[base + 1] = shifted;
arr[base + 2] = shifted;
arr[base + 3] = shifted;
}
}
}
protected _updateBoundingSphereSegment(segmentIndex: number) {
const path = this._path3v[segmentIndex] as Vec3[] | undefined;
if (!path || path.length === 0) {
return;
}
if (!this._hasTexture(segmentIndex)) {
return;
}
const startGroup = this._getSegmentAttrGroupStart(segmentIndex);
const groupsCount = this._getSegmentAttrGroupCount(segmentIndex);
const sphere = this._calculateSegmentTextureScaleSphere(path, segmentIndex);
for (let i = 0; i < groupsCount; i++) {
this._setBoundingSphereQuad(startGroup + i, sphere);
}
}
protected _updateTextureMetrics(segmentIndex: number) {
if (!this._hasTexture(segmentIndex)) {
return;
}
const phaseDelta = this._updatePathPhaseSegment(segmentIndex);
this._shiftPathPhaseTailAfterSegment(segmentIndex, phaseDelta);
this._updateBoundingSphereSegment(segmentIndex);
}
protected _updateAllTextureMetrics() {
if (!this.isTextured) {
return;
}
this._rebuildPathPhaseArr();
this._rebuildBoundingSphereArr();
this._setChangedBuffer(PATHPHASE_BUFFER);
this._setChangedBuffer(BOUNDING_SPHERE_BUFFER);
}
protected _updateTextureMetricsAfterPointChange(segmentIndex: number) {
if (!this._hasTexture(segmentIndex)) {
return;
}
this._updateTextureMetrics(segmentIndex);
this._setChangedBuffer(PATHPHASE_BUFFER);
this._setChangedBuffer(BOUNDING_SPHERE_BUFFER);
}
protected _markGeometryBuffersChanged(includeTexCoords: boolean) {
this._setChangedBuffer(VERTICES_BUFFER);
this._setChangedBuffer(INDEX_BUFFER);
this._setChangedBuffer(COLORS_BUFFER);
this._setChangedBuffer(THICKNESS_BUFFER);
this._setChangedBuffer(PICKINGCOLORS_BUFFER);
if (this.isTextured) {
this._updateAllTextureMetrics();
this._setChangedBuffer(TEXPARAM_BUFFER);
if (includeTexCoords) {
this._setChangedBuffer(TEXCOORD_BUFFER);
}
}
}
protected _applyForceEqualSegmentUpdate<TSegment extends any[]>(
segmentPath: TSegment,
segmentIndex: number,
pathColors: SegmentPathColor | NumberArray4 | undefined,
currentPaths: TSegment[],
equalUpdate: (
segmentPath: TSegment,
segmentIndex: number,
pathColors?: SegmentPathColor | NumberArray4
) => void,
recreateData: (nextPaths: TSegment[]) => void,
includeTexCoords: boolean
): boolean {
const targetSegmentPath = currentPaths[segmentIndex] as unknown as any[];
const canUseEqualUpdate =
!!targetSegmentPath && targetSegmentPath.length === (segmentPath as unknown as any[]).length;
if (canUseEqualUpdate) {
equalUpdate(segmentPath, segmentIndex, pathColors);
if (this._hasTexture(segmentIndex)) {
this._updateTextureMetrics(segmentIndex);
this._setChangedBuffer(PATHPHASE_BUFFER);
this._setChangedBuffer(BOUNDING_SPHERE_BUFFER);
}
this._setChangedBuffer(VERTICES_BUFFER);
if (pathColors) {
this._setChangedBuffer(COLORS_BUFFER);
}
return true;
}
const nextPaths = ([] as TSegment[]).concat(currentPaths);
nextPaths[segmentIndex] = segmentPath;
if (pathColors) {
this._pathColors[segmentIndex] = this._normalizeSegmentPathColors(
(segmentPath as unknown as any[]).length,
pathColors
);
}
recreateData(nextPaths);
this._markGeometryBuffersChanged(includeTexCoords);
return true;
}
protected __appendLineDataCore(
pathInput: SegmentPath3vExt[] | SegmentPathLonLatExt[],
pathColors: SegmentPathColor[],
pathPickingColors: NumberArray3[][],
defaultColor: NumberArray4,
opacity: number[],
pathClosed: boolean[],
outVerticesHigh: number[],
outVerticesLow: number[],
outOrders: number[],
outIndexes: number[],
outTexCoords: number[],
ellipsoid: Ellipsoid | null,
outPath3v: SegmentPath3vExt[],
outPathLonLat: SegmentPathLonLatExt[],
outPathMerc: LonLat[][],
outExtent: Extent,
outColors: number[],
outThickness: number[],
outTexParams: number[],
outBoundingSpheres: number[],
outPickingColors: number[],
sourceType: LineSourceMode,
segmentOffset: number = 0,
indexFormat: IndexFormatMode = sourceType,
skipTexCoords: boolean = sourceType === FLONLAT,
resetExtent: boolean = true
) {
let index = initLineIndexes(outIndexes, indexFormat);
const vHigh = new Vec3();
const vLow = new Vec3();
if (outExtent && resetExtent) {
resetExtentBounds(outExtent);
}
for (let j = 0, len = pathInput.length; j < len; j++) {
const segIndex = segmentOffset + j;
const segOpacity = opacity[segIndex] ?? 1.0;
const segmentClosed = pathClosed[segIndex] === true;
const path = pathInput[j] as Cartesian[] | Geodetic[];
const pathColors_j = pathColors[j];
const pathPickingColors_j = pathPickingColors[j];
outPath3v[segIndex] = [];
outPathLonLat[segIndex] = [];
outPathMerc[segIndex] = [];
if (path.length === 0) {
continue;
}
const path3v: Vec3[] = new Array(path.length);
const pathLonLat: LonLat[] = new Array(path.length);
for (let i = 0; i < path.length; i++) {
if (sourceType === F3V) {
const cart = toVec3(path[i] as Cartesian);
path3v[i] = cart;
if (ellipsoid) {
pathLonLat[i] = ellipsoid.cartesianToLonLat(cart);
}
} else {
const lonLat = toLonLat(path[i] as Geodetic);
pathLonLat[i] = lonLat;
path3v[i] = (ellipsoid as Ellipsoid).lonLatToCartesian(lonLat);
}
}
const thickness = this._resolveSegmentThickness(segIndex);
const useTextureData = this.isTextured;
const hasSegmentTexture = this._hasTexture(segIndex);
let segAtlas: number[] | null = null;
let texParams = this._defaultTexParam;
let sphere: NumberArray4 = [0, 0, 0, 0];
if (useTextureData) {
segAtlas = hasSegmentTexture && !skipTexCoords ? this._getAtlasTexCoordsForSegment(segIndex) : null;
texParams = hasSegmentTexture
? this._resolveSegmentTexParams(segIndex)
: { texOffset: 0, strokeSize: 0, texOffsetSpeed: 0 };
sphere = hasSegmentTexture ? this._calculateSegmentTextureScaleSphere(path3v, segIndex) : [0, 0, 0, 0];
}
const p0 = path3v[0];
const p1 = path3v[1] || p0;
const last = segmentClosed ? path3v[path3v.length - 1] : createMirroredPoint(p0, p1);
const startIndex = index;
let color = defaultColor;
if (pathColors_j && pathColors_j[0]) {
color = pathColors_j[0];
}
let pickingColor: NumberArray3 =
pathPickingColors_j && pathPickingColors_j[0]
? pathPickingColors_j[0]
: (this._pickingColor as unknown as NumberArray3);
this._pushQuadVertices(last, vHigh, vLow, outVerticesHigh, outVerticesLow);
if (segIndex > 0) {
pushQuadColor(outColors, color, segOpacity);
pushQuadThickness(outThickness, thickness);
pushQuadPicking(outPickingColors, pickingColor);
if (useTextureData) {
pushQuadTexParams(outTexParams, texParams);
pushQuadBoundingSphere(outBoundingSpheres, sphere[0], sphere[1], sphere[2], sphere[3]);
pushQuadTexCoords(outTexCoords, segAtlas, skipTexCoords);
}
}
pushQuadOrders(outOrders);
for (let i = 0, plen = path3v.length; i < plen; i++) {
const cur = path3v[i];
const lonLat = pathLonLat[i];
outPath3v[segIndex].push(cur);
if (lonLat) {
outPathLonLat[segIndex].push(lonLat);
outPathMerc[segIndex].push(lonLat.forwardMercator());
extendExtent(outExtent, lonLat);
}
if (pathColors_j && pathColors_j[i]) {
color = pathColors_j[i];
}
if (pathPickingColors_j && pathPickingColors_j[i]) {
pickingColor = pathPickingColors_j[i];
}
this._pushQuadVertices(cur, vHigh, vLow, outVerticesHigh, outVerticesLow);
pushQuadColor(outColors, color, segOpacity);
pushQuadThickness(outThickness, thickness);
pushQuadPicking(outPickingColors, pickingColor);
if (useTextureData) {
pushQuadTexParams(outTexParams, texParams);
pushQuadBoundingSphere(outBoundingSpheres, sphere[0], sphere[1], sphere[2], sphere[3]);
pushQuadTexCoords(outTexCoords, segAtlas, skipTexCoords);
}
pushQuadOrders(outOrders);
outIndexes.push(index++, index++, index++, index++);
}
const lp0 = path3v[path3v.length - 1];
const lp1 = path3v[path3v.length - 2] || lp0;
const first = segmentClosed ? path3v[0] : createMirroredPoint(lp0, lp1);
if (segmentClosed) {
outIndexes.push(startIndex, startIndex + 1, startIndex + 1, startIndex + 1);
} else {
outIndexes.push(index - 1, index - 1, index - 1, index - 1);
}
if (pathColors_j && pathColors_j[path3v.length - 1]) {
color = pathColors_j[path3v.length - 1];
}
if (pathPickingColors_j && pathPickingColors_j[path3v.length - 1]) {
pickingColor = pathPickingColors_j[path3v.length - 1];
}
this._pushQuadVertices(first, vHigh, vLow, outVerticesHigh, outVerticesLow);
pushQuadColor(outColors, color, segOpacity);
pushQuadThickness(outThickness, thickness);
pushQuadPicking(outPickingColors, pickingColor);
if (useTextureData) {
pushQuadTexParams(outTexParams, texParams);
pushQuadBoundingSphere(outBoundingSpheres, sphere[0], sphere[1], sphere[2], sphere[3]);
pushQuadTexCoords(outTexCoords, segAtlas, skipTexCoords);
}
pushQuadOrders(outOrders);
if (j < pathInput.length - 1 && pathInput[j + 1].length !== 0) {
index += 8;
outIndexes.push(index, index);
}
}
}
/**
* Appends to the line array new cartesian coordinates line data.
*/
protected __appendLineData3v(
path3v: SegmentPath3vExt[],
pathColors: SegmentPathColor[],
pathPickingColors: NumberArray3[][],
defaultColor: NumberArray4,
opacity: number[],
pathClosed: boolean[],
outVerticesHigh: number[],
outVerticesLow: number[],
outOrders: number[],
outIndexes: number[],
ellipsoid: Ellipsoid,
outTransformedPathLonLat: SegmentPathLonLatExt[],
outPath3v: SegmentPath3vExt[],
outTransformedPathMerc: LonLat[][],
outExtent: Extent,
outColors: number[],
outThickness: number[],
outTexParams: number[],
outTexCoords: number[],
outBoundingSpheres: number[],
outPickingColors: number[]
) {
this.__appendLineDataCore(
path3v,
pathColors,
pathPickingColors,
defaultColor,
opacity,
pathClosed,
outVerticesHigh,
outVerticesLow,
outOrders,
outIndexes,
outTexCoords,
ellipsoid,
outPath3v,
outTransformedPathLonLat,
outTransformedPathMerc,
outExtent,
outColors,
outThickness,
outTexParams,
outBoundingSpheres,
outPickingColors,
F3V,
0,
F3V,
false
);
}
/**
[1, -1, 2, -2] - orders for triangle strip line segment
t2 t3
(2)-------(-2)
| |
| |
| |
| |
(1)-------(-1)
t0 t1
*/
static setPathTexCoords(path3v: SegmentPath3vExt[], tCoordArrs: (number[] | null)[], outTexCoords: number[]) {
for (let j = 0, len = path3v.length; j < len; j++) {
const path = path3v[j];
const plen = path.length;
if (!plen) {
continue;
}
const tc = tCoordArrs[j] ?? null;
const times = plen + 1 + (j > 0 ? 1 : 0);
let a0 = 0,
a1 = 0,
a2 = 0,
a3 = 0,
a4 = 0,
a5 = 0,
a6 = 0,
a7 = 0,
a8 = 0,
a9 = 0,
a10 = 0,
a11 = 0,
a12 = 0,
a13 = 0,
a14 = 0,
a15 = 0;
if (tc && tc.length >= 10) {
const minY = tc[1],
imgHeight = tc[3] - minY;
const t0x = tc[4],
t0y = tc[5],
t1x = tc[2],
t1y = tc[3];
const t2x = tc[8],
t2y = tc[9],
t3x = tc[0],
t3y = tc[1];
a0 = t0x;
a1 = t0y;
a2 = minY;
a3 = imgHeight;
a4 = t1x;
a5 = t1y;
a6 = minY;
a7 = imgHeight;
a8 = t2x;
a9 = t2y;
a10 = minY;
a11 = imgHeight;
a12 = t3x;
a13 = t3y;
a14 = minY;
a15 = imgHeight;
}
let p = outTexCoords.length;
outTexCoords.length = p + times * 16;
for (let k = 0; k < times; k++) {
outTexCoords[p++] = a0;
outTexCoords[p++] = a1;
outTexCoords[p++] = a2;
outTexCoords[p++] = a3;
outTexCoords[p++] = a4;
outTexCoords[p++] = a5;
outTexCoords[p++] = a6;
outTexCoords[p++] = a7;
outTexCoords[p++] = a8;
outTexCoords[p++] = a9;
outTexCoords[p++] = a10;
outTexCoords[p++] = a11;
outTexCoords[p++] = a12;
outTexCoords[p++] = a13;
outTexCoords[p++] = a14;
outTexCoords[p++] = a15;
}
}
}
static setPathColors(
pathLonLat: SegmentPathLonLatExt[],
pathColors: SegmentPathColor[],
defaultColor: NumberArray4,
outColors: number[],
opacity: number = 1.0
) {
for (let j = 0, len = pathLonLat.length; j < len; j++) {
var path = pathLonLat[j],
pathColors_j = pathColors[j];
if (path.length === 0) {
continue;
}
let color = defaultColor;
if (pathColors_j && pathColors_j[0]) {
color = pathColors_j[0];
}
let r = srgbToLinear(color[R]),
g = srgbToLinear(color[G]),
b = srgbToLinear(color[B]),
a = (color[A] != undefined ? color[A] : 1.0) * opacity;
if (j > 0) {
outColors.push(r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a);
}
for (let i = 0, len = path.length; i < len; i++) {
if (pathColors_j && pathColors_j[i]) {
color = pathColors_j[i];
}
r = srgbToLinear(color[R]);
g = srgbToLinear(color[G]);
b = srgbToLinear(color[B]);
a = (color[A] != undefined ? color[A] : 1.0) * opacity;
outColors.push(r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a);
}
if (pathColors_j && pathColors_j[path.length - 1]) {
color = pathColors_j[path.length - 1];
}
r = srgbToLinear(color[R]);
g = srgbToLinear(color[G]);
b = srgbToLinear(color[B]);
a = (color[A] != undefined ? color[A] : 1.0) * opacity;
outColors.push(r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a);
}
}
/**
* Appends to the line array new geodetic coordinates line data.
* @static
*/
protected __appendLineDataLonLat(
pathLonLat: SegmentPathLonLatExt[],
pathColors: SegmentPathColor[],
pathPickingColors: NumberArray3[][],
defaultColor: NumberArray4,
opacity: number[],
pathClosed: boolean[],
outVerticesHigh: number[],
outVerticesLow: number[],
outOrders: number[],
outIndexes: number[],
outTexCoords: number[],
ellipsoid: Ellipsoid,
outTransformedPathCartesian: SegmentPath3vExt[],
outPathLonLat: SegmentPathLonLatExt[],
outTransformedPathMerc: LonLat[][],
outExtent: Extent,
outColors: number[],
outThickness: number[],
outTexParams: number[],
outBoundingSpheres: number[],
outPickingColors: number[]
) {
this.__appendLineDataCore(
pathLonLat,
pathColors,
pathPickingColors,
defaultColor,
opacity,
pathClosed,
outVerticesHigh,
outVerticesLow,
outOrders,
outIndexes,
outTexCoords,
ellipsoid,
outTransformedPathCartesian,
outPathLonLat,
outTransformedPathMerc,
outExtent,
outColors,
outThickness,
outTexParams,
outBoundingSpheres,
outPickingColors,
FLONLAT,
0,
FLONLAT,
true
);
}
protected _setEqualPath3v(
path3v: SegmentPath3vExt[],
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
) {
var extent = this._extent;
var v_high = new Vec3(),
v_low = new Vec3();
var vh = this._verticesHigh,
vl = this._verticesLow,
l = this._pathLonLat,
m = this._pathLonLatMerc,
k = 0;
var ellipsoid = (this._scene as Planet).ellipsoid;
if (ellipsoid) {
extent.southWest.set(180, 90);
extent.northEast.set(-180, -90);
}
const colors = this._colors;
let ck = 0;
const pathColorsAny = pathColors as any;
const hasUniformPathColor =
!!pathColorsAny &&
Array.isArray(pathColorsAny) &&
pathColorsAny.length > 0 &&
typeof pathColorsAny[0] === "number";
for (let j = 0; j < path3v.length; j++) {
var path = path3v[j] as Vec3[];
const segmentClosed = this._isSegmentClosed(j);
const pathColors_j_any = hasUniformPathColor ? pathColorsAny : pathColorsAny ? pathColorsAny[j] : undefined;
const hasUniformSegmentColor =
!!pathColors_j_any &&
Array.isArray(pathColors_j_any) &&
pathColors_j_any.length > 0 &&
typeof pathColors_j_any[0] === "number";
const segmentColorCount =
!hasUniformSegmentColor && pathColors_j_any && Array.isArray(pathColors_j_any)
? pathColors_j_any.length
: 0;
const lastSegmentColor =
segmentColorCount > 0 ? (pathColors_j_any[segmentColorCount - 1] as NumberArray4) : undefined;
let targetSegmentColors: SegmentPathColor | undefined;
if (pathColors) {
targetSegmentColors = this._pathColors[j] || (this._pathColors[j] = new Array(path.length));
}
var last;
if (segmentClosed) {
last = path[path.length - 1];
} else {
last = new Vec3(
path[0].x + path[0].x - path[1].x,
path[0].y + path[0].y - path[1].y,
path[0].z + path[0].z - path[1].z
);
}
this.__doubleToTwoFloats(last, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
let color = this._defaultColor as NumberArray4;
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[0]) {
color = pathColors_j_any[0];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
if (j > 0 && pathColors) {
writeQuadColor(colors, ck, color);
ck += 16;
}
for (let i = 0; i < path.length; i++) {
var cur = path[i] as Vec3,
pji = this._path3v[j][i] as Vec3;
pji.x = cur.x;
pji.y = cur.y;
pji.z = cur.z;
if (ellipsoid) {
var lonLat = ellipsoid.cartesianToLonLat(cur);
this._pathLonLat[j][i] = lonLat;
l[j][i] = lonLat;
m[j][i] = lonLat.forwardMercator();
if (lonLat.lon < extent.southWest.lon) {
extent.southWest.lon = lonLat.lon;
}
if (lonLat.lat < extent.southWest.lat) {
extent.southWest.lat = lonLat.lat;
}
if (lonLat.lon > extent.northEast.lon) {
extent.northEast.lon = lonLat.lon;
}
if (lonLat.lat > extent.northEast.lat) {
extent.northEast.lat = lonLat.lat;
}
}
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[i]) {
color = pathColors_j_any[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
targetSegmentColors![i] = color;
writeQuadColor(colors, ck, color);
ck += 16;
}
this.__doubleToTwoFloats(cur, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
}
var first;
if (segmentClosed) {
first = path[0];
} else {
const l1 = path.length - 1;
first = new Vec3(
path[l1].x + path[l1].x - path[l1 - 1].x,
path[l1].y + path[l1].y - path[l1 - 1].y,
path[l1].z + path[l1].z - path[l1 - 1].z
);
}
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[path.length - 1]) {
color = pathColors_j_any[path.length - 1];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
}
this.__doubleToTwoFloats(first as Vec3, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
if (pathColors) {
writeQuadColor(colors, ck, color);
ck += 16;
}
}
}
/**
* Sets one polyline segment with cartesian coordinates.
* @protected
* @param {SegmentPath3vExt} path3v - Cartesian coordinates for one segment.
* @param {number} segmentIndex - Segment index to update.
* @param {SegmentPathColor | NumberArray4} [pathColors] - Optional colors for the segment path.
*/
protected _setSegmentEqualPath3v(
path3v: SegmentPath3vExt,
segmentIndex: number,
pathColors?: SegmentPathColor | NumberArray4
) {
const path = path3v as Vec3[];
const targetPath = this._path3v[segmentIndex] as Vec3[];
if (!path || !targetPath || !path.length || path.length !== targetPath.length) {
return;
}
const v_high = new Vec3();
const v_low = new Vec3();
const vh = this._verticesHigh;
const vl = this._verticesLow;
const l = this._pathLonLat;
const m = this._pathLonLatMerc;
const ellipsoid = (this._scene as Planet).ellipsoid;
const k0 = this._pathLengths[segmentIndex] * 12 + 24 * segmentIndex;
const c = this._colors;
const c0 = this._pathLengths[segmentIndex] * 16 + 32 * segmentIndex;
let k = k0;
let ck = c0;
const hasUniformSegmentColor =
!!pathColors &&
Array.isArray(pathColors) &&
pathColors.length > 0 &&
typeof (pathColors as any)[0] === "number";
const pathColorsAny = pathColors as any;
const segmentColorCount =
!hasUniformSegmentColor && pathColorsAny && Array.isArray(pathColorsAny) ? pathColorsAny.length : 0;
const lastSegmentColor =
segmentColorCount > 0 ? (pathColorsAny[segmentColorCount - 1] as NumberArray4) : undefined;
const targetSegmentColors = pathColors
? this._pathColors[segmentIndex] || (this._pathColors[segmentIndex] = new Array(path.length))
: undefined;
const segmentClosed = this._isSegmentClosed(segmentIndex);
const last = segmentClosed
? path[path.length - 1]
: new Vec3(
path[0].x + path[0].x - path[1].x,
path[0].y + path[0].y - path[1].y,
path[0].z + path[0].z - path[1].z
);
this.__doubleToTwoFloats(last, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
let color = this._defaultColor as NumberArray4;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[0]) {
color = pathColorsAny[0];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
}
if (pathColors && segmentIndex > 0) {
writeQuadColor(c, ck - 16, color);
}
for (let i = 0; i < targetPath.length; i++) {
const cur = path[i];
const p = targetPath[i];
p.x = cur.x;
p.y = cur.y;
p.z = cur.z;
if (ellipsoid) {
const lonLat = ellipsoid.cartesianToLonLat(cur);
l[segmentIndex][i] = lonLat;
m[segmentIndex][i] = lonLat.forwardMercator();
}
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[i]) {
color = pathColorsAny[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
targetSegmentColors![i] = color;
writeQuadColor(c, ck, color);
ck += 16;
}
this.__doubleToTwoFloats(cur, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
}
const first = segmentClosed
? path[0]
: new Vec3(
path[path.length - 1].x + path[path.length - 1].x - path[path.length - 2].x,
path[path.length - 1].y + path[path.length - 1].y - path[path.length - 2].y,
path[path.length - 1].z + path[path.length - 1].z - path[path.length - 2].z
);
this.__doubleToTwoFloats(first, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[path.length - 1]) {
color = pathColorsAny[path.length - 1];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
writeQuadColor(c, ck, color);
}
}
protected _setEqualPathLonLat(
pathLonLat: SegmentPathLonLat[],
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
) {
const extent = this._extent;
extent.southWest.set(180.0, 90.0);
extent.northEast.set(-180.0, -90.0);
let v_high = new Vec3(),
v_low = new Vec3();
let vh = this._verticesHigh,
vl = this._verticesLow,
l = this._pathLonLat,
m = this._pathLonLatMerc,
c = this._path3v,
k = 0;
const ellipsoid = (this._scene as Planet).ellipsoid;
const colors = this._colors;
let ck = 0;
const pathColorsAny = pathColors as any;
const hasUniformPathColor =
!!pathColorsAny &&
Array.isArray(pathColorsAny) &&
pathColorsAny.length > 0 &&
typeof pathColorsAny[0] === "number";
for (let j = 0; j < pathLonLat.length; j++) {
var path = pathLonLat[j] as LonLat[];
const segmentClosed = this._isSegmentClosed(j);
const pathColors_j_any = hasUniformPathColor ? pathColorsAny : pathColorsAny ? pathColorsAny[j] : undefined;
const hasUniformSegmentColor =
!!pathColors_j_any &&
Array.isArray(pathColors_j_any) &&
pathColors_j_any.length > 0 &&
typeof pathColors_j_any[0] === "number";
const segmentColorCount =
!hasUniformSegmentColor && pathColors_j_any && Array.isArray(pathColors_j_any)
? pathColors_j_any.length
: 0;
const lastSegmentColor =
segmentColorCount > 0 ? (pathColors_j_any[segmentColorCount - 1] as NumberArray4) : undefined;
let targetSegmentColors: SegmentPathColor | undefined;
if (pathColors) {
targetSegmentColors = this._pathColors[j] || (this._pathColors[j] = new Array(path.length));
}
var last;
if (segmentClosed) {
last = ellipsoid.lonLatToCartesian(path[path.length - 1]);
} else {
let p0 = ellipsoid.lonLatToCartesian(path[0]),
p1 = ellipsoid.lonLatToCartesian(path[1]);
last = new Vec3(p0.x + p0.x - p1.x, p0.y + p0.y - p1.y, p0.z + p0.z - p1.z);
}
this.__doubleToTwoFloats(last, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
let color = this._defaultColor as NumberArray4;
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[0]) {
color = pathColors_j_any[0];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
if (j > 0 && pathColors) {
writeQuadColor(colors, ck, color);
ck += 16;
}
for (let i = 0; i < path.length; i++) {
var cur = path[i] as LonLat;
var cartesian = ellipsoid.lonLatToCartesian(cur);
c[j][i] = cartesian;
m[j][i] = cur.forwardMercator();
l[j][i] = cur;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[i]) {
color = pathColors_j_any[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
targetSegmentColors![i] = color;
writeQuadColor(colors, ck, color);
ck += 16;
}
this.__doubleToTwoFloats(cartesian, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
if (cur.lon < extent.southWest.lon) {
extent.southWest.lon = cur.lon;
}
if (cur.lat < extent.southWest.lat) {
extent.southWest.lat = cur.lat;
}
if (cur.lon > extent.northEast.lon) {
extent.northEast.lon = cur.lon;
}
if (cur.lat > extent.northEast.lat) {
extent.northEast.lat = cur.lat;
}
}
var first;
if (segmentClosed) {
first = ellipsoid.lonLatToCartesian(path[0]);
} else {
let p0 = ellipsoid.lonLatToCartesian(path[path.length - 1]),
p1 = ellipsoid.lonLatToCartesian(path[path.length - 2]);
first = new Vec3(p0.x + p0.x - p1.x, p0.y + p0.y - p1.y, p0.z + p0.z - p1.z);
}
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors_j_any as NumberArray4;
} else if (pathColors_j_any && pathColors_j_any[path.length - 1]) {
color = pathColors_j_any[path.length - 1];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
}
this.__doubleToTwoFloats(first, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
if (pathColors) {
writeQuadColor(colors, ck, color);
ck += 16;
}
}
}
protected _setSegmentEqualLonLat(
pathLonLat: SegmentPathLonLatExt,
segmentIndex: number,
pathColors?: SegmentPathColor | NumberArray4
) {
const path = pathLonLat as SegmentPathLonLat;
const targetPathLonLat = this._pathLonLat[segmentIndex] as SegmentPathLonLat;
const targetPath3v = this._path3v[segmentIndex] as SegmentPath3v;
if (
!path ||
!targetPathLonLat ||
!targetPath3v ||
!path.length ||
path.length !== targetPathLonLat.length ||
path.length !== targetPath3v.length
) {
return;
}
const ellipsoid = (this._scene as Planet).ellipsoid;
const v_high = new Vec3();
const v_low = new Vec3();
const vh = this._verticesHigh;
const vl = this._verticesLow;
const l = this._pathLonLat;
const m = this._pathLonLatMerc;
const c = this._path3v;
const k0 = this._pathLengths[segmentIndex] * 12 + 24 * segmentIndex;
const cb = this._colors;
const c0 = this._pathLengths[segmentIndex] * 16 + 32 * segmentIndex;
let k = k0;
let ck = c0;
const hasUniformSegmentColor =
!!pathColors &&
Array.isArray(pathColors) &&
pathColors.length > 0 &&
typeof (pathColors as any)[0] === "number";
const pathColorsAny = pathColors as any;
const segmentColorCount =
!hasUniformSegmentColor && pathColorsAny && Array.isArray(pathColorsAny) ? pathColorsAny.length : 0;
const lastSegmentColor =
segmentColorCount > 0 ? (pathColorsAny[segmentColorCount - 1] as NumberArray4) : undefined;
const targetSegmentColors = pathColors
? this._pathColors[segmentIndex] || (this._pathColors[segmentIndex] = new Array(path.length))
: undefined;
const segmentClosed = this._isSegmentClosed(segmentIndex);
const p0 = ellipsoid.lonLatToCartesian(path[0]);
const p1 = ellipsoid.lonLatToCartesian(path[1] || path[0]);
const last = segmentClosed
? ellipsoid.lonLatToCartesian(path[path.length - 1])
: new Vec3(p0.x + p0.x - p1.x, p0.y + p0.y - p1.y, p0.z + p0.z - p1.z);
this.__doubleToTwoFloats(last, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
let color = this._defaultColor as NumberArray4;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[0]) {
color = pathColorsAny[0];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
}
if (pathColors && segmentIndex > 0) {
writeQuadColor(cb, ck - 16, color);
}
for (let i = 0, len = path.length; i < len; i++) {
const cur = path[i] as LonLat;
const cartesian = ellipsoid.lonLatToCartesian(cur);
l[segmentIndex][i] = cur;
m[segmentIndex][i] = cur.forwardMercator();
c[segmentIndex][i] = cartesian;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[i]) {
color = pathColorsAny[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
targetSegmentColors![i] = color;
writeQuadColor(cb, ck, color);
ck += 16;
}
this.__doubleToTwoFloats(cartesian, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
}
const lp0 = ellipsoid.lonLatToCartesian(path[path.length - 1]);
const lp1 = ellipsoid.lonLatToCartesian(path[path.length - 2] || path[path.length - 1]);
const first = segmentClosed
? ellipsoid.lonLatToCartesian(path[0])
: new Vec3(lp0.x + lp0.x - lp1.x, lp0.y + lp0.y - lp1.y, lp0.z + lp0.z - lp1.z);
this.__doubleToTwoFloats(first, v_high, v_low);
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
vh[k] = v_high.x;
vl[k++] = v_low.x;
vh[k] = v_high.y;
vl[k++] = v_low.y;
vh[k] = v_high.z;
vl[k++] = v_low.z;
if (pathColors) {
if (hasUniformSegmentColor) {
color = pathColors as NumberArray4;
} else if (pathColorsAny[path.length - 1]) {
color = pathColorsAny[path.length - 1];
} else if (lastSegmentColor) {
color = lastSegmentColor;
}
writeQuadColor(cb, ck, color);
}
}
public setPointLonLat(lonlat: LonLat, index: number, segmentIndex: number) {
if (this._scene && (this._scene as Planet).ellipsoid) {
let l = this._pathLonLat,
m = this._pathLonLatMerc;
l[segmentIndex][index] = lonlat;
m[segmentIndex][index] = lonlat.forwardMercator();
this._recalculateExtentFromLonLatPaths();
this.setPoint3v((this._scene as Planet).ellipsoid.lonLatToCartesian(lonlat), index, segmentIndex, true);
} else {
let path = this._pathLonLat[segmentIndex] as LonLat[];
path[index].lon = lonlat.lon;
path[index].lat = lonlat.lat;
path[index].height = lonlat.height;
}
}
/**
* Changes cartesian point coordinates of the path
* @param {Vec3} coordinates - New coordinates
* @param {number} [index=0] - Path segment index
* @param {number} [segmentIndex=0] - Index of the point in the path segment
* @param {boolean} [skipLonLat=false] - Do not update geodetic coordinates
*/
public setPoint3v(coordinates: Vec3, index: number = 0, segmentIndex: number = 0, skipLonLat: boolean = false) {
if (this._scene) {
let v_high = new Vec3(),
v_low = new Vec3();
let vh = this._verticesHigh,
vl = this._verticesLow,
l = this._pathLonLat,
m = this._pathLonLatMerc,
k = 0,
kk = 0;
kk = this._pathLengths[segmentIndex] * 12 + 24 * segmentIndex;
let path = this._path3v[segmentIndex] as Vec3[];
path[index].x = coordinates.x;
path[index].y = coordinates.y;
path[index].z = coordinates.z;
let _closedLine = this._isSegmentClosed(segmentIndex) || path.length === 1;
if (index === 0 || index === 1) {
let last;
if (_closedLine) {
last = path[path.length - 1];
} else {
last = new Vec3(
path[0].x + path[0].x - path[1].x,
path[0].y + path[0].y - path[1].y,
path[0].z + path[0].z - path[1].z
);
}
k = kk;
this.__doubleToTwoFloats(last, v_high, v_low);
vh[k] = v_high.x;
vh[k + 1] = v_high.y;
vh[k + 2] = v_high.z;
vh[k + 3] = v_high.x;
vh[k + 4] = v_high.y;
vh[k + 5] = v_high.z;
vh[k + 6] = v_high.x;
vh[k + 7] = v_high.y;
vh[k + 8] = v_high.z;
vh[k + 9] = v_high.x;
vh[k + 10] = v_high.y;
vh[k + 11] = v_high.z;
vl[k] = v_low.x;
vl[k + 1] = v_low.y;
vl[k + 2] = v_low.z;
vl[k + 3] = v_low.x;
vl[k + 4] = v_low.y;
vl[k + 5] = v_low.z;
vl[k + 6] = v_low.x;
vl[k + 7] = v_low.y;
vl[k + 8] = v_low.z;
vl[k + 9] = v_low.x;
vl[k + 10] = v_low.y;
vl[k + 11] = v_low.z;
}
if (!skipLonLat && (this._scene as Planet).ellipsoid) {
let lonLat = (this._scene as Planet).ellipsoid.cartesianToLonLat(coordinates);
l[segmentIndex][index] = lonLat;
m[segmentIndex][index] = lonLat.forwardMercator();
this._recalculateExtentFromLonLatPaths();
}
k = kk + index * 12 + 12;
this.__doubleToTwoFloats(coordinates, v_high, v_low);
vh[k] = v_high.x;
vh[k + 1] = v_high.y;
vh[k + 2] = v_high.z;
vh[k + 3] = v_high.x;
vh[k + 4] = v_high.y;
vh[k + 5] = v_high.z;
vh[k + 6] = v_high.x;
vh[k + 7] = v_high.y;
vh[k + 8] = v_high.z;
vh[k + 9] = v_high.x;
vh[k + 10] = v_high.y;
vh[k + 11] = v_high.z;
vl[k] = v_low.x;
vl[k + 1] = v_low.y;
vl[k + 2] = v_low.z;
vl[k + 3] = v_low.x;
vl[k + 4] = v_low.y;
vl[k + 5] = v_low.z;
vl[k + 6] = v_low.x;
vl[k + 7] = v_low.y;
vl[k + 8] = v_low.z;
vl[k + 9] = v_low.x;
vl[k + 10] = v_low.y;
vl[k + 11] = v_low.z;
if (index === path.length - 1 || index === path.length - 2) {
let first;
if (_closedLine) {
first = path[0];
} else {
let l1 = path.length - 1;
first = new Vec3(
path[l1].x + path[l1].x - path[l1 - 1].x,
path[l1].y + path[l1].y - path[l1 - 1].y,
path[l1].z + path[l1].z - path[l1 - 1].z
);
}
k = kk + path.length * 12 + 12;
this.__doubleToTwoFloats(first, v_high, v_low);
vh[k] = v_high.x;
vh[k + 1] = v_high.y;
vh[k + 2] = v_high.z;
vh[k + 3] = v_high.x;
vh[k + 4] = v_high.y;
vh[k + 5] = v_high.z;
vh[k + 6] = v_high.x;
vh[k + 7] = v_high.y;
vh[k + 8] = v_high.z;
vh[k + 9] = v_high.x;
vh[k + 10] = v_high.y;
vh[k + 11] = v_high.z;
vl[k] = v_low.x;
vl[k + 1] = v_low.y;
vl[k + 2] = v_low.z;
vl[k + 3] = v_low.x;
vl[k + 4] = v_low.y;
vl[k + 5] = v_low.z;
vl[k + 6] = v_low.x;
vl[k + 7] = v_low.y;
vl[k + 8] = v_low.z;
vl[k + 9] = v_low.x;
vl[k + 10] = v_low.y;
vl[k + 11] = v_low.z;
}
this._updateTextureMetricsAfterPointChange(segmentIndex);
this._setChangedBuffer(VERTICES_BUFFER);
} else {
let path = this._path3v[segmentIndex] as Vec3[];
path[index].x = coordinates.x;
path[index].y = coordinates.y;
path[index].z = coordinates.z;
}
}
protected _resizePathLengths(index: number = 0) {
this._pathLengths[0] = 0;
for (let i = index + 1, len = this._path3v.length; i <= len; i++) {
this._pathLengths[i] = this._pathLengths[i - 1] + this._path3v[i - 1].length;
}
}
protected _rebuildIndexes() {
const oldIdx = this._indexes as number[];
const is3vIndexFormat = oldIdx.length > 1 && oldIdx[0] === 0 && oldIdx[1] === 0;
const newIndexes: number[] = [];
let index = 0;
if (is3vIndexFormat) {
newIndexes.push(0, 0);
} else {
newIndexes.push(0);
}
for (let j = 0, len = this._path3v.length; j < len; j++) {
const seg = this._path3v[j];
if (!seg || seg.length === 0) continue;
if (j > 0) {
if (is3vIndexFormat) {
newIndexes.push(index, index);
} else {
newIndexes.push(index);
}
}
const startIndex = index;
for (let i = 0; i < seg.length; i++) {
newIndexes.push(index++, index++, index++, index++);
}
if (this._isSegmentClosed(j)) {
newIndexes.push(startIndex, startIndex + 1, startIndex + 1, startIndex + 1);
} else {
newIndexes.push(index - 1, index - 1, index - 1, index - 1);
}
if (j < this._path3v.length - 1 && this._path3v[j + 1].length !== 0) {
index += 8;
newIndexes.push(index, index);
}
}
this._indexes = newIndexes;
}
/**
* Remove multiline path segment
* @param {number} index - Segment index in multiline
*/
public removePath(index: number) {
if (index < 0 || index >= this._path3v.length) {
return;
}
const removedLen = this._path3v[index].length;
this._path3v.splice(index, 1);
if (this._pathColors && index < this._pathColors.length) {
this._pathColors.splice(index, 1);
}
if (this._pathPickingColors && index < this._pathPickingColors.length) {
this._pathPickingColors.splice(index, 1);
}
if (index < this._segmentOpacity.length) {
this._segmentOpacity.splice(index, 1);
}
if (index < this._src.length) {
this._src.splice(index, 1);
}
if (this._pathClosed && index < this._pathClosed.length) {
this._pathClosed.splice(index, 1);
}
if (this._segmentThickness && index < this._segmentThickness.length) {
this._segmentThickness.splice(index, 1);
}
if (this._segmentTexParams && index < this._segmentTexParams.length) {
this._segmentTexParams.splice(index, 1);
}
if (index < this._image.length) {
this._image.splice(index, 1);
}
if (this._pathLonLat && index < this._pathLonLat.length) {
this._pathLonLat.splice(index, 1);
}
if (this._pathLonLatMerc && index < this._pathLonLatMerc.length) {
this._pathLonLatMerc.splice(index, 1);
}
this._pathLengths.length = this._path3v.length + 1;
this._resizePathLengths(index > 0 ? index - 1 : 0);
if (!this._scene || removedLen === 0) {
return;
}
this._verticesHigh = makeArrayTyped(this._verticesHigh);
this._verticesLow = makeArrayTyped(this._verticesLow);
this._colors = makeArrayTyped(this._colors);
this._thicknessArr = makeArrayTyped(this._thicknessArr);
this._orders = makeArrayTyped(this._orders);
this._pickingColors = makeArrayTyped(this._pickingColors);
if (this.isTextured) {
this._pathTexParamArr = makeArrayTyped(this._pathTexParamArr);
this._texCoordArr = makeArrayTyped(this._texCoordArr);
}
const pointsBefore = this._pathLengths[index];
const vertexGroupStart = pointsBefore + 2 * index;
const vertexGroupCount = removedLen + 2;
const v0 = vertexGroupStart * 12;
const vN = vertexGroupCount * 12;
this._verticesHigh = spliceTypedArray(this._verticesHigh as TypedArray, v0, vN);
this._verticesLow = spliceTypedArray(this._verticesLow as TypedArray, v0, vN);
const o0 = vertexGroupStart * 4;
const oN = vertexGroupCount * 4;
this._orders = spliceTypedArray(this._orders as TypedArray, o0, oN);
const attrGroupStart = pointsBefore + (index === 0 ? 0 : 2 * index - 1);
const attrGroupCount = removedLen + (index === 0 ? 1 : 2);
this._colors = spliceTypedArray(this._colors as TypedArray, attrGroupStart * 16, attrGroupCount * 16);
this._thicknessArr = spliceTypedArray(this._thicknessArr as TypedArray, attrGroupStart * 4, attrGroupCount * 4);
this._pickingColors = spliceTypedArray(
this._pickingColors as TypedArray,
attrGroupStart * 12,
attrGroupCount * 12
);
if (this.isTextured) {
this._pathTexParamArr = spliceTypedArray(
this._pathTexParamArr as TypedArray,
attrGroupStart * 12,
attrGroupCount * 12
);
this._texCoordArr = spliceTypedArray(
this._texCoordArr as TypedArray,
attrGroupStart * 16,
attrGroupCount * 16
);
}
if (index === 0 && this._path3v.length > 0) {
this._colors = spliceTypedArray(this._colors as TypedArray, 0, 16);
this._thicknessArr = spliceTypedArray(this._thicknessArr as TypedArray, 0, 4);
this._pickingColors = spliceTypedArray(this._pickingColors as TypedArray, 0, 12);
if (this.isTextured) {
this._pathTexParamArr = spliceTypedArray(this._pathTexParamArr as TypedArray, 0, 12);
this._texCoordArr = spliceTypedArray(this._texCoordArr as TypedArray, 0, 16);
}
}
this._rebuildIndexes();
this._markGeometryBuffersChanged(true);
}
public appendPath3v(path3v: SegmentPath3vExt, pathColors?: NumberArray4[], opacity: number = 1.0) {
if (!path3v || path3v.length === 0) {
return;
}
const segIndex = this._path3v.length;
this._segmentOpacity[segIndex] = opacity;
this._path3v.push(path3v);
this._pathLonLat.push([]);
this._syncPathClosedLength(this._path3v.length);
this._syncSrcLength(this._path3v.length);
this._pathColors[segIndex] = pathColors && pathColors.length ? pathColors : this._pathColors[segIndex] || [];
const segPickingColors = this._pathPickingColors[segIndex] || (this._pathPickingColors[segIndex] = []);
if (!segPickingColors.length) {
segPickingColors.push([this._pickingColor[R], this._pickingColor[G], this._pickingColor[B]]);
}
this._segmentThickness[segIndex] = this._segmentThickness[segIndex] ?? this._thickness;
if (this.isTextured) {
this._resolveSegmentTexParams(segIndex);
}
this._pathLengths.length = this._path3v.length + 1;
this._pathLengths[segIndex + 1] = (this._pathLengths[segIndex] || 0) + path3v.length;
if (!this._scene) {
return;
}
const ellipsoid = (this._scene as Planet).ellipsoid;
this._verticesHigh = makeArray(this._verticesHigh);
this._verticesLow = makeArray(this._verticesLow);
this._colors = makeArray(this._colors);
this._thicknessArr = makeArray(this._thicknessArr);
this._orders = makeArray(this._orders);
this._indexes = makeArray(this._indexes);
this._pickingColors = makeArray(this._pickingColors);
if (this.isTextured) {
this._pathTexParamArr = makeArray(this._pathTexParamArr);
this._texCoordArr = makeArray(this._texCoordArr);
this._boundingSphereArr = makeArray(this._boundingSphereArr);
}
this._pathLonLat[segIndex] = [];
this._pathLonLatMerc[segIndex] = [];
this._path3v[segIndex] = [];
this.__appendLineDataCore(
[path3v],
[this._pathColors[segIndex] || []],
[segPickingColors],
this._defaultColor as NumberArray4,
this._segmentOpacity,
this._pathClosed,
this._verticesHigh as number[],
this._verticesLow as number[],
this._orders as number[],
this._indexes as number[],
this._texCoordArr as number[],
ellipsoid || null,
this._path3v,
this._pathLonLat,
this._pathLonLatMerc,
this._extent,
this._colors as number[],
this._thicknessArr as number[],
this._pathTexParamArr as number[],
this._boundingSphereArr as number[],
this._pickingColors as number[],
F3V,
segIndex,
FDETECT,
false,
false
);
this._markGeometryBuffersChanged(true);
if (this.isTextured && this._handler) {
this.setSrc(this._src);
}
}
public appendPathLonLat(pathLonLat: SegmentPathLonLatExt, pathColors?: NumberArray4[], opacity: number = 1) {
if (!pathLonLat || pathLonLat.length === 0) return;
const segIndex = this._pathLonLat.length;
this._segmentOpacity[segIndex] = opacity;
this._path3v.push([]);
this._pathLonLat.push(pathLonLat);
this._syncPathClosedLength(this._pathLonLat.length);
this._syncSrcLength(this._pathLonLat.length);
this._pathColors[segIndex] = pathColors && pathColors.length ? pathColors : this._pathColors[segIndex] || [];
const segPickingColors = this._pathPickingColors[segIndex] || (this._pathPickingColors[segIndex] = []);
if (!segPickingColors.length) {
segPickingColors.push([this._pickingColor[R], this._pickingColor[G], this._pickingColor[B]]);
}
this._segmentThickness[segIndex] = this._segmentThickness[segIndex] ?? this._thickness;
if (this.isTextured) {
this._resolveSegmentTexParams(segIndex);
}
this._pathLengths.length = segIndex + 2;
this._pathLengths[segIndex + 1] = (this._pathLengths[segIndex] || 0) + pathLonLat.length;
if (!this._scene) {
return;
}
const ellipsoid = (this._scene as Planet).ellipsoid;
if (!ellipsoid) return;
this._verticesHigh = makeArray(this._verticesHigh);
this._verticesLow = makeArray(this._verticesLow);
this._colors = makeArray(this._colors);
this._thicknessArr = makeArray(this._thicknessArr);
this._orders = makeArray(this._orders);
this._indexes = makeArray(this._indexes);
this._pickingColors = makeArray(this._pickingColors);
if (this.isTextured) {
this._pathTexParamArr = makeArray(this._pathTexParamArr);
this._texCoordArr = makeArray(this._texCoordArr);
this._boundingSphereArr = makeArray(this._boundingSphereArr);
}
this._path3v[segIndex] = [];
this._pathLonLatMerc[segIndex] = [];
this.__appendLineDataCore(
[pathLonLat],
[this._pathColors[segIndex] || []],
[segPickingColors],
this._defaultColor as NumberArray4,
this._segmentOpacity,
this._pathClosed,
this._verticesHigh as number[],
this._verticesLow as number[],
this._orders as number[],
this._indexes as number[],
this._texCoordArr as number[],
ellipsoid,
this._path3v,
this._pathLonLat,
this._pathLonLatMerc,
this._extent,
this._colors as number[],
this._thicknessArr as number[],
this._pathTexParamArr as number[],
this._boundingSphereArr as number[],
this._pickingColors as number[],
FLONLAT,
segIndex,
FDETECT,
false,
false
);
this._markGeometryBuffersChanged(true);
if (this.isTextured && this._handler) {
this.setSrc(this._src);
}
}
/**
* Remove point from the path
* @param {number} index - Point index in a path segment
* @param {number} [segmentIndex=0] - Segment path index
*/
public removePoint(index: number, segmentIndex: number = 0) {
if (!Number.isInteger(segmentIndex) || segmentIndex < 0) {
return;
}
const segment = this._path3v[segmentIndex];
if (!segment || !Number.isInteger(index) || index < 0 || index >= segment.length) {
return;
}
segment.splice(index, 1);
if (segment.length === 0) {
this._path3v.splice(segmentIndex, 1);
if (segmentIndex < this._pathPickingColors.length) {
this._pathPickingColors.splice(segmentIndex, 1);
}
if (segmentIndex < this._src.length) {
this._src.splice(segmentIndex, 1);
}
if (segmentIndex < this._pathClosed.length) {
this._pathClosed.splice(segmentIndex, 1);
}
if (segmentIndex < this._segmentThickness.length) {
this._segmentThickness.splice(segmentIndex, 1);
}
if (segmentIndex < this._segmentTexParams.length) {
this._segmentTexParams.splice(segmentIndex, 1);
}
}
this.setPath3v(([] as SegmentPath3vExt[]).concat(this._path3v));
}
/**
* Insert point coordinates in a path segment
* @param {Vec3} point3v - Point coordinates
* @param {number} [index=0] - Index in the path
* @param {NumberArray4} [color] - Point color
* @param {number} [segmentIndex=0] - Path segment index
*/
public insertPoint3v(point3v: Vec3, index: number = 0, color?: NumberArray4, segmentIndex: number = 0) {
let p = ([] as SegmentPath3vExt[]).concat(this._path3v),
pp = p[segmentIndex];
if (pp) {
let c = ([] as SegmentPathColor[]).concat(this._pathColors);
pp.splice(index, 0, point3v);
if (color) {
let cc = c[segmentIndex];
if (!cc) {
cc = new Array(pp.length);
}
cc.splice(index, 0, color);
}
this.setPath3v(p, c);
} else {
this.addPoint3v(point3v, segmentIndex);
}
}
/**
* Append new point in the end of the path.
* @public
* @param {Vec3} point3v - New point coordinates.
* @param {number} [segmentIndex=0] - Path segment index, first by default.
* @param {NumberArray4} [color] - Point color.
* @param {number} [opacity=1] - Point opacity.
*/
public addPoint3v(point3v: Vec3, segmentIndex: number = 0, color?: NumberArray4, opacity: number = 1) {
if (segmentIndex < 0) return;
if (!this._scene) {
while (segmentIndex >= this._path3v.length) {
this._path3v.push([]);
}
this._path3v[segmentIndex].push(point3v);
if (!this._pathColors[segmentIndex]) {
this._pathColors[segmentIndex] = [];
}
const segColors = this._pathColors[segmentIndex];
const fallbackColor = (
segColors.length > 0 ? segColors[segColors.length - 1] : this._defaultColor
) as NumberArray4;
segColors.push(color || fallbackColor);
this._segmentThickness[segmentIndex] = this._segmentThickness[segmentIndex] || this._thickness;
if (this.isTextured) {
this._resolveSegmentTexParams(segmentIndex);
}
this._pathLengths.length = this._path3v.length + 1;
this._resizePathLengths(0);
this._syncPathClosedLength(this._path3v.length);
this._syncSrcLength(this._path3v.length);
return;
}
if (segmentIndex >= this._path3v.length) {
this.appendPath3v([point3v], color ? [color] : undefined, opacity);
return;
}
const segIndex = segmentIndex;
const seg = this._path3v[segIndex] as Vec3[];
const oldLen = seg.length;
const pointsBefore = this._pathLengths[segIndex];
seg.push(point3v);
const segColors = this._pathColors[segIndex] || (this._pathColors[segIndex] = []);
const fallbackColor = (
segColors.length > 0 ? segColors[segColors.length - 1] : this._defaultColor
) as NumberArray4;
segColors.push(color || fallbackColor);
const segPickingColors = this._pathPickingColors[segIndex] || (this._pathPickingColors[segIndex] = []);
segPickingColors.push([this._pickingColor[R], this._pickingColor[G], this._pickingColor[B]]);
const ellipsoid = (this._scene as Planet).ellipsoid;
if (ellipsoid) {
const lonLat = ellipsoid.cartesianToLonLat(point3v);
this._pathLonLat[segIndex] && this._pathLonLat[segIndex].push(lonLat);
this._pathLonLatMerc[segIndex] && this._pathLonLatMerc[segIndex].push(lonLat.forwardMercator());
if (lonLat.lon < this._extent.southWest.lon) this._extent.southWest.lon = lonLat.lon;
if (lonLat.lat < this._extent.southWest.lat) this._extent.southWest.lat = lonLat.lat;
if (lonLat.lon > this._extent.northEast.lon) this._extent.northEast.lon = lonLat.lon;
if (lonLat.lat > this._extent.northEast.lat) this._extent.northEast.lat = lonLat.lat;
}
// update pathLengths tail (+1 from segIndex+1)
for (let i = segIndex + 1, len = this._pathLengths.length; i < len; i++) {
this._pathLengths[i] += 1;
}
this._verticesHigh = makeArrayTyped(this._verticesHigh);
this._verticesLow = makeArrayTyped(this._verticesLow);
this._orders = makeArrayTyped(this._orders);
this._colors = makeArrayTyped(this._colors);
this._thicknessArr = makeArrayTyped(this._thicknessArr);
this._pickingColors = makeArrayTyped(this._pickingColors);
if (this.isTextured) {
this._pathTexParamArr = makeArrayTyped(this._pathTexParamArr);
this._texCoordArr = makeArrayTyped(this._texCoordArr);
}
const vertexGroupStart = pointsBefore + 2 * segIndex;
const oldCapGroup = vertexGroupStart + oldLen + 1;
const insertGroup = oldCapGroup + 1;
const v_high = new Vec3(),
v_low = new Vec3();
// overwrite old cap with new point
this.__doubleToTwoFloats(point3v, v_high, v_low);
const vh = this._verticesHigh;
const vl = this._verticesLow;
const vBase = oldCapGroup * 12;
for (let k = 0; k < 12; k += 3) {
vh[vBase + k] = v_high.x;
vh[vBase + k + 1] = v_high.y;
vh[vBase + k + 2] = v_high.z;
vl[vBase + k] = v_low.x;
vl[vBase + k + 1] = v_low.y;
vl[vBase + k + 2] = v_low.z;
}
// compute new cap
let cap: Vec3;
if (this._isSegmentClosed(segIndex)) {
cap = seg[0];
} else {
const p0 = seg[seg.length - 1];
const p1 = seg[seg.length - 2] || p0;
cap = new Vec3(p0.x + p0.x - p1.x, p0.y + p0.y - p1.y, p0.z + p0.z - p1.z);
}
this.__doubleToTwoFloats(cap, v_high, v_low);
const capBlock = new Float32Array([
v_high.x,
v_high.y,
v_high.z,
v_high.x,
v_high.y,
v_high.z,
v_high.x,
v_high.y,
v_high.z,
v_high.x,
v_high.y,
v_high.z
]);
const capBlockL = new Float32Array([
v_low.x,
v_low.y,
v_low.z,
v_low.x,
v_low.y,
v_low.z,
v_low.x,
v_low.y,
v_low.z,
v_low.x,
v_low.y,
v_low.z
]);
this._verticesHigh = insertTypedArray(this._verticesHigh as Float32Array, insertGroup * 12, capBlock);
this._verticesLow = insertTypedArray(this._verticesLow as Float32Array, insertGroup * 12, capBlockL);
const orderBlock = new Float32Array([1, -1, 2, -2]);
this._orders = insertTypedArray(this._orders as Float32Array, insertGroup * 4, orderBlock);
// overwrite cap group then insert new cap group
const attrGroupStart = pointsBefore + (segIndex === 0 ? 0 : 2 * segIndex - 1);
const oldAttrGroups = oldLen + (segIndex === 0 ? 1 : 2);
const oldAttrCapGroup = attrGroupStart + oldAttrGroups - 1;
const insertAttrGroup = oldAttrCapGroup + 1;
//
// thickness block
const thickness = this._segmentThickness[segIndex] ?? this._thickness;
const tArr = this._thicknessArr as Float32Array;
const tBase = oldAttrCapGroup * 4;
tArr[tBase] = tArr[tBase + 1] = tArr[tBase + 2] = tArr[tBase + 3] = thickness;
this._thicknessArr = insertTypedArray(
tArr,
insertAttrGroup * 4,
new Float32Array([thickness, thickness, thickness, thickness])
);
//
// colors block
const cArr = this._colors as Float32Array;
const cBase = oldAttrCapGroup * 16;
const cc: NumberArray4 = segColors[segColors.length - 1] || this._defaultColor;
const cr = srgbToLinear(cc[R]),
cg = srgbToLinear(cc[G]),
cb = srgbToLinear(cc[B]),
ca = (cc[A] != undefined ? cc[A] : 1.0) * opacity;
for (let k = 0; k < 16; k += 4) {
cArr[cBase + k] = cr;
cArr[cBase + k + 1] = cg;
cArr[cBase + k + 2] = cb;
cArr[cBase + k + 3] = ca;
}
const cBlock = new Float32Array([cr, cg, cb, ca, cr, cg, cb, ca, cr, cg, cb, ca, cr, cg, cb, ca]);
this._colors = insertTypedArray(cArr, insertAttrGroup * 16, cBlock);
//
// picking colors block
const pcArr = this._pickingColors as Float32Array;
const pcBase = oldAttrCapGroup * 12;
const pcc: NumberArray3 = segPickingColors[segPickingColors.length - 1] || this._pickingColor;
const pcr = pcc[R],
pcg = pcc[G],
pcb = pcc[B];
for (let k = 0; k < 12; k += 3) {
pcArr[pcBase + k] = pcr;
pcArr[pcBase + k + 1] = pcg;
pcArr[pcBase + k + 2] = pcb;
}
const pcBlock = new Float32Array([pcr, pcg, pcb, pcr, pcg, pcb, pcr, pcg, pcb, pcr, pcg, pcb]);
this._pickingColors = insertTypedArray(pcArr, insertAttrGroup * 12, pcBlock);
if (this.isTextured) {
//
// texture params block
const texParams = this._hasTexture(segIndex)
? this._resolveSegmentTexParams(segIndex)
: { texOffset: 0, strokeSize: 0, texOffsetSpeed: 0 };
const tpArr = this._pathTexParamArr as Float32Array;
const tpBase = oldAttrCapGroup * 12;
const tpBlock = new Float32Array([
texParams.texOffset,
texParams.strokeSize,
texParams.texOffsetSpeed,
texParams.texOffset,
texParams.strokeSize,
texParams.texOffsetSpeed,
texParams.texOffset,
texParams.strokeSize,
texParams.texOffsetSpeed,
texParams.texOffset,
texParams.strokeSize,
texParams.texOffsetSpeed
]);
tpArr.set(tpBlock, tpBase);
this._pathTexParamArr = insertTypedArray(tpArr, insertAttrGroup * 12, tpBlock);
//
// textures block
const atlas = this._hasTexture(segIndex) ? this._getAtlasTexCoordsForSegment(segIndex) : null;
const tcArr = this._texCoordArr as Float32Array;
const tcBase = oldAttrCapGroup * 16;
let tcBlock: Float32Array;
if (atlas) {
const minY = atlas[1],
imgHeight = atlas[3] - minY;
tcBlock = new Float32Array([
atlas[4],
atlas[5],
minY,
imgHeight,
atlas[2],
atlas[3],
minY,
imgHeight,
atlas[8],
atlas[9],
minY,
imgHeight,
atlas[0],
atlas[1],
minY,
imgHeight
]);
} else {
tcBlock = new Float32Array(16);
}
tcArr.set(tcBlock, tcBase);
this._texCoordArr = insertTypedArray(tcArr, insertAttrGroup * 16, tcBlock);
}
this._rebuildIndexes();
this._markGeometryBuffersChanged(true);
}
/**
* Append new geodetic point in the end of the path.
* @public
* @param {LonLat} lonLat - New coordinate.
* @param {number} [segmentIndex=0] - Path segment index, first by default.
* @param {NumberArray4} [color] - Point color.
* @param {number} [opacity=1]
*/
public addPointLonLat(lonLat: LonLat, segmentIndex: number = 0, color?: NumberArray4, opacity: number = 1) {
if (segmentIndex < 0) {
return;
}
const ellipsoid = this._scene ? (this._scene as Planet).ellipsoid : null;
if (!ellipsoid) {
while (segmentIndex >= this._pathLonLat.length) {
this._pathLonLat.push([]);
}
this._pathLonLat[segmentIndex].push(lonLat);
if (!this._pathColors[segmentIndex]) {
this._pathColors[segmentIndex] = [];
}
const segColors = this._pathColors[segmentIndex];
const fallbackColor = (
segColors.length > 0 ? segColors[segColors.length - 1] : this._defaultColor
) as NumberArray4;
segColors.push(color || fallbackColor);
this._segmentThickness[segmentIndex] = this._segmentThickness[segmentIndex] || this._thickness;
if (this.isTextured) {
this._resolveSegmentTexParams(segmentIndex);
}
this._pathLengths.length = this._pathLonLat.length + 1;
this._resizePathLengths(0);
this._syncPathClosedLength(this._pathLonLat.length);
this._syncSrcLength(this._pathLonLat.length);
return;
}
const point3v = ellipsoid.lonLatToCartesian(lonLat);
if (segmentIndex >= this._path3v.length) {
this.appendPath3v([point3v], color ? [color] : undefined, opacity);
segmentIndex = this._path3v.length - 1;
} else {
this.addPoint3v(point3v, segmentIndex, color, opacity);
}
const segLonLat = this._pathLonLat[segmentIndex] || (this._pathLonLat[segmentIndex] = []);
if (segLonLat.length === 0) {
segLonLat.push(lonLat);
} else {
segLonLat[segLonLat.length - 1] = lonLat;
}
const merc = lonLat.forwardMercator();
const segMerc = this._pathLonLatMerc[segmentIndex] || (this._pathLonLatMerc[segmentIndex] = []);
if (segMerc.length === 0) {
segMerc.push(merc);
} else {
segMerc[segMerc.length - 1] = merc;
}
if (lonLat.lon < this._extent.southWest.lon) this._extent.southWest.lon = lonLat.lon;
if (lonLat.lat < this._extent.southWest.lat) this._extent.southWest.lat = lonLat.lat;
if (lonLat.lon > this._extent.northEast.lon) this._extent.northEast.lon = lonLat.lon;
if (lonLat.lat > this._extent.northEast.lat) this._extent.northEast.lat = lonLat.lat;
}
/**
* Clear polyline data.
* @public
*/
public clear() {
this._clearData();
this._pathClosed = [];
this._segmentOpacity = [];
this._src = [];
this._image = [];
}
/**
* Change path point color
* @param {NumberArray4} color - New color
* @param {number} [index=0] - Point index
* @param {number} [segmentIndex=0] - Path segment index
* @param {number} [opacity=1.0]
*/
public setPointColor(color: NumberArray4, index: number = 0, segmentIndex: number = 0, opacity: number = 1.0) {
if (this._scene && index < this._path3v[segmentIndex].length) {
let colors = this._pathColors[segmentIndex];
if (!colors) {
if (this._path3v[segmentIndex] && index < this._path3v[segmentIndex].length) {
this._pathColors[segmentIndex] = new Array(this._path3v[segmentIndex].length);
} else {
return;
}
}
if (!colors[index]) {
colors[index] = [color[R], color[G], color[B], color[A] || 1.0];
} else {
colors[index][R] = color[R];
colors[index][G] = color[G];
colors[index][B] = color[B];
colors[index][A] = color[A] || 1.0;
}
let c = this._colors;
let k = index * 16 + this._pathLengths[segmentIndex] * 16 + 32 * segmentIndex;
writeQuadColor(c, k, color, opacity);
this._setChangedBuffer(COLORS_BUFFER);
} else {
let pathColors = this._pathColors[segmentIndex];
pathColors[index] = color;
}
}
// /**
// * Sets polyline opacity.
// * @public
// * @param {number} opacity - Opacity.
// */
// public setOpacity(opacity: number) {
// this._opacity = opacity;
// }
// /**
// * Gets polyline opacity.
// * @public
// */
// public getOpacity(): number {
// return this._opacity;
// }
/**
* Sets Polyline thickness in screen pixels.
* @public
* @param {number} altitude - ALtitude value.
*/
public setAltitude(altitude: number) {
this.altitude = altitude;
}
/**
* Sets polyline thickness in screen pixels.
* @public
* @param {number} thickness - Segment thickness.
* @param {number} [segmentIndex] - Segment index. If omitted, the last segment is used.
*/
public setThickness(thickness: number): void;
public setThickness(thickness: number, segmentIndex: number): void;
public setThickness(thickness: number, segmentIndex?: number): void {
const segIndex = segmentIndex !== undefined ? segmentIndex : Math.max(0, this._path3v.length - 1);
if (this._path3v.length === 0) {
this._thickness = thickness;
return;
}
if (segIndex < 0 || segIndex >= this._path3v.length) return;
this._segmentThickness[segIndex] = thickness;
if (this._scene) {
const groupsBefore = segIndex === 0 ? 0 : this._pathLengths[segIndex] + 2 * segIndex - 1;
const groupsCount = this._path3v[segIndex].length + 1 + (segIndex > 0 ? 1 : 0);
const start = groupsBefore * 4;
const end = (groupsBefore + groupsCount) * 4;
const ta = this._thicknessArr;
for (let i = start; i < end; i++) {
ta[i] = thickness;
}
this._setChangedBuffer(THICKNESS_BUFFER);
}
}
/**
* Sets polyline segment color.
* @public
* @param {string} htmlColor - HTML color.
* @param {number} [segmentIndex] - Segment index. If omitted, the last segment is used.
* @param {number} [opacity=1] - Segment opacity.
*/
public setColor(htmlColor: string): void;
public setColor(htmlColor: string, segmentIndex: number, opacity?: number): void;
public setColor(htmlColor: string, segmentIndex?: number, opacity: number = 1): void {
const segIndex = segmentIndex !== undefined ? segmentIndex : Math.max(0, this._path3v.length - 1);
const rgba = htmlColorToRgba(htmlColor);
const color: NumberArray4 = [rgba.x, rgba.y, rgba.z, rgba.w];
if (this._path3v.length === 0) {
this._defaultColor = new Float32Array(color);
return;
}
if (segIndex < 0 || segIndex >= this._path3v.length) return;
this.setPathColors([color], segIndex, opacity);
}
public setPathTexParams(texOffset: number | undefined, strokeSize: number | undefined, segmentIndex: number): void;
public setPathTexParams(
texOffset: number | undefined,
strokeSize: number | undefined,
texOffsetSpeed: number | undefined,
segmentIndex: number
): void;
public setPathTexParams(
texOffset: number | undefined,
strokeSize: number | undefined,
texOffsetSpeedOrSegmentIndex: number | undefined,
segmentIndex?: number
): void {
if (!this.isTextured) {
return;
}
const resolvedSegmentIndex = segmentIndex ?? texOffsetSpeedOrSegmentIndex ?? 0;
const texOffsetSpeed = segmentIndex === undefined ? undefined : texOffsetSpeedOrSegmentIndex;
const texParams = this._resolveSegmentTexParams(resolvedSegmentIndex);
if (texOffset !== undefined) {
texParams.texOffset = texOffset;
}
if (strokeSize !== undefined) {
texParams.strokeSize = strokeSize;
}
if (texOffsetSpeed !== undefined) {
texParams.texOffsetSpeed = texOffsetSpeed;
}
const resolvedTexOffset = texParams.texOffset;
const resolvedStrokeSize = texParams.strokeSize;
const resolvedTexOffsetSpeed = texParams.texOffsetSpeed;
if (!this._scene || resolvedSegmentIndex < 0 || resolvedSegmentIndex >= this._path3v.length) {
return;
}
const groupsBefore =
resolvedSegmentIndex === 0 ? 0 : this._pathLengths[resolvedSegmentIndex] + 2 * resolvedSegmentIndex - 1;
const groupsCount = this._path3v[resolvedSegmentIndex].length + 1 + (resolvedSegmentIndex > 0 ? 1 : 0);
const start = groupsBefore * 12;
const end = (groupsBefore + groupsCount) * 12;
const ta = this._pathTexParamArr;
for (let i = start; i < end; i += 3) {
ta[i] = resolvedTexOffset;
ta[i + 1] = resolvedStrokeSize;
ta[i + 2] = resolvedTexOffsetSpeed;
}
this._setChangedBuffer(TEXPARAM_BUFFER);
}
public setPathTexOffset(texOffset: number, segmentIndex: number): void {
if (!this.isTextured) {
return;
}
const texParams = this._resolveSegmentTexParams(segmentIndex);
this.setPathTexParams(texOffset, texParams.strokeSize, texParams.texOffsetSpeed, segmentIndex);
}
public setPathStrokeSize(strokeSize: number, segmentIndex: number): void {
if (!this.isTextured) {
return;
}
const texParams = this._resolveSegmentTexParams(segmentIndex);
this.setPathTexParams(texParams.texOffset, strokeSize, texParams.texOffsetSpeed, segmentIndex);
}
public setPathTexOffsetSpeed(texOffsetSpeed: number, segmentIndex: number): void {
if (!this.isTextured) {
return;
}
const texParams = this._resolveSegmentTexParams(segmentIndex);
this.setPathTexParams(texParams.texOffset, texParams.strokeSize, texOffsetSpeed, segmentIndex);
}
/**
* Sets visibility.
* @public
* @param {boolean} visibility - Polyline visibility.
*/
public setVisibility(visibility: boolean) {
this.visibility = visibility;
}
/**
* Gets Polyline visibility.
* @public
* @returns {boolean} Polyline visibility.
*/
public getVisibility(): boolean {
return this.visibility;
}
/**
* Assign with render node.
* @public
* @param {Scene} scene -
*/
public bindScene(scene: Scene) {
if (scene) {
this._scene = scene;
const hasLonLatData = this._pathLonLat.some((segment) => segment.length > 0);
if (hasLonLatData) {
this._createDataLonLat(([] as SegmentPathLonLatExt[]).concat(this._pathLonLat));
} else {
this._createData3v(([] as SegmentPath3vExt[]).concat(this._path3v));
}
this._refresh();
if (scene.renderer && scene.renderer.isInitialized()) {
this._update();
}
}
}
protected _clearData() {
//@ts-ignore
this._verticesHigh = null;
//@ts-ignore
this._verticesLow = null;
//@ts-ignore
this._orders = null;
//@ts-ignore
this._indexes = null;
//@ts-ignore
this._colors = null;
//@ts-ignore
this._thicknessArr = null;
//@ts-ignore
this._pathTexParamArr = null;
//@ts-ignore
this._pathPhaseArr = null;
//@ts-ignore
this._boundingSphereArr = null;
//@ts-ignore
this._texCoordArr = null;
//@ts-ignore
this._pickingColors = null;
this._verticesHigh = [];
this._verticesLow = [];
this._orders = [];
this._indexes = [];
this._colors = [];
this._thicknessArr = [];
this._pathTexParamArr = [];
this._pathPhaseArr = [];
this._boundingSphereArr = [];
this._texCoordArr = [];
this._pickingColors = [];
this._path3v.length = 0;
this._pathLonLat.length = 0;
this._pathLonLatMerc.length = 0;
this._path3v = [];
this._pathLonLat = [];
this._pathLonLatMerc = [];
}
protected _createData3v(path3v: SegmentPath3vExt[]) {
this._syncPathClosedLength(path3v.length);
this._syncSrcLength(path3v.length);
this._clearData();
this.__appendLineData3v(
path3v,
this._pathColors,
this._pathPickingColors,
this._defaultColor as NumberArray4,
this._segmentOpacity,
this._pathClosed,
this._verticesHigh as number[],
this._verticesLow as number[],
this._orders as number[],
this._indexes as number[],
(this._scene as Planet).ellipsoid,
this._pathLonLat,
this._path3v,
this._pathLonLatMerc,
this._extent,
this._colors as number[],
this._thicknessArr as number[],
this._pathTexParamArr as number[],
this._texCoordArr as number[],
this._boundingSphereArr as number[],
this._pickingColors as number[]
);
this._resizePathLengths(0);
this._updateAllTextureMetrics();
if (this.isTextured && this._handler) {
this.setSrc(this._src);
}
}
protected _createDataLonLat(pathLonlat: SegmentPathLonLatExt[]) {
this._syncPathClosedLength(pathLonlat.length);
this._syncSrcLength(pathLonlat.length);
this._clearData();
this.__appendLineDataLonLat(
pathLonlat,
this._pathColors,
this._pathPickingColors,
this._defaultColor as NumberArray4,
this._segmentOpacity,
this._pathClosed,
this._verticesHigh as number[],
this._verticesLow as number[],
this._orders as number[],
this._indexes as number[],
this._texCoordArr as number[],
(this._scene as Planet).ellipsoid,
this._path3v,
this._pathLonLat,
this._pathLonLatMerc,
this._extent,
this._colors as number[],
this._thicknessArr as number[],
this._pathTexParamArr as number[],
this._boundingSphereArr as number[],
this._pickingColors as number[]
);
this._resizePathLengths(0);
this._updateAllTextureMetrics();
if (this.isTextured && this._handler) {
this.setSrc(this._src);
}
}
/**
* Removes from an entity.
* @public
*/
public remove() {
this._entity = null;
this._pathColors.length = 0;
this._pathColors = [];
this._segmentOpacity.length = 0;
this._segmentOpacity = [];
this._segmentThickness.length = 0;
this._segmentThickness = [];
this._segmentTexParams.length = 0;
this._segmentTexParams = [];
this._pathClosed.length = 0;
this._pathClosed = [];
this._src.length = 0;
this._src = [];
this._image.length = 0;
this._image = [];
//@ts-ignore
this._verticesHigh = null;
//@ts-ignore
this._verticesLow = null;
//@ts-ignore
this._orders = null;
//@ts-ignore
this._indexes = null;
//@ts-ignore
this._colors = null;
//@ts-ignore
this._thicknessArr = null;
//@ts-ignore
this._pathTexParamArr = null;
//@ts-ignore
this._pathPhaseArr = null;
//@ts-ignore
this._boundingSphereArr = null;
//@ts-ignore
this._texCoordArr = null;
//@ts-ignore
this._pickingColors = null;
this._verticesHigh = [];
this._verticesLow = [];
this._orders = [];
this._indexes = [];
this._colors = [];
this._thicknessArr = [];
this._pathTexParamArr = [];
this._pathPhaseArr = [];
this._boundingSphereArr = [];
this._texCoordArr = [];
this._pickingColors = [];
this._deleteBuffers();
}
public setPathPickingColor3v(color: Vec3, segmentIndex: number) {
const r = color.x / 255.0;
const g = color.y / 255.0;
const b = color.z / 255.0;
const segmentPickingColors =
this._pathPickingColors[segmentIndex] || (this._pathPickingColors[segmentIndex] = []);
if (segmentPickingColors.length === 0) {
segmentPickingColors.push([r, g, b]);
} else {
for (let j = 0; j < segmentPickingColors.length; j++) {
const p = segmentPickingColors[j];
if (p) {
p[R] = r;
p[G] = g;
p[B] = b;
}
}
}
if (!this._scene) return;
const path = this._path3v[segmentIndex];
if (!path || path.length === 0) return;
const pointsBefore = this._pathLengths[segmentIndex];
const attrGroupStart = pointsBefore + (segmentIndex === 0 ? 0 : 2 * segmentIndex - 1);
const attrGroupCount = path.length + (segmentIndex === 0 ? 1 : 2);
const start = attrGroupStart * 12;
const end = (attrGroupStart + attrGroupCount) * 12;
const pc = this._pickingColors;
for (let i = start; i < end; i += 3) {
pc[i] = r;
pc[i + 1] = g;
pc[i + 2] = b;
}
this._setChangedBuffer(PICKINGCOLORS_BUFFER);
}
/**
* Returns polyline geodetic extent.
* @public
* @returns {Extent} Geodetic extent.
*/
public getExtent(): Extent {
return this._extent.clone();
}
/**
* Returns path cartesian coordinates.
* @returns {SegmentPath3vExt[]} Polyline path.
*/
public getPath3v(): SegmentPath3vExt[] {
return this._path3v;
}
/**
* Returns geodetic path coordinates.
* @returns {SegmentPathLonLatExt[]} Polyline path.
*/
public getPathLonLat(): SegmentPathLonLatExt[] {
return this._pathLonLat;
}
public getPathColors(): NumberArray4[][] {
return this._pathColors;
}
public setPathOpacity(opacity: number): void;
public setPathOpacity(opacity: number, segmentIndex: number): void;
public setPathOpacity(opacity: number, segmentIndex?: number) {
if (segmentIndex === undefined) {
for (let i = 0; i < this._path3v.length; i++) {
this.setPathOpacity(opacity, i);
}
return;
}
if (segmentIndex < 0 || segmentIndex >= this._path3v.length) {
return;
}
this._segmentOpacity[segmentIndex] = opacity;
const segPath = this._path3v[segmentIndex];
if (!segPath || segPath.length === 0 || !this._scene) {
return;
}
const segColors = this._pathColors[segmentIndex];
const segInputCount = segColors ? segColors.length : 0;
const lastInputColor = segInputCount > 0 ? segColors![segInputCount - 1] : undefined;
let currentColor = segColors && segColors[0] ? segColors[0] : (this._defaultColor as NumberArray4);
const groupsBefore = segmentIndex === 0 ? 0 : this._pathLengths[segmentIndex] + 2 * segmentIndex - 1;
const start = groupsBefore * 16;
const c = this._colors as number[];
let ck = start;
if (segmentIndex > 0) {
const a = (currentColor[A] != undefined ? currentColor[A] : 1.0) * opacity;
c[ck + 3] = c[ck + 7] = c[ck + 11] = c[ck + 15] = a;
ck += 16;
}
for (let i = 0, len = segPath.length; i < len; i++) {
if (segColors && segColors[i]) {
currentColor = segColors[i];
} else if (lastInputColor && i >= segInputCount) {
currentColor = lastInputColor;
}
const a = (currentColor[A] != undefined ? currentColor[A] : 1.0) * opacity;
c[ck + 3] = c[ck + 7] = c[ck + 11] = c[ck + 15] = a;
ck += 16;
}
currentColor = (segColors && segColors[segPath.length - 1]) || lastInputColor || currentColor;
const a = (currentColor[A] != undefined ? currentColor[A] : 1.0) * opacity;
c[ck + 3] = c[ck + 7] = c[ck + 11] = c[ck + 15] = a;
this._setChangedBuffer(COLORS_BUFFER);
}
public setPathColors(pathColors: SegmentPathColor[]): void;
public setPathColors(pathColors: SegmentPathColor, segmentIndex: number, opacity?: number): void;
public setPathColors(
pathColors: SegmentPathColor[] | SegmentPathColor,
segmentIndex?: number,
opacity: number = 1.0
) {
if (!pathColors) return;
if (segmentIndex === undefined) {
this._colors = [];
this._pathColors = ([] as SegmentPathColor[]).concat(pathColors as SegmentPathColor[]);
const hasLonLatTemplate =
this._pathLonLat.length > 0 && this._pathLonLat.some((seg) => seg && seg.length > 0);
const colorTemplate = (hasLonLatTemplate
? this._pathLonLat
: this._path3v) as unknown as SegmentPathLonLatExt[];
PolylineBatchRenderer.setPathColors(
colorTemplate,
pathColors as SegmentPathColor[],
this._defaultColor as NumberArray4,
this._colors,
opacity
);
this._setChangedBuffer(COLORS_BUFFER);
return;
}
if (segmentIndex < 0 || segmentIndex >= this._path3v.length) {
return;
}
this._segmentOpacity[segmentIndex] = opacity;
const segPath = this._path3v[segmentIndex];
if (!segPath || segPath.length === 0) {
return;
}
const segColorsInput = pathColors as SegmentPathColor;
let segColors = this._pathColors[segmentIndex];
if (!segColors) {
segColors = [];
this._pathColors[segmentIndex] = segColors;
}
if (segColors.length !== segPath.length) {
segColors.length = segPath.length;
}
const segInputCount = segColorsInput.length;
const lastInputColor = segInputCount > 0 ? segColorsInput[segInputCount - 1] : undefined;
let currentColor = this._defaultColor as NumberArray4;
if (!this._scene) {
for (let i = 0; i < segPath.length; i++) {
if (segColorsInput[i]) {
currentColor = segColorsInput[i];
} else if (lastInputColor && i >= segInputCount) {
currentColor = lastInputColor;
}
segColors[i] = currentColor;
}
return;
}
const groupsBefore = segmentIndex === 0 ? 0 : this._pathLengths[segmentIndex] + 2 * segmentIndex - 1;
const start = groupsBefore * 16;
const c = this._colors as number[];
let ck = start;
currentColor = segColorsInput[0] || (this._defaultColor as NumberArray4);
if (!segColorsInput[0] && lastInputColor) {
currentColor = lastInputColor;
}
if (segmentIndex > 0) {
writeQuadColor(c, ck, currentColor, opacity);
ck += 16;
}
for (let i = 0, len = segPath.length; i < len; i++) {
if (segColorsInput[i]) {
currentColor = segColorsInput[i];
} else if (lastInputColor && i >= segInputCount) {
currentColor = lastInputColor;
}
segColors[i] = currentColor;
writeQuadColor(c, ck, currentColor, opacity);
ck += 16;
}
currentColor = segColorsInput[segPath.length - 1] || lastInputColor || currentColor;
writeQuadColor(c, ck, currentColor, opacity);
this._setChangedBuffer(COLORS_BUFFER);
}
// /**
// * Sets polyline color
// * @param {string} htmlColor - HTML color.
// */
// public setColorHTML(htmlColor: string) {
//
// this._defaultColor = htmlColorToFloat32Array(htmlColor);
//
// let color = htmlColorToRgba(htmlColor),
// p = this._pathColors;
//
// for (let i = 0, len = p.length; i < len; i++) {
// let s = p[i];
// for (let j = 0, slen = s.length; j < slen; j++) {
// s[j][0] = color.x;
// s[j][1] = color.y;
// s[j][2] = color.z;
// s[j][3] = color.w;
// }
// }
//
// let c = this._colors;
// for (let i = 0, len = c.length; i < len; i += 4) {
// c[i] = color.x;
// c[i + 1] = color.y;
// c[i + 2] = color.z;
// c[i + 3] = color.w;
// }
//
// this._changedBuffers[COLORS_BUFFER] = true;
// }
public setPathLonLatFast(
pathLonLat: SegmentPathLonLatExt[],
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
) {
if (!pathColors) {
this.setPathLonLat(pathLonLat, undefined, true);
return;
}
const isSingleColor = Array.isArray(pathColors) && pathColors.length > 0 && typeof pathColors[0] === "number";
const normalizedPathColors = isSingleColor
? [pathColors as NumberArray4]
: (pathColors as (SegmentPathColor | NumberArray4)[]);
this.setPathLonLat(pathLonLat, normalizedPathColors, true);
}
public setPath3vFast(
path3v: SegmentPath3vExt[],
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
) {
if (!pathColors) {
this.setPath3v(path3v, undefined, true);
return;
}
const isSingleColor = Array.isArray(pathColors) && pathColors.length > 0 && typeof pathColors[0] === "number";
const normalizedPathColors = isSingleColor
? [pathColors as NumberArray4]
: (pathColors as (SegmentPathColor | NumberArray4)[]);
this.setPath3v(path3v, normalizedPathColors, true);
}
protected _applyPathColorsFromInput(
pathInput: SegmentPath3vExt[] | SegmentPathLonLatExt[],
colorsInput: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
) {
const hasUniformPathColor =
Array.isArray(colorsInput) && colorsInput.length > 0 && typeof colorsInput[0] === "number";
this._pathColors = new Array(pathInput.length);
for (let j = 0, len = pathInput.length; j < len; j++) {
const segmentColorInput: SegmentPathColor | NumberArray4 | undefined = hasUniformPathColor
? (colorsInput as NumberArray4)
: (colorsInput as (SegmentPathColor | NumberArray4)[])[j];
const hasUniformSegmentColor =
!!segmentColorInput &&
Array.isArray(segmentColorInput) &&
segmentColorInput.length > 0 &&
typeof segmentColorInput[0] === "number";
const segmentColorCount =
!hasUniformSegmentColor && segmentColorInput && Array.isArray(segmentColorInput)
? (segmentColorInput as SegmentPathColor).length
: 0;
const lastSegmentColor =
segmentColorCount > 0 ? (segmentColorInput as SegmentPathColor)[segmentColorCount - 1] : undefined;
const outSegmentColors = new Array(pathInput[j].length);
let color = this._defaultColor as NumberArray4;
for (let i = 0, plen = pathInput[j].length; i < plen; i++) {
if (hasUniformSegmentColor) {
color = segmentColorInput as NumberArray4;
} else if (segmentColorInput && (segmentColorInput as SegmentPathColor)[i]) {
color = (segmentColorInput as SegmentPathColor)[i];
} else if (lastSegmentColor && i >= segmentColorCount) {
color = lastSegmentColor;
}
outSegmentColors[i] = color;
}
this._pathColors[j] = outSegmentColors;
}
}
public getSize(index: number = 0): number {
return this._path3v[index].length;
}
/**
* Sets Polyline cartesian coordinates.
* @public
* @param {SegmentPath3vExt[]} path3v - Polyline path cartesian coordinates. (exactly 3 entries)
* @param {SegmentPathColor[]} [pathColors] - Polyline path cartesian coordinates. (exactly 3 entries)
* @param {Boolean} [forceEqual=false] - Makes assigning faster for size equal coordinates array.
*/
public setPath3v(
path3v: SegmentPath3vExt[],
pathColors?: (SegmentPathColor | NumberArray4)[],
forceEqual?: boolean
): void;
public setPath3v(
path3v: SegmentPath3vExt,
pathColors?: SegmentPathColor | NumberArray4,
forceEqual?: boolean,
segmentIndex?: number
): void;
public setPath3v(
path3v: SegmentPath3vExt[] | SegmentPath3vExt,
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4,
forceEqual: boolean = false,
segmentIndex?: number
) {
if (this._scene) {
if (forceEqual) {
if (segmentIndex !== undefined) {
const segmentPath = path3v as SegmentPath3vExt;
this._applyForceEqualSegmentUpdate(
segmentPath,
segmentIndex,
pathColors as SegmentPathColor | NumberArray4,
this._path3v as SegmentPath3vExt[],
(p, idx, c) => this._setSegmentEqualPath3v(p, idx, c),
(next) => this._createData3v(next),
true
);
return;
} else {
this._setEqualPath3v(
path3v as SegmentPath3vExt[],
pathColors as (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
);
}
this._updateAllTextureMetrics();
this._setChangedBuffer(VERTICES_BUFFER);
if (pathColors) {
this._setChangedBuffer(COLORS_BUFFER);
}
} else {
if (pathColors) {
this._applyPathColorsFromInput(path3v as SegmentPath3vExt[], pathColors);
}
this._createData3v(path3v as SegmentPath3vExt[]);
this._markGeometryBuffersChanged(true);
}
} else {
if (pathColors) {
this._applyPathColorsFromInput(path3v as SegmentPath3vExt[], pathColors);
}
this._path3v = ([] as SegmentPath3vExt[]).concat(path3v as SegmentPath3vExt[]);
this._syncPathClosedLength(this._path3v.length);
this._syncSrcLength(this._path3v.length);
}
}
/**
* Sets polyline geodetic coordinates.
* @public
* @param {SegmentPathLonLat[]} pathLonLat - Polyline path cartesian coordinates.
* @param {SegmentPathColor[]} pathColors - Polyline path points colors.
* @param {Boolean} [forceEqual=false] - OPTIMIZATION FLAG: Makes assigning faster for size equal coordinates array.
*/
public setPathLonLat(
pathLonLat: SegmentPathLonLatExt[],
pathColors?: (SegmentPathColor | NumberArray4)[],
forceEqual?: boolean
): void;
public setPathLonLat(
pathLonLat: SegmentPathLonLatExt,
pathColors?: SegmentPathColor | NumberArray4,
forceEqual?: boolean,
segmentIndex?: number
): void;
public setPathLonLat(
pathLonLat: SegmentPathLonLatExt[] | SegmentPathLonLatExt,
pathColors?: (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4,
forceEqual: boolean = false,
segmentIndex?: number
) {
if (this._scene && (this._scene as Planet).ellipsoid) {
if (forceEqual) {
if (segmentIndex !== undefined) {
const segmentPath = pathLonLat as SegmentPathLonLatExt;
this._applyForceEqualSegmentUpdate(
segmentPath,
segmentIndex,
pathColors as SegmentPathColor | NumberArray4,
this._pathLonLat as SegmentPathLonLatExt[],
(p, idx, c) => this._setSegmentEqualLonLat(p, idx, c),
(next) => this._createDataLonLat(next),
false
);
return;
} else {
this._setEqualPathLonLat(
pathLonLat as SegmentPathLonLat[],
pathColors as (SegmentPathColor | NumberArray4)[] | SegmentPathColor | NumberArray4
);
}
this._updateAllTextureMetrics();
this._setChangedBuffer(VERTICES_BUFFER);
if (pathColors) {
this._setChangedBuffer(COLORS_BUFFER);
}
} else {
if (pathColors) {
this._applyPathColorsFromInput(pathLonLat as SegmentPathLonLatExt[], pathColors);
}
this._createDataLonLat(pathLonLat as SegmentPathLonLatExt[]);
this._markGeometryBuffersChanged(false);
}
} else {
if (pathColors) {
this._applyPathColorsFromInput(pathLonLat as SegmentPathLonLatExt[], pathColors);
}
this._pathLonLat = ([] as SegmentPathLonLatExt[]).concat(pathLonLat);
this._syncPathClosedLength(this._pathLonLat.length);
this._syncSrcLength(this._pathLonLat.length);
}
}
public drawOpaque() {
if (this.visibility && this._path3v.length) {
this._update();
let r = this._scene!.renderer!;
let sh = this.isTextured ? r.handler.programs.polylineTex : r.handler.programs.polylinePlain;
let p = sh;
let gl = r.handler.gl!,
sha = p.attributes,
shu = p.uniforms;
let ec = this._handler!._entityCollection;
sh.activate();
gl.disable(gl.CULL_FACE);
gl.uniform1f(shu.depthOffset, ec.depthOffset);
gl.uniform1f(shu.depthOffsetNear, r.activeCamera!.frustum.depthOffsetNear);
gl.uniformMatrix4fv(shu.proj, false, r.activeCamera!.getProjectionMatrix());
gl.uniformMatrix4fv(shu.view, false, r.activeCamera!.getViewMatrix());
gl.uniform3fv(shu.rtcEyePositionHigh, this._handler!._rtcEyePositionHigh);
gl.uniform3fv(shu.rtcEyePositionLow, this._handler!._rtcEyePositionLow);
gl.uniform4fv(shu.visibleSphere, this._visibleSphere);
gl.uniform2fv(shu.viewport, [r.handler.canvas!.width, r.handler.canvas!.height]);
gl.uniform1f(shu.thicknessScale, 0.5);
gl.uniform1f(shu.opacity, this._opacity * ec._fadingOpacity);
if (this.isTextured) {
const timeSec = globalThis.performance.now() * 0.001;
gl.uniform1f(shu.time, timeSec % ANIMATION_TIME_WRAP_SEC);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer!);
gl.vertexAttribPointer(sha.texCoord, this._texCoordBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pathTexParamBuffer!);
gl.vertexAttribPointer(sha.textureParams, this._pathTexParamBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pathPhaseBuffer!);
gl.vertexAttribPointer(sha.pathPhase, this._pathPhaseBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._boundingSphereBuffer!);
gl.vertexAttribPointer(sha.boundingSphere, this._boundingSphereBuffer!.itemSize, gl.FLOAT, false, 0, 0);
}
gl.bindBuffer(gl.ARRAY_BUFFER, this._colorsBuffer!);
gl.vertexAttribPointer(sha.color, this._colorsBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._thicknessBuffer!);
gl.vertexAttribPointer(sha.thickness, this._thicknessBuffer!.itemSize, gl.FLOAT, false, 0, 0);
let v = this._verticesHighBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevHigh, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentHigh, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextHigh, v.itemSize, gl.FLOAT, false, 12, 96);
v = this._verticesLowBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevLow, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentLow, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextLow, v.itemSize, gl.FLOAT, false, 12, 96);
gl.bindBuffer(gl.ARRAY_BUFFER, this._ordersBuffer!);
gl.vertexAttribPointer(sha.order, this._ordersBuffer!.itemSize, gl.FLOAT, false, 4, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexesBuffer!);
gl.drawElements(gl.TRIANGLE_STRIP, this._indexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
gl.enable(gl.CULL_FACE);
}
}
public drawTransparent() {
if (this.visibility && this._path3v.length) {
this._update();
let r = this._scene!.renderer!;
let sh = this.isTextured ? r.handler.programs.polylineTexWoit : r.handler.programs.polylineWoitPlain;
let p = sh;
let gl = r.handler.gl!,
sha = p.attributes,
shu = p.uniforms;
let ec = this._handler!._entityCollection;
sh.activate();
gl.disable(gl.CULL_FACE);
gl.uniform1f(shu.useReverseDepth, r.activeCamera.reverseDepthActive ? 1.0 : 0.0);
gl.uniform1f(shu.depthOffset, ec.depthOffset);
gl.uniform1f(shu.depthOffsetNear, r.activeCamera!.frustum.depthOffsetNear);
gl.uniformMatrix4fv(shu.proj, false, r.activeCamera!.getProjectionMatrix());
gl.uniformMatrix4fv(shu.view, false, r.activeCamera!.getViewMatrix());
gl.uniform3fv(shu.rtcEyePositionHigh, this._handler!._rtcEyePositionHigh);
gl.uniform3fv(shu.rtcEyePositionLow, this._handler!._rtcEyePositionLow);
gl.uniform4fv(shu.visibleSphere, this._visibleSphere);
gl.uniform2fv(shu.viewport, [r.handler.canvas!.width, r.handler.canvas!.height]);
gl.uniform1f(shu.thicknessScale, 0.5);
gl.uniform1f(shu.opacity, this._opacity * ec._fadingOpacity);
if (this.isTextured) {
const timeSec = globalThis.performance.now() * 0.001;
gl.uniform1f(shu.time, timeSec % ANIMATION_TIME_WRAP_SEC);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer!);
gl.vertexAttribPointer(sha.texCoord, this._texCoordBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pathTexParamBuffer!);
gl.vertexAttribPointer(sha.textureParams, this._pathTexParamBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pathPhaseBuffer!);
gl.vertexAttribPointer(sha.pathPhase, this._pathPhaseBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._boundingSphereBuffer!);
gl.vertexAttribPointer(sha.boundingSphere, this._boundingSphereBuffer!.itemSize, gl.FLOAT, false, 0, 0);
}
gl.bindBuffer(gl.ARRAY_BUFFER, this._colorsBuffer!);
gl.vertexAttribPointer(sha.color, this._colorsBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._thicknessBuffer!);
gl.vertexAttribPointer(sha.thickness, this._thicknessBuffer!.itemSize, gl.FLOAT, false, 0, 0);
let v = this._verticesHighBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevHigh, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentHigh, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextHigh, v.itemSize, gl.FLOAT, false, 12, 96);
v = this._verticesLowBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevLow, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentLow, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextLow, v.itemSize, gl.FLOAT, false, 12, 96);
gl.bindBuffer(gl.ARRAY_BUFFER, this._ordersBuffer!);
gl.vertexAttribPointer(sha.order, this._ordersBuffer!.itemSize, gl.FLOAT, false, 4, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexesBuffer!);
gl.drawElements(gl.TRIANGLE_STRIP, this._indexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
gl.enable(gl.CULL_FACE);
}
}
public drawPicking() {
if (this.visibility && this._path3v.length) {
this._update();
let rn = this._scene!;
let r = rn.renderer!;
let sh = r.handler.programs.polyline_picking;
let p = sh;
let gl = r.handler.gl!,
sha = p.attributes,
shu = p.uniforms;
let ec = this._handler!._entityCollection;
sh.activate();
gl.disable(gl.CULL_FACE);
gl.uniform1f(shu.depthOffset, ec.depthOffset);
gl.uniform1f(shu.depthOffsetNear, r.activeCamera!.frustum.depthOffsetNear);
gl.uniformMatrix4fv(shu.proj, false, r.activeCamera!.getProjectionMatrix());
gl.uniformMatrix4fv(shu.view, false, r.activeCamera!.getViewMatrix());
gl.bindBuffer(gl.ARRAY_BUFFER, this._pickingColorsBuffer!);
gl.vertexAttribPointer(sha.pickingColor, this._pickingColorsBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform3fv(shu.rtcEyePositionHigh, this._handler!._rtcEyePositionHigh);
gl.uniform3fv(shu.rtcEyePositionLow, this._handler!._rtcEyePositionLow);
gl.uniform4fv(shu.visibleSphere, this._visibleSphere);
gl.uniform2fv(shu.viewport, [r.handler.canvas!.width, r.handler.canvas!.height]);
gl.uniform1f(shu.thicknessScale, 0.5 * ec.pickingScale[0]);
let v = this._verticesHighBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevHigh, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentHigh, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextHigh, v.itemSize, gl.FLOAT, false, 12, 96);
v = this._verticesLowBuffer!;
gl.bindBuffer(gl.ARRAY_BUFFER, v);
gl.vertexAttribPointer(sha.prevLow, v.itemSize, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(sha.currentLow, v.itemSize, gl.FLOAT, false, 12, 48);
gl.vertexAttribPointer(sha.nextLow, v.itemSize, gl.FLOAT, false, 12, 96);
gl.bindBuffer(gl.ARRAY_BUFFER, this._ordersBuffer as WebGLBuffer);
gl.vertexAttribPointer(sha.order, this._ordersBuffer!.itemSize, gl.FLOAT, false, 4, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._thicknessBuffer!);
gl.vertexAttribPointer(sha.thickness, this._thicknessBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexesBuffer as WebGLBuffer);
gl.drawElements(gl.TRIANGLE_STRIP, this._indexesBuffer!.numItems, gl.UNSIGNED_INT, 0);
gl.enable(gl.CULL_FACE);
}
}
/**
* Refresh buffers.
* @protected
*/
protected _refresh() {
let i = this._changedBuffers.length;
while (i--) {
this._setChangedBuffer(i);
}
}
protected _setChangedBuffer(index: number) {
this._changedBuffers[index] = true;
this._handler && this._handler.requestRedraw();
}
/**
* Updates render buffers.
* @protected
*/
protected _update() {
if (this._scene) {
let i = this._changedBuffers.length;
while (i--) {
if (this._changedBuffers[i]) {
this._buffersUpdateCallbacks[i].call(this);
this._changedBuffers[i] = false;
}
}
}
}
/**
* Clear GL buffers.
* @public
*/
public _deleteBuffers() {
if (this._scene) {
let r = this._scene.renderer!,
gl = r.handler.gl!;
gl.deleteBuffer(this._verticesHighBuffer!);
gl.deleteBuffer(this._verticesLowBuffer!);
gl.deleteBuffer(this._ordersBuffer!);
gl.deleteBuffer(this._indexesBuffer!);
gl.deleteBuffer(this._colorsBuffer!);
gl.deleteBuffer(this._texCoordBuffer!);
gl.deleteBuffer(this._thicknessBuffer!);
gl.deleteBuffer(this._pathTexParamBuffer!);
gl.deleteBuffer(this._pathPhaseBuffer!);
gl.deleteBuffer(this._boundingSphereBuffer!);
gl.deleteBuffer(this._pickingColorsBuffer!);
this._verticesHighBuffer = null;
this._verticesLowBuffer = null;
this._ordersBuffer = null;
this._indexesBuffer = null;
this._colorsBuffer = null;
this._texCoordBuffer = null;
this._thicknessBuffer = null;
this._pathTexParamBuffer = null;
this._pathPhaseBuffer = null;
this._boundingSphereBuffer = null;
this._pickingColorsBuffer = null;
}
}
/**
* Creates vertices buffers.
* @protected
*/
protected _createVerticesBuffer() {
let h = this._scene!.renderer!.handler;
let numItems = this._verticesHigh.length / 3;
if (!this._verticesHighBuffer || this._verticesHighBuffer.numItems !== numItems) {
h.gl!.deleteBuffer(this._verticesHighBuffer!);
h.gl!.deleteBuffer(this._verticesLowBuffer!);
this._verticesHighBuffer = h.createStreamArrayBuffer(3, numItems);
this._verticesLowBuffer = h.createStreamArrayBuffer(3, numItems);
}
this._verticesHigh = makeArrayTyped(this._verticesHigh);
this._verticesLow = makeArrayTyped(this._verticesLow);
h.setStreamArrayBuffer(this._verticesHighBuffer!, this._verticesHigh as TypedArray);
h.setStreamArrayBuffer(this._verticesLowBuffer!, this._verticesLow as TypedArray);
}
/**
* Creates gl index and order buffer.
* @protected
*/
protected _createIndexBuffer() {
let h = this._scene!.renderer!.handler;
h.gl!.deleteBuffer(this._ordersBuffer!);
h.gl!.deleteBuffer(this._indexesBuffer!);
this._orders = makeArrayTyped(this._orders);
this._ordersBuffer = h.createArrayBuffer(this._orders as TypedArray, 1, this._orders.length / 2);
this._indexes = makeArrayTyped(this._indexes, Uint32Array);
this._indexesBuffer = h.createElementArrayBuffer(this._indexes as TypedArray, 1, this._indexes.length);
}
protected _createColorsBuffer() {
let h = this._scene!.renderer!.handler;
this._colors = makeArrayTyped(this._colors);
const ta = this._colors as TypedArray;
const numItems = ta.length / 4;
if (!this._colorsBuffer || this._colorsBuffer.numItems !== numItems) {
h.gl!.deleteBuffer(this._colorsBuffer!);
this._colorsBuffer = h.createStreamArrayBuffer(4, numItems);
}
h.setStreamArrayBuffer(this._colorsBuffer!, ta);
}
protected _createPickingColorsBuffer() {
let h = this._scene!.renderer!.handler;
this._pickingColors = makeArrayTyped(this._pickingColors);
const ta = this._pickingColors as TypedArray;
const numItems = ta.length / 3;
if (!this._pickingColorsBuffer || this._pickingColorsBuffer.numItems !== numItems) {
h.gl!.deleteBuffer(this._pickingColorsBuffer!);
this._pickingColorsBuffer = h.createStreamArrayBuffer(3, numItems);
}
h.setStreamArrayBuffer(this._pickingColorsBuffer!, ta);
}
protected _createThicknessBuffer() {
let h = this._scene!.renderer!.handler;
this._thicknessArr = makeArrayTyped(this._thicknessArr);
const ta = this._thicknessArr as TypedArray;
if (!this._thicknessBuffer || this._thicknessBuffer.numItems !== ta.length) {
h.gl!.deleteBuffer(this._thicknessBuffer!);
this._thicknessBuffer = h.createStreamArrayBuffer(1, ta.length);
}
h.setStreamArrayBuffer(this._thicknessBuffer!, ta);
}
protected _createTexParamsBuffer() {
if (!this.isTextured) {
return;
}
let h = this._scene!.renderer!.handler;
this._pathTexParamArr = makeArrayTyped(this._pathTexParamArr);
const ta = this._pathTexParamArr as TypedArray;
const numItems = ta.length / 3;
if (!this._pathTexParamBuffer || this._pathTexParamBuffer.numItems !== numItems) {
h.gl!.deleteBuffer(this._pathTexParamBuffer!);
this._pathTexParamBuffer = h.createStreamArrayBuffer(3, numItems);
}
h.setStreamArrayBuffer(this._pathTexParamBuffer!, ta);
}
protected _createPathPhaseBuffer() {
if (!this.isTextured) {
return;
}
let h = this._scene!.renderer!.handler;
this._pathPhaseArr = makeArrayTyped(this._pathPhaseArr);
const ta = this._pathPhaseArr as TypedArray;
if (!this._pathPhaseBuffer || this._pathPhaseBuffer.numItems !== ta.length) {
h.gl!.deleteBuffer(this._pathPhaseBuffer!);
this._pathPhaseBuffer = h.createStreamArrayBuffer(1, ta.length);
}
h.setStreamArrayBuffer(this._pathPhaseBuffer!, ta);
}
protected _createBoundingSphereBuffer() {
if (!this.isTextured) {
return;
}
let h = this._scene!.renderer!.handler;
this._boundingSphereArr = makeArrayTyped(this._boundingSphereArr);
const ta = this._boundingSphereArr as TypedArray;
const numItems = ta.length / 4;
if (!this._boundingSphereBuffer || this._boundingSphereBuffer.numItems !== numItems) {
h.gl!.deleteBuffer(this._boundingSphereBuffer!);
this._boundingSphereBuffer = h.createStreamArrayBuffer(4, numItems);
}
h.setStreamArrayBuffer(this._boundingSphereBuffer!, ta);
}
public _createTexCoordBuffer() {
if (!this.isTextured) {
return;
}
let h = this._scene!.renderer!.handler;
h.gl!.deleteBuffer(this._texCoordBuffer!);
this._texCoordArr = makeArrayTyped(this._texCoordArr);
this._texCoordBuffer = h.createArrayBuffer(this._texCoordArr as TypedArray, 4, this._texCoordArr.length / 4);
}
public setVisibleSphere(p: Vec3, r: number) {
this._visibleSpherePosition.copy(p);
this._updateVisibleSphereRTC();
this._visibleSphere[3] = r;
}
protected _updateVisibleSphereRTC() {
const relativeCenter = this._handler?._relativeCenter;
if (relativeCenter) {
this._visibleSphere[0] = this._visibleSpherePosition.x - relativeCenter.x;
this._visibleSphere[1] = this._visibleSpherePosition.y - relativeCenter.y;
this._visibleSphere[2] = this._visibleSpherePosition.z - relativeCenter.z;
} else {
this._visibleSphere[0] = this._visibleSpherePosition.x;
this._visibleSphere[1] = this._visibleSpherePosition.y;
this._visibleSphere[2] = this._visibleSpherePosition.z;
}
}
public updateRTCPosition() {
if (this._handler && this._scene) {
this._updateVisibleSphereRTC();
this._setEqualPath3v(this._path3v);
if (this.isTextured) {
this._rebuildBoundingSphereArr();
}
}
this._setChangedBuffer(VERTICES_BUFFER);
if (this.isTextured) {
this._setChangedBuffer(BOUNDING_SPHERE_BUFFER);
}
}
}
export { PolylineBatchRenderer };