entity/strip/Strip.tstypescript
import * as utils from "../../utils/shared";
import { Entity } from "../Entity";
import { Line3 } from "../../math/Line3";
import { Scene } from "../../scene/Scene";
import { Vec3 } from "../../math/Vec3";
import { Vec4 } from "../../math/Vec4";
import type { NumberArray3 } from "../../math/Vec3";
import type { NumberArray4 } from "../../math/Vec4";
import { StripHandler } from "./StripHandler";
import type { WebGLBufferExt } from "../../webgl/Handler";
import type { ShaderProgram } from "../../webgl/ShaderProgram";
import { srgbToLinear } from "../../utils/colorSpace";
import { splitPathTime } from "../pathTime";
type TPoiExt = Vec3 | NumberArray3;
type TStripExt = [TPoiExt, TPoiExt];
type TPoi = Vec3;
type TStrip = [TPoi, TPoi];
//type TStrip = TEdge[];
export interface IStripParams {
visibility?: boolean;
color?: string | NumberArray4 | Vec4;
opacity?: number;
path?: TStrip[];
pathTimes?: number[];
}
let _tempHigh = new Vec3(),
_tempLow = new Vec3(),
_tempRtc = new Vec3();
/**
* Strip object.
* @class
* @param {*} [options] - Strip options:
* @param {boolean} [options.visibility] - Strip visibility.
* @param {number[]} [options.pathTimes] - Time of every path edge. Makes the strip follow the
* entity collection time, see {@link EntityCollection#setTime} and {@link EntityCollection#setTimeOpacity}.
* @example <caption>Stripe example</caption>
* new og.Entity({
* strip: {
* gridSize: 10,
* path: [
* [[],[]],
* [[],[]]
* ]
* }
* });
*/
class Strip {
static __counter__: number = 0;
protected __id: number;
/**
* Strip visibility.
* @public
* @type {boolean}
*/
public visibility: boolean;
protected _color: Float32Array;
/**
* Parent collection render node.
* @protected
* @type {Scene}
*/
protected _scene: Scene | null;
/**
* Entity instance that holds this strip.
* @public
* @type {Entity}
*/
public _entity: Entity | null;
protected _verticesHighBuffer: WebGLBufferExt | null;
protected _verticesLowBuffer: WebGLBufferExt | null;
protected _indexBuffer: WebGLBufferExt | null;
protected _verticesHigh: number[];
protected _verticesLow: number[];
protected _indexes: number[];
protected _path: TStrip[];
protected _times: number[] | null;
protected _timesArr: Int32Array;
protected _timesBuffer: WebGLBufferExt | null;
protected _suspendBuffers: boolean;
protected _rtcCenter: Vec3;
protected _pickingColor: Float32Array;
protected _gridSize: number;
/**
* Handler that stores and renders this object.
* @public
* @type {StripHandler | null}
*/
public _handler: StripHandler | null;
public _handlerIndex: number;
constructor(options: IStripParams = {}) {
this.__id = Strip.__counter__++;
this.visibility = options.visibility != undefined ? options.visibility : true;
this._color = new Float32Array([1.0, 1.0, 1.0, 0.5]);
if (options.color) {
let color = utils.createColorRGBA(options.color);
this.setColor(color.x, color.y, color.z, color.w);
}
if (options.opacity) {
this.setOpacity(options.opacity);
}
/**
* Parent collection render node.
* @protected
* @type {Scene}
*/
this._scene = null;
/**
* Entity instance that holds this strip.
* @protected
* @type {Entity}
*/
this._entity = null;
this._verticesHighBuffer = null;
this._verticesLowBuffer = null;
this._indexBuffer = null;
this._verticesHigh = [];
this._verticesLow = [];
this._indexes = [];
this._path = [];
this._times = options.pathTimes ? options.pathTimes.slice() : null;
this._timesArr = new Int32Array(0);
this._timesBuffer = null;
this._suspendBuffers = false;
this._rtcCenter = new Vec3();
this._pickingColor = new Float32Array(4);
this._gridSize = 1;
/**
* Handler that stores and renders this object.
* @protected
* @type {StripHandler}
*/
this._handler = null;
this._handlerIndex = -1;
if (options.path) {
this.setPath(options.path);
}
}
/**
* Assign picking color.
* @public
* @param {Vec3} color - Picking RGB color.
*/
public setPickingColor3v(color: Vec3) {
this._pickingColor[0] = color.x / 255.0;
this._pickingColor[1] = color.y / 255.0;
this._pickingColor[2] = color.z / 255.0;
this._pickingColor[3] = 1.0;
}
/**
* Clears object
* @public
*/
public clear() {
this._path.length = 0;
this._path = [];
this._verticesHigh.length = 0;
this._verticesHigh = [];
this._verticesLow.length = 0;
this._verticesLow = [];
this._indexes.length = 0;
this._indexes = [];
this._times = this._times ? [] : null;
this._timesArr = new Int32Array(0);
this._deleteBuffers();
}
/**
* Sets RGBA color. Each channel from 0.0 to 1.0.
* @public
* @param {Vec4} color - RGBA vector.
*/
public setColor4v(color: Vec4) {
this.setColor(color.x, color.y, color.z, color.w);
}
/**
* Sets strip color.
* @public
* @param {string} color - HTML style color.
*/
public setColorHTML(color: string) {
this.setColor4v(utils.htmlColorToRgba(color));
}
public setColor(r: number, g: number, b: number, a?: number) {
this._color[0] = srgbToLinear(r);
this._color[1] = srgbToLinear(g);
this._color[2] = srgbToLinear(b);
if (a !== undefined) {
this._color[3] = a;
this._handler && this._handler.updateStripOpacity(this);
}
}
/**
* Sets strip opacity.
* @public
* @param {number} opacity - Opacity.
*/
public setOpacity(opacity: number) {
this._color[3] = opacity || 0;
this._handler && this._handler.updateStripOpacity(this);
}
public isOpaque(): boolean {
return this._color[3] >= 0.999999 && !this._times;
}
/**
* Returns true when the strip has edge times and follows the entity collection time.
* @public
* @returns {boolean}
*/
public isTimed(): boolean {
return this._times !== null;
}
public getPathTimes(): number[] | null {
return this._times;
}
/**
* Sets edge times. Null removes them and the strip stops following the entity collection time.
* @public
* @param {number[] | null} times - Time of every path edge, missing values repeat the previous one.
*/
public setPathTimes(times: number[] | null) {
this._times = times ? times.slice() : null;
if (this._handler) {
this._handler.updateStripOpacity(this);
this._times && this._handler.initTimedPrograms();
}
this._createBuffers();
}
protected _resolveTimes(): number[] {
const times = this._times || [];
const count = this._path.length;
const res: number[] = new Array(count);
let t = times.find((v) => Number.isFinite(v)) ?? 0;
for (let i = 0; i < count; i++) {
if (Number.isFinite(times[i])) {
t = times[i];
}
res[i] = t;
}
return res;
}
protected _rebuildTimes() {
const gs = this._gridSize,
gs1 = gs + 1;
const times = this._resolveTimes();
const quads = Math.max(0, this._path.length - 1);
const arr = new Int32Array(quads * gs1 * gs1 * 2);
for (let k = 0; k < quads; k++) {
const t0 = times[k],
t1 = times[k + 1];
for (let i = 0; i < gs1; i++) {
const [high, low] = splitPathTime(t0 + ((t1 - t0) * i) / gs);
const row = (k * gs1 + i) * gs1;
for (let j = 0; j < gs1; j++) {
arr[(row + j) * 2] = high;
arr[(row + j) * 2 + 1] = low;
}
}
}
this._timesArr = arr;
}
/**
* Sets strip visibility.
* @public
* @param {boolean} visibility - Visibility flag.
*/
public setVisibility(visibility: boolean) {
this.visibility = visibility;
}
/**
* @returns {boolean} Strip visibility.
*/
public getVisibility(): boolean {
return this.visibility;
}
/**
* Assign rendering scene node.
* @public
* @param {Scene} scene - Assigned render node.
*/
public bindScene(scene: Scene) {
this._scene = scene;
if (!this.updateRTCPosition()) {
this._createBuffers();
}
}
protected _toTwoFloats(p: Vec3) {
const c = this._rtcCenter;
_tempRtc.set(p.x - c.x, p.y - c.y, p.z - c.z);
Vec3.doubleToTwoFloats(_tempRtc, _tempHigh, _tempLow);
}
/**
* Rebuilds vertices relative to the handler relative center when it has moved.
* @public
* @returns {boolean} True when the strip was rebuilt.
*/
public updateRTCPosition(): boolean {
const center = this._handler && this._handler._relativeCenter;
if (!center || center.equal(this._rtcCenter)) {
return false;
}
this._rtcCenter.copy(center);
this.setPath(([] as TStrip[]).concat(this._path));
return true;
}
/**
* Removes from entity.
* @public
*/
public remove() {
this._entity = null;
this._handler && this._handler.remove(this);
}
protected _drawImpl(sh: ShaderProgram, color: Float32Array, opacity: number) {
if (this.visibility && this._verticesHigh.length) {
let r = this._scene!.renderer!;
let gl = r.handler.gl!;
let p = sh,
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.disable(gl.CULL_FACE);
if (sh === r.handler.programs.stripTransparent) {
gl.uniform1f(shu.useReverseDepth, r.activeCamera.reverseDepthActive ? 1.0 : 0.0);
}
gl.uniformMatrix4fv(shu.viewMatrix, false, r.activeCamera!.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, r.activeCamera!.getProjectionMatrix());
gl.uniform3fv(shu.eyePositionHigh, this._handler!._rtcEyePositionHigh);
gl.uniform3fv(shu.eyePositionLow, this._handler!._rtcEyePositionLow);
gl.uniform4fv(shu.uColor, color);
gl.uniform1f(shu.uOpacity, opacity);
if (this._times && this._timesBuffer) {
const handler = this._handler!;
const [high, low] = splitPathTime(handler.time);
gl.uniform2i(shu.timeCut, high, low);
gl.uniform2fv(shu.timeOpacity, handler.timeOpacity);
gl.bindBuffer(gl.ARRAY_BUFFER, this._timesBuffer);
gl.vertexAttribIPointer(sha.pathTime, 2, gl.INT, 0, 0);
}
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesHighBuffer!);
gl.vertexAttribPointer(sha.aVertexPositionHigh, this._verticesHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesLowBuffer!);
gl.vertexAttribPointer(sha.aVertexPositionLow, this._verticesLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer!);
gl.drawElements(r.handler.gl!.TRIANGLE_STRIP, this._indexBuffer!.numItems, gl.UNSIGNED_INT, 0);
gl.enable(gl.CULL_FACE);
}
}
public drawTransparent() {
const programs = this._scene!.renderer!.handler.programs;
this._drawImpl(
this._times ? programs.stripTransparentTime : programs.stripTransparent,
this._color,
this._entity!._entityCollection!._fadingOpacity
);
}
public drawOpaque() {
const programs = this._scene!.renderer!.handler.programs;
this._drawImpl(
this._times ? programs.stripForwardTime : programs.stripForward,
this._color,
this._entity!._entityCollection!._fadingOpacity
);
}
drawPicking() {
if (this.visibility && this._verticesHigh.length) {
let r = this._scene!.renderer!;
let gl = r.handler.gl!;
let sh = r.handler.programs.stripForward,
p = sh,
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.disable(gl.CULL_FACE);
gl.uniformMatrix4fv(shu.viewMatrix, false, r.activeCamera!.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, r.activeCamera!.getProjectionMatrix());
gl.uniform3fv(shu.eyePositionHigh, this._handler!._rtcEyePositionHigh);
gl.uniform3fv(shu.eyePositionLow, this._handler!._rtcEyePositionLow);
gl.uniform1f(shu.uOpacity, this._entity!._entityCollection!._fadingOpacity != 0 ? 1 : 0);
gl.uniform4fv(shu.uColor, this._pickingColor);
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesHighBuffer as WebGLBuffer);
gl.vertexAttribPointer(sha.aVertexPositionHigh, this._verticesHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesLowBuffer as WebGLBuffer);
gl.vertexAttribPointer(sha.aVertexPositionLow, this._verticesLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer as WebGLBuffer);
gl.drawElements(r.handler.gl!.TRIANGLE_STRIP, this._indexBuffer!.numItems, gl.UNSIGNED_INT, 0);
gl.enable(gl.CULL_FACE);
}
}
/**
* Delete buffers
* @public
*/
public _deleteBuffers() {
if (this._scene && this._scene.renderer) {
let r = this._scene.renderer,
gl = r.handler.gl!;
gl.deleteBuffer(this._indexBuffer!);
gl.deleteBuffer(this._verticesHighBuffer!);
gl.deleteBuffer(this._verticesLowBuffer!);
gl.deleteBuffer(this._timesBuffer!);
}
this._verticesHighBuffer = null;
this._verticesLowBuffer = null;
this._indexBuffer = null;
this._timesBuffer = null;
}
protected _createBuffers() {
if (this._suspendBuffers) return;
if (this._times) {
this._rebuildTimes();
}
if (this._scene && this._scene.renderer && this._scene.renderer.isInitialized()) {
this._scene.renderer.requestRedraw();
let gl = this._scene.renderer.handler.gl!;
gl.deleteBuffer(this._indexBuffer as WebGLBuffer);
gl.deleteBuffer(this._verticesHighBuffer as WebGLBuffer);
gl.deleteBuffer(this._verticesLowBuffer as WebGLBuffer);
this._verticesHighBuffer = this._scene.renderer.handler.createArrayBuffer(
new Float32Array(this._verticesHigh),
3,
this._verticesHigh.length / 3
);
this._verticesLowBuffer = this._scene.renderer.handler.createArrayBuffer(
new Float32Array(this._verticesLow),
3,
this._verticesLow.length / 3
);
this._indexBuffer = this._scene.renderer.handler.createElementArrayBuffer(
new Uint32Array(this._indexes),
1,
this._indexes.length
);
gl.deleteBuffer(this._timesBuffer as WebGLBuffer);
this._timesBuffer = this._times
? this._scene.renderer.handler.createArrayBuffer(this._timesArr, 2, this._timesArr.length / 2)
: null;
}
}
public addEdge3v(p2: Vec3, p3: Vec3, time?: number) {
let length = this._path.length;
if (this._times && time !== undefined) {
this._times.length = length;
this._times.push(time);
}
if (length === 0) {
this._path.push([p2.clone(), p3.clone()]);
} else {
let p0 = this._path[length - 1][0],
p1 = this._path[length - 1][1];
this._path.push([p2.clone(), p3.clone()]);
let vHigh = this._verticesHigh,
vLow = this._verticesLow;
let gs = this._gridSize,
gs1 = gs + 1;
let p = new Vec3();
let last = this._verticesHigh.length / 3,
ind = last;
let d = Math.abs(p0.sub(p1).normal().dot(p2.sub(p0).normal()));
for (let i = 0; i < gs1; i++) {
let di = i / gs;
let p02 = p0.lerp(p2, di),
p13 = p1.lerp(p3, di);
for (let j = 0; j < gs1; j++) {
let dj = j / gs;
let p01 = p0.lerp(p1, dj),
p23 = p2.lerp(p3, dj);
if (d !== 1.0) {
p = gridPoint(p02, p13, p01, p23, di, p);
} else {
p = p23;
}
ind = last + i * gs1 + j;
this._toTwoFloats(p);
let ind3 = ind * 3;
vHigh[ind3] = _tempHigh.x;
vHigh[ind3 + 1] = _tempHigh.y;
vHigh[ind3 + 2] = _tempHigh.z;
vLow[ind3] = _tempLow.x;
vLow[ind3 + 1] = _tempLow.y;
vLow[ind3 + 2] = _tempLow.z;
if (i < gs) {
this._indexes.push(ind, ind + gs1);
}
}
if (i < gs) {
this._indexes.push(ind + gs1, ind + 1);
}
}
this._createBuffers();
}
}
public setEdge3v(p2: Vec3, p3: Vec3, index: number, time?: number) {
if (index === this._path.length) {
this.addEdge3v(p2, p3, time);
return;
}
if (this._path[index]) {
if (this._times && time !== undefined) {
this._times[index] = time;
}
this._path[index][0] = p2;
this._path[index][1] = p3;
if (this._path.length > 1) {
let gs = this._gridSize,
gs1 = gs + 1;
let vSize = gs1 * gs1;
let p = new Vec3();
let vHigh = this._verticesHigh,
vLow = this._verticesLow;
if (index === this._path.length - 1) {
let p0 = this._path[index - 1][0],
p1 = this._path[index - 1][1];
let prev = this._verticesHigh.length / 3 - vSize,
ind = prev;
let d = Math.abs(p0.sub(p1).normal().dot(p2.sub(p0).normal()));
for (let i = 0; i < gs1; i++) {
let di = i / gs;
let p02 = p0.lerp(p2, di),
p13 = p1.lerp(p3, di);
for (let j = 0; j < gs1; j++) {
let dj = j / gs;
let p01 = p0.lerp(p1, dj),
p23 = p2.lerp(p3, dj);
if (d !== 1.0) {
p = gridPoint(p02, p13, p01, p23, di, p);
} else {
p = p23;
}
ind = prev + i * gs1 + j;
this._toTwoFloats(p);
let ind3 = ind * 3;
vHigh[ind3] = _tempHigh.x;
vHigh[ind3 + 1] = _tempHigh.y;
vHigh[ind3 + 2] = _tempHigh.z;
vLow[ind3] = _tempLow.x;
vLow[ind3 + 1] = _tempLow.y;
vLow[ind3 + 2] = _tempLow.z;
}
}
} else if (index === 0) {
let ind = 0;
let p0 = p2,
p1 = p3;
p2 = this._path[1][0];
p3 = this._path[1][1];
for (let i = 0; i < gs1; i++) {
let di = i / gs;
let p02 = p0.lerp(p2, di),
p13 = p1.lerp(p3, di);
for (let j = 0; j < gs1; j++) {
let dj = j / gs;
let p01 = p0.lerp(p1, dj),
p23 = p2.lerp(p3, dj);
p = gridPoint(p02, p13, p01, p23, di, p);
ind = i * gs1 + j;
this._toTwoFloats(p);
let ind3 = ind * 3;
vHigh[ind3] = _tempHigh.x;
vHigh[ind3 + 1] = _tempHigh.y;
vHigh[ind3 + 2] = _tempHigh.z;
vLow[ind3] = _tempLow.x;
vLow[ind3 + 1] = _tempLow.y;
vLow[ind3 + 2] = _tempLow.z;
}
}
} else if (index > 0 && index < this._path.length) {
let p0 = this._path[index - 1][0],
p1 = this._path[index - 1][1];
let p4 = this._path[index + 1][0],
p5 = this._path[index + 1][1];
let next = index * vSize,
prev = (index - 1) * vSize,
ind = prev;
for (let i = 0; i < gs1; i++) {
let di = i / gs;
let p02 = p0.lerp(p2, di),
p35 = p3.lerp(p5, di),
p24 = p2.lerp(p4, di),
p13 = p1.lerp(p3, di);
for (let j = 0; j < gs1; j++) {
let dj = j / gs;
let p01 = p0.lerp(p1, dj),
p23 = p2.lerp(p3, dj);
// prev
p = gridPoint(p02, p13, p01, p23, di, p);
let ij = i * gs1 + j;
ind = prev + ij;
this._toTwoFloats(p);
let ind3 = ind * 3;
vHigh[ind3] = _tempHigh.x;
vHigh[ind3 + 1] = _tempHigh.y;
vHigh[ind3 + 2] = _tempHigh.z;
vLow[ind3] = _tempLow.x;
vLow[ind3 + 1] = _tempLow.y;
vLow[ind3 + 2] = _tempLow.z;
// next
let p45 = p4.lerp(p5, dj);
p23 = p2.lerp(p3, dj);
p = gridPoint(p24, p35, p23, p45, di, p);
ind = next + ij;
this._toTwoFloats(p);
ind3 = ind * 3;
vHigh[ind3] = _tempHigh.x;
vHigh[ind3 + 1] = _tempHigh.y;
vHigh[ind3 + 2] = _tempHigh.z;
vLow[ind3] = _tempLow.x;
vLow[ind3 + 1] = _tempLow.y;
vLow[ind3 + 2] = _tempLow.z;
}
}
}
this._createBuffers();
}
} else {
console.warn(`strip index ${index} is out of range`);
}
}
public removeEdge(index: number) {
this._path.splice(index, 1);
if (this._times && index < this._times.length) {
this._times.splice(index, 1);
}
this.setPath(([] as TStrip[]).concat(this._path));
}
public setGridSize(gridSize: number) {
this._gridSize = gridSize;
this.setPath(([] as TStrip[]).concat(this._path));
}
public getPath(): TStrip[] {
return this._path;
}
public setPath(path: TStripExt[] | TStrip[], pathTimes?: number[]) {
if (pathTimes) {
const wasTimed = this.isTimed();
this._times = pathTimes.slice();
if (!wasTimed && this._handler) {
this._handler.updateStripOpacity(this);
this._handler.initTimedPrograms();
}
}
this._suspendBuffers = true;
this._verticesHigh = [];
this._verticesLow = [];
this._indexes = [];
this._path = [];
for (let i = 0; i < path.length; i++) {
let p0 = path[i][0],
p1 = path[i][1];
if (p0 instanceof Array) {
p0 = new Vec3(p0[0], p0[1], p0[2]);
}
if (p1 instanceof Array) {
p1 = new Vec3(p1[0], p1[1], p1[2]);
}
this.addEdge3v(p0 as Vec3, p1 as Vec3);
}
this._suspendBuffers = false;
this._createBuffers();
}
public insertEdge3v(p0: Vec3, p1: Vec3, index: number, time?: number) {
if (index < this._path.length) {
let p: TStrip[] = ([] as TStrip[]).concat(this._path);
p.splice(index, 0, [p0, p1]);
if (this._times) {
this._times.length = Math.max(this._times.length, index);
this._times.splice(index, 0, time ?? this._times[Math.max(0, index - 1)]);
}
this.setPath(p);
} else if (index === this._path.length) {
this.addEdge3v(p0, p1, time);
}
}
}
function gridPoint(p02: Vec3, p13: Vec3, p01: Vec3, p23: Vec3, di: number, res: Vec3): Vec3 {
if (new Line3(p02, p13).intersects(new Line3(p01, p23), res)) {
return res;
}
return p01.lerp(p23, di);
}
export { Strip };