git core.ignorecase set to false

This commit is contained in:
Zemledelec 2018-03-14 23:39:35 +03:00
parent 32a68ee62e
commit 80e7a33ff9
81 changed files with 22370 additions and 0 deletions

170
src/og/Clock.js Normal file
View File

@ -0,0 +1,170 @@
/**
* @module og/Clock
*/
'use strict';
import { Events } from './Events.js';
import * as jd from './astro/jd.js';
/**
* Class represents application timer that stores custom current julian datetime, and time speed multiplier.
* @class
* @param {Object} [params]: - Clock parameters:
* @param {number} [params.startDate=0.0] - Julian start date.
* @param {number} [params.endDate=0.0] - Julian end date.
* @param {number} [params.currentDate] - Julian current date. Default: current date.
* @param {number} [params.multiplier=1.0] - Time speed multiolier.
*/
class Clock {
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
constructor(params) {
params = params || {};
this._id = Clock._staticCounter++;
/**
* Clock name.
* @public
* @type {string}
*/
this.name = params.name || "";
/**
* Clock events.
* @public
* @type {Events}
*/
this.events = new Events([
"tick",
"end"
]);
/**
* Start julian date clock loop.
* @public
* @type {number}
*/
this.startDate = params.startDate || 0;
/**
* End julian date clock loop.
* @public
* @type {number}
*/
this.endDate = params.endDate || 0;
var currentDate = params.currentDate || jd.DateToUTC(new Date());
if (params.startDate && currentDate < params.startDate) {
currentDate = params.startDate;
}
if (params.endDate && currentDate > params.endDate) {
currentDate = params.endDate;
}
/**
* Current julian datetime.
* @public
* @type {number}
*/
this.currentDate = currentDate;
/**
* Timer speed multiplier.
* @public
* @type {number}
*/
this.multiplier = params.multiplier !== undefined ? params.multiplier : 1.0;
/**
* Animation frame delta time.
* @public
* @readonly
* @type {number}
*/
this.deltaTicks = 0;
/**
* Timer activity.
* @public
* @type {boolean}
*/
this.active = true;
}
/**
* Sets current clock datetime.
* @public
* @param {Object} date - JavaScript Date object.
*/
setDate(date) {
var d = jd.DateToUTC(date);
if (this.startDate && d < this.startDate) {
d = this.startDate;
}
if (this.endDate && d > this.endDate) {
d = this.endDate;
}
this.currentDate = d;
}
/**
* Returns current application date.
* @public
* @returns {Date} - Current date.
*/
getDate() {
return jd.UTCtoDate(this.currentDate);
}
reset() {
if (this.startDate) {
this.currentDate = this.startDate;
}
}
_tick(dt) {
this.deltaTicks = dt * this.multiplier;
if (this.active) {
var cd = jd.addMilliseconds(this.currentDate, this.deltaTicks);
if (this.multiplier > 0) {
if (this.endDate && cd > this.endDate) {
this.currentDate = this.startDate;
this.events.dispatch(this.events.end, this);
} else {
this.currentDate = cd;
}
} else {
if (this.startDate && cd < this.startDate) {
this.currentDate = this.endDate;
this.events.dispatch(this.events.end, this);
} else {
this.currentDate = cd;
}
}
this.events.dispatch(this.events.tick, this);
}
}
/**
* @public
* @param {Clock} clock - Clock instance to compare.
* @returns {boolean} - Returns true if a clock is the same instance.
*/
equal(clock) {
return this._id === clock._id;
}
};
export { Clock };

144
src/og/Frustum.js Normal file
View File

@ -0,0 +1,144 @@
/**
* @module og/Frustum
*/
'use strict';
/**
* Frustum object, part of the camera object.
* @class
*/
class Frustum {
constructor() {
/**
* Frustum planes.
* @private
* @type {Array.<Array.<number>>}
*/
this._f = new Array(6);
for (var i = 0; i < 6; i++)
this._f[i] = new Array(4);
}
/**
* Normalize frustum plane.
* @static
* @param {Array.<number>} plane - Frustum plane coordinates.
*/
static planeNormalize(plane) {
var t = Math.sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
plane[0] /= t;
plane[1] /= t;
plane[2] /= t;
plane[3] /= t;
}
/**
* Camera's projection matrix values.
* @public
* @param {Array.<number>} clip - Projection matrix parameters.
*/
setFrustum(clip) {
/* Right */
this._f[0][0] = clip[3] - clip[0];
this._f[0][1] = clip[7] - clip[4];
this._f[0][2] = clip[11] - clip[8];
this._f[0][3] = clip[15] - clip[12];
Frustum.planeNormalize(this._f[0]);
/* Left */
this._f[1][0] = clip[3] + clip[0];
this._f[1][1] = clip[7] + clip[4];
this._f[1][2] = clip[11] + clip[8];
this._f[1][3] = clip[15] + clip[12];
Frustum.planeNormalize(this._f[1]);
/* Bottom */
this._f[2][0] = clip[3] + clip[1];
this._f[2][1] = clip[7] + clip[5];
this._f[2][2] = clip[11] + clip[9];
this._f[2][3] = clip[15] + clip[13];
Frustum.planeNormalize(this._f[2]);
/* Top */
this._f[3][0] = clip[3] - clip[1];
this._f[3][1] = clip[7] - clip[5];
this._f[3][2] = clip[11] - clip[9];
this._f[3][3] = clip[15] - clip[13];
Frustum.planeNormalize(this._f[3]);
/* Backward */
this._f[4][0] = clip[3] - clip[2];
this._f[4][1] = clip[7] - clip[6];
this._f[4][2] = clip[11] - clip[10];
this._f[4][3] = clip[15] - clip[14];
Frustum.planeNormalize(this._f[4]);
/* Forward */
this._f[5][0] = clip[3] + clip[2];
this._f[5][1] = clip[7] + clip[6];
this._f[5][2] = clip[11] + clip[10];
this._f[5][3] = clip[15] + clip[14];
Frustum.planeNormalize(this._f[5]);
}
/**
* Returns true if a point in the frustum.
* @public
* @param {og.math.Vec3} point - Cartesian point.
* @returns {boolean}
*/
containsPoint(point) {
var d;
for (var p = 0; p < 6; p++) {
d = point.dotArr(this._f[p]) + this._f[p][3];
if (d <= 0)
return false;
}
return true;
}
/**
* Returns true if the frustum contains a bonding sphere.
* @public
* @param {og.bv.Sphere} sphere - Bounding sphere.
* @returns {boolean}
*/
containsSphere(sphere) {
var r = -sphere.radius;
if (sphere.center.dotArr(this._f[0]) + this._f[0][3] <= r) return false;
if (sphere.center.dotArr(this._f[1]) + this._f[1][3] <= r) return false;
if (sphere.center.dotArr(this._f[2]) + this._f[2][3] <= r) return false;
if (sphere.center.dotArr(this._f[3]) + this._f[3][3] <= r) return false;
if (sphere.center.dotArr(this._f[4]) + this._f[4][3] <= r) return false;
if (sphere.center.dotArr(this._f[5]) + this._f[5][3] <= r) return false;
return true;
}
/**
* Returns true if the frustum contains a bounding box.
* @public
* @param {og.bv.Box} box - Bounding box.
* @returns {boolean}
*/
containsBox(box) {
var result = true, cout, cin;
for (var i = 0; i < 6; i++) {
cout = 0; cin = 0;
for (var k = 0; k < 8 && (cin == 0 || cout == 0); k++) {
var d = box.vertices[k].dotArr(this._f[i]) + this._f[i][3];
if (d < 0)
cout++;
else
cin++;
}
if (cin == 0)
return false;
else if (cout > 0)
result = true;
}
return (result);
}
};
export { Frustum };

48
src/og/Lock.js Normal file
View File

@ -0,0 +1,48 @@
/**
* @module og/Lock
*/
'use strict';
class Lock {
constructor() {
this._lock = 0;
}
lock(key) {
this._lock |= (1 << key._id);
}
free(key) {
this._lock &= ~(1 << key._id);
}
isFree() {
return this._lock === 0;
}
isLocked() {
return this._lock !== 0;
}
};
class Key {
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
constructor() {
this._id = Key._staticCounter++;
}
};
export { Lock, Key };

175
src/og/LonLat.js Normal file
View File

@ -0,0 +1,175 @@
/**
* @module og/LonLat
*/
'use strict';
import * as mercator from './mercator.js';
/**
* Represents a geographical point with a certain latitude, longitude and height.
* @class
* @param {number} [lon] - Longitude.
* @param {number} [lat] - Latitude.
* @param {number} [height] - Height over the surface.
*/
const LonLat = function (lon, lat, height) {
/**
* Longitude.
* @public
* @type {number}
*/
this.lon = lon || 0;
/**
* Latitude.
* @public
* @type {number}
*/
this.lat = lat || 0;
/**
* Height.
* @public
* @type {number}
*/
this.height = height || 0;
};
/**
* Creates coordinates array.
* @static
* @param{Array.<Array<number,number,number>>} arr - Coordinates array data.
* @return{Array.<og.LonLat>} the same coordinates array but each element is LonLat instance.
*/
LonLat.join = function (arr) {
var res = [];
for (var i = 0; i < arr.length; i++) {
var ai = arr[i];
res[i] = new LonLat(ai[0], ai[1], ai[2]);
}
return res;
};
/**
* Creates an object by coordinate array.
* @static
* @param {Array.<number,number,number>} arr - Coordiante array, where first is longitude, second is latitude and third is a height.
* @returns {og.LonLat}
*/
LonLat.createFromArray = function (arr) {
return new LonLat(arr[0], arr[1], arr[2]);
};
/**
* Converts degrees to mercator coordinates.
* @static
* @param {number} lon - Degrees longitude.
* @param {number} lat - Degrees latitude.
* @param {number} [height] - Height.
* @returns {og.LonLat}
*/
LonLat.forwardMercator = function (lon, lat, height) {
var x = lon * mercator.POLE / 180;
var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / Math.PI * mercator.POLE;
return new LonLat(x, y, height);
};
/**
* Converts mercator to degrees coordinates.
* @static
* @param {number} x - Mercator longitude.
* @param {number} y - Mercator latitude.
* @param {number} [height] - Height.
* @returns {og.LonLat}
*/
LonLat.inverseMercator = function (x, y, height) {
var lon = 180 * x / mercator.POLE;
var lat = 180 / Math.PI * (2 * Math.atan(Math.exp((y / mercator.POLE) * Math.PI)) - Math.PI / 2);
return new LonLat(lon, lat, height);
};
/**
* Sets coordinates.
* @public
* @param {number} [lon] - Longitude.
* @param {number} [lat] - Latitude.
* @param {number} [height] - Height.
* @returns {og.LonLat}
*/
LonLat.prototype.set = function (lon, lat, height) {
this.lon = lon || 0;
this.lat = lat || 0;
this.height = height || 0;
return this;
};
/**
* Copy coordinates.
* @public
* @param {og.LonLat} [lonLat] - Coordinates to copy.
* @returns {og.LonLat}
*/
LonLat.prototype.copy = function (lonLat) {
this.lon = lonLat.lon;
this.lat = lonLat.lat;
this.height = lonLat.height;
return this;
};
/**
* Clone the coordiante.
* @public
* @returns {og.LonLat}
*/
LonLat.prototype.clone = function () {
return new LonLat(this.lon, this.lat, this.height);
};
/**
* Converts to mercator coordinates.
* @public
* @returns {og.LonLat}
*/
LonLat.prototype.forwardMercator = function () {
return LonLat.forwardMercator(this.lon, this.lat, this.height);
};
LonLat.prototype.forwardMercatorEPS01 = function () {
var lat = this.lat;
if (lat > 89.9) {
lat = 89.9;
} else if (lat < -89.9) {
lat = -89.9;
}
return new LonLat(
this.lon * mercator.POLE / 180,
Math.log(Math.tan((90 + lat) * Math.PI / 360)) / Math.PI * mercator.POLE);
};
/**
* Converts from mercator coordinates.
* @public
* @returns {og.LonLat}
*/
LonLat.prototype.inverseMercator = function () {
return LonLat.inverseMercator(this.lon, this.lat, this.height);
};
/**
* Compares coordinates.
* @public
* @param {og.LonLat} b - Coordinate to compare with.
* @returns {boolean}
*/
LonLat.prototype.equal = function (b) {
if (b.height) {
return this.lon == b.lon && this.lat == b.lat && this.height == b.height;
} else {
return this.lon == b.lon && this.lat == b.lat;
}
};
export { LonLat };

69
src/og/QueueArray.js Normal file
View File

@ -0,0 +1,69 @@
/**
* @module og/QueueArray
*/
'use strict';
class QueueArray {
constructor(size) {
this._size = size || 2048;
this._array = new Array(this._size);
this._popIndex = parseInt(this._size * 0.5);
this._shiftIndex = this._popIndex;
this.length = 0;
}
clear() {
this._array.length = 0;
this._array = new Array(this._size);
this._popIndex = parseInt(this._size * 0.5);
this._shiftIndex = this._popIndex;
this.length = 0;
}
push(data) {
this.length++;
this._array[this._popIndex++] = data;
}
pop() {
if (this.length) {
this.length--;
var res = this._array[--this._popIndex]
this._array[this._popIndex] = null;
if (!this._array[this._popIndex - 1]) {
this._popIndex = parseInt(this._size * 0.5);
this._shiftIndex = this._popIndex;
}
return res;
}
return undefined;
}
unshift(data) {
this.length++;
this._array[--this._shiftIndex] = data;
}
shift() {
if (this.length) {
this.length--;
var res = this._array[this._shiftIndex];
this._array[this._shiftIndex++] = null;
if (!this._array[this._shiftIndex]) {
this._popIndex = parseInt(this._size * 0.5);
this._shiftIndex = this._popIndex;
}
return res;
}
return undefined;
}
each(callback) {
for (var i = this._shiftIndex; i < this._popIndex; i++) {
callback(this._array[i]);
}
}
};
export { QueueArray };

106
src/og/Rectangle.js Normal file
View File

@ -0,0 +1,106 @@
/**
* @module og/Rectangle
*/
'use strict';
/**
* 2D Rectangle class.
* @class
* @param {number} [left] - Left coordinate. 0 - default.
* @param {number} [top] - Top coordinate. 0 - default.
* @param {number} [right] - Right coordinate. 0 - default.
* @param {number} [bottom] - Bottom coordinate. 0 - default.
*/
class Rectangle {
constructor(left, top, right, bottom) {
/**
* Left coordinate.
* @public
* @type {number}
*/
this.left = left || 0;
/**
* Right coordinate.
* @public
* @type {number}
*/
this.right = right || 0;
/**
* Top coordinate.
* @public
* @type {number}
*/
this.top = top || 0;
/**
* Top coordinate.
* @public
* @type {number}
*/
this.bottom = bottom || 0;
}
/**
* Clone rectangle object.
* @public
* @returns {og.Rectangle}
*/
clone() {
return new Rectangle(this.left, this.top, this.right, this.bottom);
}
/**
* Returns rectangle width.
* @public
* @type {number}
*/
getWidth() {
return Math.abs(this.right - this.left);
}
/**
* Returns rectangle height.
* @public
* @type {number}
*/
getHeight() {
return Math.abs(this.bottom - this.top);
}
/**
* Returns rectangle area.
* @public
* @type {number}
*/
getSquare() {
return this.getHeight() * this.getWidth();
}
/**
* Returns rectangle diagonal size.
* @public
* @type {number}
*/
getDiagonal() {
var w = this.getWidth(),
h = this.getHeight();
return Math.sqrt(h * h + w * w);
}
/**
* Returns true if rectangle fits their size in width and height.
* @public
* @param {number} width - Width.
* @param {number} height - Height.
* @type {boolean}
*/
fit(width, height) {
return (this.getWidth() == width && this.getHeight() == height);
}
};
export { Rectangle };

50
src/og/Stack.js Normal file
View File

@ -0,0 +1,50 @@
/**
* @module og/Stack
*/
'use strict';
class Node {
constructor() {
this.next = null;
this.prev = null;
this.data = null;
}
};
class Stack {
constructor(size = 256) {
this._current = new Node();
this._head = this._current;
for (var i = 0; i < size; i++) {
var n = new Node();
n.prev = this._current;
this._current.next = n;
this._current = n;
}
this._current = this._head;
}
current() {
return this._current;
}
push(data) {
this._current = this._current.next;
this._current.data = data;
};
pop(data) {
this._current = this._current.prev;
return this._current.next.data;
}
popPrev(data) {
this._current = this._current.prev;
return this._current.data;
}
};
export { Stack };

52
src/og/bv/Box.js Normal file
View File

@ -0,0 +1,52 @@
/**
* @module og/bv/Box
*/
'use strict';
import { Vec3 } from '../math/Vec3.js';
/**
* Bounding box class.
* @class
*/
class Box {
constructor() {
/**
* Vertices array.
* @public
* @type{Array.<og.math.Vector3>}
*/
this.vertices = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()];
}
/**
* Sets bounding box coordinates by the bounds array.
* @param {Array.<number>} bounds - Bounds is an array where [minX, maxX, minY, maxY, minZ, maxZ]
*/
setFromBounds(bounds) {
var xmin = bounds[0], xmax = bounds[1],
ymin = bounds[2], ymax = bounds[3],
zmin = bounds[4], zmax = bounds[5];
this.vertices[0].set(xmin, ymin, zmin);
this.vertices[1].set(xmax, ymin, zmin);
this.vertices[2].set(xmax, ymin, zmax);
this.vertices[3].set(xmin, ymin, zmax);
this.vertices[4].set(xmin, ymax, zmin);
this.vertices[5].set(xmax, ymax, zmin);
this.vertices[6].set(xmax, ymax, zmax);
this.vertices[7].set(xmin, ymax, zmax);
}
/**
* Sets bounding box coordiantes by ellipsoid geodetic extend.
* @param {og.Ellipsoid} ellipsoid - Ellipsoid.
* @param {og.Extent} extent - Geodetic extent.
*/
setFromExtent(ellipsoid, extent) {
this.setFromBounds(extent.getCartesianBounds(ellipsoid));
}
};
export { Box };

52
src/og/bv/Sphere.js Normal file
View File

@ -0,0 +1,52 @@
/**
* @module og/bv/Sphere
*/
'use strict';
import { Vec3 } from '../math/Vec3.js';
/**
* Bounding sphere class.
* @class
* @param {Number} [radius] - Bounding sphere radius.
* @param {og.math.Vector3} [center] - Bounding sphere coordiantes.
*/
class Sphere {
constructor(radius, center) {
/**
* Sphere radius.
* @public
* @type {Number}
*/
this.radius = radius || 0;
/**
* Sphere coordiantes.
* @public
* @type {og.math.Vector3}
*/
this.center = center ? center.clone() : new Vec3();
}
/**
* Sets bounding sphere coordinates by the bounds array.
* @param {Array.<number>} bounds - Bounds is an array where [minX, maxX, minY, maxY, minZ, maxZ]
*/
setFromBounds(bounds) {
this.center.set(bounds[0] + (bounds[1] - bounds[0]) / 2, bounds[2] + (bounds[3] - bounds[2]) / 2, bounds[4] + (bounds[5] - bounds[4]) / 2);
this.radius = this.center.distance(new Vec3(bounds[0], bounds[2], bounds[4]));
}
/**
* Sets bounding sphere coordiantes by ellipsoid geodetic extend.
* @param {og.Ellipsoid} ellipsoid - Ellipsoid.
* @param {og.Extent} extent - Geodetic extent.
*/
setFromExtent(ellipsoid, extent) {
this.setFromBounds(extent.getCartesianBounds(ellipsoid));
}
};
export { Sphere };

649
src/og/camera/Camera.js Normal file
View File

@ -0,0 +1,649 @@
/**
* @module og/camera/Camera
*/
'use strict';
import * as math from '../math.js';
import { Events } from '../Events.js';
import { Frustum } from '../Frustum.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
import { Vec4 } from '../math/Vec4.js';
import { Mat3 } from '../math/Mat3.js';
import { Mat4 } from '../math/Mat4.js';
/**
* Camera class.
* @class
* @param {og.Renderer} [renderer] - Renderer uses the camera instance.
* @param {Object} [options] - Camera options:
* @param {Object} [options.name] - Camera name.
* @param {number} [options.viewAngle=30] - Camera angle of view. Default is 30.0
* @param {number} [options.near=1] - Camera near plane distance. Default is 1.0
* @param {number} [options.far=og.math.MAX] - Camera far plane distance. Deafult is og.math.MAX
* @param {og.math.Vector3} [options.eye=[0,0,0]] - Camera eye position. Default (0,0,0)
* @param {og.math.Vector3} [options.look=[0,0,0]] - Camera look position. Default (0,0,0)
* @param {og.math.Vector3} [options.up=[0,1,0]] - Camera eye position. Default (0,1,0)
*
* @fires og.Camera#viewchange
*/
class Camera {
constructor(renderer, options) {
/**
* Assigned renderer.
* @public
* @type {og.Renderer}
*/
this.renderer = null;
/**
* Camera events handler.
* @public
* @type {og.Events}
*/
this.events = new Events(EVENT_NAMES);
/**
* Camera position.
* @public
* @type {og.math.Vector3}
*/
this.eye = new Vec3();
/**
* Camera frustum.
* @public
* @type {og.Frustum}
*/
this.frustum = new Frustum();
/**
* Aspect ratio.
* @protected
* @type {Number}
*/
this._aspect = options.aspect || 0;
/**
* Camera near distance.
* @protected
* @type {Number}
*/
this._nearDist = 0;
/**
* Camera far distance.
* @protected
* @type {Number}
*/
this._farDist = 0;
/**
* Camera view angle in degrees.
* @protected
* @type {Number}
*/
this._viewAngle = 0;
/**
* Camera normal matrix.
* @protected
* @type {og.math.Matrix3}
*/
this._normalMatrix = new Mat3();
/**
* Camera projection matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._projectionMatrix = new Mat4();
/**
* Camera view matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._viewMatrix = new Mat4();
/**
* Product of projection and view matrices.
* @protected
* @type {og.math.Matrix4}
*/
this._projectionViewMatrix = new Mat4();
/**
* Inverse projectionView Matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._inverseProjectionViewMatrix = new Mat4();
/**
* Camera projection matrix for small near and far distances.
* @protected
* @type {og.math.Matrix4}
*/
this._projectionMatrixPrecise = new Mat4();
/**
* Camera right vector.
* @protected
* @type {og.math.Vector3}
*/
this._u = new Vec3(0, 1, 0); //up x n
/**
* Camera up vector.
* @protected
* @type {og.math.Vector3}
*/
this._v = new Vec3(1, 0, 0); //n x u - UP
this.slope = 0;
/**
* Camera forward vector.
* @protected
* @type {og.math.Vector3}
*/
this._n = new Vec3(0, 0, 1); //eye - look - FORWARD
this._pu = this._u;
this._pv = this._v;
this._pn = this._n;
this._peye = this.eye;
this._moved = false;
this._tanViewAngle_hrad = 0;
this._tanViewAngle_hradOneByHeight = 0;
this.renderer = renderer;
renderer && this._initialize(options);
}
/**
* Updates model view matrix.
* @protected
*/
_setViewMatrix() {
var u = this._u,
v = this._v,
n = this._n,
eye = this.eye;
this._viewMatrix.set([u.x, v.x, n.x, 0,
u.y, v.y, n.y, 0,
u.z, v.z, n.z, 0,
-eye.dot(u), -eye.dot(v), -eye.dot(n), 1.0]);
}
checkMoveEnd() {
var u = this._u,
v = this._v,
n = this._n,
eye = this.eye;
if (this.events.moveend.handlers.length) {
if (this._peye.equal(eye) &&
this._pu.equal(u) &&
this._pv.equal(v) &&
this._pn.equal(n)) {
if (this._moved) {
this.events.dispatch(this.events.moveend, this);
}
this._moved = false;
} else {
this._moved = true;
}
}
this._pu = u;
this._pv = v;
this._pn = n;
this._peye = eye;
}
/**
* Camera initialization.
* @public
* @param {og.Renderer} renderer - OpenGlobus renderer object.
* @param {Object} [options] - Camera options:
* @param {number} [options.viewAngle] - Camera angle of view. Default is 30.0
* @param {number} [options.near] - Camera near plane distance. Default is 1.0
* @param {number} [options.far] - Camera far plane distance. Deafult is og.math.MAX
* @param {og.math.Vector3} [options.eye] - Camera eye position. Default (0,0,0)
* @param {og.math.Vector3} [options.look] - Camera look position. Default (0,0,0)
* @param {og.math.Vector3} [options.up] - Camera eye position. Default (0,1,0)
*/
_initialize(options) {
this.setProjectionMatrix(
options.viewAngle || defaultOptions.viewAngle,
this._aspect || this.renderer.handler.getClientAspect(),
options.near || defaultOptions.near,
options.far || defaultOptions.far);
this.set(
options.eye || defaultOptions.eye.clone(),
options.look || defaultOptions.look.clone(),
options.up || defaultOptions.up.clone());
}
getUp() {
return this._v;
}
getDown() {
return this._v.negateTo();
}
getRight() {
return this._u;
}
getLeft() {
return this._u.negateTo();
}
getForward() {
return this._n;
}
getBackward() {
return this._n.negateTo();
}
/**
* Clone camera instance to another one.
* @public
* @virtual
* @returns {og.Camera} - Cloned camera instance.
*/
clone() {
var newcam = new Camera();
newcam.eye.copy(cam.eye);
newcam._u.copy(cam._u);
newcam._v.copy(cam._v);
newcam._n.copy(cam._n);
newcam.renderer = cam.renderer;
newcam._projectionMatrix.copy(cam._projectionMatrix);
newcam._viewMatrix.copy(cam._viewMatrix);
newcam._projectionViewMatrix.copy(cam._projectionViewMatrix);
newcam._inverseProjectionViewMatrix.copy(cam._inverseProjectionViewMatrix);
newcam.frustum.setFrustum(newcam._projectionViewMatrix);
return newcam;
}
/**
* Updates camera view space.
* @public
* @virtual
*/
update() {
this._setViewMatrix();
this._projectionViewMatrix = this._projectionMatrix.mul(this._viewMatrix);
this.frustum.setFrustum(this._projectionViewMatrix._m);
this._inverseProjectionViewMatrix = this._projectionViewMatrix.inverseTo();
this._normalMatrix = this._viewMatrix.toMatrix3();//this._viewMatrix.toInverseMatrix3().transposeTo();
this.events.dispatch(this.events.viewchange, this);
}
/**
* Refresh camera matrices.
* @public
*/
refresh() {
this.setProjectionMatrix(this._viewAngle, this._aspect, this._nearDist, this._farDist);
this.update();
}
/**
* Sets aspect ratio.
* @public
* @param {Number} aspect - Camera aspect ratio.
*/
setAspectRatio(aspect) {
this._aspect = aspect;
this.refresh();
}
/**
* Returns aspect ratio.
* @public
* @returns {number} - Aspect ratio.
*/
getAspectRatio() {
return this._aspect;
}
/**
* Sets far camera distance.
* @public
* @param {number} distance - Far distance.
*/
setFar(distance) {
this._farDist = distance;
this.refresh();
}
/**
* Gets far distance.
* @public
* @returns {number} - Far plane distance.
*/
getFar() {
return this._farDist;
}
/**
* Sets camera's near distance.
* @public
* @param {number} distance - Near distance.
*/
setNear(distance) {
this._nearDist = distance;
this.refresh();
}
/**
* Gets near distance.
* @public
* @returns {number} - Near plane distance.
*/
getNear() {
return this._nearDist;
}
/**
* Sets up camera projection matrix.
* @public
* @param {nnumber} angle - Camera's view angle.
* @param {number} aspect - Screen aspect ration.
* @param {number} near - Near camera distance.
* @param {number} far - Far camera distance.
*/
setProjectionMatrix(angle, aspect, near, far) {
this._viewAngle = angle;
this._aspect = aspect;
this._nearDist = near;
this._farDist = far;
this._tanViewAngle_hrad = Math.tan(angle * math.RADIANS_HALF);
this._tanViewAngle_hradOneByHeight = this._tanViewAngle_hrad * this.renderer.handler._oneByHeight;
var c = this.renderer.handler.canvas;
this._projSizeConst = Math.min(c.clientWidth, c.clientHeight) / (this._viewAngle * math.RADIANS);
this._projectionMatrix.setPerspective(angle, aspect, near, far);
this._projectionMatrixPrecise.setPerspective(angle, aspect, 0.1, 10);
}
/**
* Sets camera view angle in degrees.
* @public
* @param {number} angle - View angle.
*/
setViewAngle(angle) {
this._viewAngle = angle;
this.refresh();
}
/**
* Sets camera to eye position.
* @public
* @param {og.math.Vector3} eye - Camera position.
* @param {og.math.Vector3} look - Look point.
* @param {og.math.Vector3} up - Camera up vector.
* @returns {og.Camera} - This camera.
*/
set(eye, look, up) {
this.eye.x = eye.x;
this.eye.y = eye.y;
this.eye.z = eye.z;
look = look || this._n;
up = up || this._v;
this._n.x = eye.x - look.x;
this._n.y = eye.y - look.y;
this._n.z = eye.z - look.z;
this._u.copy(up.cross(this._n));
this._n.normalize();
this._u.normalize();
this._v.copy(this._n.cross(this._u));
return this;
}
/**
* Sets camera look point.
* @public
* @param {og.math.Vector3} look - Look point.
* @param {og.math.Vector3} [up] - Camera up vector otherwise camera current up vector(this._v)
*/
look(look, up) {
this._n.set(this.eye.x - look.x, this.eye.y - look.y, this.eye.z - look.z);
this._u.copy((up || this._v).cross(this._n));
this._n.normalize();
this._u.normalize();
this._v.copy(this._n.cross(this._u));
}
/**
* Slides camera to vector d - (du, dv, dn).
* @public
* @param {number} du - delta X.
* @param {number} dv - delta Y.
* @param {number} dn - delta Z.
*/
slide(du, dv, dn) {
this.eye.x += du * this._u.x + dv * this._v.x + dn * this._n.x;
this.eye.y += du * this._u.y + dv * this._v.y + dn * this._n.y;
this.eye.z += du * this._u.z + dv * this._v.z + dn * this._n.z;
}
/**
* Roll the camera to the angle in degrees.
* @public
* @param {number} angle - Delta roll angle in degrees.
*/
roll(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._u.clone();
this._u.set(cs * t.x - sn * this._v.x, cs * t.y - sn * this._v.y, cs * t.z - sn * this._v.z);
this._v.set(sn * t.x + cs * this._v.x, sn * t.y + cs * this._v.y, sn * t.z + cs * this._v.z);
}
/**
* Pitch the camera to the angle in degrees.
* @public
* @param {number} angle - Delta pitch angle in degrees.
*/
pitch(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._n.clone();
this._n.set(cs * t.x - sn * this._v.x, cs * t.y - sn * this._v.y, cs * t.z - sn * this._v.z);
this._v.set(sn * t.x + cs * this._v.x, sn * t.y + cs * this._v.y, sn * t.z + cs * this._v.z);
}
/**
* Yaw the camera to the angle in degrees.
* @public
* @param {number} angle - Delta yaw angle in degrees.
*/
yaw(angle) {
var cs = Math.cos(math.RADIANS * angle);
var sn = Math.sin(math.RADIANS * angle);
var t = this._u.clone();
this._u.set(cs * t.x - sn * this._n.x, cs * t.y - sn * this._n.y, cs * t.z - sn * this._n.z);
this._n.set(sn * t.x + cs * this._n.x, sn * t.y + cs * this._n.y, sn * t.z + cs * this._n.z);
}
/**
* Returns normal vector direction to to the unprojected screen point from camera eye.
* @public
* @param {number} x - Scren X coordinate.
* @param {number} y - Scren Y coordinate.
* @returns {og.math.Vector3} - Direction vector.
*/
unproject(x, y) {
var c = this.renderer.handler.canvas,
w = c.width * 0.5,
h = c.height * 0.5;
var px = (x - w) / w,
py = -(y - h) / h;
var world1 = this._inverseProjectionViewMatrix.mulVec4(new Vec4(px, py, -1, 1)).affinity(),
world2 = this._inverseProjectionViewMatrix.mulVec4(new Vec4(px, py, 0, 1)).affinity();
return world2.subA(world1).toVec3().normalize();
}
/**
* Gets projected 3d point to the 2d screen coordiantes.
* @public
* @param {og.math.Vector3} v - Cartesian 3d coordiantes.
* @returns {og.math.Vector2} - Screen point coordinates.
*/
project(v) {
var r = this._projectionViewMatrix.mulVec4(v.toVector4()),
c = this.renderer.handler.canvas;
return new Vec2((1 + r.x / r.w) * c.width * 0.5, (1 - r.y / r.w) * c.height * 0.5);
}
/**
* Rotates camera around center point.
* @public
* @param {number} angle - Rotation angle in radians.
* @param {boolaen} isArc - If true camera up vector gets from current up vector every frame,
* otherwise up is always input parameter.
* @param {og.math.Vector3} center - Point that the camera rotates around.
* @param {og.math.Vecto3} [up] - Camera up vector.
*/
rotateAround(angle, isArc, center, up) {
center = center || Vec3.ZERO;
up = up || Vec3.UP;
var rot = new Mat4().setRotation(isArc ? this._v : up, angle);
var tr = new Mat4().setIdentity().translate(center);
var ntr = new Mat4().setIdentity().translate(center.negateTo());
var trm = tr.mul(rot).mul(ntr);
this.eye = trm.mulVec3(this.eye);
this._v = rot.mulVec3(this._v).normalize();
this._u = rot.mulVec3(this._u).normalize();
this._n = rot.mulVec3(this._n).normalize();
}
/**
* Rotates camera around center point by horizontal.
* @public
* @param {number} angle - Rotation angle in radians.
* @param {boolaen} isArc - If true camera up vector gets from current up vector every frame,
* otherwise up is always input parameter.
* @param {og.math.Vector3} center - Point that the camera rotates around.
* @param {og.math.Vector3} [up] - Camera up vector.
*/
rotateHorizontal(angle, isArc, center, up) {
this.rotateAround(angle, isArc, center, up);
}
/**
* Rotates camera around center point by vecrtical.
* @param {number} angle - Rotation angle in radians.
* @param {og.math.Vector3} center - Point that the camera rotates around.
*/
rotateVertical(angle, center) {
this.rotateAround(angle, false, center, this._u);
}
/**
* Gets 3d size factor. Uses in LOD distance calculation.
* @public
* @param {og.math.Vector3} p - Far point.
* @param {og.math.Vector3} r - Far point.
* @returns {number} - Size factor.
*/
projectedSize(p, r) {
return Math.atan(r / this.eye.distance(p)) * this._projSizeConst;
}
/**
* Returns normal matrix.
* @public
* @returns {og.math.Matrix3} - Normal matrix.
*/
getNormalMatrix() {
return this._normalMatrix;
}
/**
* Returns projection matrix.
* @public
* @returns {og.math.Matrix4} - Projection matrix.
*/
getProjectionMatrix() {
return this._projectionMatrix;
}
/**
* Returns model matrix.
* @public
* @returns {og.math.Matrix4} - View matrix.
*/
getViewMatrix() {
return this._viewMatrix;
}
/**
* Returns projection and model matrix product.
* @public
* @return {og.math.Matrix4} - Projection-view matrix.
*/
getProjectionViewMatrix() {
return this._projectionViewMatrix;
}
/**
* Returns inverse projection and model matrix product.
* @public
* @returns {og.math.Matrix4} - Inversed projection-view matrix.
*/
getInverseProjecttionViewMatrix() {
return this._inverseProjectionViewMatrix;
}
};
const EVENT_NAMES = [
/**
* When camera has been updated.
* @event og.Camera#viewchange
*/
"viewchange",
/**
* Camera is stopped.
* @event og.Camera#moveend
*/
"moveend"
];
const defaultOptions = {
'viewAngle': 30,
'near': 1,
'far': math.MAX,
'eye': new Vec3(0, 0, 0),
'look': new Vec3(0, 0, 0),
'up': new Vec3(0, 1, 0)
};
export { Camera };

View File

@ -0,0 +1,529 @@
/**
* @module og/camera/PlanetCamera
*/
'use strict';
import * as math from '../math.js';
import * as mercator from '../mercator.js';
import { Camera } from './Camera.js';
import { Vec3 } from '../math/Vec3.js';
import { Key } from '../Lock.js';
import { LonLat } from '../LonLat.js';
import { Mat4 } from '../math/Mat4.js';
import { Ray } from '../math/Ray.js';
/**
* Planet camera.
* @class
* @extends {og.Camera}
* @param {og.RenderNode} planet - Planet render node.
* @param {Object} [options] - Planet camera options:
* @param {Object} [options.name] - Camera name.
* @param {number} [options.viewAngle] - Camera angle of view. Default is 35.0
* @param {number} [options.near] - Camera near plane distance. Default is 1.0
* @param {number} [options.far] - Camera far plane distance. Deafult is og.math.MAX
* @param {number} [options.minAltitude] - Minimal altitude for the camera. Deafult is 50
* @param {og.math.Vector3} [options.eye] - Camera eye position. Default (0,0,0)
* @param {og.math.Vector3} [options.look] - Camera look position. Default (0,0,0)
* @param {og.math.Vector3} [options.up] - Camera eye position. Default (0,1,0)
*/
class PlanetCamera extends Camera {
constructor(planet, options) {
super(planet.renderer, options);
/**
* Assigned camera's planet.
* @public
* @type {og.scene.Planet}
*/
this.planet = planet;
/**
* Minimal alltitude that camera can reach over the terrain.
* @public
* @type {number}
*/
this.minAltitude = options.minAltitude || 50;
/**
* Current geographical degree position.
* @protected
* @type {og.LonLat}
*/
this._lonLat = this.planet.ellipsoid.cartesianToLonLat(this.eye);
/**
* Current geographical mercator position.
* @protected
* @type {og.LonLat}
*/
this._lonLatMerc = this._lonLat.forwardMercator();
/**
* Current altitude.
* @protected
* @type {number}
*/
this._terrainAltitude = this._lonLat.height;
/**
* Cartesian coordinates on the terrain.
* @protected
* @type {og.math.Vector3}
*/
this._terrainPoint = new Vec3();
/**
* Quad node that camera flies over.
* @protected
* @type {og.quadTree.Node}
*/
this._insideSegment = null;
this.slope = 0;
/**
* Coordinates that depends on what segment class we are fling over.
* It can be WGS84 or Mercator coordinates. Gets in og.quadTree.Node
* @protected
* @type {og.LonLat}
*/
this._insideSegmentPosition = null;
this._keyLock = new Key();
//Camera's flying frames
this._framesArr = [];
this._framesCounter = 0;
this._numFrames = 50;
this._completeCallback = null;
this._flying = false;
}
/**
* Clone planet camera instance to another one.
* @public
* @virtual
* @returns {og.PlanetCamera}
*/
clone() {
var newcam = new PlanetCamera();
newcam.eye.copy(cam.eye);
newcam._u.copy(cam._u);
newcam._v.copy(cam._v);
newcam._n.copy(cam._n);
newcam.renderer = cam.renderer;
newcam._projectionMatrix.copy(cam._projectionMatrix);
newcam._viewMatrix.copy(cam._viewMatrix);
newcam._projectionViewMatrix.copy(cam._projectionViewMatrix);
newcam._inverseProjectionViewMatrix.copy(cam._inverseProjectionViewMatrix);
newcam.frustum.setFrustum(newcam._projectionViewMatrix);
newcam.planet = cam.planet;
newcam._lonLat = cam._lonLat.clone();
return newcam;
}
/**
* Updates camera view space.
* @public
* @virtual
*/
update() {
this._setViewMatrix();
this._projectionViewMatrix = this._projectionMatrix.mul(this._viewMatrix);
this.frustum.setFrustum(this._projectionViewMatrix._m);
this._inverseProjectionViewMatrix = this._projectionMatrixPrecise.mul(this._viewMatrix).inverseTo();
//this._normalMatrix = this._viewMatrix.toInverseMatrix3().transposeTo();
this._normalMatrix = this._viewMatrix.toMatrix3();
this.updateGeodeticPosition();
this.slope = this._n.dot(this.eye.normal());
this.events.dispatch(this.events.viewchange, this);
}
updateGeodeticPosition() {
this._lonLat = this.planet.ellipsoid.cartesianToLonLat(this.eye);
if (Math.abs(this._lonLat.lat) <= mercator.MAX_LAT) {
this._lonLatMerc = this._lonLat.forwardMercator();
}
}
/**
* Sets altitude over the terrain.
* @public
* @param {number} alt - Altitude over the terrain.
*/
setAltitude(alt) {
var n = this.eye.normal();
var t = this._terrainPoint;
this.eye.x = n.x * alt + t.x;
this.eye.y = n.y * alt + t.y;
this.eye.z = n.z * alt + t.z;
this._terrainAltitude = alt;
this.update();
}
/**
* Gets altitude over the terrain.
* @public
*/
getAltitude() {
return this._terrainAltitude;
}
/**
* Moves camera to the geographical position.
* @public
* @param {og.LonLat} lonlat - Geographical position.
*/
setLonLat(lonlat, up) {
this._lonLat.set(lonlat.lon, lonlat.lat, lonlat.height || this._lonLat.height);
var newEye = this.planet.ellipsoid.lonLatToCartesian(this._lonLat);
var rot = new Mat4().rotateBetweenVectors(newEye.normal(), this.eye.normal());
this.eye = newEye;
this._v = rot.mulVec3(this._v);
this._u = rot.mulVec3(this._u);
this._n = rot.mulVec3(this._n);
}
/**
* Returns camera geographical position.
* @public
* @returns {og.LonLat}
*/
getLonLat() {
return this._lonLat;
}
/**
* Returns camera height.
* @public
* @returns {number}
*/
getHeight() {
return this._lonLat.height;
}
/**
* Places camera to view to the geographical point.
* @public
* @param {og.LonLat} lonlat - New camera and camera view position.
* @param {og.math.Vector3} [up] - Camera UP vector. Default (0,1,0)
*/
viewLonLat(lonlat, up) {
this._lonLat.set(lonlat.lon, lonlat.lat, lonlat.height || this._lonLat.height);
var el = this.planet.ellipsoid;
var newEye = el.lonLatToCartesian(this._lonLat);
var newLook = el.lonLatToCartesian(new LonLat(this._lonLat.lon, this._lonLat.lat, 0));
this.set(newEye, newLook, up || Vec3.UP);
}
/**
* Gets position by viewable extent.
* @public
* @param {og.Extent} extent - Viewable extent.
* @returns {og.math.Vector3}
*/
getExtentPosition(extent) {
var north = extent.getNorth();
var south = extent.getSouth();
var east = extent.getEast();
var west = extent.getWest();
if (west > east) {
east += 360;
}
var e = this.planet.ellipsoid;
var cart = new LonLat(east, north);
var northEast = e.lonLatToCartesian(cart);
cart.lat = south;
var southEast = e.lonLatToCartesian(cart);
cart.lon = west;
var southWest = e.lonLatToCartesian(cart);
cart.lat = north;
var northWest = e.lonLatToCartesian(cart);
var center = Vec3.sub(northEast, southWest).scale(0.5).addA(southWest);
var mag = center.length();
if (mag < 0.000001) {
cart.lon = (east + west) * 0.5;
cart.lat = (north + south) * 0.5;
center = e.lonLatToCartesian(cart);
}
northWest.subA(center);
southEast.subA(center);
northEast.subA(center);
southWest.subA(center);
var direction = center.normal();//ellipsoid.getSurfaceNormal(center).negate().normalize();
var right = direction.cross(Vec3.UP).normalize();
var up = right.cross(direction).normalize();
var height = Math.max(
Math.abs(up.dot(northWest)),
Math.abs(up.dot(southEast)),
Math.abs(up.dot(northEast)),
Math.abs(up.dot(southWest))
);
var width = Math.max(
Math.abs(right.dot(northWest)),
Math.abs(right.dot(southEast)),
Math.abs(right.dot(northEast)),
Math.abs(right.dot(southWest))
);
var tanPhi = Math.tan(this._viewAngle * math.RADIANS * 0.5);
var tanTheta = this._aspect * tanPhi;
var d = Math.max(width / tanTheta, height / tanPhi);
center.normalize();
center.scale(mag + d);
return center;
}
/**
* View current extent.
* @public
* @param {og.Extent} extent - Current extent.
*/
viewExtent(extent) {
this.stopFlying();
this.set(this.getExtentPosition(extent), Vec3.ZERO, Vec3.UP);
this.refresh();
}
/**
* Flies to the current extent.
* @public
* @param {og.Extent} extent - Current extent.
* @param {og.math.Vector3} [up] - Camera UP in the end of flying. Default - (0,1,0)
* @param {cameraCallback} [completeCallback] - Callback that calls after flying when flying is finished.
* @param {cameraCallback} [startCallback] - Callback that calls befor the flying begins.
*/
flyExtent(extent, up, completeCallback, startCallback) {
this.flyCartesian(this.getExtentPosition(extent), Vec3.ZERO,
up, completeCallback, startCallback);
}
/**
* Flies to the cartesian coordinates.
* @public
* @param {og.math.Vector3} cartesian - Finish cartesian coordinates.
* @param {og.math.Vector3} [look] - Camera LOOK in the end of flying. Default - (0,0,0)
* @param {og.math.Vector3} [up] - Camera UP vector in the end of flying. Default - (0,1,0)
* @param {cameraCallback} [completeCallback] - Callback that calls after flying when flying is finished.
* @param {cameraCallback} [startCallback] - Callback that calls befor the flying begins.
*/
flyCartesian(cartesian, look, up, completeCallback, startCallback) {
//???????
//if (this.eye.distance(cartesian) < 23000) {
// return;
//}
this.stopFlying();
this._completeCallback = completeCallback;
if (startCallback) {
startCallback.call(this);
}
var _look = look || Vec3.ZERO;
if (look instanceof LonLat) {
_look = this.planet.ellipsoid.lonLatToCartesian(look);
}
var ground_a = this.planet.ellipsoid.lonLatToCartesian(new LonLat(this._lonLat.lon, this._lonLat.lat));
var v_a = this._v,
n_a = this._n;
var lonlat_b = this.planet.ellipsoid.cartesianToLonLat(cartesian);
var up_b = up || Vec3.UP;
var ground_b = this.planet.ellipsoid.lonLatToCartesian(new LonLat(lonlat_b.lon, lonlat_b.lat, 0));
var eye_b = cartesian;
var n_b = Vec3.sub(eye_b, _look);
var u_b = up_b.cross(n_b);
n_b.normalize();
u_b.normalize();
var v_b = n_b.cross(u_b);
var an = ground_a.normal();
var bn = ground_b.normal();
var anbn = 1.0 - an.dot(bn);
var hM_a = math.SQRT_HALF * Math.sqrt((anbn) > 0.0 ? anbn : 0.0);
var maxHeight = 6639613;
var currMaxHeight = Math.max(this._lonLat.height, lonlat_b.height);
if (currMaxHeight > maxHeight) {
maxHeight = currMaxHeight;
}
var max_h = currMaxHeight + 2.5 * hM_a * (maxHeight - currMaxHeight);
var zero = Vec3.ZERO;
//camera path and orientations calculation
for (var i = 0; i <= this._numFrames; i++) {
var d = 1 - i / this._numFrames;
d = d * d * (3 - 2 * d);
d *= d;
//Error here
var g_i = ground_a.smerp(ground_b, d).normalize();
var ground_i = this.planet.getRayIntersectionEllipsoid(new Ray(zero, g_i));
var t = 1 - d;
var height_i = this._lonLat.height * d * d * d + max_h * 3 * d * d * t + max_h * 3 * d * t * t + lonlat_b.height * t * t * t;
var eye_i = ground_i.addA(g_i.scale(height_i));
var up_i = v_a.smerp(v_b, d);
var look_i = Vec3.add(eye_i, n_a.smerp(n_b, d).negateTo());
var n = new Vec3(eye_i.x - look_i.x, eye_i.y - look_i.y, eye_i.z - look_i.z);
var u = up_i.cross(n);
n.normalize();
u.normalize();
var v = n.cross(u);
this._framesArr[i] = {
"eye": eye_i,
"n": n,
"u": u,
"v": v
};
}
this._framesCounter = this._numFrames;
this._flying = true;
}
/**
* Flies to the geo coordiantes.
* @public
* @param {og.LonLat} lonlat - Finish coordinates.
* @param {og.math.Vector3} [look] - Camera LOOK in the end of flying. Default - (0,0,0)
* @param {og.math.Vector3} [up] - Camera UP vector in the end of flying. Default - (0,1,0)
* @param {cameraCallback} [completeCallback] - Callback that calls after flying when flying is finished.
* @param {cameraCallback} [startCallback] - Callback that calls befor the flying begins.
*/
flyLonLat(lonlat, look, up, completeCallback, startCallback) {
var _lonlat = new LonLat(lonlat.lon, lonlat.lat, lonlat.height || this._lonLat.height);
this.flyCartesian(this.planet.ellipsoid.lonLatToCartesian(_lonlat), look, up, completeCallback, startCallback);
}
/**
* Breaks the flight.
* @public
*/
stopFlying() {
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
this._flying = false;
this._framesArr.length = 0;
this._framesArr = [];
this._framesCounter = -1;
}
/**
* Returns camera is flying.
* @public
* @returns {boolean}
*/
isFlying() {
return this._flying;
}
/**
* Rotates around planet to the left.
* @public
* @param {number} angle - Rotation angle.
* @param {boolean} [spin] - If its true rotates around globe spin.
*/
rotateLeft(angle, spin) {
this.rotateHorizontal(angle * math.RADIANS, spin ^ true, Vec3.ZERO);
this.update();
}
/**
* Rotates around planet to the right.
* @public
* @param {number} angle - Rotation angle.
* @param {boolean} [spin] - If its true rotates around globe spin.
*/
rotateRight(angle, spin) {
this.rotateHorizontal(-angle * math.RADIANS, spin ^ true, Vec3.ZERO);
this.update();
}
/**
* Rotates around planet to the north pole.
* @public
* @param {number} angle - Rotation angle.
*/
rotateUp(angle) {
this.rotateVertical(angle * math.RADIANS, Vec3.ZERO);
this.update();
}
/**
* Rotates around planet to the south pole.
* @public
* @param {number} angle - Rotation angle.
*/
rotateDown(angle) {
this.rotateVertical(-angle * math.RADIANS, Vec3.ZERO);
this.update();
}
/**
* Prepare camera to the frame. Used in render node frame function.
* @public
*/
prepareFrame() {
if (this._flying) {
var c = this._numFrames - this._framesCounter;
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
this.eye = this._framesArr[c].eye;
this._u = this._framesArr[c].u;
this._v = this._framesArr[c].v;
this._n = this._framesArr[c].n;
this.update();
this._framesCounter--;
if (this._framesCounter < 0) {
this.stopFlying();
if (this._completeCallback) {
this._completeCallback();
this._completeCallback = null;
}
}
} else {
this._terrainAltitude = this._lonLat.height;
if (this._lonLat.height < 1000000) {
this._terrainAltitude = this._insideSegment.getTerrainPoint(this._terrainPoint, this.eye, this._insideSegmentPosition);
if (this._terrainAltitude < this.minAltitude) {
this.setAltitude(this.minAltitude);
}
}
}
}
};
export { PlanetCamera };

View File

@ -0,0 +1,138 @@
/**
* @module og/control/BaseControl
*/
'use strict';
/**
* Base control class for implementing renderer controls.
* All other controls extend from this class.
* @class
* @param {Object} [options] - Control activation options:
* @param {Boolean} [options.autoActivated=true] - If true - calls initialize function after the renderer assigning.
*/
class BaseControl {
constructor(options) {
options = options || {};
/**
* Control initialized.
* @protected
* @type {Boolean}
*/
this._initialized = false;
/**
* Assigned renderer.
* @public
* @type {og.Renderer}
*/
this.renderer = null;
/**
* Auto activation flag.
* @public
* @type {Boolean}
*/
this.autoActivate = options.autoActivate != undefined ? options.autoActivate : true;
/**
* Control activity.
* @protected
* @type {Boolean}
*/
this._active = false;
}
/**
* Control initialization function have to be overriden.
* @public
* @virtual
*/
oninit() { }
/**
* Control renderer assigning function have to be overriden.
* @public
* @virtual
*/
onadd() { }
/**
* Control remove function have to be overriden.
* @public
* @virtual
*/
onremove() { }
/**
* Control activation function have to be overriden.
* @public
* @virtual
*/
onactivate() { }
/**
* Control deactivation function have to be overriden.
* @public
* @virtual
*/
ondeactivate() { }
/**
* Assign renderer to the control.
* @public
* @type {og.Renderer}
*/
addTo(renderer) {
if (renderer) {
this.renderer = renderer;
renderer.controls.push(this);
this.onadd && this.onadd();
if (this.autoActivate) {
this.oninit && this.oninit();
this._initialized = true;
this._active = true;
}
}
}
/**
* Assign renderer to the control.
* @public
*/
remove() {
this.onremove && this.onremove();
this.renderer = null;
this._active = false;
this._initialized = false;
}
/**
* Activate control.
* @public
*/
activate() {
this._active = true;
this.onactivate && this.onactivate();
}
/**
* Deactivate control.
* @public
*/
deactivate() {
this._active = false;
this.ondeactivate && this.ondeactivate();
}
/**
* Is control active.
* @public
*/
isActive() {
return this._active;
}
};
export { BaseControl };

View File

@ -0,0 +1,199 @@
/**
* @module og/control/EarthCoordinates
*/
'use strict';
import { BaseControl } from './BaseControl.js';
function dec2deg(base) {
var t, t2;
var degrees = base < 0 ? Math.ceil(base) : Math.floor(base);
var minutes = Math.floor(t = Math.abs((base - degrees)) * 60);
var seconds = Math.floor(t2 = (t - minutes) * 6000);
seconds = seconds / 100.00;
return (numToFixedString(degrees, 3) + "\u00B0" +
numToFixedString(minutes, 2) + "\u0027" +
numToFixedString(seconds.toFixed(2), 2) + "\u0022");
};
function numToFixedString(num, fixed) {
var dl = num.toString().split('.')[0].length;
var white = "&nbsp;";
for (var i = dl; i < fixed; i++) {
white += '&nbsp;&nbsp;';
}
return white + num.toString();
};
function toDecimal(ll) {
return ll.lat.toFixed(5) + ", " + ll.lon.toFixed(5);
};
function toDegrees(ll) {
return dec2deg(ll.lat) + ", " + dec2deg(ll.lon);
};
function toMercator(ll) {
var m = ll.forwardMercator();
return m.lat.toFixed(5) + ", " + m.lon.toFixed(5);
};
const DisplayTypesConverters = [
toDecimal,
toDegrees,
toMercator
];
/**
* Control displays mouse or screen center Earth coordinates.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Options:
* @param {Boolean} [options.center] - Earth coordiantes by screen center otherwise mouse pointer. False is default.
* @param {Boolean} [options.type] - Coordinates shown: 0 - is decimal degrees, 1 - degrees, 2 - mercator geodetic coordinates.
*/
class EarthCoordinates extends BaseControl {
constructor(options) {
super(options);
options = options || {};
/**
* Display type.
* @private
* @type {Boolean}
*/
this._displayType = options.type || 0;
/**
* Current coordinates type converter.
* @private
* @function
*/
this._converter = DisplayTypesConverters[0];
/**
* Display dom element.
* @private
* @type {Object}
*/
this._display = null;
/**
* Screen center or mouse pointer coordinates show flag.
* @private
* @type {Boolean}
*/
var pad = false;
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
pad = true;
}
this._center = options.center || pad;
/**
* Current position.
* @public
* @type {og.math.Vector3}
*/
this.position = null;
}
oninit() {
this._display = document.createElement('div');
this._display.className = 'ogEarthCoordinatesControl';
var that = this;
function _refresh(el) {
if (that._displayType >= DisplayTypesConverters.length)
that._displayType = 0;
if (that._displayType == 0) {
el.style.width = "275px";
} else if (that._displayType == 1) {
el.style.width = "355px";
} else if (that._displayType == 2) {
el.style.width = "350px";
}
that._converter = DisplayTypesConverters[that._displayType];
that._showPosition();
};
this._display.onclick = function (e) {
that._displayType += 1;
_refresh(this);
};
this.renderer.div.appendChild(this._display);
_refresh(this._display);
let centerDiv = document.createElement('div');
centerDiv.className = 'ogCenterIcon';
centerDiv.innerHTML = '<svg width="12" height="12"><g><path stroke-width="1" stroke-opacity="1" d="M6 0L6 12M0 6L12 6" stroke="#009DFF"></path></g></svg>';
this.renderer.div.appendChild(centerDiv);
if (this._center) {
this.renderer.activeCamera.events.on("moveend", this._grabCoordinates, this);
centerDiv.style.display = "block";
} else {
this.renderer.events.on("mousemove", this._onMouseMove, this);
centerDiv.style.display = "none";
}
}
/**
* Sets coordinates capturing type.
* @public
* @param {Boolean} center - True - capture screen center, false - mouse pointer.
*/
setCenter(center) {
if (center != this._center) {
this._center = center;
if (center) {
this.renderer.events.off("mousemove", this._onMouseMove);
this.renderer.activeCamera.events.on("moveend", this._grabCoordinates, this);
centerDiv.style.display = "block";
} else {
this.renderer.events.off("draw", this._draw);
this.renderer.events.on("mousemove", this._onMouseMove, this);
centerDiv.style.display = "none";
}
}
}
_showPosition() {
if (this.position) {
this.position.height = ((this.position.height > 10000 || this.position.height < -10000) ? 0 : this.position.height);
this._display.innerHTML = "Lat/Lon: " + this._converter(this.position) + " h(km): " + (this.position.height > 0 ? "~" + (Math.round(this.position.height) / 1000).toFixed(2) : "-");
} else {
this._display.innerHTML = "Lat/Lon: " + "_____________________";
}
}
_grabCoordinates() {
var r = this.renderer;
var ts = r.events.touchState;
this.position = this.planet.getLonLatFromPixelTerrain(r.handler.getCenter());
this._showPosition();
}
_onMouseMove() {
var r = this.renderer;
var ms = r.events.mouseState;
if (!(ms.leftButtonDown || ms.rightButtonDown) &&
r.controlsBag.scaleRot <= 0 &&
!r.activeCamera._flying) {
this.position = this.planet.getLonLatFromPixelTerrain(ms, true);
this._showPosition();
}
}
};
export function earthCoordinates(options) {
return new EarthCoordinates(options);
};
export { EarthCoordinates };

View File

@ -0,0 +1,62 @@
/**
* @module og/control/GeoImageDragControl
*/
'use strict';
import { BaseControl } from './BaseControl.js';
import { BaseGeoImage } from '../layer/BaseGeoImage.js';
class GeoImageDragControl extends BaseControl {
constructor(options) {
super(options);
options = options || {};
this._cornerIndex = -1;
this._catchCorner = false;
}
oninit() {
var that = this;
var p = this.planet;
p.events.on('layeradd', function (e) {
if (e instanceof BaseGeoImage) {
e.events.on('mousemove', function (ms) {
if (that._active) {
if (that._catchCorner) {
var corners = e.getCornersLonLat();
corners[that._cornerIndex] = p.getLonLatFromPixelTerrain(ms, true);
e.setCornersLonLat(corners);
} else {
that._cornerIndex = -1;
for (var i = 0; i < e._cornersWgs84.length; i++) {
var ground = p.getLonLatFromPixelTerrain(ms, true)
if (ground && p.ellipsoid.getGreatCircleDistance(e._cornersWgs84[i], ground) / p.getDistanceFromPixel(ms, true) <= 0.05) {
that._cornerIndex = i;
break;
}
}
}
}
});
e.events.on('ldown', function (ms) {
if (that._active && that._cornerIndex != -1) {
that._catchCorner = true;
p.renderer.controls[0]._active = false;
}
});
e.events.on('lup', function (ms) {
if (that._active) {
that._catchCorner = false;
p.renderer.controls[0]._active = true;
}
});
}
});
}
};
export { GeoImageDragControl };

View File

@ -0,0 +1,117 @@
/**
* @module og/control/KeyboardNavigation
*/
'use strict';
import * as math from '../math.js';
import { BaseControl } from './BaseControl.js';
import { input } from '../input/input.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Planet camera keyboard navigation. Use W,S,A,D and left shift key for fly around a planet.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class KeyboardNavigation extends BaseControl {
constructor(options) {
options = options || {};
super(options);
}
oninit() {
this.renderer.events.on("keypress", input.KEY_W, this.onCameraMoveForward, this);
this.renderer.events.on("keypress", input.KEY_S, this.onCameraMoveBackward, this);
this.renderer.events.on("keypress", input.KEY_A, this.onCameraStrifeLeft, this);
this.renderer.events.on("keypress", input.KEY_D, this.onCameraStrifeRight, this);
this.renderer.events.on("keypress", input.KEY_UP, this.onCameraLookUp, this);
this.renderer.events.on("keypress", input.KEY_DOWN, this.onCameraLookDown, this);
this.renderer.events.on("keypress", input.KEY_LEFT, this.onCameraTurnLeft, this);
this.renderer.events.on("keypress", input.KEY_RIGHT, this.onCameraTurnRight, this);
this.renderer.events.on("keypress", input.KEY_Q, this.onCameraRollLeft, this);
this.renderer.events.on("keypress", input.KEY_E, this.onCameraRollRight, this);
}
onCameraMoveForward(event) {
var camera = this.renderer.activeCamera;
camera.slide(0, 0, -camera._lonLat.height / 30);
camera.update();
}
onCameraMoveBackward(event) {
var camera = this.renderer.activeCamera;
camera.slide(0, 0, camera._lonLat.height / 30);
camera.update();
}
onCameraStrifeLeft(event) {
var camera = this.renderer.activeCamera;
camera.slide(-camera._lonLat.height / 30, 0, 0);
camera.update();
}
onCameraStrifeRight(event) {
var camera = this.renderer.activeCamera;
camera.slide(camera._lonLat.height / 30, 0, 0);
camera.update();
}
onCameraLookUp(event) {
var cam = this.renderer.activeCamera;
if (this.renderer.events.isKeyPressed(input.KEY_SHIFT)) {
cam.pitch(5);
} else {
cam.rotateVertical(cam._lonLat.height / 3000000 * math.RADIANS, Vec3.ZERO);
}
cam.update();
}
onCameraLookDown(event) {
var cam = this.renderer.activeCamera;
if (this.renderer.events.isKeyPressed(input.KEY_SHIFT)) {
cam.pitch(-5);
} else {
cam.rotateVertical(-cam._lonLat.height / 3000000 * math.RADIANS, Vec3.ZERO);
}
cam.update();
}
onCameraTurnLeft(event) {
var cam = this.renderer.activeCamera;
if (this.renderer.events.isKeyPressed(input.KEY_SHIFT)) {
cam.yaw(5);
} else {
cam.rotateHorizontal(cam._lonLat.height / 3000000 * math.RADIANS, false, Vec3.ZERO);
}
cam.update();
}
onCameraTurnRight(event) {
var cam = this.renderer.activeCamera;
if (this.renderer.events.isKeyPressed(input.KEY_SHIFT)) {
cam.yaw(-5);
} else {
cam.rotateHorizontal(-cam._lonLat.height / 3000000 * math.RADIANS, false, Vec3.ZERO);
}
cam.update();
}
onCameraRollLeft(event) {
this.renderer.activeCamera.roll(-5);
this.renderer.activeCamera.update();
}
onCameraRollRight(event) {
this.renderer.activeCamera.roll(5);
this.renderer.activeCamera.update();
}
};
export function keyboardNavigation(options) {
return new KeyboardNavigation(options);
};
export { KeyboardNavigation };

View File

@ -0,0 +1,159 @@
/**
* @module og/control/LayerSwitcher
*/
'use strict';
import { BaseControl } from './BaseControl.js';
/**
* Simple(OpenLayers like)layer switcher, includes base layers, overlays, geo images etc. groups.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class LayerSwitcher extends BaseControl {
constructor(options) {
super(options);
this.dialog = null;
this.baseLayersDiv = null;
this.overlaysDiv = null;
this._id = LayerSwitcher.numSwitches++;
}
static get numSwitches() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set numSwitches(n) {
this._counter = n;
}
oninit() {
this.planet.events.on("layeradd", this.onLayerAdded, this);
this.planet.events.on("layerremove", this.onLayerRemoved, this);
this.createSwitcher();
this.createDialog();
}
onLayerAdded(layer) {
if (layer.displayInLayerSwitcher) {
if (layer.isBaseLayer()) {
this.addSwitcher("radio", layer, this.baseLayersDiv);
} else {
this.addSwitcher("checkbox", layer, this.overlaysDiv, this._id);
}
}
};
onLayerRemoved(layer) {
layer._removeCallback();
layer._removeCallback = null;
}
addSwitcher(type, obj, container, id) {
var lineDiv = document.createElement('div');
var that = this;
var center = document.createElement('div');
center.classList.add('ogViewExtentBtn');
center.onclick = function () {
that.planet.flyExtent(obj.getExtent());
};
var inp = document.createElement('input');
inp.type = type;
inp.name = "ogBaseLayerRadiosId" + (id || "");
inp.checked = obj.getVisibility();
inp.className = "ogLayerSwitcherInput";
inp.onclick = function () {
obj.setVisibility(this.checked);
};
obj.events && obj.events.on("visibilitychange", function (e) {
inp.checked = e.getVisibility();
});
var lbl = document.createElement('label');
lbl.className = "ogLayerSwitcherLabel";
lbl.innerHTML = (obj.name || obj.src || "noname") + "</br>";
obj._removeCallback = function () {
container.removeChild(lineDiv);
}
lineDiv.appendChild(center);
lineDiv.appendChild(inp);
lineDiv.appendChild(lbl);
container.appendChild(lineDiv);
}
createBaseLayersContainer() {
var layersDiv = document.createElement('div');
layersDiv.className = "layersDiv";
this.dialog.appendChild(layersDiv);
var baseLayersLbl = document.createElement('div');
baseLayersLbl.className = "layersDiv";
baseLayersLbl.innerHTML = "Base Layer";
layersDiv.appendChild(baseLayersLbl);
this.baseLayersDiv = document.createElement('div');
layersDiv.appendChild(this.baseLayersDiv);
}
createOverlaysContainer() {
var overlaysDiv = document.createElement('div');
overlaysDiv.className = "layersDiv";
this.dialog.appendChild(overlaysDiv);
var overlaysLbl = document.createElement('div');
overlaysLbl.className = "layersDiv";
overlaysLbl.innerHTML = "Overlays";
overlaysDiv.appendChild(overlaysLbl);
this.overlaysDiv = document.createElement('div');
overlaysDiv.appendChild(this.overlaysDiv);
}
createDialog() {
this.dialog = document.createElement('div');
this.dialog.id = "ogLayerSwitcherDialog";
this.dialog.className = "displayNone";
this.renderer.div.appendChild(this.dialog);
this.createBaseLayersContainer();
this.createOverlaysContainer();
if (this.planet) {
for (var i = 0; i < this.planet.layers.length; i++) {
this.onLayerAdded(this.planet.layers[i]);
}
}
}
createSwitcher() {
var button = document.createElement('div');
button.className = 'ogLayerSwitcherButton';
button.id = "ogLayerSwitcherButtonMaximize";
var that = this;
button.onclick = function (e) {
if (this.id === "ogLayerSwitcherButtonMaximize") {
this.id = "ogLayerSwitcherButtonMinimize";
that.dialog.className = "displayBlock";
} else {
this.id = "ogLayerSwitcherButtonMaximize";
that.dialog.className = "displayNone";
}
};
this.renderer.div.appendChild(button);
}
};
export { LayerSwitcher };

View File

@ -0,0 +1,327 @@
/**
* @module og/control/MouseNavigation
*/
'use strict';
import * as math from '../math.js';
import { BaseControl } from './BaseControl.js';
import { input } from '../input/input.js';
import { Key } from '../Lock.js';
import { LonLat } from '../LonLat.js';
import { Mat4 } from '../math/Mat4.js';
import { Quat } from '../math/Quat.js';
import { Ray } from '../math/Ray.js';
import { Sphere } from '../bv/Sphere.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Mouse planet camera dragging control.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class MouseNavigation extends BaseControl {
constructor(options) {
super(options);
options = options || {};
this.grabbedPoint = new Vec3();
this._eye0 = new Vec3();
this.pointOnEarth = new Vec3();
this.earthUp = new Vec3();
this.inertia = 0.007;
this.grabbedSpheroid = new Sphere();
this.planet = null;
this.qRot = new Quat();
this.scaleRot = 0;
this.distDiff = 0.33;
this.stepsCount = 5;
this.stepsForward = null;
this.stepIndex = 0;
this._keyLock = new Key();
}
static getMovePointsFromPixelTerrain(cam, planet, stepsCount, delta, point, forward, dir) {
var steps = []
var eye = cam.eye.clone(),
n = cam._n.clone(),
u = cam._u.clone(),
v = cam._v.clone();
var a = planet.getCartesianFromPixelTerrain(point, true);
if (!dir) {
dir = Vec3.sub(a, cam.eye).normalize();
}
var d = a ? delta * cam.eye.distance(a) / stepsCount : 1000;
if (forward) {
d = -d;
} else {
d *= 2;
}
var scaled_n = n.scaleTo(d);
if (a && cam._lonLat.height > 9000 && cam.slope > 0.6) {
var grabbedSpheroid = new Sphere();
grabbedSpheroid.radius = a.length();
var rotArr = [],
eyeArr = []
var breaked = false;
for (var i = 0; i < stepsCount; i++) {
eye.addA(scaled_n);
var b = new Ray(eye, dir).hitSphere(grabbedSpheroid);
eyeArr[i] = eye.clone();
if (b) {
rotArr[i] = new Mat4().rotateBetweenVectors(a.normal(), b.normal());
} else {
breaked = true;
break;
}
}
if (!breaked) {
for (var i = 0; i < stepsCount; i++) {
var rot = rotArr[i];
steps[i] = {};
steps[i].eye = rot.mulVec3(eyeArr[i]);
steps[i].v = rot.mulVec3(v);
steps[i].u = rot.mulVec3(u);
steps[i].n = rot.mulVec3(n);
}
} else {
eye = cam.eye.clone();
for (var i = 0; i < stepsCount; i++) {
steps[i] = {};
steps[i].eye = eye.addA(scaled_n).clone();
steps[i].v = v;
steps[i].u = u;
steps[i].n = n;
}
}
} else {
for (var i = 0; i < stepsCount; i++) {
steps[i] = {};
steps[i].eye = eye.addA(dir.scaleTo(-d)).clone();
steps[i].v = v;
steps[i].u = u;
steps[i].n = n;
}
}
return steps;
}
onactivate() {
this.renderer.events.on("mousewheel", this.onMouseWheel, this);
this.renderer.events.on("lhold", this.onMouseLeftButtonDown, this);
this.renderer.events.on("rhold", this.onMouseRightButtonDown, this);
this.renderer.events.on("ldown", this.onMouseLeftButtonClick, this);
this.renderer.events.on("lup", this.onMouseLeftButtonUp, this);
this.renderer.events.on("rdown", this.onMouseRightButtonClick, this);
this.renderer.events.on("ldblclick", this.onMouseLeftButtonDoubleClick, this);
this.renderer.events.on("draw", this.onDraw, this);
}
ondeactivate() {
this.renderer.events.off("mousewheel", this.onMouseWheel);
this.renderer.events.off("lhold", this.onMouseLeftButtonDown);
this.renderer.events.off("rhold", this.onMouseRightButtonDown);
this.renderer.events.off("ldown", this.onMouseLeftButtonClick);
this.renderer.events.off("lup", this.onMouseLeftButtonUp);
this.renderer.events.off("rdown", this.onMouseRightButtonClick);
this.renderer.events.off("ldblclick", this.onMouseLeftButtonDoubleClick);
this.renderer.events.off("draw", this.onDraw);
};
onMouseWheel(event) {
if (this.stepIndex)
return;
this.planet.stopFlying();
this.stopRotation();
this._deactivate = true;
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
var ms = this.renderer.events.mouseState;
this.stepIndex = this.stepsCount;
this.stepsForward = MouseNavigation.getMovePointsFromPixelTerrain(this.renderer.activeCamera,
this.planet, this.stepsCount, this.distDiff, ms, event.wheelDelta > 0, ms.direction);
}
oninit() {
this.activate();
}
onMouseLeftButtonDoubleClick() {
this.planet.stopFlying();
this.stopRotation();
var p = this.planet.getCartesianFromPixelTerrain(this.renderer.events.mouseState, true),
g = this.planet.ellipsoid.cartesianToLonLat(p);
if (this.renderer.events.isKeyPressed(input.KEY_SHIFT)) {
this.planet.flyLonLat(new LonLat(g.lon, g.lat, this.renderer.activeCamera.eye.distance(p) * 2.0));
} else {
this.planet.flyLonLat(new LonLat(g.lon, g.lat, this.renderer.activeCamera.eye.distance(p) * 0.57));
}
}
onMouseLeftButtonClick() {
if (this._active) {
this.renderer.handler.canvas.classList.add("ogGrabbingPoiner");
this.grabbedPoint = this.planet.getCartesianFromMouseTerrain(true);
if (this.grabbedPoint) {
this._eye0.copy(this.renderer.activeCamera.eye);
this.grabbedSpheroid.radius = this.grabbedPoint.length();
this.stopRotation();
}
}
}
stopRotation() {
this.qRot.clear();
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
}
onMouseLeftButtonUp(e) {
this.renderer.handler.canvas.classList.remove("ogGrabbingPoiner");
}
onMouseLeftButtonDown(e) {
if (this._active) {
if (!this.grabbedPoint)
return;
this.planet.stopFlying();
if (this.renderer.events.mouseState.moving) {
var cam = this.renderer.activeCamera;
if (cam.slope > 0.28) {
var targetPoint = new Ray(cam.eye, e.direction).hitSphere(this.grabbedSpheroid);
if (targetPoint) {
this.scaleRot = 1;
this.qRot = Quat.getRotationBetweenVectors(targetPoint.normal(), this.grabbedPoint.normal());
var rot = this.qRot;
cam.eye = rot.mulVec3(cam.eye);
cam._v = rot.mulVec3(cam._v);
cam._u = rot.mulVec3(cam._u);
cam._n = rot.mulVec3(cam._n);
cam.update();
}
} else {
var p0 = this.grabbedPoint,
p1 = Vec3.add(p0, cam._u),
p2 = Vec3.add(p0, p0.normal());
var px = new Vec3();
if (new Ray(cam.eye, e.direction).hitPlane(p0, p1, p2, px) === Ray.INSIDE) {
cam.eye = this._eye0.addA(px.subA(p0).negate());
cam.update();
}
}
} else {
this.scaleRot = 0;
}
}
}
onMouseRightButtonClick(e) {
this.stopRotation();
this.planet.stopFlying();
this.pointOnEarth = this.planet.getCartesianFromPixelTerrain({ x: e.x, y: e.y }, true);
if (this.pointOnEarth) {
this.earthUp = this.pointOnEarth.normal();
}
};
onMouseRightButtonDown(e) {
var cam = this.renderer.activeCamera;
if (this.pointOnEarth && this.renderer.events.mouseState.moving) {
this.renderer.controlsBag.scaleRot = 1;
var l = 0.5 / cam.eye.distance(this.pointOnEarth) * cam._lonLat.height * math.RADIANS;
if (l > 0.007) l = 0.007;
cam.rotateHorizontal(l * (e.x - e.prev_x), false, this.pointOnEarth, this.earthUp);
cam.rotateVertical(l * (e.y - e.prev_y), this.pointOnEarth);
cam.update();
}
}
onDraw(e) {
if (this._active) {
var r = this.renderer;
var cam = r.activeCamera;
var prevEye = cam.eye.clone();
if (this.stepIndex) {
r.controlsBag.scaleRot = 1;
var sf = this.stepsForward[this.stepsCount - this.stepIndex--];
cam.eye = sf.eye;
cam._v = sf.v;
cam._u = sf.u;
cam._n = sf.n;
cam.update();
} else {
if (this._deactivate) {
this._deactivate = false;
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
}
}
if (r.events.mouseState.leftButtonDown || !this.scaleRot)
return;
this.scaleRot -= this.inertia;
if (this.scaleRot <= 0) {
this.scaleRot = 0;
} else {
r.controlsBag.scaleRot = this.scaleRot;
var rot = this.qRot.slerp(Quat.IDENTITY, 1 - this.scaleRot * this.scaleRot * this.scaleRot).normalize();
if (!(rot.x || rot.y || rot.z)) {
this.scaleRot = 0;
}
cam.eye = rot.mulVec3(cam.eye);
cam._v = rot.mulVec3(cam._v);
cam._u = rot.mulVec3(cam._u);
cam._n = rot.mulVec3(cam._n);
cam.update();
}
if (cam.eye.distance(prevEye) / cam._terrainAltitude > 0.01) {
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
} else {
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
}
}
}
};
export { MouseNavigation };

39
src/og/control/ShowFps.js Normal file
View File

@ -0,0 +1,39 @@
/**
* @module og/control/ShowFps
*/
'use strict';
import { BaseControl } from './BaseControl.js';
import { print2d } from '../utils/Shared.js';
/**
* Frame per second(FPS) display control.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class ShowFps extends BaseControl {
constructor(options) {
super(options);
}
oninit() {
var d = document.createElement('div');
d.className = 'defaultText ';
d.id = "ogShowFpsControl";
document.body.appendChild(d);
this.renderer.events.on("draw", this._draw, this);
}
_draw() {
print2d("ogShowFpsControl", (1000.0 / this.renderer.handler.deltaTime).toFixed(1), this.renderer.handler.canvas.clientWidth - 60, 0);
}
};
export function showFps(options) {
return new ShowFps(options);
};
export { ShowFps };

View File

@ -0,0 +1,110 @@
/**
* @module og/control/SimpleNavigation
*/
'use strict';
import * as math from '../math.js';
import { BaseControl } from './BaseControl.js';
import { input } from '../input/input.js';
/**
* Simple keyboard camera navigation with W,S,A,D and shift keys to fly around the scene.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class SimpleNavigation extends BaseControl {
constructor(options) {
options = options || {};
super(options);
this.camera = null;
this.speed = options.speed || 1.0;
}
oninit() {
this.camera = this.renderer.activeCamera;
this.renderer.events.on("keypress", input.KEY_W, this.onCameraMoveForward, this);
this.renderer.events.on("keypress", input.KEY_S, this.onCameraMoveBackward, this);
this.renderer.events.on("keypress", input.KEY_A, this.onCameraStrifeLeft, this);
this.renderer.events.on("keypress", input.KEY_D, this.onCameraStrifeRight, this);
this.renderer.events.on("keypress", input.KEY_UP, this.onCameraLookUp, this);
this.renderer.events.on("keypress", input.KEY_DOWN, this.onCameraLookDown, this);
this.renderer.events.on("keypress", input.KEY_LEFT, this.onCameraTurnLeft, this);
this.renderer.events.on("keypress", input.KEY_RIGHT, this.onCameraTurnRight, this);
this.renderer.events.on("keypress", input.KEY_Q, this.onCameraRollLeft, this);
this.renderer.events.on("keypress", input.KEY_E, this.onCameraRollRight, this);
}
onCameraMoveForward(event) {
var camera = this.camera;
camera.slide(0, 0, -this.speed);
camera.update();
}
onCameraMoveBackward(event) {
var camera = this.camera;
camera.slide(0, 0, this.speed);
camera.update();
}
onCameraStrifeLeft(event) {
var camera = this.camera;
camera.slide(-this.speed, 0, 0);
camera.update();
}
onCameraStrifeRight(event) {
var camera = this.camera;
camera.slide(this.speed, 0, 0);
camera.update();
}
onCameraLookUp(event) {
var cam = this.camera;
cam.pitch(0.5);
cam.update();
}
onCameraLookDown(event) {
var cam = this.camera;
cam.pitch(-0.5);
cam.update();
}
onCameraTurnLeft(event) {
var cam = this.camera;
cam.yaw(0.5);
cam.update();
}
onCameraTurnRight(event) {
var cam = this.camera;
cam.yaw(-0.5);
cam.update();
}
onCameraRollLeft(event) {
var cam = this.camera;
cam.roll(-0.5);
cam.update();
}
onCameraRollRight(event) {
var cam = this.camera;
cam.roll(0.5);
cam.update();
}
};
/**
* Creates simple navigation control instance.
*/
export function simpleNavigation(options) {
return new SimpleNavigation(options);
};
export { SimpleNavigation };

153
src/og/control/Sun.js Normal file
View File

@ -0,0 +1,153 @@
/**
* @module og/control/Sun
*/
'use strict';
import { BaseControl } from './BaseControl.js';
import { getSunPosition } from '../astro/earth.js';
import { input } from '../input/input.js';
import { LightSource } from '../light/LightSource.js';
import { Quat } from '../math/Quat.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Real Sun geocentric position control that place the Sun on the right place by the Earth.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class Sun extends BaseControl {
constructor(options) {
super(options);
options = options || {};
/**
* Earth planet node.
* @public
* @type {og.scene.Planet}
*/
this.planet = null;
/**
* Sunlight position placed in the camera eye.
* @private
* @type {boolean}
*/
//this._isCameraSunlight = false;
this.offsetVertical = options.offsetVertical || -5000000;
this.offsetHorizontal = options.offsetHorizontal || 5000000;
/**
* Light source.
* @public
* @type {og.LightSource}
*/
this.sunlight = null;
/**
* Current frame handler clock date and time.
* @private
* @type {Number}
*/
this._currDate = 0;
/**
* Previous frame handler clock date and time.
* @private
* @type {Number}
*/
this._prevDate = 0;
this._clockPtr = null;
this._lightOn = false;
this._stopped = options.stopped || false;
}
oninit() {
this.planet.lightEnabled = true;
//sunlight initialization
this.sunlight = new LightSource("Sun", {
'ambient': new Vec3(0.15, 0.15, 0.25),
'diffuse': new Vec3(0.9, 0.9, 0.8),
'specular': new Vec3(0.1, 0.1, 0.06),
'shininess': 110
});
this.sunlight.addTo(this.planet);
var that = this;
this.renderer.events.on("draw", this._draw, this);
this.renderer.events.on("charkeypress", input.KEY_L, function () {
that.planet.lightEnabled = !that.planet.lightEnabled;
});
if (!this._clockPtr)
this._clockPtr = this.renderer.handler.defaultClock;
}
stop() {
this._stopped = true;
}
onactivate() {
this._stopped = false;
}
bindClock(clock) {
this._clockPtr = clock;
}
_draw() {
if (!this._stopped) {
this._currDate = this._clockPtr.currentDate;
var cam = this.renderer.activeCamera;
if (cam.getHeight() < 4650000 || !this._active) {
this._lightOn = true;
this._f = 1;
var n = cam.eye.normal();
var tu = Vec3.proj_b_to_plane(cam._v, n, cam._v).normalize().scale(this.offsetVertical);
var tr = Vec3.proj_b_to_plane(cam._u, n, cam._u).normalize().scale(this.offsetHorizontal);
var d = tu.add(tr);
var pos = cam.eye.add(d);
if (this._k > 0) {
this._k -= 0.01;
var rot = Quat.getRotationBetweenVectors(this.sunlight._position.normal(), pos.normal());
var r = rot.slerp(Quat.IDENTITY, this._k).normalize();
this.sunlight.setPosition(r.mulVec3(this.sunlight._position));
} else {
this.sunlight.setPosition(pos);
}
} else {
this._k = 1;
if (this._f > 0) {
this._f -= 0.01;
var rot = Quat.getRotationBetweenVectors(this.sunlight._position.normal(), getSunPosition(this._currDate).normal());
var r = rot.slerp(Quat.IDENTITY, this._f).normalize();
this.sunlight.setPosition(r.mulVec3(this.sunlight._position));
} else {
if (Math.abs(this._currDate - this._prevDate) > 0.00034 && this._active || this._lightOn) {
this._lightOn = false;
this._prevDate = this._currDate;
this.sunlight.setPosition(getSunPosition(this._currDate));
this._f = 0;
}
}
}
}
}
};
export function sun(options) {
return Sun(options);
};
export { Sun };

View File

@ -0,0 +1,35 @@
/**
* @module og/control/ToggleWireframe
*/
'use strict';
import { BaseControl } from './BaseControl.js';
import { input } from '../input/input.js';
/**
* Planet GL draw mode(TRIANGLE_STRIP/LINE_STRING) changer.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class ToggleWireframe extends BaseControl {
constructor(options) {
super(options);
}
oninit() {
this.renderer.events.on("charkeypress", input.KEY_X, this.toogleWireframe, this);
}
toogleWireframe(e) {
if (this.planet.drawMode === this.renderer.handler.gl.LINE_STRIP) {
this.planet.setDrawMode(this.renderer.handler.gl.TRIANGLE_STRIP);
} else {
this.planet.setDrawMode(this.renderer.handler.gl.LINE_STRIP);
}
}
};
export { ToggleWireframe };

View File

@ -0,0 +1,310 @@
/**
* @module og/control/TouchNavigation
*/
'use strict';
import * as math from '../math.js';
import { BaseControl } from './BaseControl.js';
import { input } from '../input/input.js';
import { Key } from '../Lock.js';
import { LonLat } from '../LonLat.js';
import { Mat4 } from '../math/Mat4.js';
import { Quat } from '../math/Quat.js';
import { Ray } from '../math/Ray.js';
import { Sphere } from '../bv/Sphere.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Touch pad planet camera dragging control.
* @class
* @extends {og.control.BaseControl}
* @param {Object} [options] - Control options.
*/
class TouchNavigation extends BaseControl {
constructor(options) {
super(options);
this.grabbedPoint = new Vec3();
this.inertia = 0.007;
this.grabbedSpheroid = new Sphere();
this.planet = null;
this.qRot = new Quat();
this.scaleRot = 0;
this.rot = 1;
this._eye0 = new Vec3();
this.stepsCount = 5;
this.stepsForward = null;
this.stepIndex = 0;
var Touch = function () {
this.x = 0;
this.y = 0;
this.prev_x = 0;
this.prev_y = 0;
this.grabbedPoint = new Vec3();
this.grabbedSpheroid = new Sphere();
this.dX = function () { return this.x - this.prev_x; };
this.dY = function () { return this.y - this.prev_y; };
};
this.pointOnEarth = null;
this.earthUp = null;
this.touches = [new Touch(), new Touch()];
this._keyLock = new Key();
}
oninit() {
this.renderer.events.on("touchstart", this.onTouchStart, this);
this.renderer.events.on("touchend", this.onTouchEnd, this);
this.renderer.events.on("doubletouch", this.onDoubleTouch, this);
this.renderer.events.on("touchcancel", this.onTouchCancel, this);
this.renderer.events.on("touchmove", this.onTouchMove, this);
this.renderer.events.on("draw", this.onDraw, this);
}
onTouchStart(e) {
this._touching = true;
if (e.sys.touches.length === 2) {
var t0 = this.touches[0],
t1 = this.touches[1];
t0.x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t0.y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
t0.prev_x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t0.prev_y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
t0.grabbedPoint = this.planet.getCartesianFromPixelTerrain(t0, true);
t1.x = e.sys.touches.item(1).clientX - e.sys.offsetLeft;
t1.y = e.sys.touches.item(1).clientY - e.sys.offsetTop;
t1.prev_x = e.sys.touches.item(1).clientX - e.sys.offsetLeft;
t1.prev_y = e.sys.touches.item(1).clientY - e.sys.offsetTop;
t1.grabbedPoint = this.planet.getCartesianFromPixelTerrain(t1, true);
//this.planet._viewChanged = true;
this.pointOnEarth = this.planet.getCartesianFromPixelTerrain(this.renderer.handler.getCenter(), true);
if (this.pointOnEarth) {
this.earthUp = this.pointOnEarth.normal();
}
if (t0.grabbedPoint && t1.grabbedPoint) {
t0.grabbedSpheroid.radius = t0.grabbedPoint.length();
t1.grabbedSpheroid.radius = t1.grabbedPoint.length();
this.stopRotation();
}
} else if (e.sys.touches.length === 1) {
this._startTouchOne(e);
}
}
_startTouchOne(e) {
var t = this.touches[0];
t.x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t.y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
t.prev_x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t.prev_y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
t.grabbedPoint = this.planet.getCartesianFromPixelTerrain(t, true);
this._eye0.copy(this.renderer.activeCamera.eye);
if (t.grabbedPoint) {
t.grabbedSpheroid.radius = t.grabbedPoint.length();
this.stopRotation();
}
}
stopRotation() {
this.qRot.clear();
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
}
onDoubleTouch(e) {
if (this.stepIndex)
return;
this.planet.stopFlying();
this.stopRotation();
var p = this.planet.getCartesianFromPixelTerrain(this.touches[0], true),
g = this.planet.ellipsoid.cartesianToLonLat(p);
this.planet.flyLonLat(new LonLat(g.lon, g.lat, this.renderer.activeCamera.eye.distance(p) * 0.57));
}
onTouchEnd(e) {
if (e.sys.touches.length === 0)
this._touching = false;
if (e.sys.touches.length === 1) {
this._startTouchOne(e);
}
if (Math.abs(this.touches[0].x - this.touches[0].prev_x) < 3 &&
Math.abs(this.touches[0].y - this.touches[0].prev_y) < 3)
this.scaleRot = 0;
}
onTouchCancel(e) {
}
onTouchMove(e) {
var cam = this.renderer.activeCamera;
if (e.sys.touches.length === 2) {
this.renderer.controlsBag.scaleRot = 1;
var t0 = this.touches[0],
t1 = this.touches[1];
if (!t0.grabbedPoint || !t1.grabbedPoint)
return;
this.planet.stopFlying();
t0.prev_x = t0.x;
t0.prev_y = t0.y;
t0.x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t0.y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
t1.prev_x = t1.x;
t1.prev_y = t1.y;
t1.x = e.sys.touches.item(1).clientX - e.sys.offsetLeft;
t1.y = e.sys.touches.item(1).clientY - e.sys.offsetTop;
//var center_x = Math.round(t0.x + (t1.x - t0.x) * 0.5);
//var center_y = Math.round(t0.y + (t1.y - t0.y) * 0.5);
//var dirC = cam.unproject(center_x, center_y);
//var targetPointC = this.planet.getCartesianFromPixelTerrain(new og.math.Pixel(center_x, center_y));
//var dir0 = cam.unproject(t0.x, t0.y);
//var targetPoint0 = new og.math.Ray(cam.eye, dir0).hitSphere(t0.grabbedSpheroid);
//var dir1 = cam.unproject(t1.x, t1.y);
//var targetPoint1 = new og.math.Ray(cam.eye, dir1).hitSphere(t1.grabbedSpheroid);
//print2d("t1", center_x + "," + center_y, 100, 100);
//print2d("t2", targetPointC.x + "," + targetPointC.y + "," + targetPointC.z, 100, 120);
if (t0.dY() > 0 && t1.dY() > 0 || t0.dY() < 0 && t1.dY() < 0 ||
t0.dX() > 0 && t1.dX() > 0 || t0.dX() < 0 && t1.dX() < 0) {
var l = 0.5 / cam.eye.distance(this.pointOnEarth) * cam._lonLat.height * math.RADIANS;
if (l > 0.007) l = 0.007;
cam.rotateHorizontal(l * t0.dX(), false, this.pointOnEarth, this.earthUp);
cam.rotateVertical(l * t0.dY(), this.pointOnEarth);
cam.update();
}
this.scaleRot = 0;
} else if (e.sys.touches.length === 1) {
var t = this.touches[0];
t.prev_x = t.x;
t.prev_y = t.y;
t.x = e.sys.touches.item(0).clientX - e.sys.offsetLeft;
t.y = e.sys.touches.item(0).clientY - e.sys.offsetTop;
if (!t.grabbedPoint)
return;
this.planet.stopFlying();
var direction = cam.unproject(t.x, t.y);
var targetPoint = new Ray(cam.eye, direction).hitSphere(t.grabbedSpheroid);
if (targetPoint) {
if (cam._n.dot(cam.eye.normal()) > 0.28) {
this.qRot = Quat.getRotationBetweenVectors(targetPoint.normal(), t.grabbedPoint.normal());
var rot = this.qRot;
cam.eye = rot.mulVec3(cam.eye);
cam._v = rot.mulVec3(cam._v);
cam._u = rot.mulVec3(cam._u);
cam._n = rot.mulVec3(cam._n);
cam.update();
this.scaleRot = 1;
} else {
var p0 = t.grabbedPoint,
p1 = Vec3.add(p0, cam._u),
p2 = Vec3.add(p0, p0.normal());
var dir = cam.unproject(t.x, t.y);
var px = new Vec3();
if (new Ray(cam.eye, dir).hitPlane(p0, p1, p2, px) === Ray.INSIDE) {
cam.eye = this._eye0.addA(px.subA(p0).negate());
cam.update();
this.scaleRot = 0;
}
}
}
}
};
onDraw(e) {
this.renderer.controlsBag.scaleRot = this.scaleRot;
if (this._touching)
return;
var r = this.renderer;
var cam = r.activeCamera;
var prevEye = cam.eye.clone();
if (this.stepIndex) {
r.controlsBag.scaleRot = 1;
var sf = this.stepsForward[this.stepsCount - this.stepIndex--];
var cam = this.renderer.activeCamera;
cam.eye = sf.eye;
cam._v = sf.v;
cam._u = sf.u;
cam._n = sf.n;
cam.update();
}
if (r.events.mouseState.leftButtonDown || !this.scaleRot)
return;
this.scaleRot -= this.inertia;
if (this.scaleRot <= 0)
this.scaleRot = 0;
else {
r.controlsBag.scaleRot = this.scaleRot;
var cam = r.activeCamera;
var rot = this.qRot.slerp(Quat.IDENTITY, 1 - this.scaleRot * this.scaleRot * this.scaleRot).normalize();
if (!(rot.x || rot.y || rot.z)) {
this.scaleRot = 0;
}
cam.eye = rot.mulVec3(cam.eye);
cam._v = rot.mulVec3(cam._v);
cam._u = rot.mulVec3(cam._u);
cam._n = rot.mulVec3(cam._n);
cam.update();
}
if (cam.eye.distance(prevEye) / cam._terrainAltitude > 0.01) {
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
} else {
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
}
}
};
export { TouchNavigation };

View File

@ -0,0 +1,122 @@
/**
* @module og/control/ZoomControl
*/
'use strict';
import { BaseControl } from './BaseControl.js';
import { Key } from '../Lock.js';
import { MouseNavigation } from './MouseNavigation.js';
/**
* Planet zoom buttons control.
* @class
* @extends {og.control.BaseControl}
* @params {Object} [options] - Control options.
*/
class ZoomControl extends BaseControl {
constructor(options) {
super(options);
options = options || {};
this.distDiff = 0.33;
this.stepsCount = 5;
this.stepsForward = null;
this.stepIndex = 0;
this._keyLock = new Key();
this.planet = null;
}
oninit() {
var zoomDiv = document.createElement('div'),
btnZoomIn = document.createElement('button'),
btnZoomOut = document.createElement('button');
zoomDiv.className = 'ogZoomControl';
btnZoomIn.className = 'ogZoomButton ogZoomIn';
btnZoomOut.className = 'ogZoomButton ogZoomOut';
zoomDiv.appendChild(btnZoomIn);
zoomDiv.appendChild(btnZoomOut);
this.renderer.div.appendChild(zoomDiv);
var that = this;
btnZoomIn.onclick = function (e) {
that.zoomIn();
};
btnZoomOut.onclick = function (e) {
that.zoomOut();
};
this.renderer.events.on("draw", this._draw, this);
}
/**
* Planet zoom in.
* @public
*/
zoomIn() {
this._deactivate = true;
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
this.stepIndex = this.stepsCount;
this.stepsForward = MouseNavigation.getMovePointsFromPixelTerrain(this.renderer.activeCamera,
this.planet, this.stepsCount, this.distDiff * 1.7, this.renderer.getCenter(), true, this.renderer.activeCamera._n.negateTo());
}
/**
* Planet zoom out.
* @public
*/
zoomOut() {
this._deactivate = true;
this.planet.layerLock.lock(this._keyLock);
this.planet.terrainLock.lock(this._keyLock);
this.planet._normalMapCreator.lock(this._keyLock);
this.stepIndex = this.stepsCount;
this.stepsForward = MouseNavigation.getMovePointsFromPixelTerrain(this.renderer.activeCamera,
this.planet, this.stepsCount, this.distDiff * 2, this.renderer.getCenter(), false, this.renderer.activeCamera._n.negateTo());
}
_draw(e) {
var cam = this.renderer.activeCamera;
if (this.stepIndex) {
var sf = this.stepsForward[this.stepsCount - this.stepIndex--];
cam.eye = sf.eye;
cam._v = sf.v;
cam._u = sf.u;
cam._n = sf.n;
cam.update();
} else if (!cam._flying) {
if (this._deactivate) {
this.planet.layerLock.free(this._keyLock);
this.planet.terrainLock.free(this._keyLock);
this.planet._normalMapCreator.free(this._keyLock);
this._deactivate = false;
}
}
}
};
export function zoomControl(options) {
return new ZoomControl(options);
};
export { ZoomControl };

View File

@ -0,0 +1,393 @@
/**
* @module og/ellipsoid/Ellipsoid
*/
'use strict';
import * as math from '../math.js';
import { Vec3 } from '../math/Vec3.js';
import { LonLat } from '../LonLat.js';
/**
* Class represents a plant ellipsoid.
* @class
* @param {number} equatorialSize - Equatorial ellipsoid size.
* @param {number} polarSize - Polar ellipsoid size.
*/
class Ellipsoid {
constructor(equatorialSize, polarSize) {
this._a = equatorialSize;
this._b = polarSize;
this._flattening = equatorialSize / polarSize;
this._a2 = equatorialSize * equatorialSize;
this._b2 = polarSize * polarSize;
var qa2b2 = Math.sqrt(this._a2 - this._b2);
this._e = qa2b2 / equatorialSize;
this._e2 = this._e * this._e;
this._e22 = this._e2 * this._e2;
this._k = qa2b2 / polarSize;
this._k2 = this._k * this._k;
this._radii = new Vec3(equatorialSize, polarSize, equatorialSize);
this._radii2 = new Vec3(this._a2, this._b2, this._a2);
this._invRadii = new Vec3(1 / equatorialSize, 1 / polarSize, 1 / equatorialSize);
this._invRadii2 = new Vec3(1 / this._a2, 1 / this._b2, 1 / this._a2);
}
/**
* Gets ellipsoid equatorial size.
* @public
* @returns {number} -
*/
getEquatorialSize() {
return this._a;
}
/**
* Gets ellipsoid polar size.
* @public
* @returns {number} -
*/
getPolarSize() {
return this._b;
}
/**
* Gets cartesian ECEF from Wgs84 geodetic coordiantes.
* @public
* @param {og.LonLat} lonlat - Degrees geodetic coordiantes.
* @returns {og.math.Vector3} -
*/
lonLatToCartesian(lonlat) {
var latrad = math.RADIANS * lonlat.lat,
lonrad = math.RADIANS * lonlat.lon;
var slt = Math.sin(latrad);
var N = this._a / Math.sqrt(1 - this._e2 * slt * slt);
var nc = (N + lonlat.height) * Math.cos(latrad);
return new Vec3(
nc * Math.sin(lonrad),
(N * (1 - this._e2) + lonlat.height) * slt,
nc * Math.cos(lonrad));
}
/**
* Gets Wgs84 geodetic coordiantes from cartesian ECEF.
* @public
* @param {og.math.Vector3} cartesian - Cartesian coordinates.
* @returns {og.LonLat} -
*/
cartesianToLonLat(cartesian) {
var x = cartesian.z, y = cartesian.x, z = cartesian.y;
var ecc2 = this._e2;
var ecc22 = this._e22;
var r2 = x * x + y * y;
var r = Math.sqrt(r2);
var e2 = this._a2 - this._b2;
var z2 = z * z;
var f = 54.0 * this._b2 * z2;
var g = r2 + (1 - ecc2) * z2 + ecc2 * e2;
var g2 = g * g;
var c = ecc22 * f * r2 / (g2 * g);
var s = Math.pow((1 + c + Math.sqrt(c * (c + 2))), 0.33333333333333333);
var p = f / (3 * Math.pow((1 + s + 1 / s), 2) * g2);
var q = Math.sqrt(1 + 2 * ecc22 * p);
var r0 = -(p * ecc2 * r) / 1 + q + Math.sqrt(0.5 * this._a2 * (1 + 1 / q) - p * (1 - ecc2) * z2 / (q * (1 + q)) - 0.5 * p * r2);
var recc2r0 = r - ecc2 * r0;
var recc2r02 = recc2r0 * recc2r0;
var u = Math.sqrt(recc2r02 + z2);
var v = Math.sqrt(recc2r02 + (1 - ecc2) * z2);
var z0 = this._b2 * z / (this._a * v);
var h = u * (1 - this._b2 / (this._a * v));
var phi = Math.atan((z + this._k2 * z0) / r);
var lambda = Math.atan2(y, x);
var lat = phi * math.DEGREES;
var lon = lambda * math.DEGREES;
return new LonLat(lon, lat, h);
}
/**
* Gets ellipsoid surface normal.
* @public
* @param {og.math.Vector3} coord - Spatial coordiantes.
* @returns {og.math.Vector3} -
*/
getSurfaceNormal3v(coord) {
var r2 = this._invRadii2;
var nx = coord.x * r2.x, ny = coord.y * r2.y, nz = coord.z * r2.z;
var l = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
return new Vec3(nx * l, ny * l, nz * l);
}
/**
* Gets the cartesian point on the height over the ellipsoid surface.
* @public
* @param {og.math.Vector3} coord - Spatial ellipsoid coordiantes.
* @param {number} h - Height this spatial coordinates.
* @return {og.math.Vector3} -
*/
getSurfaceHeight3v(coord, h) {
var r2 = this._invRadii2;
var nx = coord.x * r2.x, ny = coord.y * r2.y, nz = coord.z * r2.z;
var l = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
return new Vec3(coord.x + h * nx * l, coord.y + h * ny * l, coord.z + h * nz * l);
}
/**
* Returns the distance from one point to another(using haversine formula) on the great circle.
* @param {og.LonLat} lonLat1 - Longitude/latitude of source point.
* @param {og.LonLat} lonLat2 - Longitude/latitude of destination point.
* @return {number} Distance between points.
*/
getGreatCircleDistance(lonLat1, lonLat2) {
var dLat = (lonLat2.lat - lonLat1.lat) * math.RADIANS;
var dLon = (lonLat2.lon - lonLat1.lon) * math.RADIANS;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lonLat1.lat * math.RADIANS) * Math.cos(lonLat2.lat * math.RADIANS);
return this._a * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
/**
* Returns the midpoint between two points on the great circle.
* @param {og.LonLat} lonLat1 - Longitude/latitude of first point.
* @param {og.LonLat} lonLat2 - Longitude/latitude of second point.
* @return {og.LonLat} Midpoint between points.
*/
getMiddlePointOnGreatCircle(lonLat1, lonLat2) {
var f1 = lonLat1.lat * math.RADIANS,
l1 = lonLat1.lon * math.RADIANS;
var f2 = lonLat2.lat * math.RADIANS;
var dl = (lonLat2.lon - lonLat1.lon) * math.RADIANS;
var Bx = Math.cos(f2) * Math.cos(dl);
var By = Math.cos(f2) * Math.sin(dl);
var x = Math.sqrt((Math.cos(f1) + Bx) * (Math.cos(f1) + Bx) + By * By);
var y = Math.sin(f1) + Math.sin(f2);
var f3 = Math.atan2(y, x);
var l3 = l1 + Math.atan2(By, Math.cos(f1) + Bx);
return new LonLat((l3 * math.DEGREES + 540) % 360 - 180, f3 * math.DEGREES);
}
/**
* Returns the point at given fraction between two points on the great circle.
* @param {og.LonLat} lonLat1 - Longitude/Latitude of source point.
* @param {og.LonLat} lonLat2 - Longitude/Latitude of destination point.
* @param {number} fraction - Fraction between the two points (0 = source point, 1 = destination point).
* @returns {og.LonLat} Intermediate point between points.
*/
getIntermediatePointOnGreatCircle(lonLat1, lonLat2, fraction) {
var f1 = lonLat1.lat * math.RADIANS, l1 = lonLat1.lon * math.RADIANS;
var f2 = lonLat2.lat * math.RADIANS, l2 = lonLat2.lon * math.RADIANS;
var sinf1 = Math.sin(f1), cosf1 = Math.cos(f1), sinl1 = Math.sin(l1), cosl1 = Math.cos(l1);
var sinf2 = Math.sin(f2), cosf2 = Math.cos(f2), sinl2 = Math.sin(l2), cosl2 = Math.cos(l2);
var df = f2 - f1,
dl = l2 - l1;
var a = Math.sin(df / 2) * Math.sin(df / 2) + Math.cos(f1) * Math.cos(f2) * Math.sin(dl / 2) * Math.sin(dl / 2);
var d = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var A = Math.sin((1 - fraction) * d) / Math.sin(d);
var B = Math.sin(fraction * d) / Math.sin(d);
var x = A * cosf1 * cosl1 + B * cosf2 * cosl2;
var y = A * cosf1 * sinl1 + B * cosf2 * sinl2;
var z = A * sinf1 + B * sinf2;
var f3 = Math.atan2(z, Math.sqrt(x * x + y * y));
var l3 = Math.atan2(y, x);
return new LonLat((l3 * math.DEGREES + 540) % 360 - 180, f3 * math.DEGREES);
}
getRhumbBearing(lonLat1, lonLat2) {
var dLon = (lonLat2.lon - lonLat1.lon) * math.RADIANS;
var dPhi = Math.log(Math.tan(lonLat2.lat * math.RADIANS / 2 + Math.PI / 4) / Math.tan(lonLat1.lat * math.RADIANS / 2 + Math.PI / 4));
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0) {
dLon = (2 * Math.PI - dLon) * -1;
}
else {
dLon = 2 * Math.PI + dLon;
}
}
return (Math.atan2(dLon, dPhi) * math.DEGREES + 360) % 360;
}
getBearing(lonLat1, lonLat2) {
var f1 = lonLat1.lat * math.RADIANS, l1 = lonLat1.lon * math.RADIANS;
var f2 = lonLat2.lat * math.RADIANS, l2 = lonLat2.lon * math.RADIANS;
var y = Math.sin(l2 - l1) * Math.cos(f2);
var x = Math.cos(f1) * Math.sin(f2) - Math.sin(f1) * Math.cos(f2) * Math.cos(l2 - l1);
return Math.atan2(y, x) * math.DEGREES;
}
getBearingDestination(lonLat1, bearing, distance) {
bearing = bearing * math.RADIANS;
var nlon = (lonLat1.lon + 540) % 360 - 180;
var f1 = lonLat1.lat * math.RADIANS, l1 = nlon * math.RADIANS;
var dR = distance / this._a;
var f2 = Math.asin(Math.sin(f1) * Math.cos(dR) + Math.cos(f1) * Math.sin(dR) * Math.cos(bearing));
return new og.LonLat((l1 + Math.atan2(Math.sin(bearing) * Math.sin(dR) * Math.cos(f1), Math.cos(dR) - Math.sin(f1) * Math.sin(f2)))
* math.DEGREES, f2 * math.DEGREES);
}
/**
* Returns the (initial) bearing from source to destination point on the great circle.
* @param {og.LonLat} lonLat1 - Longitude/latitude of source point.
* @param {og.LonLat} lonLat2 - Longitude/latitude of destination point.
* @return {number} Initial bearing in degrees from north.
*/
getInitialBearing(lonLat1, lonLat2) {
var f1 = lonLat1.lat * math.RADIANS,
f2 = lonLat2.lat * math.RADIANS;
var dl = (lonLat2.lon - lonLat1.lon) * math.RADIANS;
var y = Math.sin(dl) * Math.cos(f2);
var x = Math.cos(f1) * Math.sin(f2) -
Math.sin(f1) * Math.cos(f2) * Math.cos(dl);
var D = Math.atan2(y, x);
return (D * math.DEGREES + 360) % 360;
}
/**
* Returns final bearing arriving at destination destination point from lonLat1 point; the final bearing
* will differ from the initial bearing by varying degrees according to distance and latitude.
* @param {og.LonLat} lonLat1 - Longitude/latitude of source point.
* @param {og.LonLat} lonLat2 - Longitude/latitude of destination point.
* @return {number} Final bearing in degrees from north.
*/
getFinalBearing(lonLat1, lonLat2) {
// get initial bearing from destination lonLat2 to lonLat1 & reverse it by adding 180°
return (this.getInitialBearing(lonLat2, lonLat1) + 180) % 360;
}
/**
* Returns the point of intersection of two paths defined by point and bearing.
* @param {og.LonLat} p1 - First point.
* @param {number} brng1 - Initial bearing from first point.
* @param {og.LonLat} p2 - Second point.
* @param {number} brng2 - Initial bearing from second point.
* @return {og.LonLat|null} Destination point (null if no unique intersection defined).
*/
intersection(p1, brng1, p2, brng2) {
var f1 = p1.lat * math.RADIANS,
l1 = p1.lon * math.RADIANS;
var f2 = p2.lat * math.RADIANS,
l2 = p2.lon * math.RADIANS;
var D13 = brng1 * math.RADIANS,
D23 = brng2 * math.RADIANS;
var df = f2 - f1,
dl = l2 - l1;
var d12 = 2 * Math.asin(Math.sqrt(Math.sin(df / 2) * Math.sin(df / 2)
+ Math.cos(f1) * Math.cos(f2) * Math.sin(dl / 2) * Math.sin(dl / 2)));
if (d12 == 0) return null;
// initial/final bearings between points
var Da = Math.acos((Math.sin(f2) - Math.sin(f1) * Math.cos(d12)) / (Math.sin(d12) * Math.cos(f1)));
if (isNaN(Da)) Da = 0; // protect against rounding
var Db = Math.acos((Math.sin(f1) - Math.sin(f2) * Math.cos(d12)) / (Math.sin(d12) * Math.cos(f2)));
var D12 = Math.sin(l2 - l1) > 0 ? Da : 2 * Math.PI - Da;
var D21 = Math.sin(l2 - l1) > 0 ? 2 * Math.PI - Db : Db;
var a1 = (D13 - D12 + Math.PI) % (2 * Math.PI) - Math.PI;
var a2 = (D21 - D23 + Math.PI) % (2 * Math.PI) - Math.PI;
if (Math.sin(a1) == 0 && Math.sin(a2) == 0) return null; // infinite intersections
if (Math.sin(a1) * Math.sin(a2) < 0) return null; // ambiguous intersection
//a1 = Math.abs(a1);
//a2 = Math.abs(a2);
// ... Ed Williams takes abs of a1/a2, but seems to break calculation?
var a3 = Math.acos(-Math.cos(a1) * Math.cos(a2) + Math.sin(a1) * Math.sin(a2) * Math.cos(d12));
var d13 = Math.atan2(Math.sin(d12) * Math.sin(a1) * Math.sin(a2), Math.cos(a2) + Math.cos(a1) * Math.cos(a3));
var f3 = Math.asin(Math.sin(f1) * Math.cos(d13) + Math.cos(f1) * Math.sin(d13) * Math.cos(D13));
var dl13 = Math.atan2(Math.sin(D13) * Math.sin(d13) * Math.cos(f1), Math.cos(d13) - Math.sin(f1) * Math.sin(f3));
var l3 = l1 + dl13;
return new LonLat((l3 * math.DEGREES + 540) % 360 - 180, f3 * math.DEGREES);
}
/**
* Returns ray vector hit ellipsoid coordinates.
* If the ray doesn't hit ellipsoid returns null.
* @public
* @param {og.math.Vector3} origin - Ray origin point.
* @param {og.math.Vector3} direction - Ray direction.
* @returns {og.math.Vector3} -
*/
hitRay(origin, direction) {
var q = this._invRadii.mul(origin);
var w = this._invRadii.mul(direction);
var q2 = q.dot(q);
var qw = q.dot(w);
var difference, w2, product, discriminant, temp;
if (q2 > 1.0) {
// Outside ellipsoid.
if (qw >= 0.0) {
// Looking outward or tangent (0 intersections).
return null;
}
// qw < 0.0.
var qw2 = qw * qw;
difference = q2 - 1.0; // Positively valued.
w2 = w.dot(w);
product = w2 * difference;
if (qw2 < product) {
// Imaginary roots (0 intersections).
return null;
} else if (qw2 > product) {
// Distinct roots (2 intersections).
discriminant = qw * qw - product;
temp = -qw + Math.sqrt(discriminant); // Avoid cancellation.
var root0 = temp / w2;
var root1 = difference / temp;
if (root0 < root1) {
return origin.add(direction.scaleTo(root0));
}
return origin.add(direction.scaleTo(root1));
} else {
// qw2 == product. Repeated roots (2 intersections).
var root = Math.sqrt(difference / w2);
return origin.add(direction.scaleTo(root));
}
} else if (q2 < 1.0) {
// Inside ellipsoid (2 intersections).
difference = q2 - 1.0; // Negatively valued.
w2 = w.dot(w);
product = w2 * difference; // Negatively valued.
discriminant = qw * qw - product;
temp = -qw + Math.sqrt(discriminant); // Positively valued.
return origin.add(direction.scaleTo(temp / w2));
} else {
// q2 == 1.0. On ellipsoid.
if (qw < 0.0) {
// Looking inward.
w2 = w.dot(w);
return origin.add(direction.scaleTo(-qw / w2));
}
// qw >= 0.0. Looking outward or tangent.
return null;
}
}
};
export { Ellipsoid };

View File

@ -0,0 +1,352 @@
/**
* @module og/entity/BaseBillboard
*/
'use strict';
import * as utils from '../utils/shared.js';
/**
* Base prototype for billboard and label classes.
* @class
* @param {Object} [options] - Options:
* @param {og.math.Vector3|Array.<number>} [options.position] - Billboard spatial position.
* @param {number} [options.rotation] - Screen angle rotaion.
* @param {og.math.Vector4|string|Array.<number>} [options.color] - Billboard color.
* @param {og.math.Vector3|Array.<number>} [options.alignedAxis] - Billboard aligned vector.
* @param {og.math.Vector3|Array.<number>} [options.offset] - Billboard center screen offset.
* @param {boolean} [options.visibility] - Visibility.
*/
class BaseBillboard {
constructor(options) {
options = options || {};
/**
* Object unic identifier.
* @public
* @readonly
* @type {number}
*/
this.id = BaseBillboard._staticCounter++;
/**
* Billboard center cartesian position.
* @protected
* @type {og.math.Vector3}
*/
this._position = utils.createVector3(options.position);
/**
* Screen space rotation angle.
* @protected
* @type {number}
*/
this._rotation = options.rotation || 0;
/**
* RGBA color.
* @protected
* @type {og.math.Vector4}
*/
this._color = utils.createColorRGBA(options.color);
/**
* Cartesian aligned axis vector.
* @protected
* @type {og.math.Vector3}
*/
this._alignedAxis = utils.createVector3(options.alignedAxis);
/**
* Billboard center screen space offset. Where x,y - screen space offset and z - depth offset.
* @protected
* @type {og.math.Vecto3}
*/
this._offset = utils.createVector3(options.offset);
/**
* Billboard visibility.
* @protected
* @type {boolean}
*/
this._visibility = options.visibility != undefined ? options.visibility : true;
/**
* Billboard scale.
* @protected
* @type {number}
*/
this._scale = options.scale || 1.0;
/**
* Entity instance that holds this billboard.
* @protected
* @type {og.Entity}
*/
this._entity = null;
/**
* Handler that stores and renders this billboard object.
* @protected
* @type {og.BillboardHandler}
*/
this._handler = null;
/**
* Billboard handler array index.
* @protected
* @type {number}
*/
this._handlerIndex = -1;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
/**
* Sets billboard position.
* @public
* @param {number} x - X coordinate.
* @param {number} y - Y coordinate.
* @param {number} z - Z coordinate.
*/
setPosition(x, y, z) {
this._position.x = x;
this._position.y = y;
this._position.z = z;
this._handler && this._handler.setPositionArr(this._handlerIndex, this._position);
}
/**
* Sets billboard position.
* @public
* @param {og.math.Vector3} position - Cartesian coordinates.
*/
setPosition3v(position) {
this._position.x = position.x;
this._position.y = position.y;
this._position.z = position.z;
this._handler && this._handler.setPositionArr(this._handlerIndex, position);
}
/**
* Returns billboard position.
* @public
* @returns {og.math.Vector3}
*/
getPosition() {
return this._position;
}
/**
* Sets screen space offset.
* @public
* @param {number} x - X offset.
* @param {number} y - Y offset.
* @param {number} [z] - Z offset.
*/
setOffset(x, y, z) {
this._offset.x = x;
this._offset.y = y;
(z != undefined) && (this._offset.z = z);
this._handler && this._handler.setOffsetArr(this._handlerIndex, this._offset);
}
/**
* Sets billboard scale.
* @public
* @param {number} scale - Scale.
*/
setScale(scale) {
this._scale = scale;
this._handler && this._handler.setScaleArr(this._handlerIndex, scale);
}
/**
* Gets billboard scale.
* @public
* @returns {number}
*/
getScale() {
return this._scale;
}
/**
* Sets screen space offset.
* @public
* @param {og.math.Vector2} offset - Offset size.
*/
setOffset3v(offset) {
this._offset.x = offset.x;
this._offset.y = offset.y;
(offset.z != undefined) && (this._offset.z = offset.z);
this._handler && this._handler.setOffsetArr(this._handlerIndex, offset);
}
/**
* Returns billboard screen space offset size.
* @public
* @returns {og.math.Vector3}
*/
getOffset() {
return this._offset;
}
/**
* Sets billboard screen space rotation in radians.
* @public
* @param {number} rotation - Screen space rotation in radians.
*/
setRotation(rotation) {
this._rotation = rotation;
this._handler && this._handler.setRotationArr(this._handlerIndex, rotation);
}
/**
* Gets screen space rotation.
* @public
* @returns {number}
*/
getRotation() {
return this._rotation;
}
/**
* Sets billboard opacity.
* @public
* @param {number} a - Billboard opacity.
*/
setOpacity(a) {
this._color.w = a;
this.setColor(this._color.x, this._color.y, this._color.z, a);
}
/**
* Sets RGBA color. Each channel from 0.0 to 1.0.
* @public
* @param {number} r - Red.
* @param {number} g - Green.
* @param {number} b - Blue.
* @param {number} a - Alpha.
*/
setColor(r, g, b, a) {
this._color.x = r;
this._color.y = g;
this._color.z = b;
(a != undefined) && (this._color.w = a);
this._handler && this._handler.setRgbaArr(this._handlerIndex, this._color);
}
/**
* Sets RGBA color. Each channel from 0.0 to 1.0.
* @public
* @param {og.math.Vector4} color - RGBA vector.
*/
setColor4v(color) {
this._color.x = color.x;
this._color.y = color.y;
this._color.z = color.z;
(color.w != undefined) && (this._color.w = color.w);
this._handler && this._handler.setRgbaArr(this._handlerIndex, color);
}
/**
* Sets billboard color.
* @public
* @param {string} color - HTML style color.
*/
setColorHTML(color) {
this.setColor4v(og.utils.htmlColorToRgba(color));
}
/**
* Returns RGBA color.
* @public
* @returns {og.math.Vector4}
*/
getColor() {
return this._color;
}
/**
* Sets billboard visibility.
* @public
* @param {boolean} visibility - Visibility flag.
*/
setVisibility(visibility) {
this._visibility = visibility;
this._handler && this._handler.setVisibility(this._handlerIndex, visibility);
}
/**
* Returns billboard visibility.
* @public
* @returns {boolean}
*/
getVisibility() {
return this._visibility;
}
/**
* Sets billboard cartesian aligned vector.
* @public
* @param {number} x - Aligned vector X coordinate.
* @param {number} y - Aligned vector Y coordinate.
* @param {number} z - Aligned vector Z coordinate.
*/
setAlignedAxis(x, y, z) {
this._alignedAxis.x = x;
this._alignedAxis.y = y;
this._alignedAxis.z = z;
this._handler && this._handler.setAlignedAxisArr(this._handlerIndex, this._alignedAxis);
}
/**
* Sets billboard aligned vector.
* @public
* @param {og.math.Vecto3} alignedAxis - Vector to align.
*/
setAlignedAxis3v(alignedAxis) {
this._alignedAxis.x = alignedAxis.x;
this._alignedAxis.y = alignedAxis.y;
this._alignedAxis.z = alignedAxis.z;
this._handler && this._handler.setAlignedAxisArr(this._handlerIndex, alignedAxis);
}
/**
* Returns aligned vector.
* @public
* @returns {og.math.Vector3}
*/
getAlignedAxis() {
return this._alignedAxis;
}
/**
* Removes billboard from hander.
* @public
*/
remove() {
this._entity = null;
this._handler && this._handler.remove(this);
}
/**
* Sets billboard picking color.
* @public
* @param {og.math.Vector3} color - Picking color.
*/
setPickingColor3v(color) {
this._handler && this._handler.setPickingColorArr(this._handlerIndex, color);
}
};
export { BaseBillboard };

160
src/og/entity/Billboard.js Normal file
View File

@ -0,0 +1,160 @@
/**
* @module og/entity/Billboard
*/
'use strict';
import { BaseBillboard } from './BaseBillboard.js';
/**
* Represents basic quad billboard image.
* @class
* @extends {og.BaseBillboard}
* @param {Object} [options] - Options:
* @param {og.math.Vector3|Array.<number>} [options.position] - Billboard spatial position.
* @param {number} [options.rotation] - Screen angle rotaion.
* @param {og.math.Vector4|string|Array.<number>} [options.color] - Billboard color.
* @param {og.math.Vector3|Array.<number>} [options.alignedAxis] - Billboard aligned vector.
* @param {og.math.Vector3|Array.<number>} [options.offset] - Billboard center screen offset.
* @param {boolean} [options.visibility] - Visibility.
* @param {string} [options.src] - Billboard image url source.
* @param {Image} [options.image] - Billboard image object.
* @param {number} [options.width] - Screen width.
* @param {number} [options.height] - Screen height.
* @param {number} [options.scale] - Billboard scale.
*/
class Billboard extends BaseBillboard {
constructor(options) {
super(options);
options = options || {};
/**
* Image src.
* @protected
* @type {string}
*/
this._src = options.src || null;
/**
* Image object.
* @protected
* @type {Object}
*/
this._image = options.image || null;
/**
* Billboard screen width.
* @protected
* @type {number}
*/
this._width = options.width || (options.size ? options.size[0] : 30);
/**
* Billboard screen height.
* @protected
* @type {number}
*/
this._height = options.height || (options.size ? options.size[1] : 30);
}
/**
* Sets billboard image url source.
* @public
* @param {string} src - Image url.
*/
setSrc(src) {
this._src = src;
var bh = this._handler;
if (bh && src) {
var rn = bh._entityCollection.renderNode;
if (rn) {
var ta = rn.billboardsTextureAtlas;
var that = this;
ta.loadImage(src, function (img) {
if (ta.nodes[img.__nodeIndex]) {
that._image = img;
bh.setTexCoordArr(that._handlerIndex, ta.nodes[that._image.__nodeIndex].texCoords);
} else {
ta.addImage(img);
ta.createTexture();
that._image = img;
rn.updateBillboardsTexCoords();
}
});
}
}
}
/**
* Sets image object.
* @public
* @param {Object} image - JavaScript image object.
*/
setImage(image) {
this.setSrc(image.src);
}
/**
* Sets billboard screen size in pixels.
* @public
* @param {number} width - Billboard width.
* @param {number} height - Billboard height.
*/
setSize(width, height) {
this._width = width;
this._height = height;
this._handler && this._handler.setSizeArr(this._handlerIndex, width * this._scale, height * this._scale);
}
/**
* Returns billboard screen size.
* @public
* @returns {Object}
*/
getSize() {
return {
"width": this._width,
"height": this._height
};
}
/**
* Sets billboard screen width.
* @public
* @param {number} width - Width.
*/
setWidth(width) {
this.setSize(width, this._height);
}
/**
* Gets billboard screen width.
* @public
* @returns {number}
*/
getWidth() {
return this._width;
}
/**
* Sets billboard screen heigh.
* @public
* @param {number} height - Height.
*/
setHeight(height) {
this.setSize(this._width, height);
}
/**
* Gets billboard screen height.
* @public
* @returns {number}
*/
getHeight() {
return this._height;
}
};
export { Billboard };

View File

@ -0,0 +1,736 @@
/**
* @module og/entity/BillboardHandler
*/
'use strict';
import * as shaders from '../shaders/billboard.js';
const PICKINGCOLOR_BUFFER = 0;
const POSITION_BUFFER = 1;
const SIZE_BUFFER = 2;
const OFFSET_BUFFER = 3;
const RGBA_BUFFER = 4;
const ROTATION_BUFFER = 5;
const TEXCOORD_BUFFER = 6;
const VERTEX_BUFFER = 7;
const ALIGNEDAXIS_BUFFER = 8;
/*
* og.BillboardHandler
*
*
*/
class BillboardHandler {
constructor(entityCollection) {
/**
* Picking rendering option.
* @public
* @type {boolean}
*/
this.pickingEnabled = true;
this._entityCollection = entityCollection;
this._renderer = null;
this._billboards = [];
this._positionBuffer = null;
this._sizeBuffer = null;
this._offsetBuffer = null;
this._rgbaBuffer = null;
this._rotationBuffer = null;
this._texCoordBuffer = null;
this._vertexBuffer = null;
this._alignedAxisBuffer = null;
this._texCoordArr = [];
this._vertexArr = [];
this._positionArr = [];
this._sizeArr = [];
this._offsetArr = [];
this._rgbaArr = [];
this._rotationArr = [];
this._alignedAxisArr = [];
this._pickingColorBuffer = null;
this._pickingColorArr = [];
this._buffersUpdateCallbacks = [];
this._buffersUpdateCallbacks[PICKINGCOLOR_BUFFER] = this.createPickingColorBuffer;
this._buffersUpdateCallbacks[POSITION_BUFFER] = this.createPositionBuffer;
this._buffersUpdateCallbacks[SIZE_BUFFER] = this.createSizeBuffer;
this._buffersUpdateCallbacks[OFFSET_BUFFER] = this.createOffsetBuffer;
this._buffersUpdateCallbacks[RGBA_BUFFER] = this.createRgbaBuffer;
this._buffersUpdateCallbacks[ROTATION_BUFFER] = this.createRotationBuffer;
this._buffersUpdateCallbacks[TEXCOORD_BUFFER] = this.createTexCoordBuffer;
this._buffersUpdateCallbacks[VERTEX_BUFFER] = this.createVertexBuffer;
this._buffersUpdateCallbacks[ALIGNEDAXIS_BUFFER] = this.createAlignedAxisBuffer;
this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);
this.__staticId = BillboardHandler.staticCounter++;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
static concArr(dest, curr) {
for (var i = 0; i < curr.length; i++) {
dest.push(curr[i]);
}
}
initShaderProgram() {
if (this._renderer.handler) {
if (!this._renderer.handler.shaderPrograms.billboard) {
var isSingleBuffer = !this._renderer.isMultiFramebufferCompatible();
this._renderer.handler.addShaderProgram(shaders.billboard(isSingleBuffer));
}
if (!this._renderer.handler.shaderPrograms.billboardPicking) {
this._renderer.handler.addShaderProgram(shaders.billboardPicking());
}
}
}
setRenderer(renderer) {
this._renderer = renderer;
this.initShaderProgram();
}
refresh() {
var i = this._changedBuffers.length;
while (i--) {
this._changedBuffers[i] = true;
}
}
_removeBillboards() {
var i = this._billboards.length;
while (i--) {
var bi = this._billboards[i];
bi._handlerIndex = -1;
bi._handler = null;
}
this._billboards.length = 0;
this._billboards = [];
}
clear() {
this._texCoordArr.length = 0;
this._vertexArr.length = 0;
this._positionArr.length = 0;
this._sizeArr.length = 0;
this._offsetArr.length = 0;
this._rgbaArr.length = 0;
this._rotationArr.length = 0;
this._alignedAxisArr.length = 0;
this._pickingColorArr.length = 0;
this._texCoordArr = [];
this._vertexArr = [];
this._positionArr = [];
this._sizeArr = [];
this._offsetArr = [];
this._rgbaArr = [];
this._rotationArr = [];
this._alignedAxisArr = [];
this._pickingColorArr = [];
this._removeBillboards();
this._deleteBuffers();
this.refresh();
}
_deleteBuffers() {
var gl = this._renderer.handler.gl;
gl.deleteBuffer(this._positionBuffer);
gl.deleteBuffer(this._sizeBuffer);
gl.deleteBuffer(this._offsetBuffer);
gl.deleteBuffer(this._rgbaBuffer);
gl.deleteBuffer(this._rotationBuffer);
gl.deleteBuffer(this._vertexBuffer);
gl.deleteBuffer(this._texCoordBuffer);
gl.deleteBuffer(this._alignedAxisBuffer);
gl.deleteBuffer(this._pickingColorBuffer);
this._positionBuffer = null;
this._sizeBuffer = null;
this._offsetBuffer = null;
this._rgbaBuffer = null;
this._rotationBuffer = null;
this._vertexBuffer = null;
this._texCoordBuffer = null;
this._alignedAxisBuffer = null;
this._pickingColorBuffer = null;
}
update() {
if (this._renderer) {
var i = this._changedBuffers.length;
while (i--) {
if (this._changedBuffers[i]) {
this._buffersUpdateCallbacks[i].call(this);
this._changedBuffers[i] = false;
}
}
}
}
add(billboard) {
if (billboard._handlerIndex == -1) {
billboard._handler = this;
billboard._handlerIndex = this._billboards.length;
this._billboards.push(billboard);
this._addBillboardToArrays(billboard);
this.refresh();
billboard.setSrc(billboard._src || billboard._image && billboard._image.src);
}
}
_addBillboardToArrays(billboard) {
if (billboard._visibility) {
BillboardHandler.concArr(this._vertexArr, [-0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5]);
} else {
BillboardHandler.concArr(this._vertexArr, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
BillboardHandler.concArr(this._texCoordArr, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
var x = billboard._position.x, y = billboard._position.y, z = billboard._position.z, w = billboard._scale;
BillboardHandler.concArr(this._positionArr, [x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w]);
x = billboard._width; y = billboard._height;
BillboardHandler.concArr(this._sizeArr, [x, y, x, y, x, y, x, y, x, y, x, y]);
x = billboard._offset.x; y = billboard._offset.y; z = billboard._offset.z;
BillboardHandler.concArr(this._offsetArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
x = billboard._color.x; y = billboard._color.y; z = billboard._color.z; w = billboard._color.w;
BillboardHandler.concArr(this._rgbaArr, [x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w]);
x = billboard._rotation;
BillboardHandler.concArr(this._rotationArr, [x, x, x, x, x, x]);
x = billboard._alignedAxis.x, y = billboard._alignedAxis.y, z = billboard._alignedAxis.z;
BillboardHandler.concArr(this._alignedAxisArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
x = billboard._entity._pickingColor.x / 255, y = billboard._entity._pickingColor.y / 255, z = billboard._entity._pickingColor.z / 255;
BillboardHandler.concArr(this._pickingColorArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
}
_displayPASS() {
var r = this._renderer;
var h = r.handler;
h.shaderPrograms.billboard.activate();
var sh = h.shaderPrograms.billboard._program;
var sha = sh.attributes,
shu = sh.uniforms;
var gl = h.gl;
gl.uniform1i(shu.u_texture._pName, 0);
gl.uniformMatrix4fv(shu.viewMatrix._pName, false, r.activeCamera._viewMatrix._m);
gl.uniformMatrix4fv(shu.projectionMatrix._pName, false, r.activeCamera._projectionMatrix._m);
gl.uniform3fv(shu.uCamPos._pName, r.activeCamera.eye.toVec());
gl.uniform3fv(shu.uScaleByDistance._pName, this._entityCollection.scaleByDistance);
gl.uniform1f(shu.uOpacity._pName, this._entityCollection._animatedOpacity);
gl.uniform2fv(shu.uFloatParams._pName, [this._entityCollection.renderNode._planetRadius2 || 0, r.activeCamera._tanViewAngle_hradOneByHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer);
gl.vertexAttribPointer(sha.a_texCoord._pName, this._texCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.a_vertices._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(sha.a_positions._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rgbaBuffer);
gl.vertexAttribPointer(sha.a_rgba._pName, this._rgbaBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer);
gl.vertexAttribPointer(sha.a_size._pName, this._sizeBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer);
gl.vertexAttribPointer(sha.a_offset._pName, this._offsetBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer);
gl.vertexAttribPointer(sha.a_rotation._pName, this._rotationBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._alignedAxisBuffer);
gl.vertexAttribPointer(sha.a_alignedAxis._pName, this._alignedAxisBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer.numItems);
}
_pickingPASS() {
var r = this._renderer;
var h = r.handler;
h.shaderPrograms.billboardPicking.activate();
var sh = h.shaderPrograms.billboardPicking._program;
var sha = sh.attributes,
shu = sh.uniforms;
var gl = h.gl;
gl.uniformMatrix4fv(shu.viewMatrix._pName, false, r.activeCamera._viewMatrix._m);
gl.uniformMatrix4fv(shu.projectionMatrix._pName, false, r.activeCamera._projectionMatrix._m);
gl.uniform3fv(shu.uCamPos._pName, r.activeCamera.eye.toVec());
gl.uniform3fv(shu.uScaleByDistance._pName, this._entityCollection.scaleByDistance);
gl.uniform1f(shu.uOpacity._pName, this._entityCollection._animatedOpacity);
gl.uniform2fv(shu.uFloatParams._pName, [this._entityCollection.renderNode._planetRadius2 || 0, r.activeCamera._tanViewAngle_hradOneByHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.a_vertices._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(sha.a_positions._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pickingColorBuffer);
gl.vertexAttribPointer(sha.a_pickingColor._pName, this._pickingColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer);
gl.vertexAttribPointer(sha.a_size._pName, this._sizeBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer);
gl.vertexAttribPointer(sha.a_offset._pName, this._offsetBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer);
gl.vertexAttribPointer(sha.a_rotation._pName, this._rotationBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._alignedAxisBuffer);
gl.vertexAttribPointer(sha.a_alignedAxis._pName, this._alignedAxisBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer.numItems);
};
draw() {
if (this._billboards.length) {
this.update();
this._displayPASS();
}
}
drawPicking() {
if (this._billboards.length && this.pickingEnabled) {
this._pickingPASS();
}
}
reindexBillbordsArray(startIndex) {
var b = this._billboards;
for (var i = startIndex; i < b.length; i++) {
b[i]._handlerIndex = i;
}
}
_removeBillboard(billboard) {
var bi = billboard._handlerIndex;
this._billboards.splice(bi, 1);
var i = bi * 24;
this._rgbaArr.splice(i, 24);
this._positionArr.splice(i, 24);
i = bi * 18;
this._offsetArr.splice(i, 18);
this._alignedAxisArr.splice(i, 18);
this._pickingColorArr.splice(i, 18);
i = bi * 12;
this._vertexArr.splice(i, 12);
this._sizeArr.splice(i, 12);
this._texCoordArr.splice(i, 12);
i = bi * 6;
this._rotationArr.splice(i, 6);
this.reindexBillbordsArray(bi);
this.refresh();
billboard._handlerIndex = -1;
billboard._handler = null;
}
remove(billboard) {
if (billboard._handler && this.__staticId == billboard._handler.__staticId) {
this._removeBillboard(billboard);
}
}
setPositionArr(index, position) {
var i = index * 24;
var a = this._positionArr, x = position.x, y = position.y, z = position.z;
a[i] = x;
a[i + 1] = y;
a[i + 2] = z;
a[i + 4] = x;
a[i + 5] = y;
a[i + 6] = z;
a[i + 8] = x;
a[i + 9] = y;
a[i + 10] = z;
a[i + 12] = x;
a[i + 13] = y;
a[i + 14] = z;
a[i + 16] = x;
a[i + 17] = y;
a[i + 18] = z;
a[i + 20] = x;
a[i + 21] = y;
a[i + 22] = z;
this._changedBuffers[POSITION_BUFFER] = true;
}
setScaleArr(index, scale) {
var i = index * 24;
var a = this._positionArr;
a[i + 3] = scale;
a[i + 7] = scale;
a[i + 11] = scale;
a[i + 15] = scale;
a[i + 19] = scale;
a[i + 23] = scale;
this._changedBuffers[POSITION_BUFFER] = true;
}
setPickingColorArr(index, color) {
var i = index * 18;
var a = this._pickingColorArr, x = color.x / 255, y = color.y / 255, z = color.z / 255;
a[i] = x;
a[i + 1] = y;
a[i + 2] = z;
a[i + 3] = x;
a[i + 4] = y;
a[i + 5] = z;
a[i + 6] = x;
a[i + 7] = y;
a[i + 8] = z;
a[i + 9] = x;
a[i + 10] = y;
a[i + 11] = z;
a[i + 12] = x;
a[i + 13] = y;
a[i + 14] = z;
a[i + 15] = x;
a[i + 16] = y;
a[i + 17] = z;
this._changedBuffers[PICKINGCOLOR_BUFFER] = true;
}
setSizeArr(index, width, height) {
var i = index * 12;
var a = this._sizeArr, x = width, y = height;
a[i] = x;
a[i + 1] = y;
a[i + 2] = x;
a[i + 3] = y;
a[i + 4] = x;
a[i + 5] = y;
a[i + 6] = x;
a[i + 7] = y;
a[i + 8] = x;
a[i + 9] = y;
a[i + 10] = x;
a[i + 11] = y;
this._changedBuffers[SIZE_BUFFER] = true;
}
setOffsetArr(index, offset) {
var i = index * 18;
var a = this._offsetArr, x = offset.x, y = offset.y, z = offset.z;
a[i] = x;
a[i + 1] = y;
a[i + 2] = z;
a[i + 3] = x;
a[i + 4] = y;
a[i + 5] = z;
a[i + 6] = x;
a[i + 7] = y;
a[i + 8] = z;
a[i + 9] = x;
a[i + 10] = y;
a[i + 11] = z;
a[i + 12] = x;
a[i + 13] = y;
a[i + 14] = z;
a[i + 15] = x;
a[i + 16] = y;
a[i + 17] = z;
this._changedBuffers[OFFSET_BUFFER] = true;
}
setRgbaArr(index, rgba) {
var i = index * 24;
var a = this._rgbaArr, x = rgba.x, y = rgba.y, z = rgba.z, w = rgba.w;
a[i] = x;
a[i + 1] = y;
a[i + 2] = z;
a[i + 3] = w;
a[i + 4] = x;
a[i + 5] = y;
a[i + 6] = z;
a[i + 7] = w;
a[i + 8] = x;
a[i + 9] = y;
a[i + 10] = z;
a[i + 11] = w;
a[i + 12] = x;
a[i + 13] = y;
a[i + 14] = z;
a[i + 15] = w;
a[i + 16] = x;
a[i + 17] = y;
a[i + 18] = z;
a[i + 19] = w;
a[i + 20] = x;
a[i + 21] = y;
a[i + 22] = z;
a[i + 23] = w;
this._changedBuffers[RGBA_BUFFER] = true;
}
setRotationArr(index, rotation) {
var i = index * 6;
var a = this._rotationArr;
a[i] = rotation;
a[i + 1] = rotation;
a[i + 2] = rotation;
a[i + 3] = rotation;
a[i + 4] = rotation;
a[i + 5] = rotation;
this._changedBuffers[ROTATION_BUFFER] = true
}
setTexCoordArr(index, tcoordArr) {
var i = index * 12;
var a = this._texCoordArr;
a[i] = tcoordArr[0];
a[i + 1] = tcoordArr[1];
a[i + 2] = tcoordArr[2];
a[i + 3] = tcoordArr[3];
a[i + 4] = tcoordArr[4];
a[i + 5] = tcoordArr[5];
a[i + 6] = tcoordArr[6];
a[i + 7] = tcoordArr[7];
a[i + 8] = tcoordArr[8];
a[i + 9] = tcoordArr[9];
a[i + 10] = tcoordArr[10];
a[i + 11] = tcoordArr[11];
this._changedBuffers[TEXCOORD_BUFFER] = true;
}
setVisibility(index, visibility) {
var vArr;
if (visibility) {
vArr = [-0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5];
} else {
vArr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
this.setVertexArr(index, vArr);
}
setVertexArr(index, vertexArr) {
var i = index * 12;
var a = this._vertexArr;
a[i] = vertexArr[0];
a[i + 1] = vertexArr[1];
a[i + 2] = vertexArr[2];
a[i + 3] = vertexArr[3];
a[i + 4] = vertexArr[4];
a[i + 5] = vertexArr[5];
a[i + 6] = vertexArr[6];
a[i + 7] = vertexArr[7];
a[i + 8] = vertexArr[8];
a[i + 9] = vertexArr[9];
a[i + 10] = vertexArr[10];
a[i + 11] = vertexArr[11];
this._changedBuffers[VERTEX_BUFFER] = true;
}
setAlignedAxisArr(index, alignedAxis) {
var i = index * 18;
var a = this._alignedAxisArr, x = alignedAxis.x, y = alignedAxis.y, z = alignedAxis.z;
a[i] = x;
a[i + 1] = y;
a[i + 2] = z;
a[i + 3] = x;
a[i + 4] = y;
a[i + 5] = z;
a[i + 6] = x;
a[i + 7] = y;
a[i + 8] = z;
a[i + 9] = x;
a[i + 10] = y;
a[i + 11] = z;
a[i + 12] = x;
a[i + 13] = y;
a[i + 14] = z;
a[i + 15] = x;
a[i + 16] = y;
a[i + 17] = z;
this._changedBuffers[ALIGNEDAXIS_BUFFER] = true;
}
createPositionBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._positionBuffer);
this._positionBuffer = h.createArrayBuffer(new Float32Array(this._positionArr), 4, this._positionArr.length / 4, h.gl.DYNAMIC_DRAW);
}
createSizeBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._sizeBuffer);
this._sizeBuffer = h.createArrayBuffer(new Float32Array(this._sizeArr), 2, this._sizeArr.length / 2);
}
createOffsetBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._offsetBuffer);
this._offsetBuffer = h.createArrayBuffer(new Float32Array(this._offsetArr), 3, this._offsetArr.length / 3);
}
createRgbaBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._rgbaBuffer);
this._rgbaBuffer = h.createArrayBuffer(new Float32Array(this._rgbaArr), 4, this._rgbaArr.length / 4);
}
createRotationBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._rotationBuffer);
this._rotationBuffer = h.createArrayBuffer(new Float32Array(this._rotationArr), 1, this._rotationArr.length, h.gl.DYNAMIC_DRAW);
}
createVertexBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._vertexBuffer);
this._vertexBuffer = h.createArrayBuffer(new Float32Array(this._vertexArr), 2, this._vertexArr.length / 2, h.gl.DYNAMIC_DRAW);
}
createTexCoordBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._texCoordBuffer);
this._texCoordBuffer = h.createArrayBuffer(new Float32Array(this._texCoordArr), 2, this._texCoordArr.length / 2, h.gl.DYNAMIC_DRAW);
}
createAlignedAxisBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._alignedAxisBuffer);
this._alignedAxisBuffer = h.createArrayBuffer(new Float32Array(this._alignedAxisArr), 3, this._alignedAxisArr.length / 3);
}
createPickingColorBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._pickingColorBuffer);
this._pickingColorBuffer = h.createArrayBuffer(new Float32Array(this._pickingColorArr), 3, this._pickingColorArr.length / 3);
}
refreshTexCoordsArr() {
var bc = this._entityCollection;
if (bc && bc.renderNode) {
var ta = bc.renderNode.billboardsTextureAtlas;
for (var i = 0; i < this._billboards.length; i++) {
var bi = this._billboards[i];
var img = bi._image;
if (img) {
var imageNode = ta.nodes[bi._image.__nodeIndex];
if (imageNode) {
this.setTexCoordArr(bi._handlerIndex, imageNode.texCoords);
}
}
}
}
}
};
export { BillboardHandler };

607
src/og/entity/Entity.js Normal file
View File

@ -0,0 +1,607 @@
/**
* @module og/entity/Entity
*/
'use strict';
import * as mercator from '../mercator.js';
import * as utils from '../utils/shared.js';
import { Billboard } from './Billboard.js';
//import { Box } from '../shapes/Box.js';
import { Extent } from '../Extent.js';
import { Geometry } from './Geometry.js';
import { Label } from './Label.js';
import { LonLat } from '../LonLat.js';
import { Polyline } from './Polyline.js';
import { PointCloud } from './PointCloud.js';
//import { Sphere } from '../shapes/Sphere.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Entity instances aggregate multiple forms of visualization into a single high-level object.
* They can be created manually and added to entity collection.
*
* @class
* @param {Object} [options] - Entity options:
* @param {string} [options.name] - A human readable name to display to users. It does not have to be unique.
* @param {og.math.Vector3|Array.<number>} [options.cartesian] - Spatial entities like billboard, label, sphere etc. cartesian position.
* @param {og.LonLat} [options.lonlat] - Geodetic coordiantes for an entities like billboard, label, sphere etc.
* @param {boolean} [options.aground] - True for entities that have to be placed on the relief.
* @param {boolean} [options.visibility] - Entity visibility.
* @param {*} [options.billboard] - Billboard options(see {@link og.Billboard}).
* @param {*} [options.label] - Label options(see {@link og.Label}).
* @param {*} [options.sphere] - Sphere options(see {@link og.shape.Sphere}).
* @param {*} [options.box] - Sphere options(see {@link og.shape.Box}).
* @param {*} [options.Polyline] - Polyline options(see {@link og.Polyline}).
* @param {*} [options.pointCloud] - Point cloud options(see {@link og.PointCloud}).
* @param {*} [options.geometry] - Geometry options (see {@link og.Geometry}), available for vector layer only.
* @param {*} [options.properties] - Entity custom properties.
*/
class Entity {
constructor(options) {
options = options || {};
options.properties = options.properties || {};
/**
* Unic identifier.
* @public
* @readonly
*/
this.id = Entity.__staticCounter++;
/**
* Entity user defined properties.
* @public
* @type {Object}
*/
this.properties = options.properties || {};
/**
* Entity name.
* @public
* @type {string}
*/
this.properties.name = this.properties.name || "noname";
/**
* Children entities.
* @public
* @type {Array.<og.Entity>}
*/
this.childrenNodes = [];
/**
* Parent entity.
* @public
* @type {og.Entity}
*/
this.parent = null;
/**
* Entity cartesian position.
* @protected
* @type {og.math.Vector3}
*/
this._cartesian = utils.createVector3(options.cartesian);
/**
* Geodetic entity coordiantes.
* @protected
* @type {og.LonLat}
*/
this._lonlat = utils.createLonLat(options.lonlat);
/**
* World Mercator entity coordinates.
* @protected
* @type {og.LonLat}
*/
this._lonlatMerc = null;
/**
* Entity visible terrain altitude.
* @protected
* @type {number}
*/
this._altitude = options.altitude || 0.0;
/**
* Visibility flag.
* @protected
* @type {boolean}
*/
this._visibility = options.visibility != undefined ? options.visibility : true;
/**
* Entity collection that this entity belongs to.
* @protected
* @type {og.EntityCollection}
*/
this._entityCollection = null;
/**
* Entity collection array store index.
* @protected
* @type {number}
*/
this._entityCollectionIndex = -1;
/**
* Assigned vector layer pointer.
* @protected
* @type {og.layer.Vector}
*/
this._layer = null;
/**
* Assigned vector layer entity array index.
* @protected
* @type {number}
*/
this._layerIndex = -1;
/**
* Picking color.
* @protected
* @type {og.math.Vector3}
*/
this._pickingColor = new Vec3(0, 0, 0);
this._featureConstructorArray = {
"billboard": [Billboard, this.setBillboard],
"label": [Label, this.setLabel],
"sphere": [Sphere, this.setShape],
"box": [Box, this.setShape],
"polyline": [Polyline, this.setPolyline],
"pointCloud": [PointCloud, this.setPointCloud],
"geometry": [Geometry, this.setGeometry],
};
/**
* Billboard entity.
* @public
* @type {og.Billboard}
*/
this.billboard = this._createOptionFeature('billboard', options.billboard);
/**
* Text label entity.
* @public
* @type {og.Label}
*/
this.label = this._createOptionFeature('label', options.label);
/**
* Shape entity.
* @public
* @type {og.shape.BaseShape}
*/
this.shape = this._createOptionFeature('sphere', options.sphere || options.box);
/**
* Polyline entity.
* @public
* @type {og.Polyline}
*/
this.polyline = this._createOptionFeature('polyline', options.polyline);
/**
* PointCloud entity.
* @public
* @type {og.PointCloud}
*/
this.pointCloud = this._createOptionFeature('pointCloud', options.pointCloud);
/**
* Geometry entity(available only for vector layer).
* @public
* @type {og.Geometry}
*/
this.geometry = this._createOptionFeature('geometry', options.geometry);
//this.model = null;
//...
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
_createOptionFeature(featureName, options) {
if (options) {
var c = this._featureConstructorArray[featureName];
return c[1].call(this, new c[0](options));
}
return null;
}
/**
* Adds current entity into the specified entity collection.
* @public
* @param {og.EntityCollection|og.layer.Vector} collection - Specified entity collection or vector layer.
* @param {Boolean} [rightNow=false] - Entity insertion option for vector layer.
* @returns {og.Entity} - This object.
*/
addTo(collection, rightNow) {
collection.add(this, rightNow);
return this;
}
/**
* Removes current entity from collection and layer.
* @public
*/
remove() {
this._layer && this._layer.removeEntity(this);
this._entityCollection && this._entityCollection.removeEntity(this);
}
/**
* Sets the entity visibility.
* @public
* @param {boolean} visibility - Entity visibility.
*/
setVisibility(visibility) {
this._visibility = visibility;
//billboards
this.billboard && this.billboard.setVisibility(visibility);
//labels
this.label && this.label.setVisibility(visibility);
//shape
this.shape && this.shape.setVisibility(visibility);
//polyline
this.polyline && this.polyline.setVisibility(visibility);
//geometry
this.geometry && this.geometry.setVisibility(visibility);
for (var i = 0; i < this.childrenNodes.length; i++) {
this.childrenNodes[i].setVisibility(visibility);
}
}
/**
* Returns entity visibility.
* @public
* @returns {boolean} -
*/
getVisibility() {
return this._visibility;
}
/**
* Sets entity cartesian position.
* @public
* @param {og.math.Vector3} cartesian - Cartesian position in 3d space.
*/
setCartesian3v(cartesian) {
this.setCartesian(cartesian.x, cartesian.y, cartesian.z);
}
/**
* Sets entity cartesian position.
* @public
* @param {number} x - 3d space X - position.
* @param {number} y - 3d space Y - position.
* @param {number} z - 3d space Z - position.
*/
setCartesian(x, y, z) {
var p = this._cartesian;
p.x = x;
p.y = y;
p.z = z;
//billboards
this.billboard && this.billboard.setPosition3v(p);
//labels
this.label && this.label.setPosition3v(p);
//shape
this.shape && this.shape.setPosition3v(p);
for (var i = 0; i < this.childrenNodes.length; i++) {
this.childrenNodes[i].setCartesian(x, y, z);
}
var ec = this._entityCollection;
if (ec && ec.renderNode && ec.renderNode.ellipsoid) {
this._lonlat = ec.renderNode.ellipsoid.cartesianToLonLat(p);
if (Math.abs(this._lonlat.lat) < mercator.MAX_LAT) {
this._lonlatMerc = this._lonlat.forwardMercator();
} else {
this._lonlatMerc = null;
}
}
ec && ec.events.dispatch(ec.events.entitymove, this);
}
/**
* Sets entity cartesian position without moveentity event dispatching.
* @protected
* @param {og.math.Vector3} cartesian - Cartesian position in 3d space.
*/
_setCartesian3vSilent(cartesian) {
var p = this._cartesian;
p.x = cartesian.x;
p.y = cartesian.y;
p.z = cartesian.z;
//billboards
this.billboard && this.billboard.setPosition3v(p);
//labels
this.label && this.label.setPosition3v(p);
//shape
this.shape && this.shape.setPosition3v(p);
for (var i = 0; i < this.childrenNodes.length; i++) {
this.childrenNodes[i].setCartesian(x, y, z);
}
}
/**
* Gets entity geodetic coordinates.
* @public
* @returns {og.LonLat} -
*/
getLonLat() {
return this._lonlat.clone();
}
/**
* Sets geodetic coordinates of the entity point object.
* @public
* @param {og.LonLat} lonlat - WGS84 coordinates.
*/
setLonLat(lonlat) {
var l = this._lonlat;
l.lon = lonlat.lon;
l.lat = lonlat.lat;
l.height = lonlat.height;
var ec = this._entityCollection;
if (ec && ec.renderNode && ec.renderNode.ellipsoid) {
if (Math.abs(lonlat.lat) < mercator.MAX_LAT) {
this._lonlatMerc = lonlat.forwardMercator();
} else {
this._lonlatMerc = null;
}
this._cartesian = ec.renderNode.ellipsoid.lonLatToCartesian(lonlat);
this.setCartesian3v(this._cartesian);
}
}
/**
* Sets entity altitude over the planet.
* @public
* @param {number} altitude - Altitude.
*/
setAltitude(altitude) {
this._altitude = altitude;
}
/**
* Returns carteain position.
* @public
* @returns {og.math.Vector3} -
*/
getCartesian() {
return this._cartesian;
}
/**
* Sets entity billboard.
* @public
* @param {og.Billboard} billboard - Billboard object.
* @returns {og.Billboard} -
*/
setBillboard(billboard) {
if (this.billboard) {
this.billboard.remove();
}
this.billboard = billboard;
this.billboard._entity = this;
this.billboard.setPosition3v(this._cartesian);
this.billboard.setVisibility(this._visibility);
this._entityCollection && this._entityCollection._billboardHandler.add(billboard);
return billboard;
}
/**
* Sets entity label.
* @public
* @param {og.Label} label - Text label.
* @returns {og.Label} -
*/
setLabel(label) {
if (this.label) {
this.label.remove();
}
this.label = label;
this.label._entity = this;
this.label.setPosition3v(this._cartesian);
this.label.setVisibility(this._visibility);
this._entityCollection && this._entityCollection._labelHandler.add(label);
return label;
}
/**
* Sets entity shape.
* @public
* @param {og.BaseShape} shape - Shape object.
* @returns {og.Polyline} -
*/
setShape(shape) {
if (this.shape) {
this.shape.remove();
}
this.shape = shape;
this.shape._entity = this;
this.shape.setPosition3v(this._cartesian);
this.shape.setVisibility(this._visibility);
this._entityCollection && this._entityCollection._shapeHandler.add(shape);
return shape;
}
/**
* Sets entity polyline.
* @public
* @param {og.Polyline} polyline - Polyline object.
* @returns {og.Polyline} -
*/
setPolyline(polyline) {
if (this.polyline) {
this.polyline.remove();
}
this.polyline = polyline;
this.polyline._entity = this;
this.polyline.setVisibility(this._visibility);
this._entityCollection && this._entityCollection._polylineHandler.add(polyline);
return polyline;
}
/**
* Sets entity pointCloud.
* @public
* @param {og.PointCloud} pointCloud - PointCloud object.
* @returns {og.PointCloud} -
*/
setPointCloud(pointCloud) {
if (this.pointCloud) {
this.pointCloud.remove();
}
this.pointCloud = pointCloud;
this.pointCloud._entity = this;
this.pointCloud.setVisibility(this._visibility);
this._entityCollection && this._entityCollection._pointCloudHandler.add(pointCloud);
return pointCloud;
}
/**
* Sets entity geometry.
* @public
* @param {og.Geometry} geometry - Geometry object.
* @returns {og.Geometry} -
*/
setGeometry(geometry) {
if (this.geometry) {
this.geometry.remove();
}
this.geometry = geometry;
this.geometry._entity = this;
this.geometry.setVisibility(this._visibility);
this._layer && this._layer.add(this);
return geometry;
}
/**
* Append child entity.
* @public
* @param {og.Entity} entity - Child entity.
*/
appendChild(entity) {
entity._entityCollection = this._entityCollection;
entity._pickingColor = this._pickingColor;
entity.parent = this;
this.childrenNodes.push(entity);
this._entityCollection && this._entityCollection._addRecursively(entity);
}
/**
* Appends entity items(billboard, label etc.) picking color.
* @public
*/
setPickingColor() {
var c = this._pickingColor;
//billboard
this.billboard && this.billboard.setPickingColor3v(c);
//label
this.label && this.label.setPickingColor3v(c);
//shape
this.shape && this.shape.setPickingColor3v(c);
//polyline
this.polyline && this.polyline.setPickingColor3v(c);
for (var i = 0; i < this.childrenNodes.length; i++) {
this.childrenNodes[i].setPickingColor();
}
}
/**
* Return geodethic extent.
* @returns {og.Extent} -
*/
getExtent() {
var res;
var c = this._lonlat;
if (this.billboard || this.label) {
res = new Extent(new LonLat(c.lon, c.lat), new LonLat(c.lon, c.lat));
} else {
res = new Extent(new LonLat(180.0, 90.0), new LonLat(-180.0, -90.0));
}
var sw = res.southWest,
ne = res.northEast;
if (this.polyline) {
var e = this.polyline.getExtent();
if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
}
if (this.geometry) {
var e = this.geometry.getExtent();
if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
}
for (var i = 0; i < this.childrenNodes.length; i++) {
var e = this.childrenNodes[i].getExtent();
if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
}
return res;
}
};
export { Entity };

View File

@ -0,0 +1,774 @@
/**
* @module og/entity/EntityCollection
*/
'use strict';
import * as math from '../math.js';
import { BillboardHandler } from './BillboardHandler.js';
import { Events } from '../Events.js';
import { LabelHandler } from './LabelHandler.js';
import { PolylineHandler } from './PolylineHandler.js';
import { PointCloudHandler } from './PointCloudHandler.js';
import { ShapeHandler } from './ShapeHandler.js';
/**
* An observable collection of og.Entity instances where each entity has a unique id.
* Entity collection provide handlers for an each type of entity like billboard, label or 3ds object.
* @constructor
* @param {Object} [options] - Entity options:
* @param {Array.<og.Entity>} [options.entities] - Entities array.
* @param {boolean} [options.visibility=true] - Entity visibility.
* @param {Array.<number,number,number>} [options.scaleByDistance] - Entity scale by distance parameters.
* First index - near distance to the entity, after entity becomes full scale.
* Second index - far distance to the entity, when entity becomes zero scale.
* Third index - far distance to the entity, when entity becomes invisible.
* @param {number} [options.opacity] - Entity global opacity.
* @param {boolean} [options.pickingEnabled=true] - Entity picking enable.
* @fires og.EntityCollection#entitymove
* @fires og.EntityCollection#draw
* @fires og.EntityCollection#drawend
* @fires og.EntityCollection#add
* @fires og.EntityCollection#remove
* @fires og.EntityCollection#entityadd
* @fires og.EntityCollection#entityremove
* @fires og.EntityCollection#visibilitychange
* @fires og.EntityCollection#mousemove
* @fires og.EntityCollection#mouseenter
* @fires og.EntityCollection#mouseleave
* @fires og.EntityCollection#lclick
* @fires og.EntityCollection#rclick
* @fires og.EntityCollection#mclick
* @fires og.EntityCollection#ldblclick
* @fires og.EntityCollection#rdblclick
* @fires og.EntityCollection#mdblclick
* @fires og.EntityCollection#lup
* @fires og.EntityCollection#rup
* @fires og.EntityCollection#mup
* @fires og.EntityCollection#ldown
* @fires og.EntityCollection#rdown
* @fires og.EntityCollection#mdown
* @fires og.EntityCollection#lhold
* @fires og.EntityCollection#rhold
* @fires og.EntityCollection#mhold
* @fires og.EntityCollection#mousewheel
* @fires og.EntityCollection#touchmove
* @fires og.EntityCollection#touchstart
* @fires og.EntityCollection#touchend
* @fires og.EntityCollection#doubletouch
* @fires og.EntityCollection#touchleave
* @fires og.EntityCollection#touchenter
*/
class EntityCollection {
constructor(options) {
options = options || {};
/**
* Unic identifier.
* @public
* @readonly
*/
this.id = EntityCollection.__staticCounter++;
/**
* Render node collections array index.
* @protected
* @type {number}
*/
this._renderNodeIndex = -1;
/**
* Render node context.
* @public
* @type {og.scene.RenderNode}
*/
this.renderNode = null;
/**
* Visibility option.
* @protected
* @type {boolean}
*/
this._visibility = options.visibility == undefined ? true : options.visibility;
/**
* Billboards handler
* @public
* @type {og.BillboardHandler}
*/
this.billboardHandler = new BillboardHandler(this);
/**
* Labels handler
* @public
* @type {og.LabelHandler}
*/
this.labelHandler = new LabelHandler(this);
/**
* Shape handler
* @public
* @type {og.ShapeHandler}
*/
this.shapeHandler = new ShapeHandler(this);
/**
* Polyline handler
* @public
* @type {og.PolylineHandler}
*/
this.polylineHandler = new PolylineHandler(this);
/**
* PointCloud handler
* @public
* @type {og.PointCloudHandler}
*/
this.pointCloudHandler = new PointCloudHandler(this);
//
//...
if (options.pickingEnabled != undefined) {
this.setPickingEnabled(options.pickingEnabled);
}
/**
* Entities array.
* @protected
* @type {Array.<og.Entity>}
*/
this._entities = options.entities || [];
/**
* First index - near distance to the entity, after entity becomes full scale.
* Second index - far distance to the entity, when entity becomes zero scale.
* Third index - far distance to the entity, when entity becomes invisible.
* @public
* @type {Array.<number,number,number>}
*/
this.scaleByDistance = options.scaleByDistance || [math.MAX32, math.MAX32, math.MAX32];
/**
* Global opacity.
* @protected
* @type {number}
*/
this._opacity = options.opacity == undefined ? 1.0 : options.opacity;
/**
* Opacity state during the animated opacity.
* @protected
* @type {number}
*/
this._animatedOpacity = this._opacity;
/**
* Entity collection events handler.
* @public
* @type {og.Events}
*/
this.events = new Events(EVENT_NAMES);
//initialize current entities
this.addEntities(this._entities);
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
/**
* Sets collection visibility.
* @public
* @param {boolean} visibility - Visibility flag.
*/
setVisibility(visibility) {
this._visibility = visibility;
this._animatedOpacity = this._opacity * (visibility ? 1 : 0);
this.events.dispatch(this.events.visibilitychange, this);
}
/**
* Returns collection visibility.
* @public
* @returns {boolean}
*/
getVisibility() {
return this._visibility;
}
/**
* Sets collection opacity.
* @public
* @param {number} opacity - Opacity.
*/
setOpacity(opacity) {
this._opacity = opacity;
}
/**
* Sets collection picking ability.
* @public
* @param {boolean} enable - Picking enable flag.
*/
setPickingEnabled(enable) {
this.billboardHandler.pickingEnabled = enable;
this.labelHandler.pickingEnabled = enable;
this.polylineHandler.pickingEnabled = enable;
this.shapeHandler.pickingEnabled = enable;
this.pointCloudHandler.pickingEnabled = enable;
}
/**
* Gets collection opacity.
* @public
* @param {number} opacity - Opacity.
*/
getOpacity() {
return this._opacity;
}
/**
* Sets scale by distance parameters.
* @public
* @param {number} near - Full scale entity distance.
* @param {number} far - Zerol scale entity distance.
* @param {number} [farInvisible] - Entity visibility distance.
*/
setScaleByDistance(near, far, farInvisible) {
this.scaleByDistance[0] = near;
this.scaleByDistance[1] = far;
this.scaleByDistance[2] = farInvisible || math.MAX32;
}
_addRecursively(entity) {
//billboard
entity.billboard && this.billboardHandler.add(entity.billboard);
//label
entity.label && this.labelHandler.add(entity.label);
//shape
entity.shape && this.shapeHandler.add(entity.shape);
//polyline
entity.polyline && this.polylineHandler.add(entity.polyline);
//pointCloud
entity.pointCloud && this.pointCloudHandler.add(entity.pointCloud);
this.events.dispatch(this.events.entityadd, entity);
for (var i = 0; i < entity.childrenNodes.length; i++) {
entity.childrenNodes[i]._entityCollection = this;
entity.childrenNodes[i]._entityCollectionIndex = entity._entityCollectionIndex;
entity.childrenNodes[i]._pickingColor = entity._pickingColor;
this._addRecursively(entity.childrenNodes[i]);
}
}
/**
* Adds entity to the collection and returns collection.
* @public
* @param {og.Entity} entity - Entity.
* @returns {og.EntityCollection}
*/
add(entity) {
if (!entity._entityCollection) {
entity._entityCollection = this;
entity._entityCollectionIndex = this._entities.length;
this._entities.push(entity);
var rn = this.renderNode;
if (rn) {
rn.renderer && rn.renderer.assignPickingColor(entity);
rn.ellipsoid && entity._lonlat && entity.setCartesian3v(rn.ellipsoid.lonLatToCartesian(entity._lonlat));
}
this._addRecursively(entity);
}
return this;
}
/**
* Adds entities array to the collection and returns collection.
* @public
* @param {Array.<og.Entity>} entities - Entities array.
* @returns {og.EntityCollection}
*/
addEntities(entities) {
var i = entities.length;
while (i--) {
this.add(entities[i]);
}
return this;
}
/**
* Returns true if the entity belongs this collection, otherwise returns false.
* @public
* @param {og.Entity} entity - Entity.
* @returns {boolean}
*/
belongs(entity) {
return (entity._entityCollection && this._renderNodeIndex == entity._entityCollection._renderNodeIndex);
}
_removeRecursively(entity) {
entity._entityCollection = null;
entity._entityCollectionIndex = -1;
//billboard
entity.billboard && this.billboardHandler.remove(entity.billboard);
//label
entity.label && this.labelHandler.remove(entity.label);
//shape
entity.shape && this.shapeHandler.remove(entity.shape);
//polyline
entity.polyline && this.polylineHandler.remove(entity.polyline);
//pointCloud
entity.pointCloud && this.pointCloudHandler.remove(entity.pointCloud);
for (var i = 0; i < entity.childrenNodes.length; i++) {
this._removeRecursively(entity.childrenNodes[i]);
}
}
/**
* Removes entity from this collection.
* @public
* @param {og.Entity} entity - Entity to remove.
*/
removeEntity(entity) {
this._entities.splice(entity._entityCollectionIndex, 1);
this.reindexEntitiesArray(entity._entityCollectionIndex);
//clear picking color
if (this.renderNode && this.renderNode.renderer) {
this.renderNode.renderer.clearPickingColor(entity);
entity._pickingColor.clear();
}
if (this.belongs(entity)) {
this._removeRecursively(entity);
}
this.events.dispatch(this.events.entityremove, entity);
}
_removeEntitySilent(entity) {
this._entities.splice(entity._entityCollectionIndex, 1);
this.reindexEntitiesArray(entity._entityCollectionIndex);
//clear picking color
if (this.renderNode && this.renderNode.renderer) {
this.renderNode.renderer.clearPickingColor(entity);
entity._pickingColor.clear();
}
if (this.belongs(entity)) {
this._removeRecursively(entity);
}
}
/**
* Creates or refresh collected entities picking color.
* @public
*/
createPickingColors() {
var e = this._entities;
for (var i = 0; i < e.length; i++) {
if (!e[i].parent) {
this.renderNode.renderer.assignPickingColor(e[i]);
e[i].setPickingColor();
}
}
}
/**
* Refresh collected entities indexes from startIndex entitytes collection array position.
* @public
* @param {number} startIndex - Entities collection array index.
*/
reindexEntitiesArray(startIndex) {
var e = this._entities;
for (var i = startIndex; i < e.length; i++) {
e[i]._entityCollectionIndex = i;
}
}
/**
* Adds this collection to render node.
* @public
* @param {og.scene.RenderNode} renderNode - Render node.
* @param {boolean} [isHidden] - Uses in vector layers that render in planet render specific function.
* @returns {og.EntityCollection}
*/
addTo(renderNode, isHidden) {
if (!this.renderNode) {
this.renderNode = renderNode;
if (!isHidden) {
this._renderNodeIndex = renderNode.entityCollections.length;
renderNode.entityCollections.push(this);
}
renderNode.ellipsoid && this._updateGeodeticCoordinates(renderNode.ellipsoid);
this.setRenderer(renderNode.renderer);
this.shapeHandler.setRenderNode(renderNode);
this.polylineHandler.setRenderNode(renderNode);
this.pointCloudHandler.setRenderNode(renderNode);
this.events.dispatch(this.events.add, this);
}
return this;
}
/**
* Updates coordiantes all lonLat entities in collection after collecction attached to the planet node.
* @private
* @param {og.Ellipsoid} ellipsoid - Globe ellipsoid.
*/
_updateGeodeticCoordinates(ellipsoid) {
var e = this._entities;
var i = e.length;
while (i--) {
var ei = e[i];
ei._lonlat && ei.setCartesian3v(ellipsoid.lonLatToCartesian(ei._lonlat));
}
}
/**
* Sets renderer. Used in renderer initialization, when entity collection starts before renderer has initialized.
* @public
* @param {og.Renderer} renderer - Renderer.
*/
setRenderer(renderer) {
//todo: better to replace to setRenderNode function
if (renderer) {
this.billboardHandler.setRenderer(renderer);
this.labelHandler.setRenderer(renderer);
this.updateBillboardsTextureAtlas();
this.updateLabelsFontAtlas();
this.createPickingColors();
}
}
/**
* Updates billboard texture atlas.
* @public
*/
updateBillboardsTextureAtlas() {
var b = this.billboardHandler._billboards;
for (var i = 0; i < b.length; i++) {
b[i].setSrc(b[i]._src);
}
}
/**
* Updates labels font atlas.
* @public
*/
updateLabelsFontAtlas() {
if (this.renderNode) {
var l = this.labelHandler._billboards;
for (var i = 0; i < l.length; i++) {
l[i].assignFontAtlas(this.renderNode.fontAtlas);
}
}
}
/**
* Removes collection from render node.
* @public
*/
remove() {
if (this.renderNode) {
if (this._renderNodeIndex != -1) {
this.renderNode.entityCollections.splice(this._renderNodeIndex, 1);
//reindex in the renderNode
for (var i = this._renderNodeIndex; i < this.renderNode.entityCollections.length; i++) {
this.renderNode.entityCollections._renderNodeIndex = i;
}
}
this.renderNode = null;
this._renderNodeIndex = -1;
this.events.dispatch(this.events.remove, this);
}
}
/**
* Gets entities.
* @public
* @returns {Array.<og.Entity>}
*/
getEntities() {
return [].concat(this._entities);
}
/**
* Safety entities loop.
* @public
* @param {function} callback - Entity callback.
*/
each(callback) {
var i = this._entities.length;
while (i--) {
var ei = this._entities[i];
ei && callback(ei);
}
}
/**
* Removes all entities from colection and clear handlers.
* @public
*/
clear() {
//TODO: Optimize by replace delete
//code to the clearEntity function.
this.billboardHandler.clear();
this.labelHandler.clear();
this.shapeHandler.clear();
this.polylineHandler.clear();
this.pointCloudHandler.clear();
var i = this._entities.length;
while (i--) {
var ei = this._entities[i];
if (this.renderNode && this.renderNode.renderer) {
this.renderNode.renderer.clearPickingColor(ei);
ei._pickingColor.clear();
}
this._clearEntity(ei);
}
this._entities.length = 0;
this._entities = [];
}
/**
* Clears entity recursevely.
* @private
* @param {og.Entity} entity - Entity to clear.
*/
_clearEntity(entity) {
entity._entityCollection = null;
entity._entityCollectionIndex = -1;
for (var i = 0; i < entity.childrenNodes.length; i++) {
this._clearEntity(entity.childrenNodes[i]);
}
}
};
const EVENT_NAMES = [
/**
* Triggered when entity has moved.
* @event og.EntityCollection#entitymove
*/
"entitymove",
/**
* Triggered when collection entities begin draw.
* @event og.EntityCollection#draw
*/
"draw",
/**
* Triggered after collection has drawn.
* @event og.EntityCollection#drawend
*/
"drawend",
/**
* Triggered when added to the render node.
* @event og.EntityCollection#add
*/
"add",
/**
* Triggered when removed from the render node.
* @event og.EntityCollection#remove
*/
"remove",
/**
* Triggered when new entity added to the collection.
* @event og.EntityCollection#entityadd
*/
"entityadd",
/**
* Triggered when entity removes from the collection.
* @event og.EntityCollection#entityremove
*/
"entityremove",
/**
* Triggered when visibility changes.
* @event og.EntityCollection#visibilitychange
*/
"visibilitychange",
/**
* Triggered when mouse moves over the entity.
* @event og.EntityCollection#mousemove
*/
"mousemove",
/**
* Triggered when mouse has entered over the entity.
* @event og.EntityCollection#mouseenter
*/
"mouseenter",
/**
* Triggered when mouse leaves the entity.
* @event og.EntityCollection#mouseleave
*/
"mouseleave",
/**
* Mouse left button clicked.
* @event og.EntityCollection#lclick
*/
"lclick",
/**
* Mouse right button clicked.
* @event og.EntityCollection#rclick
*/
"rclick",
/**
* Mouse right button clicked.
* @event og.EntityCollection#mclick
*/
"mclick",
/**
* Mouse left button double click.
* @event og.EntityCollection#ldblclick
*/
"ldblclick",
/**
* Mouse right button double click.
* @event og.EntityCollection#rdblclick
*/
"rdblclick",
/**
* Mouse middle button double click.
* @event og.EntityCollection#mdblclick
*/
"mdblclick",
/**
* Mouse left button up(stop pressing).
* @event og.EntityCollection#lup
*/
"lup",
/**
* Mouse right button up(stop pressing).
* @event og.EntityCollection#rup
*/
"rup",
/**
* Mouse middle button up(stop pressing).
* @event og.EntityCollection#mup
*/
"mup",
/**
* Mouse left button is just pressed down(start pressing).
* @event og.EntityCollection#ldown
*/
"ldown",
/**
* Mouse right button is just pressed down(start pressing).
* @event og.EntityCollection#rdown
*/
"rdown",
/**
* Mouse middle button is just pressed down(start pressing).
* @event og.EntityCollection#mdown
*/
"mdown",
/**
* Mouse left button is pressing.
* @event og.EntityCollection#lhold
*/
"lhold",
/**
* Mouse right button is pressing.
* @event og.EntityCollection#rhold
*/
"rhold",
/**
* Mouse middle button is pressing.
* @event og.EntityCollection#mhold
*/
"mhold",
/**
* Mouse wheel is rotated.
* @event og.EntityCollection#mousewheel
*/
"mousewheel",
/**
* Triggered when touch moves over the entity.
* @event og.EntityCollection#touchmove
*/
"touchmove",
/**
* Triggered when entity begins to touch.
* @event og.EntityCollection#touchstart
*/
"touchstart",
/**
* Triggered when entity ends touching.
* @event og.EntityCollection#touchend
*/
"touchend",
/**
* Triggered entity double touch.
* @event og.EntityCollection#doubletouch
*/
"doubletouch",
/**
* Triggered when touching leaves entity.
* @event og.EntityCollection#touchleave
*/
"touchleave",
/**
* Triggered when touch enters over the entity.
* @event og.EntityCollection#touchenter
*/
"touchenter"
];
export { EntityCollection };

317
src/og/entity/Geometry.js Normal file
View File

@ -0,0 +1,317 @@
/**
* @module og/entity/Geometry
*/
'use strict';
import * as utils from '../utils/shared.js';
import { Extent } from '../Extent.js';
import { Vec4 } from '../math/Vec4.js';
import { LonLat } from '../LonLat.js';
const GeometryType = {
POINT: 1,
LINESTRING: 2,
POLYGON: 3,
MULTIPOLYGON: 4,
MULTILINESTRING: 5
};
class Geometry {
constructor(options) {
this._id = Geometry._staticCounter++;
options = options || {};
options.style = options.style || {};
/**
* Entity instance that holds this geometry.
* @protected
* @type {og.Entity}
*/
this._entity = null;
this._handler = null;
this._handlerIndex = -1;
//Polygon
this._polyVerticesMerc = [];
this._polyVerticesLength = -1;
this._polyIndexesLength = -1;
this._polyVerticesHandlerIndex = -1;
this._polyIndexesHandlerIndex = -1;
//Line(Linestring and polygon's stroke(s)
this._lineVerticesMerc = [];
this._lineVerticesLength = -1;
this._lineOrdersLength = -1;
this._lineIndexesLength = -1;
this._lineColorsLength = -1;
this._lineThicknessLength = -1;
this._lineVerticesHandlerIndex = -1;
this._lineOrdersHandlerIndex = -1;
this._lineIndexesHandlerIndex = -1;
this._lineThicknessHandlerIndex = -1;
this._lineColorsHandlerIndex = -1;
this._type = options.type && Geometry.getType(options.type) || GeometryType.POINT;
this._coordinates = [];
this._extent = Geometry.getExtent({
'type': options.type || "Point",
'coordinates': options.coordinates || []
}, this._coordinates);
this._style = options.style || {};
this._style.fillColor = utils.createColorRGBA(options.style.fillColor, new Vec4(0.19, 0.62, 0.85, 0.4));
this._style.lineColor = utils.createColorRGBA(options.style.lineColor, new Vec4(0.19, 0.62, 0.85, 1));
this._style.strokeColor = utils.createColorRGBA(options.style.strokeColor, new Vec4(1, 1, 1, 0.95));
this._style.lineWidth = options.style.lineWidth || 3;
this._style.strokeWidth = options.style.strokeWidth || 0;
this._visibility = options.visibility || true;
//optimization flag for picking mask rendering pass
this._pickingReady = false;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
static getType(typeStr) {
return GeometryType[typeStr.toUpperCase()];
}
/**
* Returns geometry feature extent.
@static
@param {Object} geometryObj - GeoJSON style geometry feature.
@param {Array} outoordinates - Geometry feature coordinates clone.
@returns {og.Extent}
*/
static getExtent(geometryObj, outCoordinates) {
var res = new Extent(new LonLat(180.0, 90.0), new LonLat(-180.0, -90.0));
var t = Geometry.getType(geometryObj.type);
if (t === GeometryType.POINT) {
var lon = res.coordinates[0],
lat = res.coordinates[1];
res.southWest.lon = lon;
res.southWest.lat = lat;
res.northEast.lon = lon;
res.northEast.lat = lat;
outCoordinates && (outCoordinates[0] = lon) && (outCoordinates[1] = lat);
} else if (t === GeometryType.LINESTRING) {
var c = geometryObj.coordinates;
for (var i = 0; i < c.length; i++) {
var lon = c[i][0],
lat = c[i][1];
if (lon < res.southWest.lon) res.southWest.lon = lon;
if (lat < res.southWest.lat) res.southWest.lat = lat;
if (lon > res.northEast.lon) res.northEast.lon = lon;
if (lat > res.northEast.lat) res.northEast.lat = lat;
outCoordinates && (outCoordinates[i] = [lon, lat]);
}
} else if (t === GeometryType.POLYGON) {
var c = geometryObj.coordinates;
for (var i = 0; i < c.length; i++) {
var ci = c[i];
outCoordinates && (outCoordinates[i] = []);
for (var j = 0; j < ci.length; j++) {
var cij = ci[j];
var lon = cij[0],
lat = cij[1];
if (lon < res.southWest.lon) res.southWest.lon = lon;
if (lat < res.southWest.lat) res.southWest.lat = lat;
if (lon > res.northEast.lon) res.northEast.lon = lon;
if (lat > res.northEast.lat) res.northEast.lat = lat;
outCoordinates && (outCoordinates[i][j] = [lon, lat]);
}
}
} else if (t === GeometryType.MULTIPOLYGON) {
var p = geometryObj.coordinates;
for (var i = 0; i < p.length; i++) {
var pi = p[i];
outCoordinates && (outCoordinates[i] = []);
for (var j = 0; j < pi.length; j++) {
var pij = pi[j];
outCoordinates && (outCoordinates[i][j] = []);
for (var k = 0; k < pij.length; k++) {
var pijk = pij[k];
var lon = pijk[0],
lat = pijk[1];
if (lon < res.southWest.lon) res.southWest.lon = lon;
if (lat < res.southWest.lat) res.southWest.lat = lat;
if (lon > res.northEast.lon) res.northEast.lon = lon;
if (lat > res.northEast.lat) res.northEast.lat = lat;
outCoordinates && (outCoordinates[i][j][k] = [lon, lat]);
}
}
}
} else if (t === GeometryType.MULTILINESTRING) {
var c = geometryObj.coordinates;
for (var i = 0; i < c.length; i++) {
var ci = c[i];
outCoordinates && (outCoordinates[i] = []);
for (var j = 0; j < ci.length; j++) {
var cij = ci[j];
var lon = cij[0],
lat = cij[1];
if (lon < res.southWest.lon) res.southWest.lon = lon;
if (lat < res.southWest.lat) res.southWest.lat = lat;
if (lon > res.northEast.lon) res.northEast.lon = lon;
if (lat > res.northEast.lat) res.northEast.lat = lat;
outCoordinates && (outCoordinates[i][j] = [lon, lat]);
}
}
} else {
res.southWest.lon = res.southWest.lat = res.northEast.lon = res.northEast.lat = 0.0;
outCoordinates && (outCoordinates[0] = lon) && (outCoordinates[1] = lat);
}
return res;
}
/**
*/
setGeometry(geoJson) {
var h = this._handler;
this.remove();
this._type = Geometry.getType(geoJson.type || "Point");
this._extent = Geometry.getExtent(geoJson, this._coordinates);
h.add(this);
return this;
}
setFillColor(r, g, b, a) {
var c = this._style.fillColor;
if (c.w === 0.0 && a !== 0.0 || c.w !== 0.0 && a === 0.0) {
this._pickingReady = false;
}
c.x = r;
c.y = g;
c.z = b;
c.w = a;
this._handler && this._handler.setPolyColorArr(this, c);
return this;
}
setFillColor4v(rgba) {
return this.setFillColor(rgba.x, rgba.y, rgba.z, rgba.w);
}
setStrokeColor(r, g, b, a) {
var c = this._style.strokeColor;
if (c.w === 0.0 && a !== 0.0 || c.w !== 0.0 && a === 0.0) {
this._pickingReady = false;
}
c.x = r;
c.y = g;
c.z = b;
c.w = a;
this._handler && this._handler.setStrokeColorArr(this, c);
return this;
}
setLineColor(r, g, b, a) {
var c = this._style.lineColor;
if (c.w === 0.0 && a !== 0.0 || c.w !== 0.0 && a === 0.0) {
this._pickingReady = false;
}
c.x = r;
c.y = g;
c.z = b;
c.w = a;
this._handler && this._handler.setLineColorArr(this, c);
return this;
}
setStrokeColor4v(rgba) {
return this.setStrokeColor(rgba.x, rgba.y, rgba.z, rgba.w);
}
setLineColor4v(rgba) {
return this.setLineColor(rgba.x, rgba.y, rgba.z, rgba.w);
}
setStrokeOpacity(opacity) {
var c = this._style.strokeColor;
c.w = opacity;
return this.setStrokeColor(c.x, c.y, c.z, opacity);
}
setLineOpacity(opacity) {
var c = this._style.lineColor;
c.w = opacity;
return this.setLineColor(c.x, c.y, c.z, opacity);
}
setStrokeWidth(width) {
this._style.strokeWidth = width;
this._pickingReady = false;
this._handler && this._handler.setLineStrokeArr(this, width);
return this;
}
bringToFront() {
this._handler && this._handler.bringToFront(this);
return this;
}
setLineWidth(width) {
this._style.lineWidth = width;
this._pickingReady = false;
this._handler && this._handler.setLineThicknessArr(this, width);
return this;
}
setFillOpacity(opacity) {
var c = this._style.fillColor;
if (c.w === 0.0 && opacity !== 0.0 || c.w !== 0.0 && opacity === 0.0) {
this._pickingReady = false;
}
c.w = opacity;
this._handler && this._handler.setPolyColorArr(this, c);
return this;
}
setVisibility(visibility) {
this._visibility = visibility;
this._handler && this._handler.setGeometryVisibility(this);
return this;
}
getVisibility() {
return this._visibility;
}
remove() {
this._handler && this._handler.remove(this);
}
etExtent() {
return this._extent.clone();
}
getType() {
return this._type;
}
};
export { Geometry, GeometryType };

372
src/og/entity/Label.js Normal file
View File

@ -0,0 +1,372 @@
/**
* @module og/entity/Label
*/
'use strict';
import * as utils from '../utils/shared.js';
import { BaseBillboard } from './BaseBillboard.js';
import { Vec4 } from '../math/Vec4.js';
const ALIGN = {
RIGHT: 0,
LEFT: 1,
CENTER: 2
};
/**
* Text align options.
* @readonly
* @enum {number}
*/
const STR2ALIGN = {
"left": ALIGN.LEFT,
"right": ALIGN.RIGHT,
"center": ALIGN.CENTER
};
/**
* Billboard text label.
* @class
* @extends {og.BaseBillboard}
* @param {Object} [options] - Label options:
* @param {og.math.Vector3|Array.<number>} [options.position] - Billboard spatial position.
* @param {number} [options.rotation] - Screen angle rotaion.
* @param {og.math.Vector4|string|Array.<number>} [options.color] - Billboard color.
* @param {og.math.Vector3|Array.<number>} [options.alignedAxis] - Billboard aligned vector.
* @param {og.math.Vector3|Array.<number>} [options.offset] - Billboard center screen offset.
* @param {boolean} [options.visibility] - Visibility.
* @param {string} [options.text] - Text string.
* @param {string} [options.face] - HTML5 font face.
* @param {number} [options.size] - Font size in pixels.
* @param {string} [options.style] - HTML5 font style. Example 'normal', 'italic'.
* @param {string} [options.weight] - HTML5 font weight. Example 'normal', 'bold'.
* @param {number} [options.outline] - Text outline size. 0 - no outline, 1 - maximum outline. Default 0.58.
* @param {og.math.Vector4|string|Array.<number>} [options.outlineColor] - Outline color.
* @param {og.Label.ALIGN} [options.align] - Text horizontal align: "left", "right" and "center".
*/
class Label extends BaseBillboard {
constructor(options) {
super(options);
options = options || {};
/**
* Label text string.
* @private
* @type {string}
*/
this._text = options.text;
/**
* HTML5 font face.
* @private
* @type {string}
*/
this._face = utils.defaultString(options.face, null);
/**
* Font size in pixels.
* @private
* @type {number}
*/
this._size = options.size || 33;
/**
* HTML5 font style. Example 'normal', 'italic'.
* @private
* @type {string}
*/
this._style = utils.defaultString(options.style, null);
/**
* HTML5 font weight style. Example 'normal', 'bold'.
* @private
* @type {string}
*/
this._weight = utils.defaultString(options.weight, null);
/**
* Label outline.
* @private
* @type {number}
*/
this._outline = options.outline != undefined ? options.outline : 0.5;
/**
* Label outline color.
* @private
* @type {og.math.Vector4}
*/
this._outlineColor = utils.createColorRGBA(options.outlineColor, new Vec4(0.0, 0.0, 0.0, 1.0));
/**
* Text horizontal align: "left", "right" and "center".
* @private
* @type {og.Label.ALIGN}
*/
this._align = options.align ? STR2ALIGN[options.align.trim().toLowerCase()] || ALIGN.RIGHT : ALIGN.RIGHT;
/**
* Label font atlas index.
* @private
* @type {number}
*/
this._fontIndex = 0;
/**
* Font atlas pointer.
* @private
* @type {og.utils.FontAtlas}
*/
this._fontAtlas = null;
}
/**
* Sets lablel text.
* @public
* @param {string} text - Text string.
* It can't be bigger than maximum labelHandler _maxLetters value.
*/
setText(text) {
this._text = text;
this._handler && this._handler.setText(this._handlerIndex, text, this._fontIndex, this._align);
}
/**
* Gets current text string.
* @public
* @returns {string}
*/
getText() {
return this._text;
}
/**
* Sets label text align. Could be center, left or right. Left is default.
* @public
* @param {og.Label.ALIGN} align - Text align.
*/
setAlign(align) {
this._align = STR2ALIGN[align.trim().toLowerCase()];
this._handler && this._handler.setText(this._handlerIndex, this._text, this._fontIndex, this._align);
};
/**
* Gets label text current alignment.
* @public
* @returns {og.Label.ALIGN}
*/
getAlign() {
return this._align;
}
/**
* Sets font face family.
* @public
* @param {string} face - Font face family.
*/
setFace(face) {
this._face = face.trim().toLowerCase();
this.update();
}
/**
* Gets current font face.
* @public
* @returns {string}
*/
getFace() {
return this._face;
}
/**
* Sets label font size in pixels.
* @public
* @param {number} size - Label size in pixels.
*/
setSize(size) {
this._size = size;
this._handler && this._handler.setSizeArr(this._handlerIndex, size);
}
/**
* Gets label size in pixels.
* @public
* @returns {number}
*/
getSize() {
return this._size;
}
/**
* Sets font HTML5 style. It's can be Italic or Normal values.
* @public
* @param {string} style - HTML5 font style.
*/
setStyle(style) {
this._style = style.trim().toLowerCase();
this.update();
}
/**
* Gets label font style.
* @public
* @returns {string}
*/
getStyle() {
return this._style;
}
/**
* Sets label font HTML5 weight style. It's can be bold or normal.
* @public
* @param {string} weight - HTML5 font weight style.
*/
setWeight(weight) {
this._weight = weight.trim().toLowerCase();
this.update();
}
/**
* Gets label font weight.
* @public
* @returns {string}
*/
getWeight() {
return this._wight;
}
/**
* Sets text outline border size. Where 0 - is no outline and 1 - is the maximum outline size.
* @public
* @param {number} outline - Text outline size.
*/
setOutline(outline) {
this._outline = outline;
this._handler && this._handler.setOutlineArr(this._handlerIndex, 1.0 - outline);
}
/**
* Gets text current outline size.
* @public
* @returns {number}
*/
getOutline() {
return this._outline;
}
/**
* Sets label opacity.
* @public
* @param {number} a - Label opacity.
*/
setOpacity(a) {
this._color.w = a;
this.setColor4v(this._color);
this._outlineColor.w = a;
this.setOutlineColor4v(this._outlineColor);
}
/**
* Sets text outline color.
* @public
* @param {number} r - Red.
* @param {number} g - Green.
* @param {number} b - Blue.
* @param {number} a - Alpha.
*/
setOutlineColor(r, g, b, a) {
this._outlineColor.x = r;
this._outlineColor.y = g;
this._outlineColor.z = b;
this._outlineColor.w = a;
this._handler && this._handler.setOutlineColorArr(this._handlerIndex, this._outlineColor);
}
/**
* Sets text outline color.
* @public
* @param {og.math.Vector4} rgba - Color vector.
*/
setOutlineColor4v(rgba) {
this._outlineColor.x = rgba.x;
this._outlineColor.y = rgba.y;
this._outlineColor.z = rgba.z;
this._outlineColor.w = rgba.w;
this._handler && this._handler.setOutlineColorArr(this._handlerIndex, rgba);
}
/**
* Sets text outline color HTML string.
* @public
* @param {string} color - HTML string color.
*/
setOutlineColorHTML(color) {
this.setOutlineColor4v(utils.htmlColorToRgba(color));
};
/**
* Gets outline color vector.
* @public
* @returns {og.math.Vector4}
*/
getOutlineColor() {
return this._outlineColor;
}
/**
* Sets outline opacity. Actually outline color alpha value.
* @public
* @param {number} opacity - Outline opacity.
*/
setOutlineOpacity(opacity) {
this._outlineColor.w = opacity;
this._handler && this._handler.setOutlineColorArr(this._handlerIndex, this._outlineColor);
}
/**
* Gets outline opacity value.
* @public
* @returns {number}
*/
getOutlineOpacity() {
return this._outlineColor.w;
}
/**
* Updates label parameters.
* @public
*/
update() {
if (this._fontAtlas) {
var fontIndex = this._fontAtlas.getFontIndex(this._face, this._style, this._weight);
if (fontIndex == undefined) {
this._fontAtlas.createFontAsync(this._face, this._style, this._weight, this._applyFontIndex.bind(this));
} else {
this._applyFontIndex(fontIndex);
}
}
}
_applyFontIndex(fontIndex) {
this._fontIndex = fontIndex;
if (this._handler) {
this._handler.setFontIndexArr(this._handlerIndex, this._fontIndex);
this._handler.setText(this._handlerIndex, this._text, this._fontIndex, this._align);
}
}
/**
* Assigns font atlas and update.
* @public
* @param {og.utils.FontAtlas} fontAtlas - Font atlas.
*/
assignFontAtlas(fontAtlas) {
!this._fontAtlas && (this._fontAtlas = fontAtlas);
this.update();
}
};
export { Label, ALIGN };

View File

@ -0,0 +1,818 @@
/**
* @module og/entity/LabelHandler
*/
'use strict';
import * as shaders from '../shaders/label.js';
import { ALIGN } from './Label.js';
import { BillboardHandler } from './BillboardHandler.js';
const FONTINDEX_BUFFER = 9;
const OUTLINE_BUFFER = 10;
const OUTLINECOLOR_BUFFER = 11;
/*
* og.LabelHandler
*
*
*/
class LabelHandler extends BillboardHandler {
constructor(entityCollection) {
super(entityCollection);
this._fontIndexBuffer = null;
this._noOutlineBuffer = null;
this._outlineBuffer = null;
this._outlineColorBuffer = null;
this._fontIndexArr = [];
this._noOutlineArr = [];
this._outlineArr = [];
this._outlineColorArr = [];
this._buffersUpdateCallbacks[FONTINDEX_BUFFER] = this.createFontIndexBuffer;
this._buffersUpdateCallbacks[OUTLINE_BUFFER] = this.createOutlineBuffer;
this._buffersUpdateCallbacks[OUTLINECOLOR_BUFFER] = this.createOutlineColorBuffer;
this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);
this._maxLetters = 25;
}
initShaderProgram() {
if (this._renderer.handler) {
if (!this._renderer.handler.shaderPrograms.label) {
var isSingleBuffer = !this._renderer.isMultiFramebufferCompatible();
this._renderer.handler.addShaderProgram(shaders.label(isSingleBuffer));
}
if (!this._renderer.handler.shaderPrograms.labelPicking) {
this._renderer.handler.addShaderProgram(shaders.labelPicking());
}
}
}
add(label) {
if (label._handlerIndex == -1) {
label._handler = this;
label._handlerIndex = this._billboards.length;
this._billboards.push(label);
this._addBillboardToArrays(label);
this.refresh();
this.assignFontAtlas(label);
}
}
assignFontAtlas(label) {
if (this._entityCollection && this._entityCollection.renderNode) {
label.assignFontAtlas(this._entityCollection.renderNode.fontAtlas);
}
}
clear() {
this._texCoordArr.length = 0;
this._vertexArr.length = 0;
this._positionArr.length = 0;
this._sizeArr.length = 0;
this._offsetArr.length = 0;
this._rgbaArr.length = 0;
this._rotationArr.length = 0;
this._alignedAxisArr.length = 0;
this._fontIndexArr.length = 0;
this._noOutlineArr.length = 0;
this._outlineArr.length = 0;
this._outlineColorArr.length = 0;
this._texCoordArr = [];
this._vertexArr = [];
this._positionArr = [];
this._sizeArr = [];
this._offsetArr = [];
this._rgbaArr = [];
this._rotationArr = [];
this._alignedAxisArr = [];
this._fontIndexArr = [];
this._noOutlineArr = [];
this._outlineArr = [];
this._outlineColorArr = [];
this._removeBillboards();
this._deleteBuffers();
this.refresh();
}
_deleteBuffers() {
var gl = this._renderer.handler.gl;
gl.deleteBuffer(this._sizeBuffer);
gl.deleteBuffer(this._fontIndexBuffer);
gl.deleteBuffer(this._texCoordBuffer);
gl.deleteBuffer(this._outlineBuffer);
gl.deleteBuffer(this._noOutlineBuffer);
gl.deleteBuffer(this._outlineColorBuffer);
gl.deleteBuffer(this._positionBuffer);
gl.deleteBuffer(this._sizeBuffer);
gl.deleteBuffer(this._offsetBuffer);
gl.deleteBuffer(this._rgbaBuffer);
gl.deleteBuffer(this._rotationBuffer);
gl.deleteBuffer(this._vertexBuffer);
gl.deleteBuffer(this._texCoordBuffer);
gl.deleteBuffer(this._alignedAxisBuffer);
gl.deleteBuffer(this._pickingColorBuffer);
this._sizeBuffer = null;
this._fontIndexBuffer = null;
this._texCoordBuffer = null;
this._outlineBuffer = null;
this._outlineColorBuffer = null;
this._positionBuffer = null;
this._sizeBuffer = null;
this._offsetBuffer = null;
this._rgbaBuffer = null;
this._rotationBuffer = null;
this._vertexBuffer = null;
this._texCoordBuffer = null;
this._alignedAxisBuffer = null;
this._pickingColorBuffer = null;
}
_addBillboardToArrays(label) {
for (var i = 0; i < this._maxLetters; i++) {
if (label._visibility) {
BillboardHandler.concArr(this._vertexArr, [-0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5]);
} else {
BillboardHandler.concArr(this._vertexArr, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
BillboardHandler.concArr(this._texCoordArr, [0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0]);
var x = label._position.x, y = label._position.y, z = label._position.z, w = label._scale;
BillboardHandler.concArr(this._positionArr, [x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w]);
x = label._size;
BillboardHandler.concArr(this._sizeArr, [x, x, x, x, x, x]);
x = label._offset.x; y = label._offset.y; z = label._offset.z - 0.05;
BillboardHandler.concArr(this._offsetArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
x = label._color.x; y = label._color.y; z = label._color.z; w = label._color.w;
BillboardHandler.concArr(this._rgbaArr, [x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w]);
x = label._rotation;
BillboardHandler.concArr(this._rotationArr, [x, x, x, x, x, x]);
x = label._alignedAxis.x, y = label._alignedAxis.y, z = label._alignedAxis.z;
BillboardHandler.concArr(this._alignedAxisArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
x = label._fontIndex;
BillboardHandler.concArr(this._fontIndexArr, [0, 0, 0, 0, 0, 0]);
x = 1.0 - label._outline, y = 0.0;
BillboardHandler.concArr(this._outlineArr, [x, y, x, y, x, y, x, y, x, y, x, y]);
x = 0.75, y = 0.7;
BillboardHandler.concArr(this._noOutlineArr, [x, y, x, y, x, y, x, y, x, y, x, y]);
x = label._outlineColor.x; y = label._outlineColor.y; z = label._outlineColor.z; w = label._outlineColor.w;
BillboardHandler.concArr(this._outlineColorArr, [x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w, x, y, z, w]);
x = label._entity._pickingColor.x / 255, y = label._entity._pickingColor.y / 255, z = label._entity._pickingColor.z / 255;
BillboardHandler.concArr(this._pickingColorArr, [x, y, z, x, y, z, x, y, z, x, y, z, x, y, z, x, y, z]);
}
};
_displayPASS() {
var r = this._renderer;
var h = r.handler;
h.shaderPrograms.label.activate();
var sh = h.shaderPrograms.label._program;
var sha = sh.attributes,
shu = sh.uniforms;
var gl = h.gl;
var ec = this._entityCollection;
var rn = ec.renderNode;
gl.uniform1iv(shu.u_fontTextureArr._pName, rn.fontAtlas.samplerArr);
gl.uniformMatrix4fv(shu.viewMatrix._pName, false, r.activeCamera._viewMatrix._m);
gl.uniformMatrix4fv(shu.projectionMatrix._pName, false, r.activeCamera._projectionMatrix._m);
gl.uniform3fv(shu.uCamPos._pName, r.activeCamera.eye.toVec());
gl.uniform3fv(shu.uScaleByDistance._pName, ec.scaleByDistance);
gl.uniform1f(shu.uOpacity._pName, ec._animatedOpacity);
gl.uniform2fv(shu.uFloatParams._pName, [rn._planetRadius2 || 0, r.activeCamera._tanViewAngle_hradOneByHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer);
gl.vertexAttribPointer(sha.a_texCoord._pName, this._texCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.a_vertices._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(sha.a_positions._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer);
gl.vertexAttribPointer(sha.a_size._pName, this._sizeBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer);
gl.vertexAttribPointer(sha.a_offset._pName, this._offsetBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer);
gl.vertexAttribPointer(sha.a_rotation._pName, this._rotationBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._alignedAxisBuffer);
gl.vertexAttribPointer(sha.a_alignedAxis._pName, this._alignedAxisBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._fontIndexBuffer);
gl.vertexAttribPointer(sha.a_fontIndex._pName, this._fontIndexBuffer.itemSize, gl.FLOAT, false, 0, 0);
//buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this._outlineColorBuffer);
gl.vertexAttribPointer(sha.a_rgba._pName, this._outlineColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._outlineBuffer);
gl.vertexAttribPointer(sha.a_bufferAA._pName, this._outlineBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform1f(shu.uZ._pName, -2.0);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer.numItems);
//nobuffer
gl.bindBuffer(gl.ARRAY_BUFFER, this._rgbaBuffer);
gl.vertexAttribPointer(sha.a_rgba._pName, this._rgbaBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._noOutlineBuffer);
gl.vertexAttribPointer(sha.a_bufferAA._pName, this._noOutlineBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform1f(shu.uZ._pName, -10.0);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer.numItems);
}
_pickingPASS() {
var r = this._renderer;
var h = r.handler;
h.shaderPrograms.labelPicking.activate();
var sh = h.shaderPrograms.labelPicking._program;
var sha = sh.attributes,
shu = sh.uniforms;
var gl = h.gl;
gl.uniformMatrix4fv(shu.viewMatrix._pName, false, r.activeCamera._viewMatrix._m);
gl.uniformMatrix4fv(shu.projectionMatrix._pName, false, r.activeCamera._projectionMatrix._m);
gl.uniform3fv(shu.uCamPos._pName, r.activeCamera.eye.toVec());
gl.uniform3fv(shu.uScaleByDistance._pName, this._entityCollection.scaleByDistance);
gl.uniform1f(shu.uOpacity._pName, this._entityCollection._animatedOpacity);
gl.uniform2fv(shu.uFloatParams._pName, [this._entityCollection.renderNode._planetRadius2 || 0, r.activeCamera._tanViewAngle_hradOneByHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.a_vertices._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer);
gl.vertexAttribPointer(sha.a_texCoord._pName, this._texCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(sha.a_positions._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer);
gl.vertexAttribPointer(sha.a_size._pName, this._sizeBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer);
gl.vertexAttribPointer(sha.a_offset._pName, this._offsetBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer);
gl.vertexAttribPointer(sha.a_rotation._pName, this._rotationBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._alignedAxisBuffer);
gl.vertexAttribPointer(sha.a_alignedAxis._pName, this._alignedAxisBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pickingColorBuffer);
gl.vertexAttribPointer(sha.a_pickingColor._pName, this._pickingColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer.numItems);
}
_removeBillboard(label) {
var li = label._handlerIndex;
this._billboards.splice(li, 1);
var ml = 24 * this._maxLetters;
var i = li * ml;
this._rgbaArr.splice(i, ml);
this._outlineColorArr.splice(i, ml);
this._texCoordArr.splice(i, ml);
this._positionArr.splice(i, ml);
ml = 18 * this._maxLetters;
i = li * ml;
this._offsetArr.splice(i, ml);
this._alignedAxisArr.splice(i, ml);
this._pickingColorArr.splice(i, ml);
ml = 12 * this._maxLetters;
i = li * ml;
this._vertexArr.splice(i, ml);
this._outlineArr.splice(i, ml);
this._noOutlineArr.splice(i, ml);
ml = 6 * this._maxLetters;
i = li * ml;
this._sizeArr.splice(i, ml);
this._rotationArr.splice(i, ml);
this._fontIndexArr.splice(i, ml);
this.reindexBillbordsArray(li);
this.refresh();
label._handlerIndex = -1;
label._handler = null;
//label._fontIndex = 0;
//label._fontAtlas = null;
};
setText(index, text, fontIndex, align) {
var fa = this._entityCollection.renderNode.fontAtlas.atlasesArr[fontIndex];
if (!fa) return;
var i = index * 24 * this._maxLetters;
var a = this._texCoordArr;
var c = 0;
var j = i + c * 24;
var n = fa.nodes[text[c]];
var f = n ? n.emptySize : 0.0;
var offset = f;
for (c = 0; c < text.length; c++) {
var j = i + c * 24;
var n = fa.nodes[text[c]] || fa.nodes[" "];
var tc = n.texCoords;
a[j] = tc[0];
a[j + 1] = tc[1];
a[j + 2] = offset;
a[j + 3] = 0.0;
a[j + 4] = tc[2];
a[j + 5] = tc[3];
a[j + 6] = offset;
a[j + 7] = 0.0;
a[j + 8] = tc[4];
a[j + 9] = tc[5];
a[j + 10] = offset;
a[j + 11] = 0.0;
a[j + 12] = tc[6];
a[j + 13] = tc[7];
a[j + 14] = offset;
a[j + 15] = 0.0;
a[j + 16] = tc[8];
a[j + 17] = tc[9];
a[j + 18] = offset;
a[j + 19] = 0.0;
a[j + 20] = tc[10];
a[j + 21] = tc[11];
a[j + 22] = offset;
a[j + 23] = 0.0;
offset += n.emptySize;
}
//49/512 - font atlas left border letter offset
if (align == ALIGN.CENTER) {
offset = (f + 49 / 512 - offset) * 0.5;
for (c = 0; c < text.length; c++) {
var j = i + c * 24;
a[j + 3] = offset;
a[j + 7] = offset;
a[j + 11] = offset;
a[j + 15] = offset;
a[j + 19] = offset;
a[j + 23] = offset;
}
} else if (align == ALIGN.LEFT) {
offset = (f + 49 / 512 - offset);
for (c = 0; c < text.length; c++) {
var j = i + c * 24;
a[j + 3] = offset;
a[j + 7] = offset;
a[j + 11] = offset;
a[j + 15] = offset;
a[j + 19] = offset;
a[j + 23] = offset;
}
}
for (var c = c; c < this._maxLetters; c++) {
var j = i + c * 24;
a[j + 2] = -1.0;
a[j + 6] = -1.0;
a[j + 10] = -1.0;
a[j + 14] = -1.0;
a[j + 18] = -1.0;
a[j + 17] = -1.0;
}
this._changedBuffers[TEXCOORD_BUFFER] = true;
}
setPositionArr(index, position) {
var i = index * 24 * this._maxLetters;
var a = this._positionArr, x = position.x, y = position.y, z = position.z;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 24;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = z;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 16] = x;
a[j + 17] = y;
a[j + 18] = z;
a[j + 20] = x;
a[j + 21] = y;
a[j + 22] = z;
}
this._changedBuffers[POSITION_BUFFER] = true;
}
setScaleArr(index, scale) {
var i = index * 24 * this._maxLetters;
var a = this._positionArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 24;
a[j + 3] = scale;
a[j + 7] = scale;
a[j + 11] = scale;
a[j + 15] = scale;
a[j + 19] = scale;
a[j + 23] = scale;
}
this._changedBuffers[POSITION_BUFFER] = true;
}
setPickingColorArr(index, color) {
var i = index * 18 * this._maxLetters;
var a = this._pickingColorArr, x = color.x / 255, y = color.y / 255, z = color.z / 255;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 18;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = x;
a[j + 4] = y;
a[j + 5] = z;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = z;
a[j + 9] = x;
a[j + 10] = y;
a[j + 11] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = x;
a[j + 16] = y;
a[j + 17] = z;
}
this._changedBuffers[PICKINGCOLOR_BUFFER] = true;
}
setSizeArr(index, size) {
var i = index * 6 * this._maxLetters;
var a = this._sizeArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 6;
a[j] = size;
a[j + 1] = size;
a[j + 2] = size;
a[j + 3] = size;
a[j + 4] = size;
a[j + 5] = size;
}
this._changedBuffers[SIZE_BUFFER] = true;
}
setOffsetArr(index, offset) {
var i = index * 18 * this._maxLetters;
var a = this._offsetArr, x = offset.x, y = offset.y, z = offset.z;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 18;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = x;
a[j + 4] = y;
a[j + 5] = z;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = z;
a[j + 9] = x;
a[j + 10] = y;
a[j + 11] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = x;
a[j + 16] = y;
a[j + 17] = z;
}
this._changedBuffers[OFFSET_BUFFER] = true;
}
setRgbaArr(index, rgba) {
var i = index * 24 * this._maxLetters;
var a = this._rgbaArr, x = rgba.x, y = rgba.y, z = rgba.z, w = rgba.w;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 24;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = w;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = z;
a[j + 7] = w;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = z;
a[j + 11] = w;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = w;
a[j + 16] = x;
a[j + 17] = y;
a[j + 18] = z;
a[j + 19] = w;
a[j + 20] = x;
a[j + 21] = y;
a[j + 22] = z;
a[j + 23] = w;
}
this._changedBuffers[RGBA_BUFFER] = true;
}
setOutlineColorArr(index, rgba) {
var i = index * 24 * this._maxLetters;
var a = this._outlineColorArr, x = rgba.x, y = rgba.y, z = rgba.z, w = rgba.w;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 24;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = w;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = z;
a[j + 7] = w;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = z;
a[j + 11] = w;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = w;
a[j + 16] = x;
a[j + 17] = y;
a[j + 18] = z;
a[j + 19] = w;
a[j + 20] = x;
a[j + 21] = y;
a[j + 22] = z;
a[j + 23] = w;
}
this._changedBuffers[OUTLINECOLOR_BUFFER] = true;
}
setOutlineArr(index, outline) {
var i = index * 12 * this._maxLetters;
var a = this._outlineArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 12;
a[j] = outline;
a[j + 2] = outline;
a[j + 4] = outline;
a[j + 6] = outline;
a[j + 8] = outline;
a[j + 10] = outline;
}
this._changedBuffers[OUTLINE_BUFFER] = true;
}
setRotationArr(index, rotation) {
var i = index * 6 * this._maxLetters;
var a = this._rotationArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 6;
a[j] = rotation;
a[j + 1] = rotation;
a[j + 2] = rotation;
a[j + 3] = rotation;
a[j + 4] = rotation;
a[j + 5] = rotation;
}
this._changedBuffers[ROTATION_BUFFER] = true
}
setVertexArr(index, vertexArr) {
var i = index * 12 * this._maxLetters;
var a = this._vertexArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 12;
a[j] = vertexArr[0];
a[j + 1] = vertexArr[1];
a[j + 2] = vertexArr[2];
a[j + 3] = vertexArr[3];
a[j + 4] = vertexArr[4];
a[j + 5] = vertexArr[5];
a[j + 6] = vertexArr[6];
a[j + 7] = vertexArr[7];
a[j + 8] = vertexArr[8];
a[j + 9] = vertexArr[9];
a[j + 10] = vertexArr[10];
a[j + 11] = vertexArr[11];
}
this._changedBuffers[VERTEX_BUFFER] = true;
}
setAlignedAxisArr(index, alignedAxis) {
var i = index * 18 * this._maxLetters;
var a = this._alignedAxisArr, x = alignedAxis.x, y = alignedAxis.y, z = alignedAxis.z;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 18;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = x;
a[j + 4] = y;
a[j + 5] = z;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = z;
a[j + 9] = x;
a[j + 10] = y;
a[j + 11] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = x;
a[j + 16] = y;
a[j + 17] = z;
}
this._changedBuffers[ALIGNEDAXIS_BUFFER] = true;
}
setFontIndexArr(index, fontIndex) {
var i = index * 6 * this._maxLetters;
var a = this._fontIndexArr;
for (var q = 0; q < this._maxLetters; q++) {
var j = i + q * 6;
a[j] = fontIndex;
a[j + 1] = fontIndex;
a[j + 2] = fontIndex;
a[j + 3] = fontIndex;
a[j + 4] = fontIndex;
a[j + 5] = fontIndex;
}
this._changedBuffers[FONTINDEX_BUFFER] = true;
}
createSizeBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._sizeBuffer);
this._sizeBuffer = h.createArrayBuffer(new Float32Array(this._sizeArr), 1, this._sizeArr.length);
}
createFontIndexBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._fontIndexBuffer);
this._fontIndexBuffer = h.createArrayBuffer(new Float32Array(this._fontIndexArr), 1, this._fontIndexArr.length);
}
createTexCoordBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._texCoordBuffer);
this._texCoordBuffer = h.createArrayBuffer(new Float32Array(this._texCoordArr), 4, this._texCoordArr.length / 4);
}
createOutlineBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._outlineBuffer);
this._outlineBuffer = h.createArrayBuffer(new Float32Array(this._outlineArr), 2, this._outlineArr.length / 2);
h.gl.deleteBuffer(this._noOutlineBuffer);
this._noOutlineBuffer = h.createArrayBuffer(new Float32Array(this._noOutlineArr), 2, this._noOutlineArr.length / 2);
}
createOutlineColorBuffer() {
var h = this._renderer.handler;
h.gl.deleteBuffer(this._outlineColorBuffer);
this._outlineColorBuffer = h.createArrayBuffer(new Float32Array(this._outlineColorArr), 4, this._outlineColorArr.length / 4);
}
setMaxLetters(c) {
this._maxLetters = c;
//...
}
refreshTexCoordsArr() {
//it is empty
return null;
}
};
export { LabelHandler };

461
src/og/entity/PointCloud.js Normal file
View File

@ -0,0 +1,461 @@
/**
* @module og/entity/PointCloud
*/
'use strict';
import { Vec3 } from '../math/Vec3.js';
import { Vec4 } from '../math/Vec4.js';
const COORDINATES_BUFFER = 0;
const COLOR_BUFFER = 1;
const PICKING_COLOR_BUFFER = 2;
/**
* PointCloud object.
* @class
* @param {*} [options] - Point cloud options:
* @param {Array.<Array.<number,number,number,number,number,number,number,*>>} [options.points] - Points cartesian coordinates array,
* where first three is cartesian coordinates, next fourth is a RGBA color, and last is an point properties.
* @param {number} [options.pointSize] - Point screen size in pixels.
* @param {number} [options.pickingDistance] - Point border picking size in screen pixels.
* @parar {boolean} [options.visibility] - Point cloud visibility.
* @example <caption>Creates point cloud with two ten pixel size points</caption>
* new og.Entity({
* pointCloud: {
* pointSize: 10,
* points: [
* [0, 0, 0, 255, 255, 255, 255, { 'name': 'White point' }],
* [100, 100, 0, 255, 0, 0, 255, { 'name': 'Red point' }]
* ]
* }
* });
*/
class PointCloud {
constructor(options) {
options = options || {};
/**
* Object unic identifier.
* @public
* @readonly
* @type {number}
*/
this.id = PointCloud._staticCounter++;
/**
* Cloud visibility.
* @public
* @type {boolean}
*/
this.visibility = (options.visibility != undefined ? options.visibility : true);
/**
* Point screen size in pixels.
* @public
* @type {number}
*/
this.pointSize = options.pointSize || 3;
/**
* Point picking border size in pixels.
* @public
* @type {number}
*/
this.pickingDistance = options.pickingDistance || 0;
/**
* Parent collection render node.
* @private
* @type {og.scene.RenderNode}
*/
this._renderNode = null;
/**
* Entity instance that holds this point cloud.
* @private
* @type {og.Entity}
*/
this._entity = null;
/**
* Points properties.
* @private
* @type {Array.<*>}
*/
this._points = [];
/**
* Coordinates array.
* @private
* @type {Array.<number>}
*/
this._coordinatesData = [];
/**
* Color array.
* @private
* @type {Array.<number>}
*/
this._colorData = [];
/**
* Picking color array.
* @private
* @type {Array.<number>}
*/
this._pickingColorData = [];
this._coordinatesBuffer = null;
this._colorBuffer = null;
this._pickingColorBuffer = null;
/**
* Handler that stores and renders this object.
* @private
* @type {og.PointCloudHandler}
*/
this._handler = null;
this._handlerIndex = -1;
this._buffersUpdateCallbacks = [];
this._buffersUpdateCallbacks[COORDINATES_BUFFER] = this._createCoordinatesBuffer;
this._buffersUpdateCallbacks[COLOR_BUFFER] = this._createColorBuffer;
this._buffersUpdateCallbacks[PICKING_COLOR_BUFFER] = this._createPickingColorBuffer;
this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);
this.setPoints(options.points);
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
/**
* Clears point cloud data
* @public
*/
clear() {
this._points.length = 0;
this._points = [];
this._coordinatesData.length = 0;
this._coordinatesData = [];
this._colorData.length = 0;
this._colorData = [];
this._pickingColorData.length = 0;
this._pickingColorData = [];
this._deleteBuffers();
}
/**
* Set point cloud opacity.
* @public
* @param {number} opacity - Cloud opacity.
*/
setOpacity(opacity) {
this.opacity = opacity;
}
/**
* Sets cloud visibility.
* @public
* @param {number} visibility - Visibility flag.
*/
setVisibility(visibility) {
this.visibility = visibility;
}
/**
* @return {boolean} Point cloud visibily.
*/
getVisibility() {
return this.visibility;
}
/**
* Assign rendering scene node.
* @public
* @param {og.scene.RenderNode} renderNode - Assigned render node.
*/
setRenderNode(renderNode) {
this._renderNode = renderNode;
this._setPickingColors();
}
/**
* Removes from entity.
* @public
*/
remove() {
this._entity = null;
this._handler && this._handler.remove(this);
}
/**
* Adds points to render.
* @public
* @param {Array.<Array<number,number,number,number,number,number,number,*>>} points - Point cloud array.
* @example
* var points = [[0, 0, 0, 255, 255, 255, 255, { 'name': 'White point' }], [100, 100, 0, 255, 0, 0, 255, { 'name': 'Red point' }]];
*/
setPoints(points) {
this.clear();
for (var i = 0; i < points.length; i++) {
var pi = points[i];
var pos = new Vec3(pi[0], pi[1], pi[2]),
col = new Vec4(pi[3], pi[4], pi[5], (pi[6] == undefined ? 255.0 : pi[6]));
this._coordinatesData.push(pos.x, pos.y, pos.z);
this._colorData.push(col.x / 255.0, col.y / 255.0, col.z / 255.0, col.w / 255.0);
var p = {
'_pickingColor': new Vec3(),
'_entityCollection': this._entity && this._entity._entityCollection,
'index': i,
'position': pos,
'color': col,
'pointCloud': this,
'properties': pi[7] || {}
};
this._points.push(p);
if (this._renderNode) {
this._renderNode.renderer.assignPickingColor(p);
this._pickingColorData.push(p._pickingColor.x / 255.0, p._pickingColor.y / 255.0, p._pickingColor.z / 255.0, 1.0);
}
}
this._changedBuffers[COORDINATES_BUFFER] = true;
this._changedBuffers[COLOR_BUFFER] = true;
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
/**
* @todo
*/
setPointPosition(index, x, y, z) {
//...
this._changedBuffers[COORDINATES_BUFFER] = true;
}
/**
* @todo
*/
setPointColor(index, r, g, b, a) {
//...
this._changedBuffers[COLOR_BUFFER] = true;
}
/**
* @todo
*/
addPoints(points) {
//...
this._changedBuffers[COORDINATES_BUFFER] = true;
this._changedBuffers[COLOR_BUFFER] = true;
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
/**
* @todo
*/
addPoint(index, point) {
//...
this._changedBuffers[COORDINATES_BUFFER] = true;
this._changedBuffers[COLOR_BUFFER] = true;
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
/**
* Returns specific point by index.
* @public
* @param {number} index - Point index.
* @return {*} Specific point
*/
getPoint(index) {
return this._points[index];
}
/**
* @todo
*/
removePoint(index) {
//...
this._changedBuffers[COORDINATES_BUFFER] = true;
this._changedBuffers[COLOR_BUFFER] = true;
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
/**
* @todo
*/
insertPoint(index, point) {
//...
this._changedBuffers[COORDINATES_BUFFER] = true;
this._changedBuffers[COLOR_BUFFER] = true;
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
/**
* Each point iterator.
* @public
* @param {callback} callback
*/
each(callback) {
var i = this._points.length;
while (i--) {
callback && callback(this._points[i]);
}
}
draw() {
if (this.visibility && this._coordinatesData.length) {
this._update();
var rn = this._renderNode;
var r = rn.renderer;
var sh = r.handler.shaderPrograms.pointCloud;
var p = sh._program;
var gl = r.handler.gl,
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.uniformMatrix4fv(shu.projectionViewMatrix._pName, false, r.activeCamera._projectionViewMatrix._m);
gl.uniform1f(shu.opacity._pName, this._handler._entityCollection._animatedOpacity);
gl.uniform1f(shu.pointSize._pName, this.pointSize);
gl.bindBuffer(gl.ARRAY_BUFFER, this._coordinatesBuffer);
gl.vertexAttribPointer(sha.coordinates._pName, this._coordinatesBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
gl.vertexAttribPointer(sha.colors._pName, this._colorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, this._coordinatesBuffer.numItems);
}
}
drawPicking() {
if (this.visibility && this._coordinatesData.length) {
var rn = this._renderNode;
var r = rn.renderer;
var sh = r.handler.shaderPrograms.pointCloud;
var p = sh._program;
var gl = r.handler.gl,
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.uniformMatrix4fv(shu.projectionViewMatrix._pName, false, r.activeCamera._projectionViewMatrix._m);
gl.uniform1f(shu.opacity._pName, this._handler._entityCollection._animatedOpacity);
gl.uniform1f(shu.pointSize._pName, this.pointSize + this.pickingDistance);
gl.bindBuffer(gl.ARRAY_BUFFER, this._coordinatesBuffer);
gl.vertexAttribPointer(sha.coordinates._pName, this._coordinatesBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pickingColorBuffer);
gl.vertexAttribPointer(sha.colors._pName, this._pickingColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, this._coordinatesBuffer.numItems);
}
}
/**
* Update gl buffers.
* @private
*/
_update() {
if (this._renderNode) {
var i = this._changedBuffers.length;
while (i--) {
if (this._changedBuffers[i]) {
this._buffersUpdateCallbacks[i].call(this);
this._changedBuffers[i] = false;
}
}
}
}
/**
* Delete buffers
* @private
*/
_deleteBuffers() {
if (this._renderNode) {
var r = this._renderNode.renderer,
gl = r.handler.gl;
gl.deleteBuffer(this._coordinatesBuffer);
gl.deleteBuffer(this._colorBuffer);
gl.deleteBuffer(this._pickingColorBuffer);
}
this._coordinatesBuffer = null;
this._colorBuffer = null;
this._pickingColorBuffer = null;
}
_createCoordinatesBuffer() {
var h = this._renderNode.renderer.handler;
h.gl.deleteBuffer(this._coordinatesBuffer);
this._coordinatesBuffer = h.createArrayBuffer(new Float32Array(this._coordinatesData), 3, (this._coordinatesData.length) / 3);
}
_createColorBuffer() {
var h = this._renderNode.renderer.handler;
h.gl.deleteBuffer(this._colorBuffer);
this._colorBuffer = h.createArrayBuffer(new Float32Array(this._colorData), 4, (this._colorData.length) / 4);
}
_createPickingColorBuffer() {
var h = this._renderNode.renderer.handler;
h.gl.deleteBuffer(this._pickingColorBuffer);
this._pickingColorBuffer = h.createArrayBuffer(new Float32Array(this._pickingColorData), 4, (this._pickingColorData.length) / 4);
}
_setPickingColors() {
if (this._renderNode) {
for (var i = 0; i < this._points.length; i++) {
var p = this._points[i];
p._entity = this._entity;
p._entityCollection = this._entity._entityCollection;
this._renderNode.renderer.assignPickingColor(p);
this._pickingColorData.push(p._pickingColor.x / 255.0, p._pickingColor.y / 255.0, p._pickingColor.z / 255.0, 1.0);
}
this._changedBuffers[PICKING_COLOR_BUFFER] = true;
}
}
};
export { PointCloud };

View File

@ -0,0 +1,126 @@
/**
* @module og/entity/PointCloudHandler
*/
'use strict';
import * as shaders from '../shaders/pointCloud.js';
class PointCloudHandler {
constructor(entityCollection) {
/**
* Picking rendering option.
* @public
* @type {boolean}
*/
this.pickingEnabled = true;
/**
* Parent collection
* @private
* @type {og.EntityCollection}
*/
this._entityCollection = entityCollection;
/**
* Renderer
* @private
* @type {og.Renderer}
*/
this._renderer = null;
/**
* Point cloud array
* @private
* @type {Array.<og.PointCloud>}
*/
this._pointClouds = [];
this.__staticId = PointCloudHandler._staticCounter++;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
_initShaderProgram() {
if (this._renderer.handler) {
if (!this._renderer.handler.shaderPrograms.pointCloud) {
this._renderer.handler.addShaderProgram(shaders.pointCloud());
}
}
}
setRenderNode(renderNode) {
this._renderer = renderNode.renderer;
this._initShaderProgram()
for (var i = 0; i < this._pointClouds.length; i++) {
this._pointClouds[i].setRenderNode(renderNode);
}
}
add(pointCloud) {
if (pointCloud._handlerIndex == -1) {
pointCloud._handler = this;
pointCloud._handlerIndex = this._pointClouds.length;
this._pointClouds.push(pointCloud);
this._entityCollection && this._entityCollection.renderNode &&
pointCloud.setRenderNode(this._entityCollection.renderNode);
}
}
remove(pointCloud) {
var index = pointCloud._handlerIndex;
if (index !== -1) {
pointCloud._deleteBuffers();
pointCloud._handlerIndex = -1;
pointCloud._handler = null;
this._pointClouds.splice(index, 1);
this.reindexPointCloudArray(index);
}
}
reindexPointCloudArray(startIndex) {
var pc = this._pointClouds;
for (var i = startIndex; i < pc.length; i++) {
pc[i]._handlerIndex = i;
}
}
draw() {
var i = this._pointClouds.length;
while (i--) {
this._pointClouds[i].draw();
}
}
drawPicking() {
if (this.pickingEnabled) {
var i = this._pointClouds.length;
while (i--) {
this._pointClouds[i].drawPicking();
}
}
}
clear() {
var i = this._pointClouds.length;
while (i--) {
this._pointClouds[i]._deleteBuffers();
this._pointClouds[i]._handler = null;
this._pointClouds[i]._handlerIndex = -1;
}
this._pointClouds.length = 0;
this._pointClouds = [];
}
};
export { PointCloudHandler };

1132
src/og/entity/Polyline.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
/**
* @module og/entity/PolylineHandler
*/
'use strict';
import * as shaders from '../shaders/polyline.js';
class PolylineHandler {
constructor(entityCollection) {
this._entityCollection = entityCollection;
this._renderer = null;
this._polylines = [];
this.__staticId = PolylineHandler._staticCounter++;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
_initShaderProgram() {
if (this._renderer.handler) {
if (!this._renderer.handler.shaderPrograms.polyline) {
this._renderer.handler.addShaderProgram(shaders.polyline(this._renderer.isMultiFramebufferCompatible()));
}
}
}
setRenderNode(renderNode) {
this._renderer = renderNode.renderer;
this._initShaderProgram()
for (var i = 0; i < this._polylines.length; i++) {
this._polylines[i].setRenderNode(renderNode);
}
}
add(polyline) {
if (polyline._handlerIndex == -1) {
polyline._handler = this;
polyline._handlerIndex = this._polylines.length;
this._polylines.push(polyline);
this._entityCollection && this._entityCollection.renderNode &&
polyline.setRenderNode(this._entityCollection.renderNode);
}
}
remove(polyline) {
var index = polyline._handlerIndex;
if (index !== -1) {
polyline._deleteBuffers();
polyline._handlerIndex = -1;
polyline._handler = null;
this._polylines.splice(index, 1);
this.reindexPolylineArray(index);
}
}
reindexPolylineArray(startIndex) {
var ls = this._polylines;
for (var i = startIndex; i < ls.length; i++) {
ls[i]._handlerIndex = i;
}
}
draw() {
var i = this._polylines.length;
while (i--) {
this._polylines[i].draw();
}
}
clear() {
var i = this._polylines.length;
while (i--) {
this._polylines[i]._deleteBuffers();
this._polylines[i]._handler = null;
this._polylines[i]._handlerIndex = -1;
}
this._polylines.length = 0;
this._polylines = [];
}
};
export { PolylineHandler };

View File

@ -0,0 +1,90 @@
/**
* @module og/entity/ShapeHandler
*/
'use strict';
import * as shaders from '../shaders/shape.js';
class ShapeHandler {
constructor(entityCollection) {
/**
* Picking rendering option.
* @public
* @type {boolean}
*/
this.pickingEnabled = true;
this._entityCollection = entityCollection;
this._renderer = null;
this._shapes = [];
this.__staticId = ShapeHandler._staticCounter++;
}
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
_initShaderProgram() {
if (this._renderer.handler) {
if (!this._renderer.handler.shaderPrograms.shape_nl) {
this._renderer.handler.addShaderProgram(shaders.shape_nl());
}
if (!this._renderer.handler.shaderPrograms.shape_wl) {
this._renderer.handler.addShaderProgram(shaders.shape_wl());
}
//if (!this._renderer.handler.shaderPrograms.shapePicking) {
// this._renderer.handler.addShaderProgram(shaders.shapePicking());
//}
}
}
setRenderNode(renderNode) {
this._renderer = renderNode.renderer;
this._initShaderProgram()
for (var i = 0; i < this._shapes.length; i++) {
this._shapes[i].setRenderNode(renderNode);
}
}
add(shape) {
if (shape._handlerIndex == -1) {
shape._handler = this;
shape._handlerIndex = this._shapes.length;
this._shapes.push(shape);
this._entityCollection && this._entityCollection.renderNode && shape.setRenderNode(this._entityCollection.renderNode);
}
}
remove(shape) {
//TODO
}
draw() {
var i = this._shapes.length;
while (i--) {
this._shapes[i].draw();
}
}
drawPicking() {
//TODO
}
clear() {
//TODO
}
};
export { ShapeHandler };

View File

@ -0,0 +1,87 @@
/**
* @module og/input/KeyboardHandler
*/
'use strict';
const KeyboardHandler = function () {
var _currentlyPressedKeys = {};
var _pressedKeysCallbacks = {};
var _charkeysCallbacks = {};
var _that = this;
var _anykeyCallback = null;
var _event = null;
if (KeyboardHandler.prototype._instance) {
return KeyboardHandler.prototype._instance;
} else {
KeyboardHandler.prototype._instance = this;
document.onkeydown = function (event) { _event = event; _that.handleKeyDown.call(_that) };
document.onkeyup = function (event) { _event = event; _that.handleKeyUp.call(_that) };
}
var _sortByPriority = function (a, b) {
return a.priority < b.priority;
};
this.addEvent = function (event, sender, callback, keyCode, priority) {
if (priority === undefined) {
priority = 1600;
}
switch (event) {
case "keypress": {
if (keyCode == null) {
_anykeyCallback = { "callback": callback, "sender": sender || _that };
} else {
if (!_pressedKeysCallbacks[keyCode]) {
_pressedKeysCallbacks[keyCode] = [];
}
_pressedKeysCallbacks[keyCode].push({ callback: callback, sender: sender, priority: priority });
_pressedKeysCallbacks[keyCode].sort(_sortByPriority);
}
} break;
case "charkeypress": {
if (!_charkeysCallbacks[keyCode]) {
_charkeysCallbacks[keyCode] = [];
}
_charkeysCallbacks[keyCode].push({ callback: callback, sender: sender, priority: priority });
_charkeysCallbacks[keyCode].sort(_sortByPriority);
} break;
}
};
this.isKeyPressed = function (keyCode) {
return _currentlyPressedKeys[keyCode];
};
this.handleKeyDown = function () {
_anykeyCallback && _anykeyCallback.callback.call(_anykeyCallback.sender, _event);
_currentlyPressedKeys[_event.keyCode] = true;
for (var ch in _charkeysCallbacks) {
if (String.fromCharCode(_event.keyCode) == String.fromCharCode(ch)) {
var ccl = _charkeysCallbacks[ch];
for (var i = 0; i < ccl.length; i++) {
ccl[i].callback.call(ccl[i].sender, _event);
}
}
}
};
this.handleKeyUp = function () {
_currentlyPressedKeys[_event.keyCode] = false;
};
this.handleEvents = function () {
for (var pk in _pressedKeysCallbacks) {
if (_currentlyPressedKeys[pk]) {
var cpk = _pressedKeysCallbacks[pk];
for (var i = 0; i < cpk.length; i++) {
cpk[i].callback.call(cpk[i].sender, _event);
}
}
}
};
};
export { KeyboardHandler };

View File

@ -0,0 +1,58 @@
/**
* @module og/input/MouseHandler
*/
'use strict';
const MouseHandler = function (htmlObject) {
this._htmlObject = htmlObject;
};
MouseHandler.prototype.setEvent = function (event, sender, callback) {
switch (event) {
case "mousewheel": {
this._htmlObject.addEventListener('mousewheel', function (evt) {
var delta = evt.deltaY || evt.detail || evt.wheelDelta;
if (evt.wheelDelta == undefined) {
evt.wheelDelta = delta * (-120);
}
callback.call(sender, evt);
evt.preventDefault();
}, false);
this._htmlObject.addEventListener('wheel', function (evt) {
var delta = evt.deltaY || evt.detail || evt.wheelDelta;
if (evt.wheelDelta == undefined) {
evt.wheelDelta = delta * (-120);
}
callback.call(sender, evt);
evt.preventDefault();
}, false);
} break;
case "mousedown": {
this._htmlObject.addEventListener('mousedown', function (event) {
callback.call(sender, event);
});
this._htmlObject.addEventListener('contextmenu', function (event) {
event.preventDefault();
return false;
});
} break;
case "mouseup": {
this._htmlObject.addEventListener('mouseup', function (event) {
callback.call(sender, event);
});
} break;
case "mousemove": {
this._htmlObject.addEventListener('mousemove', function (event) {
var rect = this.getBoundingClientRect();
callback.call(sender, {
'clientX': event.clientX - rect.left,
'clientY': event.clientY - rect.top
});
});
} break;
}
};
export { MouseHandler };

View File

@ -0,0 +1,57 @@
/**
* @module og/input/TouchHandler
*/
'use strict';
const TouchHandler = function (htmlObject) {
this._htmlObject = htmlObject;
};
TouchHandler.prototype.setEvent = function (event, sender, callback) {
switch (event) {
case "touchcancel": {
this._htmlObject.addEventListener('touchcancel', function (event) {
event.preventDefault();
var rect = this.getBoundingClientRect();
event.offsetLeft = rect.left;
event.offsetTop = rect.top;
callback.call(sender, event);
event.preventDefault();
});
}
break;
case "touchstart": {
this._htmlObject.addEventListener('touchstart', function (event) {
event.preventDefault();
var rect = this.getBoundingClientRect();
event.offsetLeft = rect.left;
event.offsetTop = rect.top;
callback.call(sender, event);
event.preventDefault();
});
} break;
case "touchend": {
this._htmlObject.addEventListener('touchend', function (event) {
event.preventDefault();
var rect = this.getBoundingClientRect();
event.offsetLeft = rect.left;
event.offsetTop = rect.top;
callback.call(sender, event);
event.preventDefault();
});
} break;
case "touchmove": {
this._htmlObject.addEventListener('touchmove', function (event) {
event.preventDefault();
var rect = this.getBoundingClientRect();
event.offsetLeft = rect.left;
event.offsetTop = rect.top;
callback.call(sender, event);
event.preventDefault();
});
} break;
}
};
export { TouchHandler };

View File

@ -0,0 +1,275 @@
/**
* @module og/layer/BaseGeoImage
*/
'use sctrict';
import * as mercator from '../mercator.js';
import { Extent } from '../Extent.js';
import { Layer } from './Layer.js';
import { LonLat } from '../LonLat.js';
/**
* BaseGeoImage layer represents square imagery layer that could be an static image, or animated video or webgl buffer object displayed on the globe.
* @class
* @extends {og.layer.Layer}
*/
class BaseGeoImage extends Layer {
constructor(name, options) {
super(name, options);
this._projType = 0;
this._frameWidth = 256;
this._frameHeight = 256;
this._sourceReady = false;
this._sourceTexture = null;
this._materialTexture = null;
this._gridBuffer = null;
this._extentWgs84Params = null;
this._refreshFrame = true;
this._frameCreated = false;
this._sourceCreated = false;
this._animate = false;
this._ready = false;
this._creationProceeding = false;
this._isRendering = false;
this._extentWgs84 = new Extent();
this._cornersWgs84 = null;
this.rendering = null;
options.corners && this.setCorners(options.corners);
}
/**
* Gets corners coordinates.
* @public
* @return {Array.<og.LonLat,og.LonLat,og.LonLat,og.LonLat>}
*/
getCornersLonLat() {
var c = this._cornersWgs84;
return [new LonLat(c[0].lon, c[0].lat), new LonLat(c[1].lon, c[1].lat),
new LonLat(c[2].lon, c[2].lat), new LonLat(c[3].lon, c[3].lat)];
}
/**
* Gets corners coordinates.
* @public
* @return {Array.<Array<number,number,number>>}
*/
getCorners() {
var c = this._cornersWgs84;
return [[c[0].lon, c[0].lat], [c[1].lon, c[1].lat], [c[2].lon, c[2].lat], [c[3].lon, c[3].lat]];
}
/**
* Sets geoImage geographical corners coordinates.
* @public
* @param {Array.<Array.<number,number,number>>} corners - GeoImage corners coordinates. Where first coordinate
* coincedents to the left top image corner, secont to the right top image corner, third to the right bottom
* and fourth - left bottom image corner.
*/
setCorners(corners) {
this.setCornersLonLat(LonLat.join(corners));
}
/**
* Sets geoImage geographical corners coordinates.
* @public
* @param {Array.<og.LonLat, og.LonLat, og.LonLat, og.LonLat>} corners - GeoImage corners coordinates. Where first coordinate
* coincedents to the left top image corner, secont to the right top image corner, third to the right bottom
* and fourth - left bottom image corner.
*/
setCornersLonLat(corners) {
this._refreshFrame = true;
this._cornersWgs84 = [corners[0].clone(), corners[1].clone(), corners[2].clone(), corners[3].clone()] || [0, 0, 0, 0];
for (var i = 0; i < this._cornersWgs84.length; i++) {
if (this._cornersWgs84[i].lat >= 89.9)
this._cornersWgs84[i].lat = 89.9;
if (this._cornersWgs84[i].lat <= -89.9)
this._cornersWgs84[i].lat = -89.9;
}
this._extent.setByCoordinates(this._cornersWgs84);
var me = this._extent;
if (me.southWest.lat > mercator.MAX_LAT ||
me.northEast.lat < mercator.MIN_LAT) {
this._projType = 0;
this.rendering = this._renderingProjType0;
} else {
this._projType = 1;
this.rendering = this._renderingProjType1;
}
if (this._ready && !this._creationProceeding) {
this._planet._geoImageCreator.add(this);
}
}
/**
* Creates geoImage frame.
* @protected
*/
_createFrame() {
this._extentWgs84 = this._extent.clone();
this._cornersMerc = [this._cornersWgs84[0].forwardMercatorEPS01(), this._cornersWgs84[1].forwardMercatorEPS01(),
this._cornersWgs84[2].forwardMercatorEPS01(), this._cornersWgs84[3].forwardMercatorEPS01()];
this._extentMerc = new Extent(this._extentWgs84.southWest.forwardMercatorEPS01(), this._extentWgs84.northEast.forwardMercatorEPS01());
if (this._projType == 0) {
this._extentWgs84Params = [this._extentWgs84.southWest.lon, this._extentWgs84.southWest.lat, 2.0 / this._extentWgs84.getWidth(), 2.0 / this._extentWgs84.getHeight()];
} else {
this._extentMercParams = [this._extentMerc.southWest.lon, this._extentMerc.southWest.lat, 2.0 / this._extentMerc.getWidth(), 2.0 / this._extentMerc.getHeight()];
}
//creates material frame textures
if (this._planet) {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl;
gl.deleteTexture(this._materialTexture);
this._materialTexture = h.createEmptyTexture_l(this._frameWidth, this._frameHeight);
this._gridBuffer = this._planet._geoImageCreator.createGridBuffer(this._cornersWgs84, this._projType);
this._refreshFrame = false;
}
}
/**
* @virtual
* @param {og.planetSegment.Material} material - GeoImage material.
*/
abortMaterialLoading(material) {
this._creationProceeding = false;
material.isLoading = false;
material.isReady = false;
}
/**
* Clear layer material.
* @virtual
*/
clear() {
var p = this._planet;
if (p) {
var gl = p.renderer.handler.gl;
this._creationProceeding && p._geoImageCreator.remove(this);
p._clearLayerMaterial(this);
gl.deleteBuffer(this._gridBuffer);
gl.deleteTexture(this._sourceTexture);
!this._materialTexture.default && gl.deleteTexture(this._materialTexture);
}
this._sourceTexture = null;
this._materialTexture = null;
this._gridBuffer = null;
this._refreshFrame = true;
this._sourceCreated = false;
this._ready = false;
this._creationProceeding = false;
}
/**
* Sets layer visibility.
* @public
* @virtual
* @param {boolean} visibility - GeoImage visibility.
*/
setVisibility(visibility) {
if (visibility != this._visibility) {
this._visibility = visibility;
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
}
this._planet.updateVisibleLayers();
this.events.dispatch(this.events.visibilitychange, this);
//remove from creator
if (visibility)
this._sourceReady && this._planet._geoImageCreator.add(this);
else
this._sourceReady && this._planet._geoImageCreator.remove(this);
}
}
/**
* @virtual
* @protected
* @param {og.planetSegment.Material} material - GeoImage material.
*/
clearMaterial(material) {
//just clear material pointer not geoimage
material.image = null;
material.texture = null;
material.isLoading = false;
material.isReady = false;
}
/**
* @virtual
* @protected
* @param {og.planetSegment.Material} material - GeoImage material.
*/
applyMaterial(material) {
var segment = material.segment;
if (this._ready) {
material.applyTexture(this._materialTexture);
} else {
material.texture = this._planet.transparentTexture;
!this._creationProceeding && this.loadMaterial(material);
}
if (this._projType === 0) {
var v0s = this._extentWgs84;
var v0t = segment._extent;
} else {
var v0s = this._extentMerc;
var v0t = segment.getExtentMerc();
}
var sSize_x = v0s.northEast.lon - v0s.southWest.lon;
var sSize_y = v0s.northEast.lat - v0s.southWest.lat;
var dV0s_x = (v0t.southWest.lon - v0s.southWest.lon) / sSize_x;
var dV0s_y = (v0s.northEast.lat - v0t.northEast.lat) / sSize_y;
var dSize_x = (v0t.northEast.lon - v0t.southWest.lon) / sSize_x;
var dSize_y = (v0t.northEast.lat - v0t.southWest.lat) / sSize_y;
return [dV0s_x, dV0s_y, dSize_x, dSize_y];
}
/**
* Gets frame width size in pixels.
* @public
*/
getFrameWidth() {
return this._frameWidth;
}
/**
* Gets frame height size in pixels.
* @public
*/
getFrameHeight() {
return this._frameHeight;
}
};
export { BaseGeoImage };

285
src/og/layer/CanvasTiles.js Normal file
View File

@ -0,0 +1,285 @@
/**
* @module og/layer/CanvasTiles
*/
'use strict';
import * as quadTree from '../quadTree/quadTree.js';
import { ImageCanvas } from '../ImageCanvas.js';
import { Layer } from './Layer.js';
/**
* Maximum tiles per frame.
* @const
* @type {number}
*/
const MAX_REQUESTS = 7;
const EVENT_NAMES = [
/**
* Triggered when current tile image has loaded before rendereing.
* @event og.layer.CanvasTiles#load
*/
"load",
/**
* Triggered when all tiles have loaded or loading has stopped.
* @event og.layer.CanvasTiles#loadend
*/
"loadend"
];
/**
* Layer used to rendering each tile as a separate canvas object.
* @class
* @extends {og.layer.Layer}
* //TODO: make asynchronous handler.
* @param {String} [name="noname"] - Layer name.
* @param {Object} options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {Array.<number,number,number>} [options.transparentColor=[-1,-1,-1]] - RGB color that defines transparent color.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - Base layer flag.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {og.layer.CanvasTiles~drawTileCallback} [options.drawTile] - Draw tile callback.
* @fires og.layer.CanvasTiles#load
* @fires og.layer.CanvasTiles#loadend
*/
class CanvasTiles extends Layer {
constructor(name, options) {
options = options || {};
super(name, options);
this.events.registerNames(EVENT_NAMES);
/**
* Current creating tiles couter.
* @protected
* @type {number}
*/
this._counter = 0;
/**
* Tile pending queue that waiting for create.
* @protected
* @type {Array.<og.planetSegment.Material>}
*/
this._pendingsQueue = []; //new og.QueueArray();
/**
* Draw tile callback.
* @type {og.layer.CanvasTiles~drawTileCallback}
* @public
*/
this.drawTile = options.drawTile || null;
}
/**
* Abort loading tiles.
* @public
*/
abortLoading() {
var q = this._pendingsQueue;
for (var i = q._shiftIndex + 1; i < q._popIndex + 1; i++) {
if (q._array[i]) {
this.abortMaterialLoading(q._array[i]);
}
}
this._pendingsQueue = [];
//this._pendingsQueue.clear();
};
/**
* Sets layer visibility.
* @public
* @param {boolean} visibility - Layer visibility.
*/
setVisibility(visibility) {
if (visibility != this._visibility) {
this._visibility = visibility;
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
} else if (!visibility) {
this.abortLoading();
}
this._planet.updateVisibleLayers();
this.events.dispatch(this.events.visibilitychange, this);
}
}
/**
* Start to load tile material.
* @public
* @virtual
* @param {og.planetSegment.Material} mateial
*/
loadMaterial(material) {
var seg = material.segment;
if (this._isBaseLayer) {
material.texture = seg._isNorth ? seg.planet.solidTextureOne : seg.planet.solidTextureTwo;
} else {
material.texture = seg.planet.transparentTexture;
}
if (this._planet.layerLock.isFree()) {
material.isReady = false;
material.isLoading = true;
if (CanvasTiles.__requestsCounter >= MAX_REQUESTS && this._counter) {
this._pendingsQueue.push(material);
} else {
this._exec(material);
}
}
}
/**
* Loads material image and apply it to the planet segment.
* @protected
* @param {og.planetSegment.Material} material - Loads material image.
*/
_exec(material) {
CanvasTiles.__requestsCounter++;
this._counter++;
var that = this;
if (this.drawTile) {
/**
* Tile custom draw function.
* @callback og.layer.CanvasTiles~drawTileCallback
* @param {og.planetSegment.Material} material
* @param {applyCanvasCallback} applyCanvasCallback
*/
setTimeout(function () {
var e = that.events.load;
if (e.handlers.length) {
that.events.dispatch(e, material);
}
that.drawTile(material,
/**
* Apply canvas.
* @callback applyCanvasCallback
* @param {Object} canvas
*/
function (canvas) {
that._counter--;
og.layer.CanvasTiles.__requestsCounter--;
if (material.isLoading) {
material.applyImage(canvas);
}
that._dequeueRequest();
});
}, 50);
} else {
material.textureNotExists();
}
}
/**
* Abort exact material loading.
* @public
* @param {og.planetSegment.Material} material - Segment material.
*/
abortMaterialLoading(material) {
if (material.isLoading && material.image) {
material.image.src = "";
this._counter--;
og.layer.CanvasTiles.__requestsCounter--;
this._dequeueRequest();
}
material.isLoading = false;
material.isReady = false;
}
_dequeueRequest() {
if (this._pendingsQueue.length) {
if (CanvasTiles.__requestsCounter < MAX_REQUESTS) {
var pmat;
if (pmat = this._whilePendings())
this._exec.call(this, pmat);
}
} else if (this._counter === 0) {
this.events.dispatch(this.events.loadend);
}
}
_whilePendings() {
while (this._pendingsQueue.length) {
var pmat = this._pendingsQueue.pop();
if (pmat.segment.node) {
if (pmat.segment.ready && pmat.segment.node.getState() === quadTree.RENDERING) {
return pmat;
}
pmat.isLoading = false;
}
}
return null;
}
applyMaterial(material) {
if (material.isReady) {
return [0, 0, 1, 1];
} else {
!material.isLoading && this.loadMaterial(material);
var segment = material.segment;
var pn = segment.node,
notEmpty = false;
var mId = this._id;
var psegm = material;
while (pn.parentNode) {
if (psegm && psegm.isReady) {
notEmpty = true;
break;
}
pn = pn.parentNode;
psegm = pn.segment.materials[mId];
}
if (notEmpty) {
material.appliedNodeId = pn.nodeId;
material.texture = psegm.texture;
var dZ2 = 1.0 / (2 << (segment.tileZoom - pn.segment.tileZoom - 1));
return [
segment.tileX * dZ2 - pn.segment.tileX,
segment.tileY * dZ2 - pn.segment.tileY,
dZ2,
dZ2
];
} else {
material.texture = segment.planet.transparentTexture;
return [0, 0, 1, 1];
}
}
}
clearMaterial(material) {
if (material.isReady) {
material.isReady = false;
!material.texture.default &&
material.segment.handler.gl.deleteTexture(material.texture);
material.texture = null;
}
this.abortMaterialLoading(material);
material.isLoading = false;
material.textureExists = false;
if (material.image) {
material.image.src = '';
material.image = null;
}
}
};
export { CanvasTiles };

214
src/og/layer/GeoImage.js Normal file
View File

@ -0,0 +1,214 @@
/**
* @module og/layer/GeoImage
*/
'use sctrict';
import { BaseGeoImage } from './BaseGeoImage.js';
/**
* Used to load and display a single image over specific corner coordinates on the globe, implements og.layer.BaseGeoImage interface.
* @class
* @extends {og.layer.BaseGeoImage}
*/
class GeoImage extends BaseGeoImage {
constructor(name, options) {
super(name, options);
/**
* Image object.
* @private
* @type {Image}
*/
this._image = options.image || null;
/**
* Image source url path.
* @private
* @type {String}
*/
this._src = options.src || null;
}
/**
* Sets image source url path.
* @public
* @param {String} srs - Image url path.
*/
setSrc(src) {
this._planet && this._planet._geoImageCreator.remove(this);
this._src = src;
this._sourceReady = false;
}
/**
* Sets image object.
* @public
* @param {Image} image - Image object.
*/
setImage(image) {
this._planet && this._planet._geoImageCreator.remove(this);
this._image = image;
this._src = image.src;
this._sourceReady = false;
}
/**
* Creates source gl texture.
* @virtual
* @protected
*/
_createSourceTexture() {
if (!this._sourceCreated) {
this._sourceTexture = this._planet.renderer.handler.createTexture_n(this._image);
this._sourceCreated = true;
}
}
/**
* @private
* @param {Image} img
*/
_onLoad(img) {
this._frameWidth = img.width;
this._frameHeight = img.height;
this._sourceReady = true;
this._planet._geoImageCreator.add(this);
}
/**
* Loads planet segment material. In this case - GeoImage source image.
* @virtual
* @public
* @param {og.planetSegment.Material} material - GeoImage planet material.
*/
loadMaterial(material) {
material.isLoading = true;
this._creationProceeding = true;
if (!this._sourceReady && this._src) {
if (this._image) {
if (this._image.complete) {
this._onLoad(this._image);
} else if (this._image.src) {
var that = this;
this._image.addEventListener('load', function (e) {
that._onLoad(this);
});
}
} else {
var that = this;
this._image = new Image();
this._image.addEventListener('load', function (e) {
that._onLoad(this);
});
this._image.src = this._src;
}
} else {
this._planet._geoImageCreator.add(this);
}
}
/**
* @virtual
* @param {og.planetSegment.Material} material - GeoImage material.
*/
abortMaterialLoading(material) {
this._image && (this._image.src = '');
this._creationProceeding = false;
material.isLoading = false;
material.isReady = false;
}
_renderingProjType1() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
this._createSourceTexture();
var f = creator._framebuffer;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentMercParams);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
_renderingProjType0() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
this._createSourceTexture();
var f = creator._framebuffer;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentWgs84Params);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
};
export { GeoImage };

View File

@ -0,0 +1,143 @@
/**
* @module og/layer/GeoTexture2d
*/
'use sctrict';
import * as math from '../math.js';
import { BaseGeoImage } from './BaseGeoImage.js';
class GeoTexture2d extends BaseGeoImage {
constructor(name, options) {
super(name, options);
this._sourceTexture = options.texture || null;
if (options.texture) {
this._sourceReady = true;
this._sourceCreated = true;
}
this._frameWidth = options.frameWidth ? math.nextHighestPowerOfTwo(options.frameWidth) : 256;
this._frameHeight = options.frameHeight ? math.nextHighestPowerOfTwo(options.frameHeight) : 256;
this._animate = true;
}
loadMaterial(material) {
this._planet._geoImageCreator.add(this);
};
bindTexture(texture) {
this._sourceReady = true;
this._sourceCreated = true;
this._sourceTexture = texture;
}
setSize(width, height) {
this._frameWidth = width;
this._frameHeight = height;
this._frameCreated = false;
}
abortMaterialLoading(material) {
this._creationProceeding = false;
material.isLoading = false;
material.isReady = false;
}
_renderingProjType1() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
var f = creator._framebuffer;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentMercParams);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
_renderingProjType0() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
var width = this._frameWidth,
height = this._frameHeight;
this._refreshFrame && this._createFrame();
var f = creator._framebuffer;
f.setSize(width, height);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentWgs84Params);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
};
export { GeoTexture2d };

287
src/og/layer/GeoVideo.js Normal file
View File

@ -0,0 +1,287 @@
/**
* @module og/layer/GeoVideo
*/
'use sctrict';
import { BaseGeoImage } from './BaseGeoImage.js';
/**
* Used to load and display a video stream by specific corners coordinates on the globe, implements og.layer.BaseGeoImage interface.
* @class
* @extends {og.layer.BaseGeoImage}
*/
class GeoVideo extends BaseGeoImage {
constructor(name, options) {
super(name, options);
/**
* @protected
* @const
* @type {Boolean}
*/
this._animate = true;
/**
* HTML5 video element object.
* @private
* @type {Object}
*/
this._video = options.videoElement || null;
/**
* VIdeo source url path.
* @private
* @type {String}
*/
this._src = options.src || null;
}
/**
* Sets video source url path.
* @public
* @param {String} srs - Video url path.
*/
setSrc(src) {
this._planet && this._planet._geoImageCreator.remove(this);
this._src = src;
this._sourceReady = false;
}
/**
* Sets HTML5 video object.
* @public
* @param {Object} video - HTML5 video element object.
*/
setVideoElement(video) {
this._planet && this._planet._geoImageCreator.remove(this);
this._video = video;
this._src = video.src;
this._sourceReady = false;
}
/**
* Sets layer visibility.
* @public
* @param {boolean} visibility - Layer visibility.
*/
setVisibility(visibility) {
if (visibility != this._visibility) {
this._visibility = visibility;
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
}
this._planet.updateVisibleLayers();
this.events.dispatch(this.events.visibilitychange, this);
//remove from creator
if (visibility) {
this._sourceReady && this._planet._geoImageCreator.add(this);
this._video && this._video.play();
} else {
this._sourceReady && this._planet._geoImageCreator.remove(this);
this._video && this._video.pause();
}
}
}
/**
* Creates or refresh source video GL texture.
* @virtual
* @protected
*/
_createSourceTexture() {
if (!this._sourceCreated) {
this._sourceTexture = this._planet.renderer.handler.createTexture_n(this._video);
this._sourceCreated = true;
} else {
var gl = this._planet.renderer.handler.gl;
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._video);
}
}
/**
* @private
*/
_onCanPlay(video) {
this._frameWidth = video.videoWidth;
this._frameHeight = video.videoHeight;
video.width = video.videoWidth;
video.height = video.videoHeight;
video.play();
this._sourceReady = true;
this._planet._geoImageCreator.add(this);
}
/**
* @private
*/
_onError(video) {
var err = "unknown error";
switch (video.error.code) {
case 1: err = "video loading aborted"; break;
case 2: err = "network loading error"; break;
case 3: err = "video decoding failed / corrupted data or unsupported codec"; break;
case 4: err = "video not supported"; break;
};
console.log("Error: " + err + " (errorcode=" + video.error.code + ")");
};
/**
* Loads planet segment material. In this case - GeoImage source video.
* @virtual
* @public
* @param {og.planetSegment.Material} material - GeoImage planet material.
*/
loadMaterial(material) {
material.isLoading = true;
this._creationProceeding = true;
if (!this._sourceReady && this._src) {
if (this._video) {
if (this._video.readyState === this._video.HAVE_ENOUGH_DATA) {
this._onCanPlay(this._video);
} else if (this._video.src) {
var that = this;
this._video.addEventListener('canplay', function (e) {
that._onCanPlay(this);
});
}
} else {
this._video = document.createElement('video');
var that = this;
this._video.addEventListener('canplay', function () {
that._onCanPlay(this);
});
this._video.addEventListener('error', function () {
that._onError(this);
});
}
this._video.autoplay = true;
this._video.loop = true;
this._video.src = this._src;
this._video.setAttribute("playsinline", "");
this._video.setAttribute("webkit-playsinline", "");
} else {
this._planet._geoImageCreator.add(this);
}
}
/**
* @virtual
* @param {og.planetSegment.Material} material - GeoImage material.
*/
abortMaterialLoading(material) {
this._video && (this._video.src = '');
this._creationProceeding = false;
material.isLoading = false;
material.isReady = false;
}
_renderingProjType1() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
if (this._sourceCreated) {
var gl = this._planet.renderer.handler.gl;
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._video);
} else {
this._sourceTexture = this._planet.renderer.handler.createTexture_n(this._video);
this._sourceCreated = true;
}
var f = creator._framebuffer;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentMercParams);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
_renderingProjType0() {
var p = this._planet,
h = p.renderer.handler,
gl = h.gl,
creator = p._geoImageCreator;
this._refreshFrame && this._createFrame();
if (!this._sourceCreated) {
this._sourceTexture = this._planet.renderer.handler.createTexture_n(this._video);
this._sourceCreated = true;
} else {
var gl = this._planet.renderer.handler.gl;
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._video);
}
var f = creator._framebuffer;
f.setSize(this._frameWidth, this._frameHeight);
f.activate();
h.shaderPrograms.geoImageTransform.activate();
var sh = h.shaderPrograms.geoImageTransform._program;
var sha = sh.attributes,
shu = sh.uniforms;
var tr = this.transparentColor[0],
tg = this.transparentColor[1],
tb = this.transparentColor[2];
gl.disable(gl.CULL_FACE);
f.bindOutputTexture(this._materialTexture);
gl.clearColor(tr, tg, tb, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, creator._texCoordsBuffer);
gl.vertexAttribPointer(sha.texCoords._pName, creator._texCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gridBuffer);
gl.vertexAttribPointer(sha.corners._pName, this._gridBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform4fv(shu.extentParams._pName, this._extentWgs84Params);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.sourceTexture._pName, 0);
sh.drawIndexBuffer(gl.TRIANGLE_STRIP, creator._indexBuffer);
f.deactivate();
gl.enable(gl.CULL_FACE);
this._ready = true;
this._creationProceeding = false;
}
};
export { GeoVideo };

649
src/og/layer/Layer.js Normal file
View File

@ -0,0 +1,649 @@
/**
* @module og/layer/Layer
*/
'use sctrict';
import * as utils from '../utils/shared.js';
import * as mercator from '../mercator.js';
import { Events } from '../Events.js';
import { Extent } from '../Extent.js';
import { LonLat } from '../LonLat.js';
import { Material } from './Material.js';
import { Vec3 } from '../math/Vec3.js';
/**
* @classdesc
* Base class; normally only used for creating subclasses and not instantiated in apps.
* A visual representation of raster or vector map data well known as a layer.
* @class
* @param {String} [name="noname"] - Layer name.
* @param {Object} [options] - Layer options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {Array.<number,number,number>} [options.transparentColor=[-1,-1,-1]] - RGB color that defines transparent color.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - This is a base layer.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {og.Extent} [options.extent=[[-180.0, -90.0], [180.0, 90.0]]] - Visible extent.
* @param {og.math.Vector3} [options.ambient=[0.1, 0.1, 0.21]] - Ambient RGB color.
* @param {og.math.Vector3} [options.diffuse=[1.0, 1.0, 1.0]] - Diffuse RGB color.
* @param {og.math.Vector3} [options.specular=[0.00025, 0.00015, 0.0001]] - Specular RGB color.
* @param {Number} [options.shininess=100] - Shininess.
*
* @fires og.layer.Layer#visibilitychange
* @fires og.layer.Layer#add
* @fires og.layer.Layer#remove
* @fires og.layer.Vector#mousemove
* @fires og.layer.Vector#mouseenter
* @fires og.layer.Vector#mouseleave
* @fires og.layer.Vector#lclick
* @fires og.layer.Vector#rclick
* @fires og.layer.Vector#mclick
* @fires og.layer.Vector#ldblclick
* @fires og.layer.Vector#rdblclick
* @fires og.layer.Vector#mdblclick
* @fires og.layer.Vector#lup
* @fires og.layer.Vector#rup
* @fires og.layer.Vector#mup
* @fires og.layer.Vector#ldown
* @fires og.layer.Vector#rdown
* @fires og.layer.Vector#mdown
* @fires og.layer.Vector#lhold
* @fires og.layer.Vector#rhold
* @fires og.layer.Vector#mhold
* @fires og.layer.Vector#mousewheel
* @fires og.layer.Vector#touchmove
* @fires og.layer.Vector#touchstart
* @fires og.layer.Vector#touchend
* @fires og.layer.Vector#doubletouch
*/
class Layer {
constructor(name, options) {
options = options || {};
/**
* Layer user name.
* @public
* @type {string}
*/
this.name = name || "noname";
this.displayInLayerSwitcher = options.displayInLayerSwitcher !== undefined ? options.displayInLayerSwitcher : true;
/**
* Layer global opacity.
* @public
* @type {number}
*/
this.opacity = options.opacity || 1.0;
/**
* Transparent RGB color mask.
* @public
* @type {Array.<number,number,number>}
*/
this.transparentColor = options.transparentColor || [-1, -1, -1];
/**
* Minimal zoom level when layer is visibile.
* @public
* @type {number}
*/
this.minZoom = options.minZoom || 0;
/**
* Maximal zoom level when layer is visibile.
* @public
* @type {number}
*/
this.maxZoom = options.maxZoom || 50;
/**
* Layer light material parameters.
* @public
* @type {Object}
*/
this.ambient = utils.createColorRGB(options.ambient, new Vec3(0.1, 0.1, 0.21));
this.diffuse = utils.createColorRGB(options.diffuse, new Vec3(1.0, 1.0, 1.0));
this.specular = utils.createColorRGB(options.specular, new Vec3(0.00025, 0.00015, 0.0001));
this.shininess = options.shininess || 100.0;
/**
* Planet node.
* @protected
* @type {og.scene.Planet}
*/
this._planet = null;
/**
* Unic identifier.
* @protected
* @type {number}
*/
this._id = Layer.__layersCounter++;
/**
* Layer attribution.
* @protected
* @type {string}
*/
this._attribution = options.attribution || "";
/**
* Layer z-index.
* @protected
* @type {number}
*/
this._zIndex = options.zIndex || 0;
/**
* Base layer type flag.
* @protected
* @type {boolean}
*/
this._isBaseLayer = options.isBaseLayer || false;
/**
* Layer visibility.
* @protected
* @type {boolean}
*/
this._visibility = options.visibility !== undefined ? options.visibility : true;
/**
* Height over the ground.
* @protected
* @type {number}
*/
this._height = options.height || 0;
/**
* Visible degrees extent.
* @protected
* @type {og.Extent}
*/
this._extent = null;
/**
* Visible mercator extent.
* @protected
* @type {og.Extent}
*/
this._extentMerc = null;
//Setting the extent up
this.setExtent(utils.createExtent(options.extent, new Extent(new LonLat(-180, -90), new LonLat(180, 90))));
/**
* Layer picking color. Assign when added to the planet.
* @protected
* @type {og.math.Vector3}
*/
this._pickingColor = new Vec3();
/**
* Events handler.
* @public
* @type {og.Events}
*/
this.events = new Events();
this.events.registerNames(EVENT_NAMES);
}
static getTileIndex(...arr) {
return arr.join("_");
}
static get __layersCounter() {
if (!this.__lcounter && this.__lcounter !== 0) {
this.__lcounter = 0;
}
return this.__lcounter;
}
static set __layersCounter(n) {
this.__lcounter = n;
}
/**
* Returns true if a layer has imagery tiles.
* @public
* @virtual
* @returns {boolean} - Imagery tiles flag.
*/
hasImageryTiles() {
return true;
}
/**
* Gets layer identifier.
* @public
* @returns {string} - Layer object id.
*/
getID() {
return this._id;
}
/**
* Compares layers instances.
* @public
* @param {og.layer.Layer} layer - Layer instance to compare.
* @returns {boolean} - Returns true if the layers is the same instance of the input.
*/
isEqual(layer) {
return layer._id === this._id;
}
/**
* Assign the planet.
* @protected
* @virtual
* @param {og.scene.Planet} planet - Planet render node.
*/
_assignPlanet(planet) {
planet.layers.push(this);
this._planet = planet;
this.events.on("visibilitychange", planet._onLayerVisibilityChanged, planet);
if (this._isBaseLayer && this._visibility) {
planet.setBaseLayer(this);
}
planet.events.dispatch(planet.events.layeradd, this);
this.events.dispatch(this.events.add, planet);
planet.updateVisibleLayers();
this._bindPicking();
}
/**
* Assign picking color to the layer.
* @protected
* @virtual
*/
_bindPicking() {
this._planet && this._planet.renderer.assignPickingColor(this);
}
/**
* Adds layer to the planet.
* @public
* @param {og.scene.Planet} planet - Adds layer to the planet.
*/
addTo(planet) {
this._assignPlanet(planet);
}
/**
* Removes from planet.
* @public
* @returns {og.layer.Layer} -This layer.
*/
remove() {
var p = this._planet;
if (p) {
var lid = this._id;
for (var i = 0; i < p.layers.length; i++) {
if (p.layers[i]._id === lid) {
p.renderer.clearPickingColor(this);
p.layers.splice(i, 1);
p.updateVisibleLayers();
this.clear();
p.events.dispatch(p.events.layerremove, this);
this.events.dispatch(this.events.remove, p);
this._planet = null;
return this;
}
}
}
return this;
}
/**
* Clears layer material.
* @virtual
*/
clear() {
this._planet && this._planet._clearLayerMaterial(this);
}
/**
* Sets layer attribution text.
* @public
* @param {string} html - HTML code that represents layer attribution, it could be just a text.
*/
setAttribution(html) {
this._attribution = html;
this._planet.updateAttributionsList();
}
/**
* Sets height over the ground.
* @public
* @param {number} height - Layer height.
*/
setHeight(height) {
this._height = height;
this._planet.updateVisibleLayers();
}
/**
* Gets layer height.
* @public
* @returns {number} -
*/
getHeight() {
return this._height;
}
/**
* Sets z-index.
* @public
* @param {number} zIndex - Layer z-index.
*/
setZIndex(zIndex) {
this._zIndex = zIndex;
this._planet.updateVisibleLayers();
}
/**
* Gets z-index.
* @public
* @returns {number} -
*/
getZIndex() {
return this._zIndex;
}
/**
* Set zIndex to the maximal value depend on other layers on the planet.
* @public
*/
bringToFront() {
if (this._planet) {
var vl = this._planet.visibleTileLayers;
var l = vl[vl.length - 1];
if (!l.isEqual(this)) {
this.setZIndex(l.getZIndex() + 1);
}
}
}
/**
* Returns true if the layer is a base.
* @public
* @returns {boolean} - Base layer flag.
*/
isBaseLayer() {
return this._isBaseLayer;
}
/**
* Sets base layer type true.
* @public
* @param {boolean} flag - Base layer flag.
*/
setBaseLayer(flag) {
this._isBaseLayer = flag;
if (this._planet && !flag && this.isEqual(this._planet.baseLayer)) {
this._planet.baseLayer = null;
}
this._planet.updateVisibleLayers();
}
/**
* Sets layer visibility.
* @public
* @virtual
* @param {boolean} visibility - Layer visibility.
*/
setVisibility(visibility) {
if (visibility !== this._visibility) {
this._visibility = visibility;
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
}
this._planet.updateVisibleLayers();
this.events.dispatch(this.events.visibilitychange, this);
}
}
/**
* Gets layer visibility.
* @public
* @returns {boolean} - Layer visibility.
*/
getVisibility() {
return this._visibility;
}
/**
* Sets visible geographical extent.
* @public
* @param {og.Extent} extent - Layer visible geographical extent.
*/
setExtent(extent) {
var sw = extent.southWest.clone(),
ne = extent.northEast.clone();
if (sw.lat < mercator.MIN_LAT) {
sw.lat = mercator.MIN_LAT;
}
if (ne.lat > mercator.MAX_LAT) {
ne.lat = mercator.MAX_LAT;
}
this._extent = extent.clone();
this._extentMerc = new Extent(sw.forwardMercator(), ne.forwardMercator());
this._correctFullExtent();
}
/**
* Gets layer extent.
* @public
* @return {og.Extent} - Layer geodetic extent.
*/
getExtent() {
return this._extent;
}
/**
* Special correction of the whole globe extent.
* @protected
*/
_correctFullExtent() {
//var e = this._extent,
// em = this._extentMerc;
//var ENLARGE_MERCATOR_LON = og.mercator.POLE + 50000;
//var ENLARGE_MERCATOR_LAT = og.mercator.POLE + 50000;
//if (e.northEast.lat === 90.0) {
// em.northEast.lat = ENLARGE_MERCATOR_LAT;
//}
//if (e.northEast.lon === 180.0) {
// em.northEast.lon = ENLARGE_MERCATOR_LON;
//}
//if (e.southWest.lat === -90.0) {
// em.southWest.lat = -ENLARGE_MERCATOR_LAT;
//}
//if (e.southWest.lon === -180.0) {
// em.southWest.lon = -ENLARGE_MERCATOR_LON;
//}
}
createMaterial(segment) {
return new Material(segment, this);
}
};
const EVENT_NAMES = [
/**
* Triggered when layer visibilty chanched.
* @event og.layer.Layer#visibilitychange
*/
"visibilitychange",
/**
* Triggered when layer has added to the planet.
* @event og.layer.Layer#add
*/
"add",
/**
* Triggered when layer has removed from the planet.
* @event og.layer.Layer#remove
*/
"remove",
/**
* Triggered when mouse moves over the layer.
* @event og.layer.Layer#mousemove
*/
"mousemove",
/**
* Triggered when mouse has entered over the layer.
* @event og.layer.Layer#mouseenter
*/
"mouseenter",
/**
* Triggered when mouse leaves the layer.
* @event og.layer.Layer#mouseenter
*/
"mouseleave",
/**
* Mouse left button clicked.
* @event og.layer.Layer#lclick
*/
"lclick",
/**
* Mouse right button clicked.
* @event og.layer.Layer#rclick
*/
"rclick",
/**
* Mouse right button clicked.
* @event og.layer.Layer#mclick
*/
"mclick",
/**
* Mouse left button double click.
* @event og.layer.Layer#ldblclick
*/
"ldblclick",
/**
* Mouse right button double click.
* @event og.layer.Layer#rdblclick
*/
"rdblclick",
/**
* Mouse middle button double click.
* @event og.layer.Layer#mdblclick
*/
"mdblclick",
/**
* Mouse left button up(stop pressing).
* @event og.layer.Layer#lup
*/
"lup",
/**
* Mouse right button up(stop pressing).
* @event og.layer.Layer#rup
*/
"rup",
/**
* Mouse middle button up(stop pressing).
* @event og.layer.Layer#mup
*/
"mup",
/**
* Mouse left button is just pressed down(start pressing).
* @event og.layer.Layer#ldown
*/
"ldown",
/**
* Mouse right button is just pressed down(start pressing).
* @event og.layer.Layer#rdown
*/
"rdown",
/**
* Mouse middle button is just pressed down(start pressing).
* @event og.layer.Layer#mdown
*/
"mdown",
/**
* Mouse left button is pressing.
* @event og.layer.Layer#lhold
*/
"lhold",
/**
* Mouse right button is pressing.
* @event og.layer.Layer#rhold
*/
"rhold",
/**
* Mouse middle button is pressing.
* @event og.layer.Layer#mhold
*/
"mhold",
/**
* Mouse wheel is rotated.
* @event og.layer.Layer#mousewheel
*/
"mousewheel",
/**
* Triggered when touching moves over the layer.
* @event og.layer.Layer#touchmove
*/
"touchmove",
/**
* Triggered when layer begins to touch.
* @event og.layer.Layer#touchstart
*/
"touchstart",
/**
* Triggered when layer has finished touching.
* @event og.layer.Layer#touchend
*/
"touchend",
/**
* Triggered layer has double touched.
* @event og.layer.Layer#doubletouch
*/
"doubletouch",
/**
* Triggered when touching leaves layer borders.
* @event og.layer.Layer#touchleave
*/
"touchleave",
/**
* Triggered when touch enters over the layer.
* @event og.layer.Layer#touchenter
*/
"touchenter"
];
export { Layer };

70
src/og/layer/Material.js Normal file
View File

@ -0,0 +1,70 @@
/**
* @module og/layer/Material
*/
'use strict';
const Material = function (segment, layer) {
this.segment = segment;
this.layer = layer;
this.isReady = false;
this.isLoading = false;
this.texture = null;
this.pickingMask = null;
this.image = null;
this.textureExists = false;
this.appliedNodeId = 0;
//vector data
this._updateTexture = null;
this._updatePickingMask = null;
this.pickingReady = false;
};
Material.prototype.assignLayer = function (layer) {
this.layer = layer;
};
Material.prototype.abortLoading = function () {
this.layer.abortMaterialLoading(this);
};
Material.prototype.applyImage = function (img) {
if (this.segment.ready) {
this._updateTexture = null;
this.image = img;
this.texture = this.segment.handler.createTexture(img);
this.appliedNodeId = this.segment.node.nodeId;
this.isReady = true;
this.pickingReady = true;
this.textureExists = true;
this.isLoading = false;
}
};
Material.prototype.applyTexture = function (texture, pickingMask) {
this.texture = texture;
this._updateTexture = null;
this.pickingMask = pickingMask || null;
this._updatePickingMask = null;
this.isReady = true;
this.pickingReady = true;
this.textureExists = true;
this.isLoading = false;
this.appliedNodeId = this.segment.node.nodeId;
};
Material.prototype.textureNotExists = function () {
this.isLoading = true;
this.textureExists = false;
};
Material.prototype.clear = function () {
this.layer.clearMaterial(this);
};
export { Material };

814
src/og/layer/Vector.js Normal file
View File

@ -0,0 +1,814 @@
/**
* @module og/layer/Vector
*/
'use strict';
import * as math from '../math.js';
import * as mercator from '../mercator.js';
import * as quadTree from '../quadTree/quadTree.js';
import { Entity } from '../entity/Entity.js';
import { EntityCollection } from '../entity/EntityCollection.js';
import { Extent } from '../Extent.js';
import {
EntityCollectionNode,
EntityCollectionNodeWGS84
} from '../quadTree/EntityCollectionNode.js';
import { GeometryHandler } from '../entity/GeometryHandler.js';
import { Layer } from './Layer.js';
import { LonLat } from '../LonLat.js';
import { QueueArray } from '../QueueArray.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Creates entity instance array.
* @static
* @param {og.Entities[] || Object[]} entities - Entity array.
* @returns {og.Entity[]} - Entity object array.
*/
function _entitiesConstructor(entities) {
var res = [];
for (var i = 0; i < entities.length; i++) {
var ei = entities[i];
if (ei instanceof Entity) {
res.push(ei);
} else {
res.push(new Entity(ei));
}
}
return res;
};
/**
* Vector layer represents alternative entities store. Used for geospatial data rendering like
* points, lines, polygons, geometry objects etc.
* @class
* @extends {og.layer.Layer}
* @param {string} [name="noname"] - Layer name.
* @param {Object} [options] - Layer options:
* @param {number} [options.minZoom=0] - Minimal visible zoom. 0 is default
* @param {number} [options.maxZoom=50] - Maximal visible zoom. 50 is default.
* @param {string} [options.attribution] - Layer attribution.
* @param {string} [options.zIndex=0] - Layer Z-order index. 0 is default.
* @param {boolean} [options.visibility=true] - Layer visibility. True is default.
* @param {boolean} [options.isBaseLayer=false] - Layer base layer. False is default.
* @param {Array.<og.Entity>} [options.entities] - Entities array.
* @param {Array.<number,number,number>} [options.scaleByDistance] - Scale by distance parameters.
* First index - near distance to the entity, after entity becomes full scale.
* Second index - far distance to the entity, when entity becomes zero scale.
* Third index - far distance to the entity, when entity becomes invisible.
* @param {number} [options.nodeCapacity=30] - Maximum entities quantity in the tree node. Rendering optimization parameter. 30 is default.
* @param {boolean} [options.async=true] - Asynchronous vector data handling before rendering. True for optimization huge data.
* @param {boolean} [options.clampToGround = false] - Clamp vector data to the ground.
* @param {boolean} [options.relativeToGround = false] - Place vector data relative to the ground relief.
*
* @fires og.layer.Vector#entitymove
* @fires og.layer.Vector#draw
* @fires og.layer.Vector#add
* @fires og.layer.Vector#remove
* @fires og.layer.Vector#entityadd
* @fires og.layer.Vector#entityremove
* @fires og.layer.Vector#visibilitychange
*/
class Vector extends Layer {
constructor(name, options) {
options = options || {};
super(name, options);
this.events.registerNames(EVENT_NAMES);
this.isVector = true;
/**
* First index - near distance to the entity, after that entity becomes full scale.
* Second index - far distance to the entity, when entity becomes zero scale.
* Third index - far distance to the entity, when entity becomes invisible.
* @public
* @type {Array.<number,number,number>}
*/
this.scaleByDistance = options.scaleByDistance || [math.MAX32, math.MAX32, math.MAX32];
/**
* Asynchronous data handling before rendering.
* @public
* @type {boolean}
*/
this.async = options.async !== undefined ? options.async : true;
/**
* Vector data clamp to ground flag.
* @public
* @type {boolean}
*/
this.clampToGround = options.clampToGround || false;
/**
* Sets vector data relative to the ground relief.
* @public
* @type {boolean}
*/
this.relativeToGround = options.relativeToGround || false;
/**
* Maximum entities quantity in the tree node.
* @private
*/
this._nodeCapacity = options.nodeCapacity || 30;
this._pickingEnabled = options.pickingEnabled !== undefined ? options.pickingEnabled : true;
/**
* Manimal tree node depth index.
* @private
*/
this._minDepth = options.minDepth || 1;
/**
* Stored entities.
* @private
*/
this._entities = _entitiesConstructor(options.entities || []);
this._polylineEntityCollection = new EntityCollection({
'pickingEnabled': this._pickingEnabled
});
this._bindEventsDefault(this._polylineEntityCollection);
this._geometryHandler = new GeometryHandler(this);
this._entityCollectionsTree = null;
this._entityCollectionsTreeNorth = null;
this._entityCollectionsTreeSouth = null;
this._renderingNodes = {};
this._renderingNodesNorth = {};
this._renderingNodesSouth = {};
this._counter = 0;
this._deferredEntitiesPendingQueue = new QueueArray();
this._pendingsQueue = [];
/** Creates collections tree*/
this.setEntities(this._entities);
}
_bindPicking() {
this._pickingColor.clear();
}
/**
* Adds layer to the planet.
* @public
*/
addTo(planet) {
this._assignPlanet(planet);
this._geometryHandler.assignHandler(planet.renderer.handler);
this._polylineEntityCollection.addTo(planet, true);
this.setEntities(this._entities);
return this;
}
/**
* Returns true if a layer has rasterized vector data like polygons.
* @public
* @virtual
* @returns {boolean}
*/
hasImageryTiles() {
return true;
}
/**
* Returns stored entities.
* @public
* @returns {Array.<og.Entity>}
*/
getEntities() {
return [].concat(this._entities);
}
/**
* @private
*/
_fitExtent(entity) {
var ee = entity.getExtent(),
e = this._extent;
if (ee.southWest.lon < e.southWest.lon) {
e.southWest.lon = ee.southWest.lon;
}
if (ee.southWest.lat < e.southWest.lat) {
e.southWest.lat = ee.southWest.lat;
}
if (ee.northEast.lon > e.northEast.lon) {
e.northEast.lon = ee.northEast.lon;
}
if (ee.northEast.lat > e.northEast.lat) {
e.northEast.lat = ee.northEast.lat;
}
this.setExtent(this._extent);
}
/**
* Adds entity to the layer.
* @public
* @param {og.Entity} entity - Entity.
* @param {boolean} [rightNow] - Entity insertion option. False is deafult.
* @returns {og.layer.Vector} - Returns this layer.
*/
add(entity, rightNow) {
if (!(entity._layer || entity._entityCollection)) {
entity._layer = this;
entity._layerIndex = this._entities.length;
this._entities.push(entity);
this._fitExtent(entity);
//
//...pointCloud, shape, model etc.
//
if (entity.polyline) {
this._polylineEntityCollection.add(entity);
}
if (entity.geometry) {
if (this._planet) {
this._planet.renderer.assignPickingColor(entity);
this._geometryHandler.add(entity.geometry);
}
}
if (entity.billboard || entity.label) {
if (this._planet) {
if (!entity._lonlat) {
entity._lonlat = this.layer._planet.ellipsoid.cartesianToLonLat(entity._cartesian);
}
//poles trees
if (entity._lonlat.lat > mercator.MAX_LAT) {
this._entityCollectionsTreeNorth.insertEntity(entity, rightNow);
} else if (entity._lonlat.lat < mercator.MIN_LAT) {
this._entityCollectionsTreeSouth.insertEntity(entity, rightNow);
} else {
this._entityCollectionsTree.insertEntity(entity, rightNow);
}
}
}
this.events.dispatch(this.events.entityadd, entity);
}
return this;
}
/**
* Adds entity array to the layer.
* @public
* @param {Array.<og.Entity>} entities - Entities array.
* @param {boolean} [rightNow] - Entity insertion option. False is deafult.
* @returns {og.layer.Vector} - Returns this layer.
*/
addEntities(entities, rightNow) {
var i = entities.length;
while (i--) {
this.add(entities[i], rightNow);
}
return this;
}
/**
* Remove entity from layer.
* TODO: memory leaks.
* @public
* @param {og.Entity} entity - Entity to remove.
* @returns {og.layer.Vector} - Returns this layer.
*/
removeEntity(entity) {
if (entity._layer && this.isEqual(entity._layer)) {
this._entities.splice(entity._layerIndex, 1);
this._reindexEntitiesArray(entity._layerIndex);
entity._layer = null;
entity._layerIndex = -1;
if (entity._entityCollection) {
entity._entityCollection._removeEntitySilent(entity);
var node = entity._nodePtr;
while (node) {
node.count--;
node = node.parentNode;
}
if (entity._nodePtr && entity._nodePtr.count === 0 &&
entity._nodePtr.deferredEntities.length === 0) {
entity._nodePtr.entityCollection = null;
//
//...
//
}
} else if (entity._nodePtr &&
entity._nodePtr.deferredEntities.length) {
var defEntities = entity._nodePtr.deferredEntities;
var j = defEntities.length;
while (j--) {
if (defEntities[j].id === entity.id) {
defEntities.splice(j, 1);
var node = entity._nodePtr;
while (node) {
node.count--;
node = node.parentNode;
}
break;
}
}
}
if (entity.geometry) {
if (this._planet) {
this._geometryHandler.remove(entity.geometry);
this._planet.renderer.clearPickingColor(entity);
}
}
entity._nodePtr && (entity._nodePtr = null);
this.events.dispatch(this.events.entityremove, entity);
}
return this;
}
/**
* Set layer picking events active.
* @public
* @param {number} enable
*/
setPickingEnabled(enable) {
this._pickingEnabled = enable;
this._polylineEntityCollection.setPickingEnabled(enable);
this._entityCollectionsTree.traverseTree(function (ec) {
ec.setPickingEnabled(enable);
});
this._entityCollectionsTreeNorth.traverseTree(function (ec) {
ec.setPickingEnabled(enable);
});
this._entityCollectionsTreeSouth.traverseTree(function (ec) {
ec.setPickingEnabled(enable);
});
}
/**
* Refresh collected entities indexes from startIndex entitytes collection array position.
* @public
* @param {number} startIndex - Entity array index.
*/
_reindexEntitiesArray(startIndex) {
var e = this._entities;
for (var i = startIndex; i < e.length; i++) {
e[i]._layerIndex = i;
}
}
/**
* Removes entities from layer.
* @public
* @param {Array.<og.Entity>} entities - Entity array.
* @returns {og.layer.Vector} - Returns this layer.
*/
removeEntities(entities) {
var i = entities.length;
while (i--) {
this.removeEntity(entities[i]);
}
return this;
}
/**
* Sets scale by distance parameters.
* @public
* @param {number} near - Full scale entity distance.
* @param {number} far - Zerol scale entity distance.
* @param {number} [farInvisible] - Entity visibility distance.
*/
setScaleByDistance(near, far, farInisible) {
this.scaleByDistance[0] = near;
this.scaleByDistance[1] = far;
this.scaleByDistance[2] = farInisible || math.MAX32;
return this;
}
/**
* TODO: Clear the layer.
* @public
*/
clear() {
//TODO
}
/**
* Safety entities loop.
* @public
* @param {callback} callback - Entity callback.
*/
each(callback) {
var e = this._entities;
var i = e.length;
while (i--) {
callback(e[i]);
}
}
/**
* Removes current entities from layer and adds new entities.
* @public
* @param {Array.<og.Entity>} entities - New entity array.
*/
setEntities(entities) {
this.clear();
var e = this._extent = new Extent(new LonLat(180, 90), new LonLat(-180, -90));
this._entities = new Array(entities.length);
var entitiesForTree = [];
for (var i = 0; i < entities.length; i++) {
var ei = entities[i];
ei._layer = this;
ei._layerIndex = i;
if (ei.polyline) {
this._polylineEntityCollection.add(ei);
} else if (ei.billboard || ei.label || ei.shape) {
entitiesForTree.push(ei);
}
if (ei.geometry) {
if (this._planet) {
this._planet.renderer.assignPickingColor(ei);
this._geometryHandler.add(ei.geometry);
}
}
this._entities[i] = ei;
var ext = ei.getExtent();
if (ext.northEast.lon > e.northEast.lon) e.northEast.lon = ext.northEast.lon;
if (ext.northEast.lat > e.northEast.lat) e.northEast.lat = ext.northEast.lat;
if (ext.southWest.lon < e.southWest.lon) e.southWest.lon = ext.southWest.lon;
if (ext.southWest.lat < e.southWest.lat) e.southWest.lat = ext.southWest.lat;
}
this._createEntityCollectionsTree(entitiesForTree);
return this;
}
_createEntityCollectionsTree(entitiesForTree) {
if (this._planet) {
this._entityCollectionsTree = new EntityCollectionNode(this, quadTree.NW, null, 0,
Extent.createFromArray([-20037508.34, -20037508.34, 20037508.34, 20037508.34]), this._planet, 0);
this._entityCollectionsTreeNorth = new EntityCollectionNodeWGS84(this, quadTree.NW, null, 0,
Extent.createFromArray([-180, mercator.MAX_LAT, 180, 90]), this._planet, 0);
this._entityCollectionsTreeSouth = new EntityCollectionNodeWGS84(this, quadTree.NW, null, 0,
Extent.createFromArray([-180, -90, 180, mercator.MIN_LAT]), this._planet, 0);
this._entityCollectionsTree.buildTree(entitiesForTree);
this._entityCollectionsTreeNorth.buildTree(entitiesForTree);
this._entityCollectionsTreeSouth.buildTree(entitiesForTree);
}
}
_bindEventsDefault(entityCollection) {
var ve = this.events;
entityCollection.events.on("entitymove", function (e) {
ve.dispatch(ve.entitymove, e);
});
entityCollection.events.on("mousemove", function (e) {
ve.dispatch(ve.mousemove, e);
});
entityCollection.events.on("mouseenter", function (e) {
ve.dispatch(ve.mouseenter, e);
});
entityCollection.events.on("mouseleave", function (e) {
ve.dispatch(ve.mouseleave, e);
});
entityCollection.events.on("lclick", function (e) {
ve.dispatch(ve.lclick, e);
});
entityCollection.events.on("rclick", function (e) {
ve.dispatch(ve.rclick, e);
});
entityCollection.events.on("mclick", function (e) {
ve.dispatch(ve.mclick, e);
});
entityCollection.events.on("ldblclick", function (e) {
ve.dispatch(ve.ldblclick, e);
});
entityCollection.events.on("rdblclick", function (e) {
ve.dispatch(ve.rdblclick, e);
});
entityCollection.events.on("mdblclick", function (e) {
ve.dispatch(ve.mdblclick, e);
});
entityCollection.events.on("lup", function (e) {
ve.dispatch(ve.lup, e);
});
entityCollection.events.on("rup", function (e) {
ve.dispatch(ve.rup, e);
});
entityCollection.events.on("mup", function (e) {
ve.dispatch(ve.mup, e);
});
entityCollection.events.on("ldown", function (e) {
ve.dispatch(ve.ldown, e);
});
entityCollection.events.on("rdown", function (e) {
ve.dispatch(ve.rdown, e);
});
entityCollection.events.on("mdown", function (e) {
ve.dispatch(ve.mdown, e);
});
entityCollection.events.on("lhold", function (e) {
ve.dispatch(ve.lhold, e);
});
entityCollection.events.on("rhold", function (e) {
ve.dispatch(ve.rhold, e);
});
entityCollection.events.on("mhold", function (e) {
ve.dispatch(ve.mhold, e);
});
entityCollection.events.on("mousewheel", function (e) {
ve.dispatch(ve.mousewheel, e);
});
entityCollection.events.on("touchmove", function (e) {
ve.dispatch(ve.touchmove, e);
});
entityCollection.events.on("touchstart", function (e) {
ve.dispatch(ve.touchstart, e);
});
entityCollection.events.on("touchend", function (e) {
ve.dispatch(ve.touchend, e);
});
entityCollection.events.on("doubletouch", function (e) {
ve.dispatch(ve.doubletouch, e);
});
entityCollection.events.on("touchleave", function (e) {
ve.dispatch(ve.touchleave, e);
});
entityCollection.events.on("touchenter", function (e) {
ve.dispatch(ve.touchenter, e);
});
}
_collectPolylineCollectionPASS(outArr) {
outArr.push(this._polylineEntityCollection);
if (this.clampToGround || this.relativeToGround) {
var rtg = this.relativeToGround;
var nodes = this._planet._renderedNodes;
var visibleExtent = this._planet.getViewExtent();
var e = this._polylineEntityCollection._entities;
var e_i = e.length;
while (e_i--) {
var p = e[e_i].polyline;
if (visibleExtent.overlaps(p._extent)) {
var coords = p._pathLonLatMerc;
var c_j = coords.length;
while (c_j--) {
var c_j_h = coords[c_j].length;
while (c_j_h--) {
var ll = coords[c_j][c_j_h];
var n_k = nodes.length;
while (n_k--) {
var seg = nodes[n_k].segment;
if (seg._extent.isInside(ll)) {
var cart = p._path3v[c_j][c_j_h];
var res = new Vec3();
seg.getTerrainPoint(res, cart, ll);
p.setPoint3v(res.addA(res.normal().scale(rtg && p.altitude + 1.0)), c_j_h, c_j, true);
break;
}
}
}
}
}
}
}
}
collectVisibleCollections(outArr) {
var p = this._planet;
if (this.minZoom <= this._planet.maxCurrZoom && this.maxZoom >= p.maxCurrZoom) {
this._renderingNodes = {};
this._renderingNodesNorth = {};
this._renderingNodesSouth = {};
//Common collection first
this._collectPolylineCollectionPASS(outArr);
//Merc nodes
this._secondPASS = [];
this._entityCollectionsTree.collectRenderCollectionsPASS1(p._visibleNodes, outArr);
var i = this._secondPASS.length;
while (i--) {
this._secondPASS[i].collectRenderCollectionsPASS2(p._visibleNodes, outArr, this._secondPASS[i].nodeId);
}
//North nodes
this._secondPASS = [];
this._entityCollectionsTreeNorth.collectRenderCollectionsPASS1(p._visibleNodesNorth, outArr);
i = this._secondPASS.length;
while (i--) {
this._secondPASS[i].collectRenderCollectionsPASS2(p._visibleNodesNorth, outArr, this._secondPASS[i].nodeId);
}
//South nodes
this._secondPASS = [];
this._entityCollectionsTreeSouth.collectRenderCollectionsPASS1(p._visibleNodesSouth, outArr);
i = this._secondPASS.length;
while (i--) {
this._secondPASS[i].collectRenderCollectionsPASS2(p._visibleNodesSouth, outArr, this._secondPASS[i].nodeId);
}
}
}
_queueDeferredNode(node) {
if (this._visibility) {
node._inTheQueue = true;
if (this._counter >= 1) {
this._deferredEntitiesPendingQueue.push(node);
} else {
this._execDeferredNode(node);
}
}
}
_execDeferredNode(node) {
this._counter++;
var that = this;
setTimeout(function () {
node.applyCollection();
that._counter--;
if (that._deferredEntitiesPendingQueue.length && that._counter < 1) {
while (that._deferredEntitiesPendingQueue.length) {
var n = that._deferredEntitiesPendingQueue.pop();
n._inTheQueue = false;
if (n.isVisible()) {
that._execDeferredNode(node);
return;
}
}
}
}, 0);
}
/**
* Start to load tile material.
* @public
* @virtual
* @param {og.Segment.Material} material - Current material.
*/
loadMaterial(material) {
var seg = material.segment;
if (this._isBaseLayer) {
material.texture = seg._isNorth ? seg.planet.solidTextureOne : seg.planet.solidTextureTwo;
} else {
material.texture = seg.planet.transparentTexture;
}
if (this._planet.layerLock.isFree()) {
material.isReady = false;
material.isLoading = true;
this._planet._vectorTileCreator.add(material);
}
}
/**
* Abort exact material loading.
* @public
* @param {og.planetSegment.Material} material - Segment material.
*/
abortMaterialLoading(material) {
material.isLoading = false;
material.isReady = false;
}
applyMaterial(material) {
if (material.isReady) {
return [0, 0, 1, 1];
} else {
!material.isLoading && this.loadMaterial(material);
var segment = material.segment;
var pn = segment.node,
notEmpty = false;
var mId = this._id;
var psegm = material;
var i = 0;
while (pn.parentNode && i < 2) {
if (psegm && psegm.isReady) {
notEmpty = true;
break;
}
pn = pn.parentNode;
psegm = pn.segment.materials[mId];
}
if (notEmpty) {
material.appliedNodeId = pn.nodeId;
material.texture = psegm.texture;
material.pickingMask = psegm.pickingMask;
var dZ2 = 1.0 / (2 << (segment.tileZoom - pn.segment.tileZoom - 1));
return [
segment.tileX * dZ2 - pn.segment.tileX,
segment.tileY * dZ2 - pn.segment.tileY,
dZ2,
dZ2
];
} else {
if (material.textureExists && material._updateTexture) {
material.texture = material._updateTexture;
material.pickingMask = material._updatePickingMask;
} else {
material.texture = segment.planet.transparentTexture;
material.pickingMask = segment.planet.transparentTexture;
}
return [0, 0, 1, 1];
}
}
}
clearMaterial(material) {
if (material.isReady) {
var gl = material.segment.handler.gl;
material.isReady = false;
material.pickingReady = false;
var t = material.texture;
material.texture = null;
t && !t.default && gl.deleteTexture(t);
t = material.pickingMask;
material.pickingMask = null;
t && !t.default && gl.deleteTexture(t);
t = material._updateTexture;
material._updateTexture = null;
t && !t.default && gl.deleteTexture(t);
t = material._updatePickingMask;
material._updatePickingMask = null;
t && !t.default && gl.deleteTexture(t);
}
this.abortMaterialLoading(material);
material.isLoading = false;
material.textureExists = false;
}
update() {
this._geometryHandler.update();
this.collectVisibleCollections(this._planet._frustumEntityCollections);
this.events.dispatch(this.events.draw, this);
}
};
const EVENT_NAMES = [
/**
* Triggered when entity has moved.
* @event og.layer.Vector#draw
*/
"entitymove",
/**
* Triggered when layer begin draw.
* @event og.layer.Vector#draw
*/
"draw",
/**
* Triggered when new entity added to the layer.
* @event og.layer.Vector#entityadd
*/
"entityadd",
/**
* Triggered when entity removes from the collection.
* @event og.layer.Vector#entityremove
*/
"entityremove"
];
export { Vector };

171
src/og/layer/WMS.js Normal file
View File

@ -0,0 +1,171 @@
/**
* @module og/layer/WMS
*/
'use strict';
import * as mercator from '../mercator.js';
import { Extent } from '../Extent.js';
import { LonLat } from '../LonLat.js';
import { XYZ } from './XYZ.js';
/**
* Used to display WMS services as tile layers on the globe.
* @class
* @extends {og.layer.XYZ}
* //TODO: WMS version, format, and custom srs cpecification.
* @param {string} name - Layer name.
* @param {Object} options - Options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {Array.<number,number,number>} [options.transparentColor=[-1,-1,-1]] - RGB color that defines transparent color.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - Base layer flag.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {string} options.url - WMS url source.
* @param {number} [options.width=256] - Tile width.
* @param {number} [options.height=256] - Tile height.
* @param {string} options.layers - WMS layers string.
* @param {string} [options.version="1.1.1"] - WMS version.
* @example:
* new og.layer.WMS("USA States", {
* isBaseLayer: false,
* url: "http://openglobus.org/geoserver/",
* layers: "topp:states",
* opacity: 0.5,
* zIndex: 50,
* attribution: 'USA states - geoserver WMS example',
* transparentColor: [1.0, 1.0, 1.0],
* version: "1.1.1",
* visibility: false }
* );
*
* @fires og.layer.XYZ#load
* @fires og.layer.XYZ#loadend
*/
class WMS extends XYZ {
constructor(name, options) {
super(name, options);
if (!options.extent) {
this.setExtent(new Extent(new LonLat(-180.0, -90), new LonLat(180.0, 90)));
}
/**
* WMS layers string.
* @public
* @type {string}
*/
this.layers = options.layers;
/**
* WMS tile width.
* @public
* @type {number}
*/
this.imageWidth = options.imageWidth || 256;
/**
* WMS tile height.
* @public
* @type {number}
*/
this.imageHeight = options.imageHeight || 256;
this.setVersion(options.version);
}
/**
* Start to load tile material.
* @public
* @virtual
* @param {og.planetSegment.Material} mateial
*/
loadMaterial(material) {
var seg = material.segment;
if (!material.isLoading) {
if (this._planet.layerLock.isFree()) {
material.isReady = false;
material.isLoading = true;
if (WMS.__requestsCounter >= WMS.MAX_REQUESTS && this.counter) {
this.pendingsQueue.push(material);
} else {
this._exec(material);
}
}
}
}
/**
* Creates query url.
* @protected
* @virtual
* @param {og.planetSegment.Segment}
*/
_createUrl(segment) {
return this.url + "wms?" + "LAYERS=" + this.layers +
"&FORMAT=image/jpeg&SERVICE=WMS&VERSION=" + this._version + "&REQUEST=GetMap" +
"&SRS=" + segment._projection.code +
"&BBOX=" + this._getBbox(segment) +
"&WIDTH=" + this.imageWidth +
"&HEIGHT=" + this.imageHeight;
}
setVersion(version) {
if (version) {
this._version = version;
} else {
this._version = "1.1.1";
}
if (this._version === "1.1.1") {
this._getBbox = this._getBbox111;
} else if (version === "1.3.0") {
this._getBbox = this._getBbox130;
} else {
this._getBbox = this._getBbox111;
}
}
/**
* @private
* @return {string}
*/
_getBbox111(segment) {
return segment._extent.getWest() + "," + segment._extent.getSouth() + "," + segment._extent.getEast() + "," + segment._extent.getNorth();
}
/**
* @private
* @return {string}
*/
_getBbox130(segment) {
return segment._extent.getSouth() + "," + segment._extent.getWest() + "," + segment._extent.getNorth() + "," + segment._extent.getEast();
}
/**
* @protected
*/
_correctFullExtent() {
var e = this._extent,
em = this._extentMerc;
var ENLARGE_MERCATOR_LON = mercator.POLE + 50000;
var ENLARGE_MERCATOR_LAT = mercator.POLE + 50000;
if (e.northEast.lat === 90.0) {
em.northEast.lat = ENLARGE_MERCATOR_LAT;
}
if (e.northEast.lon === 180.0) {
em.northEast.lon = ENLARGE_MERCATOR_LON;
}
if (e.southWest.lat === -90.0) {
em.southWest.lat = -ENLARGE_MERCATOR_LAT;
}
if (e.southWest.lon === -180.0) {
em.southWest.lon = -ENLARGE_MERCATOR_LON;
}
}
};
export { WMS };

412
src/og/layer/XYZ.js Normal file
View File

@ -0,0 +1,412 @@
/**
* @module og/layer/XYZ
*/
'use strict';
import * as mercator from '../mercator.js';
import * as quadTree from '../quadTree/quadTree.js';
import { EPSG3857 } from '../proj/EPSG3857.js';
import { Extent } from '../Extent.js';
import { Layer } from './Layer.js';
import { stringTemplate } from '../utils/shared.js';
import { LonLat } from '../LonLat.js';
import { QueueArray } from '../QueueArray.js';
/**
* Represents an imagery tiles source provider.
* @class
* @extends {og.layer.Layer}
* @param {string} name - Layer name.
* @param {Object} options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {Array.<number,number,number>} [options.transparentColor=[-1,-1,-1]] - RGB color that defines transparent color.
* @param {Array.<string>} [options.subdomains=['a','b','c']] - Subdomains of the tile service.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - Base layer flag.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {string} [options.crossOrigin=true] - If true, all tiles will have their crossOrigin attribute set to ''.
* @param {string} options.url - Tile url source template(see example below).
* @param {og.layer.XYZ~_urlRewriteCallback} options.urlRewrite - Url rewrite function.
* @fires og.layer.XYZ#load
* @fires og.layer.XYZ#loadend
*
* @example <caption>Creates OpenStreetMap base tile layer</caption>
* new og.layer.XYZ("OpenStreetMap", {
* isBaseLayer: true,
* url: "http://b.tile.openstreetmap.org/{z}/{x}/{y}.png",
* visibility: true,
* attribution: 'Data @ <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL</a>'
* });
*/
class XYZ extends Layer {
constructor(name, options) {
super(name, options);
this.events.registerNames(EVENT_NAMES);
if (!options.extent) {
this.setExtent(new Extent(new LonLat(-180.0, mercator.MIN_LAT), new LonLat(180.0, mercator.MAX_LAT)));
}
this.transparentColor = options.transparentColor || [-1, -1, -1];
/**
* Tile url source template.
* @public
* @type {string}
*/
this.url = options.url || "";
/**
* Current loading tiles couter.
* @protected
* @type {number}
*/
this._counter = 0;
/**
* Tile pending queue that waiting for loading.
* @protected
* @type {Array.<og.planetSegment.Material>}
*/
this._pendingsQueue = new QueueArray();
/**
* @protected
*/
this._s = options.subdomains || ['a', 'b', 'c'];
/**
* @protected
*/
this._crossOrigin = options.crossOrigin === undefined ? '' : options.crossOrigin;
/**
* Rewrites imagery tile url query.
* @private
* @callback og.layer.XYZ~_urlRewriteCallback
* @param {og.planetSegment.Segment} segment - Segment to load.
* @param {string} url - Created url.
* @returns {string} - Url query string.
*/
this._urlRewriteCallback = options.urlRewrite || null;
}
static get __requestsCounter() {
return this.__reqcounter;
}
static set __requestsCounter(v) {
this.__reqcounter = v;
}
/**
* Maximum loading queries at one time.
* @const
* @type {number}
*/
static get MAX_REQUESTS() {
return 7;
}
/**
* Abort loading tiles.
* @public
*/
abortLoading() {
var that = this;
this._pendingsQueue.each(function (q) {
q && that.abortMaterialLoading(q);
});
this._pendingsQueue.clear();
}
/**
* Sets layer visibility.
* @public
* @param {boolean} visibility - Layer visibility.
*/
setVisibility(visibility) {
if (visibility !== this._visibility) {
this._visibility = visibility;
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
} else if (!visibility) {
this.abortLoading();
}
this._planet.updateVisibleLayers();
this.events.dispatch(this.events.visibilitychange, this);
}
}
/**
* Sets imagery tiles url source template.
* @public
* @param {string} url - Url template.
* @example
* http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
* where {z}, {x} and {y} - replaces by current tile values, {s} - random domen.
*/
setUrl(url) {
this.url = url;
}
/**
* Start to load tile material.
* @public
* @virtual
* @param {og.planetSegment.Material} material - Loads current material.
*/
loadMaterial(material) {
var seg = material.segment;
if (this._isBaseLayer) {
material.texture = seg._isNorth ? seg.planet.solidTextureOne : seg.planet.solidTextureTwo;
} else {
material.texture = seg.planet.transparentTexture;
}
if (this._planet.layerLock.isFree()) {
material.isReady = false;
material.isLoading = true;
if (material.segment._projection.id === EPSG3857.id) {
if (XYZ.__requestsCounter >= XYZ.MAX_REQUESTS && this._counter) {
this._pendingsQueue.push(material);
} else {
this._exec(material);
}
} else {
material.textureNotExists();
}
}
}
/**
* Creates query url.
* @protected
* @virtual
* @param {og.planetSegment.Segment} segment - Creates specific url for current segment.
* @returns {String} - Returns url string.
*/
_createUrl(segment) {
return stringTemplate(this.url, {
"s": this._s[Math.floor(Math.random() * this._s.length)],
"x": segment.tileX.toString(),
"y": segment.tileY.toString(),
"z": segment.tileZoom.toString()
});
}
/**
* Returns actual url query string.
* @protected
* @param {og.planetSegment.Segment} segment - Segment that loads image data.
* @returns {string} - Url string.
*/
_getHTTPRequestString(segment) {
var url = this._createUrl(segment);
return this._urlRewriteCallback ? this._urlRewriteCallback(segment, url) : url;
}
/**
* Sets url rewrite callback, used for custom url rewriting for every tile laoding.
* @public
* @param {og.layer.XYZ~_urlRewriteCallback} ur - The callback that returns tile custom created url.
*/
setUrlRewriteCallback(ur) {
this._urlRewriteCallback = ur;
}
/**
* Loads material image and apply it to the planet segment.
* @protected
* @param {og.planetSegment.Material} material - Loads material image.
*/
_exec(material) {
XYZ.__requestsCounter++;
this._counter++;
material.image = new Image();
material.image.crossOrigin = this._crossOrigin;
var that = this;
material.image.onload = function (evt) {
that._counter--;
XYZ.__requestsCounter--;
if (material.isLoading) {
var e = that.events.load;
if (e.handlers.length) {
that.events.dispatch(e, material);
}
this.onerror = null;
this.onload = null;
material.applyImage(this);
}
that._dequeueRequest();
};
material.image.onerror = function (evt) {
that._counter--;
XYZ.__requestsCounter--;
this.onerror = null;
this.onload = null;
if (material.isLoading && material.image) {
material.textureNotExists.call(material);
}
that._dequeueRequest();
};
material.image.src = this._getHTTPRequestString(material.segment);
}
/**
* Abort exact material loading.
* @public
* @param {og.planetSegment.Material} material - Segment material.
*/
abortMaterialLoading(material) {
if (material.isLoading && material.image) {
material.image.src = "";
material.image.__og_canceled = true;
material.image = null;
} else {
this._dequeueRequest();
}
}
_dequeueRequest() {
if (this._pendingsQueue.length) {
if (XYZ.__requestsCounter < XYZ.MAX_REQUESTS) {
var pmat = this._whilePendings();
if (pmat)
this._exec.call(this, pmat);
}
} else if (this._counter === 0) {
this.events.dispatch(this.events.loadend);
}
}
_whilePendings() {
while (this._pendingsQueue.length) {
var pmat = this._pendingsQueue.pop();
if (pmat.segment.node) {
if (pmat.segment.ready && pmat.segment.node.getState() === quadTree.RENDERING) {
return pmat;
}
pmat.isLoading = false;
}
}
return null;
}
applyMaterial(material) {
if (material.isReady) {
return [0, 0, 1, 1];
} else {
!material.isLoading && this.loadMaterial(material);
var segment = material.segment;
var pn = segment.node,
notEmpty = false;
var mId = this._id;
var psegm = material;
while (pn.parentNode) {
if (psegm && psegm.isReady) {
notEmpty = true;
break;
}
pn = pn.parentNode;
psegm = pn.segment.materials[mId];
}
if (notEmpty) {
material.appliedNodeId = pn.nodeId;
material.texture = psegm.texture;
var dZ2 = 1.0 / (2 << (segment.tileZoom - pn.segment.tileZoom - 1));
return [
segment.tileX * dZ2 - pn.segment.tileX,
segment.tileY * dZ2 - pn.segment.tileY,
dZ2,
dZ2
];
} else {
material.texture = segment.planet.transparentTexture;
return [0, 0, 1, 1];
}
}
}
clearMaterial(material) {
if (material.isReady) {
material.isReady = false;
!material.texture.default &&
material.segment.handler.gl.deleteTexture(material.texture);
material.texture = null;
} else {
this.abortMaterialLoading(material);
}
material.textureExists = false;
if (material.image) {
material.image.onload = null;
material.image.onerror = null;
material.image.src = '';
material.image = null;
}
}
/**
* @protected
*/
_correctFullExtent() {
var e = this._extent,
em = this._extentMerc;
var ENLARGE_MERCATOR_LON = mercator.POLE + 50000;
var ENLARGE_MERCATOR_LAT = mercator.POLE + 50000;
if (e.northEast.lat === 90.0) {
em.northEast.lat = ENLARGE_MERCATOR_LAT;
}
if (e.northEast.lon === 180.0) {
em.northEast.lon = ENLARGE_MERCATOR_LON;
}
if (e.southWest.lat === -90.0) {
em.southWest.lat = -ENLARGE_MERCATOR_LAT;
}
if (e.southWest.lon === -180.0) {
em.southWest.lon = -ENLARGE_MERCATOR_LON;
}
if (e.northEast.lat >= mercator.MAX_LAT) {
e.northEast.lat = mercator.MAX_LAT;
}
if (e.northEast.lat <= mercator.MIN_LAT) {
e.northEast.lat = mercator.MIN_LAT;
}
};
};
const EVENT_NAMES = [
/**
* Triggered when current tile image has loaded before rendereing.
* @event og.layer.XYZ#load
*/
"load",
/**
* Triggered when all tiles have loaded or loading has stopped.
* @event og.layer.XYZ#loadend
*/
"loadend"
];
export { XYZ };

316
src/og/light/LightSource.js Normal file
View File

@ -0,0 +1,316 @@
/**
* @module og/light/LightSource
*/
'use strict';
import { Vec3 } from '../math/Vec3.js';
/**
* Represents basic light source.
* @class
* @param {string} [name] - Light source name.
* @param {Object} [params] - Light parameters:
* @param {og.math.Vector3} [params.position] - Light source position if it is a point light, otherwise it is a light direction vector.
* @param {og.math.Vector3} [params.ambient] - Ambient RGB color.
* @param {og.math.Vector3} [params.diffuse] - Diffuse RGB color.
* @param {og.math.Vector3} [params.specular] - Specular RGB color.
* @param {number} [params.shininess] - Specular shininess.
*/
class LightSource {
static get _staticCounter() {
if (!this._counter && this._counter !== 0) {
this._counter = 0;
}
return this._counter;
}
static set _staticCounter(n) {
this._counter = n;
}
constructor(name, params) {
params = params || {};
/**
* Light name.
* @protected
* @type {string}
*/
this._name = name || ("light_" + LightSource._staticCounter++);
/**
* Render node where light is shines.
* @protected
* @type {og.scene.RenderNode}
*/
this._renderNode = null;
/**
* Light position.
* @protected
* @type {og.math.Vector3}
*/
this._position = params.position || new Vec3();
/**
* True if the light is directional.
* @public
* @type {boolean}
*/
this.directional = params.derectional != undefined ? params.derectional : true;
/**
* Ambient color.
* @protected
* @type {og.math.Vector3}
*/
this._ambient = params.ambient || new Vec3();
/**
* Diffuse color.
* @protected
* @type {og.math.Vector3}
*/
this._diffuse = params.diffuse || new Vec3(0.8, 0.8, 0.8);
/**
* Specular color.
* @protected
* @type {og.math.Vector3}
*/
this._specular = params.specular || new Vec3(0.18, 0.18, 0.18);
/**
* Shininess.
* @protected
* @type {number}
*/
this._shininess = params.shininess != undefined ? params.shininess : 3.3;
/**
* Light activity.
* @protected
* @type {boolean}
*/
this._active = true;
this._tempAmbient = this._ambient.clone();
this._tempDiffuse = this._diffuse.clone();
this._tempSpecular = this._specular.clone();
this._tempShininess = this._shininess;
}
/**
* Creates clone of the current light object.
* @todo: TODO
* @public
* @returns {og.LightSource}
*/
clone() {
//TODO
}
/**
* Set light activity. If activity is false the light doesn't shine.
* @public
* @param {boolean} active - Light activity.
*/
setActive(active) {
if (active && !this._active) {
var rn = this._renderNode;
if (rn) {
var index = rn._lightsNames.indexOf(this._name);
this._shininess = rn._lightsParamsf[index] = this._tempShininess;
if (index != -1) {
index *= 9;
this._ambient.x = rn._lightsParamsv[index] = this._tempAmbient.x;
this._ambient.y = rn._lightsParamsv[index + 1] = this._tempAmbient.y;
this._ambient.z = rn._lightsParamsv[index + 2] = this._tempAmbient.z;
this._diffuse.x = rn._lightsParamsv[index + 3] = this._tempDiffuse.x;
this._diffuse.y = rn._lightsParamsv[index + 4] = this._tempDiffuse.y;
this._diffuse.z = rn._lightsParamsv[index + 5] = this._tempDiffuse.z;
this._specular.x = rn._lightsParamsv[index + 6] = this._tempSpecular.x;
this._specular.y = rn._lightsParamsv[index + 7] = this._tempSpecular.y;
this._specular.z = rn._lightsParamsv[index + 8] = this._tempSpecular.z;
}
}
this._active = true;
} else if (!active && this._active) {
this._tempAmbient = this._ambient.clone();
this._tempDiffuse = this._diffuse.clone();
this._tempSpecular = this._specular.clone();
this._tempShininess = this._shininess;
this.setBlack();
this._active = false;
}
}
/**
* Gets light activity.
* @public
* @returns {boolean}
*/
isActive() {
return this._active;
}
/**
* Set light source position, or if it is a directional type sets light direction vector.
* @public
* @param {og.math.Vector3} position - Light position or direction vector.
* @returns {og.LightSource}
*/
setPosition(position) {
this._position.x = position.x;
this._position.y = position.y;
this._position.z = position.z;
return this;
}
/**
* Returns light source position, or if it is a directional type sets light direction vector.
* @public
* @returns {og.math.Vector3} - Light source position/direction.
*/
getPosition() {
return this._position.clone();
}
/**
* Set ambient color.
* @public
* @param {og.math.Vector3} rgb - Ambient color.
* @returns {og.LightSource}
*/
setAmbient(rgb) {
this._ambient = rgb;
var rn = this._renderNode;
if (rn) {
var index = 9 * rn._lightsNames.indexOf(this._name);
if (index != -1) {
rn._lightsParamsv[index] = rgb.x;
rn._lightsParamsv[index + 1] = rgb.y;
rn._lightsParamsv[index + 2] = rgb.z;
}
}
return this;
}
/**
* Set diffuse color.
* @public
* @param {og.math.Vector3} rgb - Diffuse color.
* @returns {og.LightSource}
*/
setDiffuse(rgb) {
this._diffuse = rgb;
var rn = this._renderNode;
if (rn) {
var index = 9 * rn._lightsNames.indexOf(this._name);
if (index != -1) {
rn._lightsParamsv[index + 3] = rgb.x;
rn._lightsParamsv[index + 4] = rgb.y;
rn._lightsParamsv[index + 5] = rgb.z;
}
}
return this;
}
/**
* Set specular color.
* @public
* @param {og.math.Vector3} rgb - Specular color.
* @returns {og.LightSource}
*/
setSpecular(rgb) {
this._specular = rgb;
var rn = this._renderNode;
if (rn) {
var index = 9 * rn._lightsNames.indexOf(this._name);
if (index != -1) {
rn._lightsParamsv[index + 6] = rgb.x;
rn._lightsParamsv[index + 7] = rgb.y;
rn._lightsParamsv[index + 8] = rgb.z;
}
}
return this;
}
/**
* Set material shininess.
* @public
* @param {number} shininess - Material shininess.
* @returns {og.LightSource}
*/
setShininess(shininess) {
this._shininess = shininess;
var rn = this._renderNode;
if (rn) {
var index = rn._lightsNames.indexOf(this._name);
if (index != -1) {
rn._lightsParamsf[index] = shininess;
}
}
return this;
}
/**
* Sets light to black.
* @public
* @returns {og.LightSource}
*/
setBlack() {
this._ambient.clear();
this._diffuse.clear();
this._specular.clear();
this._shininess = 0;
var rn = this._renderNode;
if (rn) {
var index = 9 * rn._lightsNames.indexOf(this._name);
if (index != -1) {
rn._lightsParamsv[index] = rn._lightsParamsv[index + 1] = rn._lightsParamsv[index + 2] =
rn._lightsParamsv[index + 3] = rn._lightsParamsv[index + 4] = rn._lightsParamsv[index + 5] =
rn._lightsParamsv[index + 6] = rn._lightsParamsv[index + 7] = rn._lightsParamsv[index + 8] = 0;
}
}
return this;
}
/**
* Adds current light to the render node scene.
* @public
* @param {og.scene.RenderNode} renderNode - Render node scene.
* @returns {og.LightSource}
*/
addTo(renderNode) {
this._renderNode = renderNode;
renderNode._lights.push(this);
renderNode._lightsNames.push(this._name);
renderNode._lightsParamsf.push(this._shininess);
renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._ambient.toVec());
renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._diffuse.toVec());
renderNode._lightsParamsv.push.apply(renderNode._lightsParamsv, this._specular.toVec());
return this;
}
/**
* Removes from render node scene.
* @public
*/
remove() {
var rn = this.renderNode;
if (rn) {
var li = rn.getLightById(this._name);
if (li != -1) {
rn._lights.splice(li, 1);
rn._lightsNames.splice(li, 1);
rn._lightsParamsf.splice(li, 1);
rn._lightsParamsv.splice(li, 9);
}
}
this._renderNode = null;
}
};
export { LightSource };

32
src/og/math/Line2.js Normal file
View File

@ -0,0 +1,32 @@
/**
* @module og/math/Line2
*/
'use strict';
import { Vec2 } from './Vec2.js';
const Line2 = function (a, b, c) {
this.a = a || 0;
this.b = b || 0;
this.c = c || 0;
};
Line2.get = function (p0, p1) {
return new Line2(p1.y - p0.y, p0.x - p1.x, p1.x * p0.y - p0.x * p1.y);
};
Line2.getParallel = function (l, p) {
return new Line2(l.a, l.b, -l.a * p.x - l.b * p.y);
};
Line2.getIntersection = function (L0, L1) {
var x = (L1.b * L0.c - L0.b * L1.c) / (L0.b * L1.a - L1.b * L0.a);
return new Vec2(x, -(L0.c + L0.a * x) / L0.b);
};
Line2.prototype.intersects = function (l) {
return Line2.getIntersection(this, l);
};
export { Line2 };

66
src/og/math/Line3.js Normal file
View File

@ -0,0 +1,66 @@
/**
* @module og/math/Line3
*/
'use strict';
import { Vec3 } from './Vec3.js';
const Line3 = function (p0, p1) {
this.p0 = (p0 ? p0.clone() : new Vec3());
this.p1 = (p1 ? p1.clone() : new Vec3());
};
Line3.prototype.getSphereIntersection = function (sphere) {
var p0 = this.p0,
p1 = this.p1;
var cx = sphere.x,
cy = sphere.y,
cz = sphere.z;
var px = p0.x,
py = p0.y,
pz = p0.z;
var vx = p1.x - px,
vy = p1.y - py,
vz = p1.z - pz;
var A = vx * vx + vy * vy + vz * vz,
B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz),
C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy +
pz * pz - 2 * pz * cz + cz * cz - sphere.radius * sphere.radius;
var D = B * B - 4 * A * C;
if (D < 0) {
return null;
}
var t1 = (-B - Math.Sqrt(D)) / (2.0 * A);
var solution1 = new Vec3(
p0.x * (1 - t1) + t1 * p1.x,
p0.y * (1 - t1) + t1 * p1.y,
p0.z * (1 - t1) + t1 * p1.z);
if (D == 0) {
return solution1;
}
var t2 = (-B + Math.Sqrt(D)) / (2.0 * A);
var solution2 = new Vec3(
p0.x * (1 - t2) + t2 * p1.x,
p0.y * (1 - t2) + t2 * p1.y,
p0.z * (1 - t2) + t2 * p1.z);
// prefer a solution that's on the line segment itself
if (Math.Abs(t1 - 0.5) < Math.abs(t2 - 0.5)) {
//return new Point3D[] { solution1, solution2 };
return [solution1, solution2];
}
return [solution2, solution1];
};
export { Line3 };

88
src/og/math/Plane.js Normal file
View File

@ -0,0 +1,88 @@
/**
* @module og/math/Plane
*/
'use strict';
import * as math from '../math.js';
import { Vec3 } from './Vec3.js';
class Plane {
constructor(p, n) {
this.p = (p ? p.clone() : new Vec3());
this.n = (n ? n.clone() : new Vec3());
}
set(p, n) {
this.p.copy(p);
this.n.copy(n);
}
getNormal() {
return this.n;
}
getIntersection(Pn1, Pn2, L) {
var u = Pn1.n.cross(Pn2.n);
var ax = (u.x >= 0 ? u.x : -u.x);
var ay = (u.y >= 0 ? u.y : -u.y);
var az = (u.z >= 0 ? u.z : -u.z);
// test if the two planes are parallel
if ((ax + ay + az) < math.EPSILON5) { // Pn1 and Pn2 are near parallel
// test if disjoint or coincide
var v = Pn2.p.sub(Pn1.p);
if (Pn1.n.dot(v) == 0) // Pn2.V0 lies in Pn1
return 1; // Pn1 and Pn2 coincide
else
return 0; // Pn1 and Pn2 are disjoint
}
// Pn1 and Pn2 intersect in a line
// first determine max abs coordinate of cross product
var maxc; // max coordinate
if (ax > ay) {
if (ax > az)
maxc = 1;
else maxc = 3;
}
else {
if (ay > az)
maxc = 2;
else maxc = 3;
}
// next, to get a point on the intersect line
// zero the max coord, and solve for the other two
var iP = new Vec3(); // intersect point
var d1, d2; // the constants in the 2 plane equations
d1 = -Pn1.n.dot(Pn1.p); // note: could be pre-stored with plane
d2 = -Pn2.n.dot(Pn2.p); // ditto
// select max coordinate
if (maxc === 1) {
// intersect with x=0
iP.x = 0;
iP.y = (d2 * Pn1.n.z - d1 * Pn2.n.z) / u.x;
iP.z = (d1 * Pn2.n.y - d2 * Pn1.n.y) / u.x;
} else if (maxc === 2) {
// intersect with y=0
iP.x = (d1 * Pn2.n.z - d2 * Pn1.n.z) / u.y;
iP.y = 0;
iP.z = (d2 * Pn1.n.x - d1 * Pn2.n.x) / u.y;
} else if (maxc === 3) {
// intersect with z=0
iP.x = (d2 * Pn1.n.y - d1 * Pn2.n.y) / u.z;
iP.y = (d1 * Pn2.n.x - d2 * Pn1.n.x) / u.z;
iP.z = 0;
}
L.p0.copy(iP);
L.p1.copy(iP.add(u));
return 2;
}
};
export { Plane };

230
src/og/math/Ray.js Normal file
View File

@ -0,0 +1,230 @@
/**
* @module og/math/Ray
*/
'use strict';
import * as math from '../math.js';
import { Vec3 } from './Vec3.js';
/**
* Represents a ray that extends infinitely from the provided origin in the provided direction.
* @class
* @param {og.math.Vec3} origin - The origin of the ray.
* @param {og.math.Vec3} direction - The direction of the ray.
*/
const Ray = function (origin, direction) {
/**
* The origin of the ray.
* @public
* @type {og.math.Vec3}
*/
this.origin = origin.clone();
/**
* The direction of the ray.
* @public
* @type {og.math.Vec3}
*/
this.direction = direction.clone();
};
/**
* Ray object creator.
* @function
* @param {og.math.Vec3} origin - The origin of the ray.
* @param {og.math.Vec3} direction - The direction of the ray.
* @returns {og.math.Ray}
*/
export function ray(origin, direction) {
return new Ray(origin, direction);
};
/** @const */
Ray.OUTSIDE = 0;
/** @const */
Ray.INSIDE = 1;
/** @const */
Ray.INPLANE = 2;
/** @const */
Ray.AWAY = 3;
/**
* Sets a ray parameters.
* @public
* @param {og.math.Vec3} origin - The origin of the ray.
* @param {og.math.Vec3} direction - The direction of the ray.
* @returns {og.math.Ray}
*/
Ray.prototype.set = function (origin, direction) {
this.origin = origin.clone();
this.direction = direction.clone();
return this;
};
/**
* Computes the point along the ray on the distance.
* @public
* @param {number} distance - Point distance.
* @returns {og.math.Vec3}
*/
Ray.prototype.getPoint = function (distance) {
return Vec3.add(this.origin, this.direction.scaleTo(distance));
};
/**
* Returns ray hit a triange result.
* @public
* @param {og.math.Vec3} v0 - First triangle corner coordinate.
* @param {og.math.Vec3} v1 - Second triangle corner coordinate.
* @param {og.math.Vec3} v2 - Third triangle corner coordinate.
* @param {og.math.Vec3} res - Hit point object pointer that stores hit result.
* @returns {number} - Hit code, could 0 - og.math.Ray.OUTSIDE, 1 - og.math.Ray.INSIDE,
* 2 - og.math.Ray.INPLANE and 3 - og.math.Ray.AWAY(ray goes away from triangle).
*/
Ray.prototype.hitTriangle = function (v0, v1, v2, res) {
var state;
var u = v1.sub(v0);
var v = v2.sub(v0);
var n = u.cross(v);
var w0 = this.origin.sub(v0);
var a = -n.dot(w0);
var b = n.dot(this.direction);
// ray is parallel to triangle plane
if (Math.abs(b) < math.EPSILON10) {
if (a === 0) {
res.copy(this.origin);
// ray lies in triangle plane
return Ray.INPLANE;
} else {
// ray disjoint from plane
return Ray.OUTSIDE;
}
}
var r = a / b;
// intersect point of ray and plane
res.copy(this.origin.add(this.direction.scaleTo(r)));
// ray goes away from triangle
if (r < 0.0)
return Ray.AWAY;
// is res point inside the triangle?
var uu = u.dot(u);
var uv = u.dot(v);
var vv = v.dot(v);
var w = res.sub(v0);
var wu = w.dot(u);
var wv = w.dot(v);
var D = uv * uv - uu * vv;
var s = (uv * wv - vv * wu) / D;
if (s < 0.0 || s > 1.0)
return Ray.OUTSIDE;
var t = (uv * wu - uu * wv) / D;
if (t < 0.0 || (s + t) > 1.0)
return Ray.OUTSIDE;
return Ray.INSIDE;
};
/**
* Gets a ray hit a plane result. If the ray cross the plane returns 1 - og.math.Ray.INSIDE otherwise returns 0 - og.math.Ray.OUTSIDE.
* @public
* @param {og.math.Vec3} v0 - First plane point.
* @param {og.math.Vec3} v1 - Second plane point.
* @param {og.math.Vec3} v2 - Third plane point.
* @param {og.math.Vec3} res - Hit point object pointer that stores hit result.
* @returns {number}
*/
Ray.prototype.hitPlane = function (v0, v1, v2, res) {
var u = Vec3.sub(v1, v0);
var v = Vec3.sub(v2, v0);
var n = u.cross(v);
var w0 = Vec3.sub(this.origin, v0);
var a = -n.dot(w0);
var b = n.dot(this.direction);
// ray is parallel to the plane
if (Math.abs(b) < math.EPSILON10) {
if (a === 0) {
return Ray.OUTSIDE;
}
}
var r = a / b;
if (r < 0) {
return Ray.OUTSIDE;
}
var d = this.direction.scaleTo(r);
// intersect point of ray and plane
res.x = this.origin.x + d.x;
res.y = this.origin.y + d.y;
res.z = this.origin.z + d.z;
return Ray.INSIDE;
};
/**
* Returns a ray hit sphere coordiante. If there isn't hit returns null.
* @public
* @param {og.bv.Sphere} sphere - Sphere object.
* @returns {og.math.Vec3}
*/
Ray.prototype.hitSphere = function (sphere) {
var r = sphere.radius,
c = sphere.center,
o = this.origin,
d = this.direction;
var vpc = Vec3.sub(c, o);
if (vpc.dot(d) < 0) {
var l = vpc.length();
if (l > r) {
return null;
} else if (l === r) {
return o.clone();
}
var pc = c.projToRay(o, vpc);
var lc = Vec3.sub(pc, c).length();
var dist = Math.sqrt(r * r - lc * lc);
var di1 = dist - Vec3.sub(pc, o).length();
var intersection = Vec3.add(o, d.scaleTo(di1));
return intersection;
} else {
var pc = c.projToRay(o, d);
var cpcl = Vec3.sub(c, pc).length();
if (cpcl > sphere.radius) {
return null;
} else {
var dist = Math.sqrt(r * r - cpcl * cpcl);
var di1;
pc.subA(o);
if (vpc.length() > r) {
di1 = pc.length() - dist;
} else {
di1 = pc.length() + dist;
}
var intersection = Vec3.add(o, d.scaleTo(di1));
return intersection;
}
}
};
Ray.prototype.hitBox = function (box) {
//
//TODO
//
};
export { Ray };

13
src/og/proj/EPSG3857.js Normal file
View File

@ -0,0 +1,13 @@
/**
* @module og/proj/EPSG3857
*/
'use strict';
import { Units, Proj } from './Proj.js';
/**
* EPSG:3857 projection object.
* @type {og.proj.Projection}
*/
export const EPSG3857 = new Proj({ code: "epsg:3857", units: Units.METERS });

13
src/og/proj/EPSG4326.js Normal file
View File

@ -0,0 +1,13 @@
/**
* @module og/proj/EPSG4326
*/
'use strict';
import { Units, Proj } from './Proj.js';
/**
* EPSG:4326 projection object.
* @type {og.proj.Proj}
*/
export const EPSG4326 = new Proj({ code: "epsg:4326", units: Units.DEGREES });

63
src/og/proj/Proj.js Normal file
View File

@ -0,0 +1,63 @@
/**
* @module og/proj/Proj
*/
'use strict';
/**
* Projection units: 'degrees', 'ft', 'm' or 'km'.
* @enum {string}
* @api
*/
export const Units = {
"DEGREES": "degrees",
"FEET": "ft",
"METERS": "m",
"KILOMETERS": "km"
};
/**
* Meters per unit lookup table.
* @const
* @type {Object.<og.proj.Units, number>}
*/
export const METERS_PER_UNIT = {};
METERS_PER_UNIT[Units.FEET] = 0.3048;
METERS_PER_UNIT[Units.METERS] = 1;
METERS_PER_UNIT[Units.KILOMETERS] = 1000;
const Proj = function (options) {
/**
* @public
* @type {string}
*/
this.code = options.code;
/**
* @public
* @type {og.proj.Units}
*/
this.units = /** @type {Units} */ (options.units);
/**
* Projection identifier, especially usefull for comparison.
* @const
* @type {integer}
*/
this.id = Proj._counter++;
};
/**
* Compare projections.
* @public
* @param {og.proj.Projection} proj - Projetion object.
* @returns {boolean}
*/
Proj.prototype.equal = function (proj) {
return proj.id == this.id;
};
Proj._counter = 0;
export { Proj };

552
src/og/renderer/Renderer.js Normal file
View File

@ -0,0 +1,552 @@
/**
* @module og/renderer/Renderer
*/
'use strict';
import { Camera } from '../camera/Camera.js';
import { Framebuffer } from '../webgl/Framebuffer.js';
import { MultiFramebuffer } from '../webgl/MultiFramebuffer.js';
import { randomi } from '../math.js';
import { RendererEvents } from './RendererEvents.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
import { cons } from '../cons.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
import { input } from '../input/input.js';
/**
* Represents high level WebGL context interface that starts WebGL handler works real time.
* @class
* @param {og.webgl.Handler} handler - WebGL handler context.
* @param {Object} [params] - Renderer parameters:
* @fires og.RendererEvents#draw
* @fires og.RendererEvents#resize
* @fires og.RendererEvents#mousemove
* @fires og.RendererEvents#mousestop
* @fires og.RendererEvents#lclick
* @fires og.RendererEvents#rclick
* @fires og.RendererEvents#mclick
* @fires og.RendererEvents#ldblclick
* @fires og.RendererEvents#rdblclick
* @fires og.RendererEvents#mdblclick
* @fires og.RendererEvents#lup
* @fires og.RendererEvents#rup
* @fires og.RendererEvents#mup
* @fires og.RendererEvents#ldown
* @fires og.RendererEvents#rdown
* @fires og.RendererEvents#mdown
* @fires og.RendererEvents#lhold
* @fires og.RendererEvents#rhold
* @fires og.RendererEvents#mhold
* @fires og.RendererEvents#mousewheel
* @fires og.RendererEvents#touchstart
* @fires og.RendererEvents#touchend
* @fires og.RendererEvents#touchcancel
* @fires og.RendererEvents#touchmove
* @fires og.RendererEvents#doubletouch
* @fires og.RendererEvents#touchleave
* @fires og.RendererEvents#touchenter
*/
const Renderer = function (handler, params) {
params = params || {};
/**
* Div element with WebGL canvas.
* @public
* @type {object}
*/
this.div = null;
/**
* WebGL handler context.
* @public
* @type {og.webgl.Handler}
*/
this.handler = handler;
/**
* Render nodes drawing queue.
* @private
* @type {Array.<og.scene.RenderNode>}
*/
this._renderNodesArr = [];
/**
* Render nodes store for the comfortable access by the node name.
* @public
* @type {Object.<og.scene.RenderNode>}
*/
this.renderNodes = {};
/**
* Cameras array.
* @public
* @type {Array.<og.Camera>}
*/
this.cameras = [];
/**
* Current active camera.
* @public
* @type {og.Camera}
*/
this.activeCamera = null;
/**
* Renderer events. Represents interface for setting events like mousemove, draw, keypress etc.
* @public
* @type {og.RendererEvents}
*/
this.events = new RendererEvents(this);
/**
* OpenGlobus controls array.
* @public
* @type {Array.<og.control.Control>}
*/
this.controls = params.controls || [];
/**
* Provides exchange between controls.
* @public
* @type {Object}
*/
this.controlsBag = {};
/**
* Hash table for drawing objects.
* @public
* @type {Object}
*/
this.colorObjects = {};
/**
* Color picking objects rendering queue.
* @type {Array.<og.Renderer~pickingCallback>}
*/
this._pickingCallbacks = [];
/**
* Picking objects(labels and billboards) framebuffer.
* @public
* @type {og.webgl.Framebuffer}
*/
this.pickingFramebuffer = null;
/**
* Whole scene rendering framebuffer.
* @public
* @type {og.webgl.Framebuffer|og.webgl.MultiFramebuffer}
*/
this.sceneFramebuffer = null;
/**
* Stores current picking rgb color.
* @private
* @type {Array.<number,number,number>}
*/
this._currPickingColor = [0, 0, 0];
/**
* Stores previous picked rgb color.
* @private
* @type {Array.<number,number,number>}
*/
this._prevPickingColor = [0, 0, 0];
/**
* @private
*/
this._fnScreenFrame = null;
if (params.autoActivate) {
this.initialize();
this.start();
}
}
/**
* Adds picking rendering callback function.
* @param {object} sender - Callback context.
* @param {og.Renderer~pickingCallback} callback - Rendering callback.
*/
Renderer.prototype.addPickingCallback = function (sender, callback) {
this._pickingCallbacks.push({ "callback": callback, "sender": sender });
}
/**
* Assign picking color to the object.
* @public
* @param {Object} obj - Object that pressume to be picked.
*/
Renderer.prototype.assignPickingColor = function (obj) {
if (!obj._pickingColor || obj._pickingColor.isZero()) {
var r = 0, g = 0, b = 0;
var str = "0_0_0";
while (!(r || g || b) || this.colorObjects[str]) {
r = randomi(1, 255);
g = randomi(1, 255);
b = randomi(1, 255);
str = r + "_" + g + "_" + b;
}
if (!obj._pickingColor)
obj._pickingColor = new Vec3(r, g, b);
else
obj._pickingColor.set(r, g, b);
this.colorObjects[str] = obj;
}
}
/**
* Removes picking color from object.
* @public
* @param {Object} obj - Object to remove picking color.
*/
Renderer.prototype.clearPickingColor = function (obj) {
if (!obj._pickingColor.isZero()) {
var c = obj._pickingColor;
if (!c.isZero()) {
this.colorObjects[c.x + "_" + c.y + "_" + c.z] = null;
delete this.colorObjects[c.x + "_" + c.y + "_" + c.z];
c.x = c.y = c.z = 0;
}
}
}
/**
* Get the client width.
* @public
* @returns {number} -
*/
Renderer.prototype.getWidth = function () {
return this.handler.canvas.width;
}
/**
* Get the client height.
* @public
* @returns {number} -
*/
Renderer.prototype.getHeight = function () {
return this.handler.canvas.height;
}
/**
* Get center of the screen
* @public
* @returns {og.math.Vec2} -
*/
Renderer.prototype.getCenter = function () {
var cnv = this.handler.canvas;
return new Vec2(Math.round(cnv.width * 0.5), Math.round(cnv.height * 0.5));
}
/**
* Add the given control to the renderer.
* @param {og.control.BaseControl} control - Control.
*/
Renderer.prototype.addControl = function (control) {
control.addTo(this);
}
/**
* Add the given controls array to the planet node.
* @param {Array.<og.control.BaseControl>} cArr - Control array.
*/
Renderer.prototype.addControls = function (cArr) {
for (var i = 0; i < cArr.length; i++) {
cArr[i].addTo(this);
}
}
/**
* Remove control from the renderer.
* @param {og.control.BaseControl} control - Control.
* @return {og.control.BaseControl|undefined} -
*/
Renderer.prototype.removeControl = function (control) {
for (var i = 0; i < this.controls.length; i++) {
if (this.controls[i].isEqual(control)) {
this.controls.splice(i, 1);
control.remove();
return control;
}
}
return undefined;
}
/**
* Renderer initialization.
* @public
*/
Renderer.prototype.initialize = function () {
var that = this;
this.handler.setFrameCallback(function () {
that.draw();
});
this.activeCamera = new Camera(this, {
'eye': new Vec3(0, 0, 0),
'look': new Vec3(0, 0, -1),
'up': new Vec3(0, 1, 0)
});
this.events.initialize();
//Bind console key
this.events.on("charkeypress", input.KEY_APOSTROPHE, function () {
cons.setVisibility(!cons.getVisibility());
});
this.handler.onCanvasResize = function (obj) {
that.activeCamera.setAspectRatio(obj.clientWidth / obj.clientHeight);
that.sceneFramebuffer.setSize(obj.clientWidth, obj.clientHeight);
that.events.dispatch(that.events.resize, obj);
};
this.pickingFramebuffer = new Framebuffer(this.handler, {
'width': 640,
'height': 480
});
this.pickingFramebuffer.init();
this.handler.addShaderProgram(new ShaderProgram("screenFrame", {
uniforms: {
texture: { type: types.SAMPLER2D }
},
attributes: {
corners: { type: types.VEC3, enableArray: true },
},
vertexShader:
'attribute vec2 corners;\
\
varying vec2 tc;\
void main(void) {\
gl_Position = vec4(corners, 0.0, 1.0);\
tc = corners * 0.5 + 0.5;\
}',
fragmentShader:
'precision highp float;\
uniform sampler2D texture;\
\
varying vec2 tc;\
\
void main(void) {\
gl_FragColor = texture2D( texture, tc );\
}'
}));
//Append multiframebuffer(WEBGL_draw_buffers) extension.
this._drawBuffersExtension = this.handler.initializeExtension("WEBGL_draw_buffers");
if (this._drawBuffersExtension) {
this.sceneFramebuffer = new MultiFramebuffer(this.handler, { size: 3 });
this.sceneFramebuffer.init();
this._fnScreenFrame = this._multiframebufferScreenFrame;
} else {
this.sceneFramebuffer = new Framebuffer(this.handler);
this.sceneFramebuffer.init();
this._fnScreenFrame = this._singleframebufferScreenFrame;
}
this._screenFrameCornersBuffer = this.handler.createArrayBuffer(new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), 2, 4);
let _c = this.controls;
this.controls = [];
this.addControls(_c);
}
/**
* Adds render node to the renderer.
* @public
* @param {og.scene.RenderNode} renderNode - Render node.
*/
Renderer.prototype.addRenderNode = function (renderNode) {
if (!this.renderNodes[renderNode.name]) {
renderNode.assignRenderer(this);
this._renderNodesArr.unshift(renderNode);
this.renderNodes[renderNode.name] = renderNode;
} else {
cons.logWrn("og.Renderer(370) - node name: " + renderNode.name + " allready exists.");
}
}
/**
* Adds render nodes array to the renderer.
* @public
* @param {Array.<og.scene.RenderNode>} nodesArr - Render nodes array.
*/
Renderer.prototype.addRenderNodes = function (nodesArr) {
for (var i = 0; i < nodesArr.length; i++) {
this.addRenderNode(nodesArr[i]);
}
}
/**
* Draw nodes.
* @public
*/
Renderer.prototype.draw = function () {
this.activeCamera.checkMoveEnd();
var e = this.events;
e.handleEvents();
e.dispatch(e.draw, this);
var sfb = this.sceneFramebuffer;
sfb.activate();
var h = this.handler;
h.clearFrame();
h.gl.activeTexture(h.gl.TEXTURE0);
h.gl.bindTexture(h.gl.TEXTURE_2D, h.transparentTexture);
//Rendering scene nodes
var rn = this._renderNodesArr;
var i = rn.length;
while (i--) {
rn[i].drawNode();
}
sfb.deactivate();
//Rendering picking callbacks and refresh pickingColor
this._drawPickingBuffer();
//Rendering on the screen
this._fnScreenFrame();
e.mouseState.moving = false;
e.touchState.moving = false;
}
Renderer.prototype._multiframebufferScreenFrame = function () {
var h = this.handler;
var sh = h.shaderPrograms.screenFrame,
p = sh._program,
gl = h.gl;
gl.disable(gl.DEPTH_TEST);
sh.activate();
gl.activeTexture(gl.TEXTURE0);
//MAYBE: Could be refactored with framebuf function like getTexture()
gl.bindTexture(gl.TEXTURE_2D, this.sceneFramebuffer.textures[0]);
//gl.bindTexture(gl.TEXTURE_2D, this.pickingFramebuffer.texture);
gl.uniform1i(p.uniforms.texture._pName, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._screenFrameCornersBuffer);
gl.vertexAttribPointer(p.attributes.corners._pName, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.enable(gl.DEPTH_TEST);
}
Renderer.prototype._singleframebufferScreenFrame = function () {
var h = this.handler;
var sh = h.shaderPrograms.screenFrame,
p = sh._program,
gl = h.gl;
gl.disable(gl.DEPTH_TEST);
sh.activate();
gl.activeTexture(gl.TEXTURE0);
//gl.bindTexture(gl.TEXTURE_2D, this.pickingFramebuffer.texture);
//gl.bindTexture(gl.TEXTURE_2D, globus.planet._heightPickingFramebuffer.texture);
gl.bindTexture(gl.TEXTURE_2D, this.sceneFramebuffer.texture);
gl.uniform1i(p.uniforms.texture._pName, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._screenFrameCornersBuffer);
gl.vertexAttribPointer(p.attributes.corners._pName, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.enable(gl.DEPTH_TEST);
}
/**
* Returns picking object by screen coordinates
* @param {number} x - X position
* @param {number} y - Y position
* @return {Object} -
*/
Renderer.prototype.getPickingObject = function (x, y) {
var cnv = this.renderer.handler.canvas;
var c;
if (this._drawBuffersExtension) {
c = this.sceneFramebuffer.readPixel(x / cnv.width, (cnv.height - y) / cnv.height, 1);
} else {
c = this.sceneFramebuffer.readPixel(x / cnv.width, (cnv.height - y) / cnv.height);
}
return this.colorObjects[c[0] + "_" + c[1] + "_" + c[2]];
}
/**
* Returns true if 'WEBGL_draw_buffers' extension initialized.
* @public
* @returns {Boolean} -
*/
Renderer.prototype.isMultiFramebufferCompatible = function () {
return (this._drawBuffersExtension ? true : false);
}
/**
* Draw picking objects framebuffer.
* @private
*/
Renderer.prototype._drawPickingBuffer = function () {
this.pickingFramebuffer.activate();
var h = this.handler;
var gl = h.gl;
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.disable(h.gl.BLEND);
var dp = this._pickingCallbacks;
var i = dp.length;
while (i--) {
/**
* This callback renders picking frame.
* @callback og.Renderer~pickingCallback
*/
dp[i].callback.call(dp[i].sender);
}
this.pickingFramebuffer.deactivate();
var ms = this.events.mouseState;
var ts = this.events.touchState;
if (!(ms.leftButtonHold || ms.rightButtonHold || ms.middleButtonHold)) {
this._prevPickingColor[0] = this._currPickingColor[0];
this._prevPickingColor[1] = this._currPickingColor[1];
this._prevPickingColor[2] = this._currPickingColor[2];
var pc;
if (ts.x || ts.y) {
pc = this.pickingFramebuffer.readPixel(ts.nx, 1.0 - ts.ny);
if (!(pc[0] || pc[1] || pc[2]) && this._drawBuffersExtension)
pc = this.sceneFramebuffer.readPixel(ts.nx, 1.0 - ts.ny, 1);
} else {
pc = this.pickingFramebuffer.readPixel(ms.nx, 1.0 - ms.ny);
if (!(pc[0] || pc[1] || pc[2]) && this._drawBuffersExtension)
pc = this.sceneFramebuffer.readPixel(ms.nx, 1.0 - ms.ny, 1);
}
this._currPickingColor = pc;
}
};
/**
* Function starts rendering.
* @public
*/
Renderer.prototype.start = function () {
this.handler.start();
}
export { Renderer };

View File

@ -0,0 +1,890 @@
/**
* @module og/renderer/RendererEvents
*/
'use strict';
import { Events } from '../Events.js';
import { input } from '../input/input.js';
import { KeyboardHandler } from '../input/KeyboardHandler.js';
import { MouseHandler } from '../input/MouseHandler.js';
import { TouchHandler } from '../input/TouchHandler.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
/**
* Renderer events handler.
* @class
* @param {og.Renderer} renderer - Renderer object, events that works for.
*/
class RendererEvents extends Events {
constructor(renderer) {
super(EVENT_NAMES);
/**
* Assigned renderer.
* @public
* @type {og.Renderer}
*/
this.renderer = renderer;
/**
* Low level touch events handler.
* @private
* @type {og.input.TouchHandler}
*/
this._touchHandler = new TouchHandler(renderer.handler.canvas);
/**
* Low level mouse events handler.
* @private
* @type {og.input.MouseHandler}
*/
this._mouseHandler = new MouseHandler(renderer.handler.canvas);
/**
* Low level keyboard events handler.
* @private
* @type {og.input.KeyboardHandler}
*/
this._keyboardHandler = new KeyboardHandler();
/**
* Current mouse state.
* @public
* @enum {Object}
*/
this.mouseState = {
/** Current mouse X position. */
x: 0,
/** Current mouse Y position. */
y: 0,
/** Current mouse X position from 0 to 1 */
nx: 0,
/** Current mouse Y position from 0 to 1 */
ny: 0,
/** Previous mouse X position. */
prev_x: 0,
/** Previous mouse Y position. */
prev_y: 0,
/** Screen mouse position world direction. */
direction: new Vec3(),
/** Left mouse button has stopped pushing down right now.*/
leftButtonUp: false,
/** Right mouse button has stopped pushing down right now.*/
rightButtonUp: false,
/** Middle mouse button has stopped pushing down right now.*/
middleButtonUp: false,
/** Left mouse button has pushed now.*/
leftButtonDown: false,
/** Right mouse button has pushed now.*/
rightButtonDown: false,
/** Middle mouse button has pushed now.*/
middleButtonDown: false,
/** Left mouse button is pushing.*/
leftButtonHold: false,
/** Right mouse button is pushing.*/
rightButtonHold: false,
/** Middle mouse button is pushing.*/
middleButtonHold: false,
/** Left mouse button has clicked twice now.*/
leftButtonDoubleClick: false,
/** Right mouse button has clicked twice now.*/
rightButtonDoubleClick: false,
/** Middle mouse button has clicked twice now.*/
middleButtonDoubleClick: false,
/** Left mouse button has clicked now. */
leftButtonClick: false,
/** Right mouse button has clicked now. */
rightButtonClick: false,
/** Middle mouse button has clicked now. */
middleButtonClick: false,
/** Mouse is moving now. */
moving: false,
/** Mouse has just stopped now. */
justStopped: false,
/** Mose double click delay response.*/
doubleClickDelay: 300,
/** Mouse wheel. */
wheelDelta: 0,
/** JavaScript mouse system event message. */
sys: null,
/** Current picking object. */
pickingObject: null
};
/**
* Current touch state.
* @public
* @enum {Object}
*/
this.touchState = {
/** Touching is moving now. */
moving: false,
/** Touch has ended right now.*/
touchEnd: false,
/** Touch has started right now.*/
touchStart: false,
/** Touch canceled.*/
touchCancel: false,
/** Touched twice.*/
doubleTouch: false,
/** Double touching responce delay.*/
doubleTouchDelay: 550,
/** Double touching responce radius in screen pixels.*/
doubleTouchRadius: 10,
/** Current touch X - coordinate. */
x: 0,
/** Current touch Y - coordinate. */
y: 0,
/** Current touch X - coordinate from 0 to 1 */
nx: 0,
/** Current touch Y - coordinate from 0 to 1 */
ny: 0,
/** Previous touch X coordinate. */
prev_x: 0,
/** Previous touch Y coordinate. */
prev_y: 0,
/** JavaScript touching system event message. */
sys: null,
/** Current touched(picking) object. */
pickingObject: null
};
this._dblTchCoords = new Vec2();
this._oneTouchStart = false;
this._dblTchBegins = 0;
this._mousestopThread = null;
this._ldblClkBegins = 0;
this._rdblClkBegins = 0;
this._mdblClkBegins = 0;
this._lclickX = 0;
this._lclickY = 0;
this._rclickX = 0;
this._rclickY = 0;
this._mclickX = 0;
this._mclickY = 0;
}
/**
* Used in render node frame.
* @public
*/
handleEvents() {
this.mouseState.direction = this.renderer.activeCamera.unproject(this.mouseState.x, this.mouseState.y);
this.entityPickingEvents();
this._keyboardHandler.handleEvents();
this.handleMouseEvents();
this.handleTouchEvents();
}
/**
* Set render event callback.
* @public
* @param {string} name - Event name
* @param {eventCallback} callback - Callback function
* @param {number} [key] - Key code from og.input
* @param {*} sender - Callback context
* @param {number} [priority] - Event callback priority
*/
on(name, p0, p1, p2, p3) {
if (name == "keypress" || name == "charkeypress") {
this._keyboardHandler.addEvent(name, p2, p1, p0, p3);
} else {
super.on(name, p0, p1);
}
}
/**
* Check key is pressed.
* @public
* @param {number} keyCode - Key code
* @return {boolean}
*/
isKeyPressed(keyCode) {
return this._keyboardHandler.isKeyPressed(keyCode);
}
/**
* Renderer events initialization.
* @public
*/
initialize() {
this._mouseHandler.setEvent("mouseup", this, this.onMouseUp);
this._mouseHandler.setEvent("mousemove", this, this.onMouseMove);
this._mouseHandler.setEvent("mousedown", this, this.onMouseDown);
this._mouseHandler.setEvent("mousewheel", this, this.onMouseWheel);
this._touchHandler.setEvent("touchstart", this, this.onTouchStart);
this._touchHandler.setEvent("touchend", this, this.onTouchEnd);
this._touchHandler.setEvent("touchcancel", this, this.onTouchCancel);
this._touchHandler.setEvent("touchmove", this, this.onTouchMove);
}
/**
* @private
*/
onMouseWheel(event) {
this.mouseState.wheelDelta = event.wheelDelta;
}
/**
* @private
*/
onMouseMove(event) {
var ms = this.mouseState;
ms.sys = event;
if (ms.x === event.clientX && ms.y === event.clientY) {
return;
}
this._ldblClkBegins = 0;
this._rdblClkBegins = 0;
this._mdblClkBegins = 0;
ms.x = event.clientX;
ms.y = event.clientY;
var cnv = this.renderer.handler.canvas;
ms.nx = ms.x / cnv.width;
ms.ny = ms.y / cnv.height;
ms.moving = true;
//dispatch stop mouse event
clearTimeout(this._mousestopThread);
var that = this;
this._mousestopThread = setTimeout(function () {
ms.justStopped = true;
}, 100);
}
/**
* @private
*/
onMouseDown(event) {
if (event.button === input.MB_LEFT) {
this._lclickX = event.clientX;
this._lclickY = event.clientY;
this.mouseState.sys = event;
this.mouseState.leftButtonDown = true;
} else if (event.button === input.MB_RIGHT) {
this._rclickX = event.clientX;
this._rclickY = event.clientY;
this.mouseState.sys = event;
this.mouseState.rightButtonDown = true;
} else if (event.button === input.MB_MIDDLE) {
this._mclickX = event.clientX;
this._mclickY = event.clientY;
this.mouseState.sys = event;
this.mouseState.middleButtonDown = true;
}
}
/**
* @private
*/
onMouseUp(event) {
var ms = this.mouseState;
ms.sys = event;
if (event.button === input.MB_LEFT) {
ms.leftButtonDown = false;
ms.leftButtonUp = true;
if (this._lclickX === event.clientX &&
this._lclickY === event.clientY) {
if (this._ldblClkBegins) {
var deltatime = new Date().getTime() - this._ldblClkBegins;
if (deltatime <= ms.doubleClickDelay) {
ms.leftButtonDoubleClick = true;
}
this._ldblClkBegins = 0;
} else {
this._ldblClkBegins = new Date().getTime();
}
ms.leftButtonClick = true;
}
} else if (event.button === input.MB_RIGHT) {
ms.rightButtonDown = false;
ms.rightButtonUp = true;
if (this._rclickX === event.clientX &&
this._rclickY === event.clientY) {
if (this._rdblClkBegins) {
var deltatime = new Date().getTime() - this._rdblClkBegins;
if (deltatime <= ms.doubleClickDelay) {
ms.rightButtonDoubleClick = true;
}
this._rdblClkBegins = 0;
} else {
this._rdblClkBegins = new Date().getTime();
}
ms.rightButtonClick = true;
}
} else if (event.button === input.MB_MIDDLE) {
ms.middleButtonDown = false;
ms.middleButtonUp = true;
if (this._mclickX === event.clientX &&
this._mclickY === event.clientY) {
if (this._mdblClkBegins) {
var deltatime = new Date().getTime() - this._mdblClkBegins;
if (deltatime <= ms.doubleClickDelay) {
ms.middleButtonDoubleClick = true;
}
this._mdblClkBegins = 0;
} else {
this._mdblClkBegins = new Date().getTime();
}
ms.middleButtonClick = true;
}
}
}
/**
* @private
*/
onTouchStart(event) {
var ts = this.touchState;
ts.sys = event;
ts.x = event.touches.item(0).clientX - event.offsetLeft;
ts.y = event.touches.item(0).clientY - event.offsetTop;
var cnv = this.renderer.handler.canvas;
ts.nx = ts.x / cnv.width;
ts.ny = ts.y / cnv.height;
ts.prev_x = ts.x;
ts.prev_y = ts.y;
ts.touchStart = true;
if (event.touches.length === 1) {
this._dblTchCoords.x = ts.x;
this._dblTchCoords.y = ts.y;
this._oneTouchStart = true;
} else {
this._oneTouchStart = false;
}
}
/**
* @private
*/
onTouchEnd(event) {
var ts = this.touchState;
ts.sys = event;
ts.touchEnd = true;
if (event.touches.length === 0) {
ts.prev_x = ts.x;
ts.prev_y = ts.y;
if (this._oneTouchStart) {
if (this._dblTchBegins) {
var deltatime = new Date().getTime() - this._dblTchBegins;
if (deltatime <= ts.doubleTouchDelay) {
ts.doubleTouch = true;
}
this._dblTchBegins = 0;
}
this._dblTchBegins = new Date().getTime();
this._oneTouchStart = false;
}
}
}
/**
* @private
*/
onTouchCancel(event) {
var ts = this.touchState;
ts.sys = event;
ts.touchCancel = true;
}
/**
* @private
*/
onTouchMove(event) {
var ts = this.touchState;
ts.x = event.touches.item(0).clientX - event.offsetLeft;
ts.y = event.touches.item(0).clientY - event.offsetTop;
var cnv = this.renderer.handler.canvas;
ts.nx = ts.x / cnv.width;
ts.ny = ts.y / cnv.height;
ts.sys = event;
ts.moving = true;
this._dblTchBegins = 0;
this._oneTouchStart = false;
}
/**
* @private
*/
entityPickingEvents() {
var ts = this.touchState,
ms = this.mouseState;
if (!(ms.leftButtonHold || ms.rightButtonHold || ms.middleButtonHold)) {
var r = this.renderer;
var o = r.colorObjects;
var c = r._currPickingColor,
p = r._prevPickingColor;
ms.pickingObject = null;
ts.pickingObject = null;
var co = o && o[c[0] + "_" + c[1] + "_" + c[2]];
ms.pickingObject = co;
ts.pickingObject = co;
//object changed
if (c[0] != p[0] || c[1] != p[1] || c[2] != p[2]) {
//current black
if (!(c[0] || c[1] || c[2])) {
var po = o[p[0] + "_" + p[1] + "_" + p[2]];
if (po) {
//TODO: This is a reason to replace geometryHandler to an entityCollection...or maybe not
var pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
ms.pickingObject = po;
pe.dispatch(pe.mouseleave, ms);
ts.pickingObject = po;
pe.dispatch(pe.touchleave, ts);
}
} else {
//current not black
//previous not black
if (p[0] || p[1] || p[2]) {
var po = o[p[0] + "_" + p[1] + "_" + p[2]];
if (po) {
var pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
ms.pickingObject = po;
pe.dispatch(pe.mouseleave, ms);
ts.pickingObject = po;
pe.dispatch(pe.touchleave, ts);
}
}
if (co) {
var ce = co.events || co._entityCollection && co._entityCollection.events || co._layer.events;
ms.pickingObject = co;
ce.dispatch(ce.mouseenter, ms);
ts.pickingObject = co;
ce.dispatch(ce.touchenter, ts);
}
}
}
}
}
/**
* @private
*/
handleMouseEvents() {
let ms = this.mouseState;
let po = ms.pickingObject,
pe = null;
if (ms.leftButtonClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.lclick, ms);
}
this.dispatch(this.lclick, ms);
ms.leftButtonClick = false;
}
if (ms.rightButtonClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.rclick, ms);
}
this.dispatch(this.rclick, ms);
ms.rightButtonClick = false;
}
if (ms.middleButtonClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mclick, ms);
}
this.dispatch(this.mclick, ms);
ms.middleButtonClick = false;
}
if (ms.leftButtonDown) {
if (ms.leftButtonHold) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.lhold, ms);
}
this.dispatch(this.lhold, ms);
} else {
ms.leftButtonHold = true;
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.ldown, ms);
}
this.dispatch(this.ldown, ms);
}
}
if (ms.rightButtonDown) {
if (ms.rightButtonHold) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.rhold, ms);
}
this.dispatch(this.rhold, ms);
} else {
ms.rightButtonHold = true;
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.rdown, ms);
}
this.dispatch(this.rdown, ms);
}
}
if (ms.middleButtonDown) {
if (ms.middleButtonHold) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mhold, ms);
}
this.dispatch(this.mhold, ms);
} else {
ms.middleButtonHold = true;
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mdown, ms);
}
this.dispatch(this.mdown, ms);
}
}
if (ms.leftButtonUp) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.lup, ms);
}
this.dispatch(this.lup, ms);
ms.leftButtonUp = false;
ms.leftButtonHold = false;
}
if (ms.rightButtonUp) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.rup, ms);
}
this.dispatch(this.rup, ms);
ms.rightButtonUp = false;
ms.rightButtonHold = false;
}
if (ms.middleButtonUp) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mup, ms);
}
this.dispatch(this.mup, ms);
ms.middleButtonUp = false;
ms.middleButtonHold = false;
}
if (ms.leftButtonDoubleClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.ldblclick, ms);
}
this.dispatch(this.ldblclick, ms);
ms.leftButtonDoubleClick = false;
}
if (ms.rightButtonDoubleClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.rdblclick, ms);
}
this.dispatch(this.rdblclick, ms);
ms.rightButtonDoubleClick = false;
}
if (ms.middleButtonDoubleClick) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mdblclick, ms);
}
this.dispatch(this.mdblclick, ms);
ms.middleButtonDoubleClick = false;
}
if (ms.wheelDelta) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mousewheel, ms);
}
this.dispatch(this.mousewheel, ms);
ms.wheelDelta = 0;
}
if (ms.moving) {
if (po) {
pe = po.events || po._entityCollection && po._entityCollection.events || po._layer.events;
pe.dispatch(pe.mousemove, ms);
}
this.dispatch(this.mousemove, ms);
ms.prev_x = ms.x;
ms.prev_y = ms.y;
}
if (ms.justStopped) {
this.dispatch(this.mousestop, ms);
ms.justStopped = false;
}
}
/**
* @private
*/
handleTouchEvents() {
var ts = this.touchState;
var tpo = ts.pickingObject,
tpe = null;
if (ts.touchCancel) {
this.dispatch(this.touchcancel, ts);
ts.touchCancel = false;
}
if (ts.touchStart) {
var r = this.renderer;
r._currPickingColor = (r._drawBuffersExtension ?
r.pickingFramebuffer.readPixel(ts.nx, 1.0 - ts.ny, 1) :
r.pickingFramebuffer.readPixel(ts.nx, 1.0 - ts.ny));
var o = r.colorObjects;
var c = r._currPickingColor;
var co = o[c[0] + "_" + c[1] + "_" + c[2]];
tpo = ts.pickingObject = co;
if (tpo) {
tpe = tpo.events || tpo._entityCollection && tpo._entityCollection.events || tpo._layer.events;
tpe.dispatch(tpe.touchstart, ts);
}
this.dispatch(this.touchstart, ts);
ts.touchStart = false;
}
if (ts.doubleTouch) {
if (tpo) {
tpe = tpo.events || tpo._entityCollection && tpo._entityCollection.events || tpo._layer.events;
tpe.dispatch(tpe.doubletouch, ts);
}
this.dispatch(this.doubletouch, ts);
ts.doubleTouch = false;
}
if (ts.touchEnd) {
if (tpo) {
tpe = tpo.events || tpo._entityCollection && tpo._entityCollection.events || tpo._layer.events;
tpe.dispatch(tpe.touchend, ts);
}
this.dispatch(this.touchend, ts);
ts.x = 0;
ts.y = 0;
ts.touchEnd = false;
}
if (ts.moving) {
if (tpo) {
tpe = tpo.events || tpo._entityCollection && tpo._entityCollection.events || tpo._layer.events;
tpe.dispatch(tpe.touchmove, ts);
}
this.dispatch(this.touchmove, ts);
ts.prev_x = ts.x;
ts.prev_y = ts.y;
}
}
};
const EVENT_NAMES = [
/**
* Triggered before scene frame is rendered(before render nodes).
* @event og.RendererEvents#draw
*/
"draw",
/**
* Triggered when screen is resized.
* @event og.RendererEvents#resize
*/
"resize",
/**
* Mouse is moving.
* @event og.RendererEvents#mousemove
*/
"mousemove",
/**
* Mouse is just stopped.
* @event og.RendererEvents#mousestop
*/
"mousestop",
/**
* Mouse left button clicked.
* @event og.RendererEvents#lclick
*/
"lclick",
/**
* Mouse right button clicked.
* @event og.RendererEvents#rclick
*/
"rclick",
/**
* Mouse middle button clicked.
* @event og.RendererEvents#mclick
*/
"mclick",
/**
* Mouse left button double click.
* @event og.RendererEvents#ldblclick
*/
"ldblclick",
/**
* Mouse right button double click.
* @event og.RendererEvents#rdblclick
*/
"rdblclick",
/**
* Mouse middle button double click.
* @event og.RendererEvents#mdblclick
*/
"mdblclick",
/**
* Mouse left button up(stop pressing).
* @event og.RendererEvents#lup
*/
"lup",
/**
* Mouse right button up(stop pressing).
* @event og.RendererEvents#rup
*/
"rup",
/**
* Mouse middle button up(stop pressing).
* @event og.RendererEvents#mup
*/
"mup",
/**
* Mouse left button is just pressed down(start pressing).
* @event og.RendererEvents#ldown
*/
"ldown",
/**
* Mouse right button is just pressed down(start pressing).
* @event og.RendererEvents#rdown
*/
"rdown",
/**
* Mouse middle button is just pressed down(start pressing).
* @event og.RendererEvents#mdown
*/
"mdown",
/**
* Mouse left button is pressing.
* @event og.RendererEvents#lhold
*/
"lhold",
/**
* Mouse right button is pressing.
* @event og.RendererEvents#rhold
*/
"rhold",
/**
* Mouse middle button is pressing.
* @event og.RendererEvents#mhold
*/
"mhold",
/**
* Mouse wheel is rotated.
* @event og.RendererEvents#mousewheel
*/
"mousewheel",
/**
* Triggered when touching starts.
* @event og.RendererEvents#touchstart
*/
"touchstart",
/**
* Triggered when touching ends.
* @event og.RendererEvents#touchend
*/
"touchend",
/**
* Triggered when touching cancel.
* @event og.RendererEvents#touchcancel
*/
"touchcancel",
/**
* Triggered when touch is move.
* @event og.RendererEvents#touchmove
*/
"touchmove",
/**
* Triggered when double touch.
* @event og.RendererEvents#doubletouch
*/
"doubletouch",
/**
* Triggered when touch leaves picked object.
* @event og.RendererEvents#touchleave
*/
"touchleave",
/**
* Triggered when touch enter picking object.
* @event og.RendererEvents#touchenter
*/
"touchenter"
];
export { RendererEvents };

79
src/og/scene/Axes.js Normal file
View File

@ -0,0 +1,79 @@
/**
* @module og/scene/Axes
*/
'use strict';
import { RenderNode } from './RenderNode.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
class Axes extends RenderNode {
constructor(size) {
super("Axes");
this.size = size || 100;
this.axesBuffer = null;
this.axesColorBuffer = null;
}
initialization() {
this.createAxisBuffer(this.size);
this.drawMode = this.renderer.handler.gl.LINES;
this.renderer.handler.addShaderProgram(new ShaderProgram("axesShader", {
uniforms: {
projectionViewMatrix: { type: types.MAT4 }
},
attributes: {
aVertexPosition: { type: types.VEC3, enableArray: true },
aVertexColor: { type: types.VEC4, enableArray: true }
},
vertexShader:
'attribute vec3 aVertexPosition;\
attribute vec4 aVertexColor;\
uniform mat4 projectionViewMatrix;\
varying vec4 vColor;\
void main(void) {\
gl_Position = projectionViewMatrix * vec4(aVertexPosition, 1.0);\
vColor = aVertexColor;\
}',
fragmentShader:
'precision highp float;\
varying vec4 vColor;\
void main(void) {\
gl_FragColor = vColor;\
}'
}));
}
frame() {
this.renderer.handler.shaderPrograms.axesShader.activate().set({
projectionViewMatrix: this.renderer.activeCamera._projectionViewMatrix._m,
aVertexPosition: this.axisBuffer,
aVertexColor: this.axisColorBuffer
});
this.renderer.handler.shaderPrograms.axesShader.drawArray(this.drawMode, this.axisBuffer.numItems);
}
createAxisBuffer(gridSize) {
var vertices = [
0.0, 0.0, 0.0, gridSize - 1, 0.0, 0.0, // x - R
0.0, 0.0, 0.0, 0.0, gridSize - 1, 0.0, // y - B
0.0, 0.0, 0.0, 0.0, 0.0, gridSize - 1 // z - G
];
var colors = [
1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // x - R
0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, // y - B
0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 // z - G
];
this.axisBuffer = this.renderer.handler.createArrayBuffer(new Float32Array(vertices), 3, 6);
this.axisColorBuffer = this.renderer.handler.createArrayBuffer(new Float32Array(colors), 4, 6);
}
};
export { Axes };

1521
src/og/scene/Planet.js Normal file

File diff suppressed because it is too large Load Diff

378
src/og/scene/RenderNode.js Normal file
View File

@ -0,0 +1,378 @@
/**
* @module og/scene/RenderNode
*/
'use strict';
import { BaseNode } from './BaseNode.js';
import { Events } from '../Events.js';
import { FontAtlas } from '../utils/FontAtlas.js';
import { TextureAtlas } from '../utils/TextureAtlas.js';
/**
* Render node is a logical part of a render mechanism. Represents scene rendering.
* Forexample one scene node for rendering the Earth, another one for rendering the Moon, another node for rendering stars etc.
* Each render node has own model view space defined with matrices(scale, rotation, translation, transformation).
* There are collections of ligh sources, entities and so on in the node.
* Access to the node is renderer.renderNodes["Earth"]
* @class
* @extends {og.scene.Node}
* @param {string} name - Node name.
*/
class RenderNode extends BaseNode {
constructor(name) {
super(name);
/**
* Renderer that calls frame() callback.
* @public
* @type {og.Renderer}
*/
this.renderer = null;
this.drawMode = null;
/** Show rendering.
* @public
*/
this.show = true;
this._isActive = true;
/**
* Lighting calculations.
* @public
* @type {boolean}
*/
this.lightEnabled = false;
/**
* Point light array.
* @private
* @type {Array.<og.LightSource>}
*/
this._lights = [];
this._lightsTransformedPositions = [];
this._lightsParamsv = [];
this._lightsParamsf = [];
this._lightsNames = [];
/**
* Entity collection array.
* @public
* @type {Array.<og.EntityCollection>}
*/
this.entityCollections = [];
/**
* Texture atlas for the billboards images. One atlas per node.
* @protected
* @type {og.utils.TextureAtlas}
*/
this.billboardsTextureAtlas = new TextureAtlas();
/**
* Texture font atlas for the font families and styles. One atlas per node.
* @public
* @type {og.utils.FontAtlas}
*/
this.fontAtlas = new FontAtlas();
/**
* Render node events.
* @public
* @type {og.Events}
*/
this.events = new Events();
}
/**
* Assign render node with renderer.
* @public
* @param {og.Renderer} renderer - Redner node's renderer.
*/
assignRenderer(renderer) {
this.renderer = renderer;
this.billboardsTextureAtlas.assignHandler(renderer.handler);
this.fontAtlas.assignHandler(renderer.handler);
renderer.addPickingCallback(this, this._entityCollectionPickingCallback);
for (var i = 0; i < this.entityCollections.length; i++) {
this.entityCollections[i].setRenderer(renderer);
}
this.initialization && this.initialization();
}
/**
* Adds entity collection.
* @public
* @param {og.EntityCollection} entityCollection - Entity collection.
* @param {boolean} [isHidden] - If it's true that this collection has specific rendering.
* @returns {og.scene.RenderNode}
*/
addEntityCollection(entityCollection, isHidden) {
entityCollection.addTo(this, isHidden);
return this;
}
/**
* Removes entity collection.
* @public
* @param {og.EntityCollection} entityCollection - Entity collection for remove.
*/
removeEntityCollection(entityCollection) {
entityCollection.remove();
}
/**
* Adds point light source.
* @public
* @param {og.LightSource} light - Light source.
* @returns {og.scene.RenderNode}
*/
addLight(light) {
light.addTo(this);
return this;
}
/**
* Gets light object by its name.
* @public
* @param {string} name - Point light name.
* @returns {og.LightSource}
*/
getLightByName(name) {
var li = this._lightsNames.indexOf(name);
return this._lights[li];
}
/**
* Removes light source.
* @public
* @param {og.LightSource} light - Light source object.
*/
removeLight(light) {
light.remove();
}
/**
* Calls render frame node's callback. Used in renderer.
* @public
*/
drawNode() {
this._isActive && this._drawNodes();
}
/**
* Gets render node activity.
* @public
* @returns {boolean}
*/
isActive() {
return this._isActive;
}
/**
* Rendering activation.
* @public
* @param {boolean} isActive - Activation flag.
*/
setActive(isActive) {
this._isActive = isActive;
for (var i = 0; i < this.childNodes.length; i++) {
this.childNodes[i].setActive(isActive);
}
}
/**
* @public
*/
setDrawMode(mode) {
this.drawMode = mode;
for (var i = 0; i < this.childNodes.length; i++) {
this.childNodes[i].setDrawMode(mode);
}
}
/**
* IMPORTANT: This function have to be called manualy in each render node frame callback, before drawing scene geometry.
* @public
*/
transformLights() {
var r = this.renderer;
for (var i = 0; i < this._lights.length; i++) {
var ii = i * 4;
var tp;
if (this._lights[i].directional) {
tp = r.activeCamera._normalMatrix.mulVec(this._lights[i]._position);
this._lightsTransformedPositions[ii + 3] = 0;
} else {
tp = r.activeCamera._viewMatrix.mulVec3(this._lights[i]._position);
this._lightsTransformedPositions[ii + 3] = 1;
}
this._lightsTransformedPositions[ii] = tp.x;
this._lightsTransformedPositions[ii + 1] = tp.y;
this._lightsTransformedPositions[ii + 2] = tp.z;
}
}
updateBillboardsTexCoords() {
for (var i = 0; i < this.entityCollections.length; i++) {
this.entityCollections[i].billboardHandler.refreshTexCoordsArr();
}
}
/**
* @private
*/
_drawNodes() {
for (var i = 0; i < this.childNodes.length; i++) {
if (this.childNodes[i]._isActive)
this.childNodes[i]._drawNodes();
}
if (this.show) {
if (this.frame) {
//this.lightEnabled && this.transformLights();
this.frame();
}
this.drawEntityCollections(this.entityCollections);
}
}
/**
* @public
*/
drawEntityCollections(ec) {
if (ec.length) {
var gl = this.renderer.handler.gl;
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
gl.disable(gl.CULL_FACE);
//Z-buffer offset
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.polygonOffset(0, -637000);
//billboards pass
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.billboardsTextureAtlas.texture);
var i = ec.length;
while (i--) {
var eci = ec[i];
if (eci._animatedOpacity) {
//first begin draw event
eci.events.dispatch(eci.events.draw, eci);
eci.billboardHandler.draw();
}
}
//labels pass
var fa = this.fontAtlas.atlasesArr;
for (i = 0; i < fa.length; i++) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, fa[i].texture);
}
i = ec.length;
while (i--) {
ec[i]._animatedOpacity && ec[i].labelHandler.draw();
}
//polyline pass
i = ec.length;
while (i--) {
ec[i]._animatedOpacity && ec[i].polylineHandler.draw();
}
//Z-buffer offset
gl.polygonOffset(0, 0);
gl.disable(gl.POLYGON_OFFSET_FILL);
gl.enable(gl.CULL_FACE);
//shapes pass
i = ec.length;
while (i--) {
var eci = ec[i];
if (eci._animatedOpacity) {
eci.shapeHandler.draw();
//post draw event
eci.events.dispatch(eci.events.drawend, eci);
}
}
//pointClouds pass
i = ec.length;
while (i--) {
ec[i]._animatedOpacity && ec[i].pointCloudHandler.draw();
}
}
}
/**
* @public
*/
drawPickingEntityCollections(ec) {
if (ec.length) {
var gl = this.renderer.handler.gl;
gl.disable(gl.CULL_FACE);
//Z-buffer offset
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.polygonOffset(0, -637000);
//billoard pass
var i = ec.length;
while (i--) {
ec[i]._visibility && ec[i].billboardHandler.drawPicking();
}
//label pass
i = ec.length;
while (i--) {
ec[i]._visibility && ec[i].labelHandler.drawPicking();
}
gl.polygonOffset(0, 0);
gl.disable(gl.POLYGON_OFFSET_FILL);
gl.enable(gl.CULL_FACE);
////polylines pass
//i = ec.length;
//while (i--) {
// ec[i]._visibility && ec[i].polylineHandler.drawPicking();
//}
////shapes pass
//i = ec.length;
//while (i--) {
// ec[i]._visibility && ec[i].shapeHandler.drawPicking();
//}
////pointClouds pass
//i = ec.length;
//while (i--) {
// ec[i]._visibility && ec[i].pointCloudHandler.drawPicking();
//}
}
}
/**
* Picking entity frame callback
* @private
*/
_entityCollectionPickingCallback() {
this.drawPickingEntityCollections(this.entityCollections);
}
};
export { RenderNode };

110
src/og/scene/SkyBox.js Normal file
View File

@ -0,0 +1,110 @@
/**
* @module og/scene/SkyBox
*/
'use strict';
import * as shaders from '../shaderProgram/skybox.js';
import { RenderNode } from './RenderNode.js';
const RESOURCES_URL = "";
class SkyBox extends RenderNode {
constructor(params) {
super("skybox");
this.params = params;
this.vertexPositionBuffer = null;
this.texture = null;
}
static createDefault(RESOURCES_URL) {
return new SkyBox({
"nx": RESOURCES_URL + "skybox/gal/_nx.jpg",
"px": RESOURCES_URL + "skybox/gal/_px.jpg",
"py": RESOURCES_URL + "skybox/gal/_py.jpg",
"ny": RESOURCES_URL + "skybox/gal/_ny.jpg",
"pz": RESOURCES_URL + "skybox/gal/_pz.jpg",
"nz": RESOURCES_URL + "skybox/gal/_nz.jpg"
});
}
initialization() {
this.renderer.handler.addShaderProgram(shaders.skybox(), true);
this.texture = this.renderer.handler.loadCubeMapTexture(this.params);
this._createBuffers();
this.drawMode = this.renderer.handler.gl.TRIANGLES;
}
frame() {
var h = this.renderer.handler;
var gl = h.gl;
var cam = this.renderer.activeCamera;
gl.disable(h.gl.DEPTH_TEST);
h.shaderPrograms.skybox.activate();
sh = h.shaderPrograms.skybox._program;
var shu = sh.uniforms;
gl.uniformMatrix4fv(shu.projectionViewMatrix._pName, false, cam._projectionViewMatrix._m);
gl.uniform3fv(shu.pos._pName, cam.eye.toVec());
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
gl.uniform1i(shu.uSampler._pName, 0);
var buf = this.vertexPositionBuffer;
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.vertexAttribPointer(sh.attributes.aVertexPosition._pName, buf.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(this.drawMode, 0, buf.numItems);
h.gl.enable(h.gl.DEPTH_TEST);
}
_createBuffers() {
var vertices = new Float32Array([
- 10000.0, 10000.0, - 10000.0,
- 10000.0, - 10000.0, - 10000.0,
10000.0, - 10000.0, - 10000.0,
10000.0, - 10000.0, - 10000.0,
10000.0, 10000.0, - 10000.0,
- 10000.0, 10000.0, - 10000.0,
- 10000.0, - 10000.0, 10000.0,
- 10000.0, - 10000.0, - 10000.0,
- 10000.0, 10000.0, - 10000.0,
- 10000.0, 10000.0, - 10000.0,
- 10000.0, 10000.0, 10000.0,
- 10000.0, - 10000.0, 10000.0,
10000.0, - 10000.0, - 10000.0,
10000.0, - 10000.0, 10000.0,
10000.0, 10000.0, 10000.0,
10000.0, 10000.0, 10000.0,
10000.0, 10000.0, - 10000.0,
10000.0, - 10000.0, - 10000.0,
- 10000.0, - 10000.0, 10000.0,
- 10000.0, 10000.0, 10000.0,
10000.0, 10000.0, 10000.0,
10000.0, 10000.0, 10000.0,
10000.0, - 10000.0, 10000.0,
- 10000.0, - 10000.0, 10000.0,
- 10000.0, 10000.0, - 10000.0,
10000.0, 10000.0, - 10000.0,
10000.0, 10000.0, 10000.0,
10000.0, 10000.0, 10000.0,
- 10000.0, 10000.0, 10000.0,
- 10000.0, 10000.0, - 10000.0,
- 10000.0, - 10000.0, - 10000.0,
- 10000.0, - 10000.0, 10000.0,
10000.0, - 10000.0, - 10000.0,
10000.0, - 10000.0, - 10000.0,
- 10000.0, - 10000.0, 10000.0,
10000.0, - 10000.0, 10000.0
]);
this.vertexPositionBuffer = this.renderer.handler.createArrayBuffer(vertices, 3, vertices.length / 3);
}
};
export { SkyBox };

430
src/og/shapes/BaseShape.js Normal file
View File

@ -0,0 +1,430 @@
goog.provide('og.shape.BaseShape');
goog.require('og.math.Vector3');
goog.require('og.math.Quaternion');
goog.require('og.math.Matrix4');
/**
* Base geometry shape class.
* @class
* @param {Object} options - Shape parameters:
* @param {og.math.Vector3} [options.position] - Shape position.
* @param {og.math.Quaternion} [options.orientation] - Shape orientation(rotation).
* @param {og.math.Vector3} [options.scale] - Scale vector.
* @param {Array.<number,number,number,number>} [options.color] - Shape RGBA color.
* @param {string} [options.src] - Texture image url source.
* @param {boolean} [options.visibility] - Shape visibility.
*/
og.shape.BaseShape = function (options) {
options = options || {};
/**
* Unic identifier.
* @public
* @readonly
* @type {number}
*/
this.id = og.shape.BaseShape.__staticId++;
/**
* Shape position.
* @public
* @type {og.math.Vector3}
*/
this.position = options.position || new og.math.Vector3();
/**
* Shape orientation(rotation)
* @public
* @type {og.math.Quaternion}
*/
this.orientation = options.orientation || new og.math.Quaternion(0.0, 0.0, 0.0, 1.0);
/**
* Scale.
* @public
* @type {og.math.Vector3}
*/
this.scale = options.scale || new og.math.Vector3(1.0, 1.0, 1.0);
/**
* Shape RGBA color.
* @public
* @type {Array.<number,number,number,number>}
*/
this.color = options.color || [1.0, 1.0, 1.0, 1.0];
/**
* Shape visibility.
* @public
* @type {boolean}
*/
this.visibility = (options.visibility != undefined ? options.visibility : true);
/**
* Image url source.
* @protected
* @type {string}
*/
this._src = options.src || null;
/**
* Vertices position gl buffer.
* @protected
*/
this._positionBuffer = null;
/**
* Vertices normal gl buffer.
* @protected
*/
this._normalBuffer = null;
/**
* Vertices indexes gl buffer.
* @protected
*/
this._indexBuffer = null;
/**
* Vertex texture coordinates gl buffer.
* @protected
*/
this._textureCoordBuffer = null;
/**
* Vertex positions.
* @protected
* @type {Array.<number>}
*/
this._positionData = [];
/**
* Vertex normals.
* @protected
* @type {Array.<number>}
*/
this._normalData = [];
/**
* Vertex indeces.
* @protected
* @type {Array.<number>}
*/
this._indexData = [];
/**
* Vertex texture coordinates.
* @protected
* @type {Array.<number>}
*/
this._textureCoordData = [];
/**
* Scale matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._mxScale = new og.math.Matrix4().setIdentity();
/**
* Translation matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._mxTranslation = new og.math.Matrix4().setIdentity();
/**
* Model matrix.
* @protected
* @type {og.math.Matrix4}
*/
this._mxModel = new og.math.Matrix4().setIdentity();
/**
* Gl texture pointer.
* @protected
*/
this.texture = null;
/**
* Assigned render node.
* @protected
* @type {og.scene.RenderNode}
*/
this._renderNode = null;
/**
* Assigned picking color.
* @protected
* @type {Array.<number,number,number>}
*/
this._pickingColor = [0.0, 0.0, 0.0, 0.0];
/**
* Entity instance that holds this shape.
* @protected
* @type {og.Entity}
*/
this._entity = null;
/**
* Handler that stores and renders this shape object.
* @protected
* @type {og.ShapeHandler}
*/
this._handler = null;
/**
* Shape handler array index.
* @protected
* @type {number}
*/
this._handlerIndex = -1;
};
og.shape.BaseShape.__staticId = 0;
/**
* Clear shape parameters.
* @public
*/
og.shape.BaseShape.prototype.clear = function () {
this.position.set(0.0, 0.0, 0.0);
this.orientation.set(0.0, 0.0, 0.0, 1.0);
this.scale.set(1.0, 1.0, 1.0);
this._positionData.length = 0;
this._normalData.length = 0;
this._indexData.length = 0;
this._mxScale.setIdentity();
this._mxTranslation.setIdentity();
this._mxModel.setIdentity();
this._renderNode.handler.gl.deleteTexture(this.texture);
this.texture = null;
this._deleteBuffers();
};
/**
* Sets shape color.
* @public
* @param {Array.<number,number,number,number>} color - RGBA color values array.
*/
og.shape.BaseShape.prototype.setColor = function (color) {
this.color[0] = color[0];
this.color[1] = color[1];
this.color[2] = color[2];
this.color[3] = color[3];
};
/**
* Sets shape color.
* @public
* @param {og.math.Vector4} color - RGBA color vector.
*/
og.shape.BaseShape.prototype.setColor4v = function (color) {
this.color[0] = color.x;
this.color[1] = color.y;
this.color[2] = color.z;
this.color[3] = color.w;
};
/**
* Sets shape opacity value.
* @public
* @param {number} opacity - Opacity value.
*/
og.shape.BaseShape.prototype.setOpacity = function (opacity) {
this.color[3] = opacity;
};
/**
* Delete gl buffers.
* @protected
*/
og.shape.BaseShape.prototype._deleteBuffers = function () {
var r = this._renderNode.renderer,
gl = r.handler.gl;
gl.deleteBuffer(this._positionBuffer);
gl.deleteBuffer(this._normalBuffer);
gl.deleteBuffer(this._indexBuffer);
this._positionBuffer = null;
this._normalBuffer = null;
this._indexBuffer = null;
};
/**
* Sets shape visibility.
* @public
* @param {boolean} visibility - Visibility.
*/
og.shape.BaseShape.prototype.setVisibility = function (visibility) {
this.visibility = visibility;
};
/**
* Gets visibilty flag.
* @public
* @returns {boolean}
*/
og.shape.BaseShape.prototype.getVisibility = function () {
return this.visibility;
};
/**
* Assign render node.
* @public
* @param {og.scene.RenderNode} renderNode - Render node to assign.
*/
og.shape.BaseShape.prototype.setRenderNode = function (renderNode) {
this._renderNode = renderNode;
this._createBuffers();
if (this._src) {
var img = new Image();
var that = this;
img.onload = function () {
that.texture = renderNode.renderer.handler.createTexture(this);
};
img.src = this._src;
}
};
/**
* Sets shape position.
* @public
* @param {og.math.Vector3} position - Shape position.
*/
og.shape.BaseShape.prototype.setPosition3v = function (position) {
this.position.copy(position);
this._mxTranslation.translateToPosition(position);
this.refresh();
};
/**
* Translate shape position to vector.
* @public
* @param {og.math.Vector3} vec - Translation vector.
*/
og.shape.BaseShape.prototype.translate3v = function (vec) {
this.position.addA(vec);
this._mxTranslation.translate(vec);
};
/**
* Sets shape scale.
* @param {og.math.Vector3} scale - Scale vector.
*/
og.shape.BaseShape.prototype.setScale3v = function (scale) {
this.scale.copy(scale);
this._mxScale.scale(scale);
};
/**
* Removes shape from shape handler.
* @public
*/
og.shape.BaseShape.prototype.remove = function () {
this._entity = null;
this._handler && this._handler.remove(this);
};
/**
* Assign picking color.
* @protected
* @param {og.math.Vector3} color - Picking RGB color.
*/
og.shape.BaseShape.prototype.setPickingColor3v = function (color) {
//...
//TODO: check the renderer before
//...
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;
};
/**
* Creates buffers.
* @protected
*/
og.shape.BaseShape.prototype._createBuffers = function () {
this._deleteBuffers();
var r = this._renderNode.renderer;
this._positionBuffer = r.handler.createArrayBuffer(new Float32Array(this._positionData), 3, this._positionData.length / 3);
this._normalBuffer = r.handler.createArrayBuffer(new Float32Array(this._normalData), 3, this._normalData.length / 3);
this._indexBuffer = r.handler.createElementArrayBuffer(new Uint16Array(this._indexData), 1, this._indexData.length);
this._textureCoordBuffer = r.handler.createArrayBuffer(new Float32Array(this._textureCoordData), 2, this._textureCoordData.length / 2);
}
/**
* Update model matrix.
* @public
*/
og.shape.BaseShape.prototype.refresh = function () {
this._mxModel = this._mxTranslation.mul(this.orientation.getMatrix4().mul(this._mxScale));
};
/**
* Shape rendering.
* @public
*/
og.shape.BaseShape.prototype.draw = function () {
if (this.visibility) {
var rn = this._renderNode;
var r = rn.renderer;
var sh, p,
gl = r.handler.gl;
if (rn.lightEnabled) {
sh = r.handler.shaderPrograms.shape_wl;
p = sh._program;
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.uniform4fv(shu.lightsPositions._pName, rn._lightsTransformedPositions);
gl.uniform3fv(shu.lightsParamsv._pName, rn._lightsParamsv);
gl.uniform1fv(shu.lightsParamsf._pName, rn._lightsParamsf);
gl.uniformMatrix4fv(shu.projectionMatrix._pName, false, r.activeCamera._projectionMatrix._m);
gl.uniformMatrix4fv(shu.viewMatrix._pName, false, r.activeCamera._viewMatrix._m);
gl.uniformMatrix3fv(shu.normalMatrix._pName, false, r.activeCamera._normalMatrix._m);
gl.bindBuffer(gl.ARRAY_BUFFER, this._normalBuffer);
gl.vertexAttribPointer(sha.aVertexNormal._pName, this._normalBuffer.itemSize, gl.FLOAT, false, 0, 0);
} else {
sh = r.handler.shaderPrograms.shape_nl;
p = sh._program;
sha = p.attributes,
shu = p.uniforms;
sh.activate();
gl.uniformMatrix4fv(shu.projectionViewMatrix._pName, false, r.activeCamera._projectionViewMatrix._m);
}
gl.uniform4fv(shu.uColor._pName, this.color);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(sha.aVertexPosition._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.uniformMatrix4fv(shu.modelMatrix._pName, false, this._mxModel._m);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.uniform1i(shu.uSampler._pName, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._textureCoordBuffer);
gl.vertexAttribPointer(sha.aTextureCoord._pName, this._textureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
gl.drawElements(r.handler.gl.TRIANGLES, this._indexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}
};

161
src/og/shapes/Icosphere.js Normal file
View File

@ -0,0 +1,161 @@
goog.provide('og.shape.Icosphere');
goog.require('og.shape.BaseShape');
/**
* @class
* @extends {og.shape.BaseShape}
* @param {Object} options - Icosphere parameters:
* @param {og.math.Vector3} [options.position] - Icosphere position.
* @param {og.math.Quaternion} [options.orientation] - Icosphere orientation(rotation).
* @param {og.math.Vector3} [options.scale] - Scale vector.
* @param {Array.<number,number,number,number>} [options.color] - Icosphere RGBA color.
* @param {string} [options.src] - Texture image url source.
* @param {boolean} [options.visibility] - Icosphere visibility.
* @param {number} [options.size] - Icosphere radius.
* @param {number} [options.level] - Icosphere complexity level.
*/
og.shape.Icosphere = function (options) {
goog.base(this, options);
/**
* Icosphere radius.
* @protected
* @type {number}
*/
this._size = options.size || 1.0;
/**
* Icosphere recursion level.
* @protected
* @type {number}
*/
this._level = options.level || 0;
this._index = 0;
this._middlePointIndexCache = {};
this._createData();
};
goog.inherits(og.shape.Icosphere, og.shape.BaseShape);
// add vertex to mesh, fix position to be on unit sphere, return index
og.shape.Icosphere.prototype._addVertex = function (p) {
var length = Math.sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
this._positionData.push(this._size * p[0] / length, this._size * p[1] / length, this._size * p[2] / length);
return this._index++;
};
// return index of point in the middle of p1 and p2
og.shape.Icosphere.prototype._getMiddlePoint = function (p1, p2) {
// first check if we have it already
var firstIsSmaller = p1 < p2;
var smallerIndex = firstIsSmaller ? p1 : p2;
var greaterIndex = firstIsSmaller ? p2 : p1;
var key = smallerIndex + "_" + greaterIndex;
var ret = this._middlePointIndexCache[key];
if (ret) {
return ret;
}
var point1 = [this._positionData[p1 * 3], this._positionData[p1 * 3 + 1], this._positionData[p1 * 3 + 2]];
var point2 = [this._positionData[p2 * 3], this._positionData[p2 * 3 + 1], this._positionData[p2 * 3 + 2]];
var middle = [(point1[0] + point2[0]) / 2.0, (point1[1] + point2[1]) / 2.0, (point1[2] + point2[2]) / 2.0];
// add vertex makes sure point is on unit sphere
var i = this._addVertex(middle);
this._middlePointIndexCache[key] = i;
return i;
};
/**
* Create specific shape vertices data.
* @protected
* @virtual
*/
og.shape.Icosphere.prototype._createData = function () {
this._positionData = [];
this._indexData = [];
this._index = 0;
this._middlePointIndexCache = {};
// create 12 vertices of a icosahedron
var t = (1.0 + Math.sqrt(5.0)) / 2.0;
this._addVertex([-1, t, 0]);
this._addVertex([1, t, 0]);
this._addVertex([-1, -t, 0]);
this._addVertex([1, -t, 0]);
this._addVertex([0, -1, t]);
this._addVertex([0, 1, t]);
this._addVertex([0, -1, -t]);
this._addVertex([0, 1, -t]);
this._addVertex([t, 0, -1]);
this._addVertex([t, 0, 1]);
this._addVertex([-t, 0, -1]);
this._addVertex([-t, 0, 1]);
// create 20 triangles of the icosahedron
var faces = [];
// 5 faces around point 0
faces.push([0, 11, 5]);
faces.push([0, 5, 1]);
faces.push([0, 1, 7]);
faces.push([0, 7, 10]);
faces.push([0, 10, 11]);
// 5 adjacent faces
faces.push([1, 5, 9]);
faces.push([5, 11, 4]);
faces.push([11, 10, 2]);
faces.push([10, 7, 6]);
faces.push([7, 1, 8]);
// 5 faces around point 3
faces.push([3, 9, 4]);
faces.push([3, 4, 2]);
faces.push([3, 2, 6]);
faces.push([3, 6, 8]);
faces.push([3, 8, 9]);
// 5 adjacent faces
faces.push([4, 9, 5]);
faces.push([2, 4, 11]);
faces.push([6, 2, 10]);
faces.push([8, 6, 7]);
faces.push([9, 8, 1]);
// refine triangles
for (var i = 0; i < this._level; i++) {
var faces2 = [];
for (var j = 0; j < faces.length; j++) {
var tri = faces[j];
// replace triangle by 4 triangles
var a = this._getMiddlePoint(tri[0], tri[1]);
var b = this._getMiddlePoint(tri[1], tri[2]);
var c = this._getMiddlePoint(tri[2], tri[0]);
faces2.push([tri[0], a, c]);
faces2.push([tri[1], b, a]);
faces2.push([tri[2], c, b]);
faces2.push([a, b, c]);
}
faces = faces2;
}
for (var i = 0; i < faces.length; i++) {
var tri = faces[i];
this._indexData.push(tri[0]);
this._indexData.push(tri[1]);
this._indexData.push(tri[2]);
}
};

95
src/og/shapes/Sphere.js Normal file
View File

@ -0,0 +1,95 @@
goog.provide('og.shape.Sphere');
goog.require('og.shape.BaseShape');
/**
* @class
* @extends {og.shape.BaseShape}
* @param {Object} options - Sphere parameters:
* @param {og.math.Vector3} [options.position] - Sphere position.
* @param {og.math.Quaternion} [options.orientation] - Sphere orientation(rotation).
* @param {og.math.Vector3} [options.scale] - Scale vector.
* @param {Array.<number,number,number,number>} [options.color] - Sphere RGBA color.
* @param {string} [options.src] - Texture image url source.
* @param {boolean} [options.visibility] - Sphere visibility.
* @param {number} [options.radius=100] - Sphere radius.
* @param {number} [options.latBands=16] - Number of latitude bands.
* @param {number} [options.lonBands=16] - Number of longitude bands.
*/
og.shape.Sphere = function (options) {
goog.base(this, options);
/**
* Sphere radius.
* @protected
* @type {number}
*/
this._radius = options.radius || 100;
/**
* Number of latitude bands.
* @protected
* @type {number}
*/
this._latBands = options.latBands || 16;
/**
* Number of longitude bands.
* @protected
* @type {number}
*/
this._lonBands = options.lonBands || 16;
this._createData();
};
goog.inherits(og.shape.Sphere, og.shape.BaseShape);
/**
* Create specific shape vertices data.
* @protected
* @virtual
*/
og.shape.Sphere.prototype._createData = function () {
for (var latNumber = 0; latNumber <= this._latBands; latNumber++) {
var theta = latNumber * Math.PI / this._latBands;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
for (var longNumber = 0; longNumber <= this._lonBands; longNumber++) {
var phi = longNumber * 2 * Math.PI / this._lonBands;
var sinPhi = Math.sin(phi);
var cosPhi = Math.cos(phi);
var x = cosPhi * sinTheta;
var y = cosTheta;
var z = sinPhi * sinTheta;
var u = 1 - (longNumber / this._lonBands);
var v = latNumber / this._latBands;
this._normalData.push(x);
this._normalData.push(y);
this._normalData.push(z);
this._textureCoordData.push(u);
this._textureCoordData.push(v);
this._positionData.push(this._radius * x);
this._positionData.push(this._radius * y);
this._positionData.push(this._radius * z);
}
}
for (var latNumber = 0; latNumber < this._latBands; latNumber++) {
for (var longNumber = 0; longNumber < this._lonBands; longNumber++) {
var first = (latNumber * (this._lonBands + 1)) + longNumber;
var second = first + this._lonBands + 1;
this._indexData.push(first);
this._indexData.push(first + 1);
this._indexData.push(second);
this._indexData.push(second);
this._indexData.push(first + 1);
this._indexData.push(second + 1);
}
}
};

136
src/og/utils/FontAtlas.js Normal file
View File

@ -0,0 +1,136 @@
/**
* @module og/utils/FontAtlas
*/
'use strict';
import { TextureAtlas } from './TextureAtlas.js';
import { ImageCanvas } from '../ImageCanvas.js';
import { QueueArray } from '../QueueArray.js';
import { FontDetector } from './FontDetector.js';
import { SDFCreator } from './SDFCreator.js';
const tokens = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ь', 'э', 'ъ', 'ю', 'я', 'й',
'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ь', 'Э', 'Ъ', 'Ю', 'Я', 'Й',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '-', '=', '[', ']', '{', '}', '\\', '|', ';', ':', '"', ',', '.', '/', '<', '>', '?', ' ', ' ', "'"];
class FontAtlas {
constructor() {
this.atlasesArr = [];
this.atlasIndexes = {};
this.tokenImageSize = 64;
this.samplerArr = [0];
this._handler = null;
this.defaultFace = "arial";
this._counter = 0;
this._pendingsQueue = new QueueArray();
this.fontDetector = new FontDetector();
this._sdfCreator = new SDFCreator(256, 256);
}
assignHandler(handler) {
this._handler = handler;
}
getFontIndex(face, style, weight) {
return this.atlasIndexes[this.getFullIndex(face, style, weight)];
}
getFullIndex(face, style, weight) {
face = face && face.trim().toLowerCase();
if (!face || face && !this.fontDetector.detect(face)) {
face = this.defaultFace;
}
return face + " " + ((style && style.toLowerCase()) || "normal") + " " + ((weight && weight.toLowerCase()) || "normal");
}
createFont(face, style, weight) {
var fontIndex = this.getFontIndex(face, style, weight);
if (fontIndex == undefined) {
var tis = this.tokenImageSize;
var atlasSize = 1024;//og.math.nextHighestPowerOfTwo(Math.ceil(Math.sqrt(og.utils.FontAtlas.tokens.length)) / tis + (og.utils.FontAtlas.tokens.length - 1) * og.utils.TextureAtlas.BORDER_SIZE);
var fontName = this.getFullIndex(face, style, weight);
fontIndex = this.atlasIndexes[fontName] = this.atlasesArr.length;
var atlas = new TextureAtlas(atlasSize, atlasSize);
atlas.assignHandler(this._handler);
atlas.borderSize = 6;
this.samplerArr[this.atlasesArr.length] = this.atlasesArr.length;
this.atlasesArr.push(atlas);
atlas.canvas.fillColor("black");
var t = tokens;
var sdfSize = 512;
var sdfCanvas = new ImageCanvas(sdfSize, sdfSize);
var sc = this._sdfCreator;
var pT = Math.round(sdfSize * 0.66);
var tF = (style || "normal") + " " + (weight || "normal") + " " + pT + "px " + (face || this.defaultFace);
for (var i = 0; i < t.length; i++) {
var ti = t[i];
sdfCanvas.fillColor("black");
sdfCanvas.drawText(ti, 49, pT, tF, "white");
var res = sc.createSDF(sdfCanvas._canvas, tis, tis);
res.__nodeIndex = ti;
var n = atlas.addImage(res, true);
var tokenWidth = sdfCanvas.getTextWidth(ti);
n.emptySize = tokenWidth / sdfSize;
}
atlas.createTexture();
sdfCanvas.destroy();
sdfCanvas = null;
}
return fontIndex;
}
createFontAsync(face, style, weight, callback) {
var obj = { "face": face, "style": style, "weight": weight, "callback": callback };
if (this._counter >= 1) {
this._pendingsQueue.push(obj);
} else {
this._exec(obj);
}
}
_exec(obj) {
this._counter++;
var that = this;
setTimeout(function () {
var fontIndex = that.createFont(obj.face, obj.style, obj.weight);
obj.callback(fontIndex);
that._dequeueRequest();
}, 0);
}
_dequeueRequest() {
this._counter--;
if (this._pendingsQueue.length && this._counter < 1) {
var obj;
if (obj = this._whilePendings())
this._exec(obj);
}
}
_whilePendings() {
while (this._pendingsQueue.length) {
var f = this._pendingsQueue.pop();
var fontIndex = this.getFontIndex(f.face, f.style, f.weight);
if (fontIndex != undefined) {
f.callback(fontIndex);
continue;
}
return f;
}
}
};
export { FontAtlas };

View File

@ -0,0 +1,50 @@
/**
* @module og/utils/FontDetector
*/
'use strict';
/**
* Font detector class.
* @class
*/
class FontDetector {
constructor() {
var baseFonts = ['monospace', 'sans-serif', 'serif'];
var testString = "mmmmmmmmmmlli";
var testSize = '72px';
var h = document.getElementsByTagName("body")[0];
var s = document.createElement("span");
s.style.fontSize = testSize;
s.innerHTML = testString;
var defaultWidth = {};
var defaultHeight = {};
for (var index in baseFonts) {
s.style.fontFamily = baseFonts[index];
h.appendChild(s);
defaultWidth[baseFonts[index]] = s.offsetWidth;
defaultHeight[baseFonts[index]] = s.offsetHeight;
h.removeChild(s);
}
/**
* Returns true if font is known.
* @public
* @param {string} font - Font name.
* @returns {boolean}
*/
this.detect = function (font) {
var detected = false;
for (var index in baseFonts) {
s.style.fontFamily = font + ',' + baseFonts[index];
h.appendChild(s);
var matched = (s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]);
h.removeChild(s);
detected = detected || matched;
}
return detected;
}
}
};
export { FontDetector };

View File

@ -0,0 +1,161 @@
/**
* @module og/utils/GeoImageCreator
*/
'use sctrict';
import * as segmentHelper from '../segment/segmentHelper.js';
import * as utils from '../utils/shared.js';
import { Framebuffer } from '../webgl/Framebuffer.js';
import { LonLat } from '../LonLat.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
const GeoImageCreator = function (handler, maxFrames) {
this._gridSize = 64;
this._handler = handler;
this._framebuffer = null;
this._texCoordsBuffer = null;
this._indexBuffer = null;
this.MAX_FRAMES = maxFrames || 5;
this._currentFrame = 0;
this._queue = [];
this._animate = [];
this._initialize();
};
GeoImageCreator.prototype._initialize = function () {
this._initShaders();
this._initBuffers();
};
/**
* Creates geoImage corners coordinates grid buffer.
* @public
* @param{Array.<og.LonLat>} c - GeoImage corners coordinates.
* @return{WebGLBuffer} Grid coordinates buffer.
*/
GeoImageCreator.prototype.createGridBuffer = function (c, toMerc) {
var gs = this._gridSize;
var v03 = new LonLat((c[3].lon - c[0].lon) / gs, (c[3].lat - c[0].lat) / gs),
v12 = new LonLat((c[2].lon - c[1].lon) / gs, (c[2].lat - c[1].lat) / gs),
v01 = new LonLat((c[1].lon - c[0].lon) / gs, (c[1].lat - c[0].lat) / gs),
v32 = new LonLat((c[2].lon - c[3].lon) / gs, (c[2].lat - c[3].lat) / gs);
var grid = new Float32Array((gs + 1) * (gs + 1) * 2);
var k = 0;
for (var i = 0; i <= gs; i++) {
var P03i = new LonLat(c[0].lon + i * v03.lon, c[0].lat + i * v03.lat),
P12i = new LonLat(c[1].lon + i * v12.lon, c[1].lat + i * v12.lat);
for (var j = 0; j <= gs; j++) {
var P01j = new LonLat(c[0].lon + j * v01.lon, c[0].lat + j * v01.lat),
P32j = new LonLat(c[3].lon + j * v32.lon, c[3].lat + j * v32.lat);
var xx = utils.getLinesIntersectionLonLat(P03i, P12i, P01j, P32j);
grid[k++] = xx.lon;
grid[k++] = xx.lat;
}
}
if (toMerc) {
for (var i = 0; i < grid.length; i += 2) {
var c = new LonLat(grid[i], grid[i + 1]).forwardMercator();
grid[i] = c.lon;
grid[i + 1] = c.lat;
}
}
return this._handler.createArrayBuffer(grid, 2, grid.length / 2);
};
GeoImageCreator.prototype.frame = function () {
var i = this.MAX_FRAMES;
while (i-- && this._queue.length) {
var q = this._queue.shift();
q._isRendering = false;
q.rendering();
}
i = this._animate.length;
while (i--) {
this._animate[i].rendering();
}
};
GeoImageCreator.prototype.add = function (geoImage) {
if (!geoImage._isRendering) {
geoImage._isRendering = true;
if (geoImage._animate) {
this._animate.push(geoImage);
} else {
this._queue.push(geoImage);
}
}
};
GeoImageCreator.prototype.remove = function (geoImage) {
if (geoImage._isRendering) {
geoImage._creationProceeding = false;
geoImage._isRendering = false;
var arr;
if (geoImage._animate) {
arr = this._animate;
} else {
arr = this._queue;
}
for (var i = 0; i < arr.length; i++) {
if (arr[i].isEqual(geoImage)) {
arr.splice(i, 1);
return;
}
}
}
};
GeoImageCreator.prototype._initBuffers = function () {
this._framebuffer = new Framebuffer(this._handler, { width: 2, height: 2, useDepth: false });
this._framebuffer.init();
this._framebufferMercProj = new Framebuffer(this._handler, { width: 2, height: 2, useDepth: false });
this._framebufferMercProj.init();
var gs = this._gridSize;
var gs1 = this._gridSize + 1;
this._texCoordsBuffer = this._handler.createArrayBuffer(segmentHelper.textureCoordsTable[gs], 2, gs1 * gs1);
var indexes = segmentHelper.createSegmentIndexes(gs, [gs, gs, gs, gs]);
this._indexBuffer = this._handler.createElementArrayBuffer(indexes, 1, indexes.length);
this._quadTexCoordsBuffer = this._handler.createArrayBuffer(new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]), 2, 4);
this._quadVertexBuffer = this._handler.createArrayBuffer(new Float32Array([-1, 1, 1, 1, -1, -1, 1, -1]), 2, 4);
};
GeoImageCreator.prototype._initShaders = function () {
this._handler.addShaderProgram(new ShaderProgram("geoImageTransform", {
uniforms: {
sourceTexture: { type: types.SAMPLER2D },
extentParams: { type: types.VEC4 }
},
attributes: {
corners: { type: types.VEC2, enableArray: true },
texCoords: { type: types.VEC2, enableArray: true }
},
vertexShader: 'attribute vec2 corners; \
attribute vec2 texCoords; \
varying vec2 v_texCoords; \
uniform vec4 extentParams; \
void main() { \
v_texCoords = texCoords; \
gl_Position = vec4((-1.0 + (corners - extentParams.xy) * extentParams.zw) * vec2(1.0, -1.0), 0.0, 1.0); \
}',
fragmentShader:
'precision highp float;\n\
uniform sampler2D sourceTexture; \
varying vec2 v_texCoords; \
void main () { \
gl_FragColor = texture2D(sourceTexture, v_texCoords); \
}'
}));
};
export { GeoImageCreator };

View File

@ -0,0 +1,73 @@
/**
* @module og/utils/ImageBitmapLoader
*/
'use strict';
const NUM_WORKERS = 1;
const MAX_REQUESTS = 12;
function program(maxRequests = 12) {
return `var maxRequests = ${maxRequests};
var _loading = 0;
var _queue = [];
var processQueue = function() {
if (_queue.length > 0 && _loading < maxRequests) {
var q = _queue.shift(),
src = q.src,
options = q.options || {};
_loading++;
return fetch(src, options).then((response) => {
if (!response.ok) {
throw Error("Unable to load '" + src + "'");
}
return response.blob();
})
.then(createImageBitmap)
.then((imageBitmap) => {
_loading--;
self.postMessage({ 'ok': true, 'imageBitmap': imageBitmap, 'queue': _queue.length }, [imageBitmap]);
})
.then(processQueue)
.catch((err) => {
_loading--;
self.postMessage({ 'ok': false, 'error': err.toString(), 'queue': _queue.length });
processQueue();
});
}
}
self.onmessage = function (e) {
var toEnqueue = e.data;
if (_queue.indexOf(toEnqueue.src) < 0) {
_queue.push(toEnqueue);
processQueue();
}
}`;
};
class ImageBitmapLoader {
constructor(options) {
options = options || {};
this._workers = new Array(options.numWorkers || NUM_WORKERS);
this._counter = 0;
var p = new Blob([program(options.maxRequests || MAX_REQUESTS)], { type: 'application/javascript' });
for (var i = 0; i < this._workers.length; i++) {
this._workers[i] = new Worker(URL.createObjectURL(p));
}
}
load(src, callback, options) {
var _w = this._workers[this._counter++ % this._workers.length];
_w.onmessage = (e) => callback && callback(e);
_w.postMessage({ 'src': src, 'options': options });
}
};
export { ImageBitmapLoader };

View File

@ -0,0 +1,73 @@
/**
* @module og/utils/ImagesCacheManager
*/
'use strict';
import { QueueArray } from '../QueueArray.js';
class ImagesCacheManager {
constructor() {
this.imagesCache = {};
this._counter = 0;
this._pendingsQueue = new QueueArray();
this._imageIndexCounter = 0;
}
load(src, success) {
if (this.imagesCache[src]) {
success(this.imagesCache[src]);
} else {
var req = { "src": src, "success": success };
if (this._counter >= 1) {
this._pendingsQueue.push(req);
} else {
this._exec(req);
}
}
};
_exec(req) {
this._counter++;
var that = this;
var img = new Image();
img.crossOrigin = '';
img.onload = function () {
that.imagesCache[req.src] = img;
this.__nodeIndex = that._imageIndexCounter++;
req.success(this);
that._dequeueRequest();
};
img.onerror = function () {
that._dequeueRequest();
};
img.src = req.src;
}
_dequeueRequest() {
this._counter--;
if (this._pendingsQueue.length && this._counter < 1) {
while (this._pendingsQueue.length) {
var req = this._pendingsQueue.pop();
if (req) {
if (this.imagesCache[req.src]) {
if (this._counter <= 0)
this._counter = 0;
else
this._counter--;
req.success(this.imagesCache[req.src]);
} else {
this._exec(req);
break;
}
}
}
}
}
};
export { ImagesCacheManager };

View File

@ -0,0 +1,308 @@
/**
* @module og/utils/NormalMapCreator
*/
'use strict';
import * as quadTree from '../quadTree/quadTree.js';
import { Framebuffer } from '../webgl/Framebuffer.js';
import { Lock } from '../Lock.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
import { QueueArray } from '../QueueArray.js';
const NormalMapCreator = function (planet, width, height, maxFrames) {
this._planet = planet;
this._handler = planet.renderer.handler;
this._verticesBufferArray = [];
this._indexBufferArray = [];
this._positionBuffer = null;
this._framebuffer = null;
this._normalMapVerticesTexture = null;
this._width = width || 128;
this._height = height || 128;
this.MAX_FRAMES = maxFrames || 5;
this._currentFrame = 0;
this._queue = new QueueArray(1024);
this._lock = new Lock();
this._init();
};
NormalMapCreator.prototype._init = function () {
var isWebkit = false;//('WebkitAppearance' in document.documentElement.style) && !/^((?!chrome).)*safari/i.test(navigator.userAgent);
/*==================================================================================
* http://www.sunsetlakesoftware.com/2013/10/21/optimizing-gaussian-blurs-mobile-gpu
*=================================================================================*/
var normalMapBlur = new ShaderProgram("normalMapBlur", {
attributes: {
a_position: { type: types.VEC2, enableArray: true }
},
uniforms: {
s_texture: { type: types.SAMPLER2D }
},
vertexShader: "attribute vec2 a_position; \n\
attribute vec2 a_texCoord; \n\
\n\
varying vec2 blurCoordinates[5]; \n\
\n\
void main() { \n\
vec2 vt = a_position * 0.5 + 0.5;" +
(isWebkit ? "vt.y = 1.0 - vt.y; " : " ") +
"gl_Position = vec4(a_position, 0.0, 1.0); \n\
blurCoordinates[0] = vt; \n\
blurCoordinates[1] = vt + " + (1.0 / this._width * 1.407333) + ";" +
"blurCoordinates[2] = vt - " + (1.0 / this._height * 1.407333) + ";" +
"blurCoordinates[3] = vt + " + (1.0 / this._width * 3.294215) + ";" +
"blurCoordinates[4] = vt - " + (1.0 / this._height * 3.294215) + ";" +
"}",
fragmentShader:
"precision highp float;\n\
uniform sampler2D s_texture; \n\
\n\
varying vec2 blurCoordinates[5]; \n\
\n\
void main() { \n\
lowp vec4 sum = vec4(0.0); \n\
if(blurCoordinates[0].x <= 0.01 || blurCoordinates[0].x >= 0.99 ||\n\
blurCoordinates[0].y <= 0.01 || blurCoordinates[0].y >= 0.99){\n\
sum = texture2D(s_texture, blurCoordinates[0]);\n\
} else {\n\
sum += texture2D(s_texture, blurCoordinates[0]) * 0.204164; \n\
sum += texture2D(s_texture, blurCoordinates[1]) * 0.304005; \n\
sum += texture2D(s_texture, blurCoordinates[2]) * 0.304005; \n\
sum += texture2D(s_texture, blurCoordinates[3]) * 0.093913; \n\
sum += texture2D(s_texture, blurCoordinates[4]) * 0.093913; \n\
}\n\
gl_FragColor = sum; \n\
}"
});
var normalMap = new ShaderProgram("normalMap", {
attributes: {
a_position: { type: types.VEC2, enableArray: true },
a_normal: { type: types.VEC3, enableArray: true }
},
vertexShader: "attribute vec2 a_position; \
attribute vec3 a_normal; \
\
varying vec3 v_color; \
\
void main() { \
gl_PointSize = 1.0; \
gl_Position = vec4(a_position, 0, 1); \
v_color = normalize(a_normal) * 0.5 + 0.5; \
}",
fragmentShader:
"precision highp float;\n\
\
varying vec3 v_color; \
\
void main () { \
gl_FragColor = vec4(v_color, 1.0); \
}"
});
this._handler.addShaderProgram(normalMapBlur);
this._handler.addShaderProgram(normalMap);
//create hidden handler buffer
this._framebuffer = new Framebuffer(this._handler, {
width: this._width,
height: this._height,
useDepth: false
});
this._framebuffer.init();
this._normalMapVerticesTexture = this._handler.createEmptyTexture_l(this._width, this._height);
//create vertices hasharray for different grid size segments
for (var p = 1; p <= 6; p++) {
var gs = Math.pow(2, p);
var gs2 = (gs / 2);
var vertices = [];
for (var i = 0; i <= gs; i++) {
for (var j = 0; j <= gs; j++) {
vertices.push(-1 + j / gs2, -1 + i / gs2);
}
}
this._verticesBufferArray[gs] = this._handler.createArrayBuffer(new Float32Array(vertices), 2, vertices.length / 2);
this._indexBufferArray[gs] = this._planet._indexesCache[gs][gs][gs][gs][gs].buffer;
}
//create 2d screen square buffer
var positions = new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0]);
this._positionBuffer = this._handler.createArrayBuffer(positions, 2, positions.length / 2);
};
NormalMapCreator.prototype._drawNormalMap = function (segment) {
var normals = segment.normalMapNormals;
if (segment.node && segment.node.getState() !== quadTree.NOTRENDERING
&& normals && normals.length) {
segment._normalMapEdgeEqualize(quadTree.N, 0);
segment._normalMapEdgeEqualize(quadTree.S, 1);
segment._normalMapEdgeEqualize(quadTree.W, 0, true);
segment._normalMapEdgeEqualize(quadTree.E, 1, true);
var outTexture = segment.normalMapTexturePtr;
var size = normals.length / 3;
var gridSize = Math.sqrt(size) - 1;
var h = this._handler;
var gl = h.gl;
var _normalsBuffer = h.createArrayBuffer(normals, 3, size, gl.DYNAMIC_DRAW);
var f = this._framebuffer;
var p = h.shaderPrograms.normalMap;
var sha = p._program.attributes;
f.bindOutputTexture(this._normalMapVerticesTexture);
p.activate();
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBufferArray[gridSize]);
gl.vertexAttribPointer(sha.a_position._pName, this._verticesBufferArray[gridSize].itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, _normalsBuffer);
gl.vertexAttribPointer(sha.a_normal._pName, _normalsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBufferArray[gridSize]);
gl.drawElements(gl.TRIANGLE_STRIP, this._indexBufferArray[gridSize].numItems, gl.UNSIGNED_SHORT, 0);
gl.deleteBuffer(_normalsBuffer);
//
// blur pass
//
f.bindOutputTexture(outTexture);
p = h.shaderPrograms.normalMapBlur;
p.activate();
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer);
gl.vertexAttribPointer(p._program.attributes.a_position._pName, this._positionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._normalMapVerticesTexture);
gl.uniform1i(p._program.uniforms.s_texture._pName, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, this._positionBuffer.numItems);
return true;
}
return false;
};
NormalMapCreator.prototype.drawSingle = function (segment) {
var h = this._handler,
gl = h.gl;
this._framebuffer.activate();
gl.disable(gl.CULL_FACE);
gl.disable(gl.DEPTH_TEST);
if (segment.terrainReady && this._drawNormalMap(segment)) {
segment.normalMapReady = true;
segment.normalMapTexture = segment.normalMapTexturePtr;
segment.normalMapTextureBias[0] = 0;
segment.normalMapTextureBias[1] = 0;
segment.normalMapTextureBias[2] = 1;
}
segment._inTheQueue = false;
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
this._framebuffer.deactivate();
};
NormalMapCreator.prototype.frame = function () {
if (this._queue.length) {
var h = this._handler,
gl = h.gl;
this._framebuffer.activate();
gl.disable(gl.CULL_FACE);
gl.disable(gl.DEPTH_TEST);
var deltaTime = 0,
startTime = window.performance.now();
var width = this._width,
height = this._height;
while (this._lock.isFree() && this._queue.length && deltaTime < 0.25) {
var segment = this._queue.shift();
if (segment.terrainReady && this._drawNormalMap(segment)) {
segment.normalMapReady = true;
segment.normalMapTexture = segment.normalMapTexturePtr;
segment.normalMapTextureBias[0] = 0;
segment.normalMapTextureBias[1] = 0;
segment.normalMapTextureBias[2] = 1;
}
segment._inTheQueue = false;
deltaTime = window.performance.now() - startTime;
}
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
this._framebuffer.deactivate();
}
};
NormalMapCreator.prototype.queue = function (segment) {
segment._inTheQueue = true;
this._queue.push(segment);
};
NormalMapCreator.prototype.unshift = function (segment) {
segment._inTheQueue = true;
this._queue.unshift(segment);
};
NormalMapCreator.prototype.remove = function (segment) {
//...
};
NormalMapCreator.prototype.clear = function () {
while (this._queue.length) {
var s = this._queue.pop();
s._inTheQueue = false;
}
};
/**
* Set activity off
* @public
*/
NormalMapCreator.prototype.lock = function (key) {
this._lock.lock(key);
};
/**
* Set activity on
* @public
*/
NormalMapCreator.prototype.free = function (key) {
this._lock.free(key);
};
export { NormalMapCreator };

289
src/og/utils/SDFCreator.js Normal file
View File

@ -0,0 +1,289 @@
/**
* @module og/utils/SDFCreator
*/
'use strict';
import { Framebuffer } from '../webgl/Framebuffer.js';
import { Handler } from '../webgl/Handler.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
class SDFCreator {
constructor(width, height) {
this._handler = null;
this._framebuffer0 = null;
this._framebuffer1 = null;
this._framebuffer2 = null;
this._vertexBuffer = null;
//default params
this._width = width || 512;
this._height = height || 512;
var s = Math.max(this._width, this._height);
this._outsideDistance = Math.round(80 * s / 512);
this._insideDistance = Math.round(10 * s / 512);
this._outsideMix = 0.710;
this._insideMix = 0.679;
this._sourceTexture = null;
this.initialize();
}
initialize() {
this._initHandler(this._width, this._height);
this._initShaders();
}
_initHandler(width, height) {
this._handler = new Handler(null, {
width: width, height: height,
context: { alpha: true, depth: false }
});
this._handler.initialize();
this._handler.deactivateFaceCulling();
this._handler.deactivateDepthTest();
this._vertexBuffer = this._handler.createArrayBuffer(new Float32Array([-1, -1, -1, 1, 1, -1, 1, 1]), 2, 4);
this._framebuffer0 = new Framebuffer(this._handler, { useDepth: false });
this._framebuffer1 = new Framebuffer(this._handler, { useDepth: false });
this._framebuffer2 = new Framebuffer(this._handler, { useDepth: false });
this._framebuffer0.init();
this._framebuffer1.init();
this._framebuffer2.init();
}
_initShaders() {
var vfield = new ShaderProgram("vfield", {
uniforms: {
uTexSize: { type: types.VEC2 },
uTex1: { type: types.SAMPLER2D },
uDistance: { type: types.INT },
uNeg: { type: types.VEC2 }
},
attributes: {
aPos: { type: types.VEC2, enableArray: true }
},
vertexShader:
"precision highp float;\
attribute vec2 aPos;\
uniform vec2 uTexSize;\
varying vec2 TexCoord;\
varying vec2 vTexSize;\
void main() {\
TexCoord = (aPos + 1.0) * 0.5;\
TexCoord *= uTexSize;\
vTexSize = uTexSize;\
gl_Position.xy = aPos;\
gl_Position.zw = vec2(0.0, 1.0);\
}",
fragmentShader:
"precision highp float;\
uniform sampler2D uTex1;\
uniform int uDistance;\
uniform vec2 uNeg;\
varying vec2 TexCoord;\
varying vec2 vTexSize;\
const int maxDistance = " + this._outsideDistance + ";\
void main() {\
if ( uNeg.x - uNeg.y * texture2D(uTex1, TexCoord / vTexSize).r > 0.5 ) {\
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\
return;\
}\
for ( int i=1; i <= maxDistance; i++ ) {\
if(i > uDistance) break;\
if ( uNeg.x - uNeg.y * texture2D(uTex1, ( TexCoord + vec2(0.0, i) ) / vTexSize ).r > 0.5 ) {\
gl_FragColor = vec4( vec3(float(i)/float(uDistance)), 1.0 );\
return;\
}\
if ( uNeg.x - uNeg.y * texture2D(uTex1, ( TexCoord - vec2(0.0, i)) / vTexSize ).r > 0.5 ) {\
gl_FragColor = vec4(vec3(float(i)/float(uDistance)), 1.0);\
return;\
}\
}\
gl_FragColor = vec4(1.0);\
}"
});
var hfield = new ShaderProgram("hfield", {
uniforms: {
uTexSize: { type: types.VEC2 },
uTex1: { type: types.SAMPLER2D },
uDistance: { type: types.INT }
},
attributes: {
aPos: { type: types.VEC2, enableArray: true }
},
vertexShader:
"precision highp float;\
attribute vec2 aPos;\
uniform vec2 uTexSize;\
varying vec2 TexCoord;\
varying vec2 vTexSize;\
void main() {\n\
TexCoord = (aPos + 1.0) * 0.5;\
TexCoord *= uTexSize;\
vTexSize = uTexSize;\
gl_Position.xy = aPos;\
gl_Position.zw = vec2(0.0, 1.0);\
}",
fragmentShader:
"precision highp float;\
uniform sampler2D uTex1;\
uniform int uDistance;\
varying vec2 TexCoord;\
varying vec2 vTexSize;\
const int maxDistance = " + this._outsideDistance + ";\
float CalcC(float H, float V) {\
return ( sqrt( H * H + V * V ) );\
}\
void main(){\
float dist = CalcC( 0.0, texture2D( uTex1, TexCoord / vTexSize ).r );\
for ( int i = 1; i <= maxDistance; i++ ) {\
if(i > uDistance) break;\
float H = float(i) / float(uDistance);\
dist = min( dist, CalcC( H, texture2D( uTex1, ( TexCoord + vec2( float(i), 0.0) ) / vTexSize ).r ) );\
dist = min( dist, CalcC( H, texture2D( uTex1, ( TexCoord - vec2( float(i), 0.0) ) / vTexSize ).r ) );\
}\
gl_FragColor = vec4(dist);\
gl_FragColor.w = 1.0;\
}"
});
var sum = new ShaderProgram("sum", {
uniforms: {
outside: { type: types.SAMPLER2D },
inside: { type: types.SAMPLER2D },
source: { type: types.SAMPLER2D }
},
attributes: {
aPos: { type: types.VEC2, enableArray: true }
},
vertexShader: "attribute vec2 aPos;\n\
varying vec2 TexCoord;\n\
void main(){\n\
TexCoord = (aPos * vec2(1.0,-1.0) + 1.0) * 0.5;\n\
gl_Position.xy = aPos;\n\
gl_Position.zw = vec2(0.0, 1.0);\n\
}",
fragmentShader:
"precision highp float;\n\
uniform sampler2D outside;\n\
uniform sampler2D inside;\n\
uniform sampler2D source;\n\
varying vec2 TexCoord;\n\
void main(){\n\
float o = texture2D(outside, TexCoord).r;\n\
float i = 1.0 - texture2D(inside, TexCoord).r;\n\
float s = texture2D(source, TexCoord).r;\n\
gl_FragColor = vec4( vec3(1.0 - mix(i, o, step(0.5, s) * " + this._outsideMix + " + (1.0 - step(0.5, s)) * " + this._insideMix + " )), 1.0);\n\
}"
});
this._handler.addShaderPrograms([vfield, hfield, sum]);
};
setSize(width, height) {
if (width != this._width || height != this._height) {
this._width = width;
this._height = height;
this._handler.setSize(width, height);
this._framebuffer0.setSize(width, height);
this._framebuffer1.setSize(width, height);
}
}
createSDF(sourceCanvas, width, height) {
var h = this._handler,
gl = h.gl;
h.setSize(this._width, this._height);
gl.deleteTexture(this._sourceTexture);
this._sourceTexture = h.createTexture_l(sourceCanvas);
h.shaderPrograms.vfield.activate();
var sh = h.shaderPrograms.vfield._program;
var sha = sh.attributes,
shu = sh.uniforms;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.uTex1._pName, 0);
gl.uniform2fv(shu.uTexSize._pName, [this._width, this._height]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.aPos._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
//VERT
this._framebuffer0.activate();
gl.uniform1i(shu.uDistance._pName, this._outsideDistance);
gl.uniform2fv(shu.uNeg._pName, [0.0, -1.0]);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this._framebuffer0.deactivate();
//NEG VERT
this._framebuffer2.activate();
gl.uniform2fv(shu.uNeg._pName, [1.0, 1.0]);
gl.uniform1i(shu.uDistance._pName, this._insideDistance);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this._framebuffer2.deactivate();
h.shaderPrograms.hfield.activate();
var sh = h.shaderPrograms.hfield._program;
var sha = sh.attributes,
shu = sh.uniforms;
gl.uniform2fv(shu.uTexSize._pName, [this._width, this._height]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.aPos._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
//HORIZ
this._framebuffer1.activate();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._framebuffer0.texture);
gl.uniform1i(shu.uTex1._pName, 0);
gl.uniform1i(shu.uDistance._pName, this._outsideDistance);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this._framebuffer1.deactivate();
//NEG HORIZ
this._framebuffer0.activate();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._framebuffer2.texture);
gl.uniform1i(shu.uTex1._pName, 0);
gl.uniform1i(shu.uDistance._pName, this._insideDistance);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
this._framebuffer0.deactivate();
h.setSize(width || this._width, height || this._height);
//SUM
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
h.shaderPrograms.sum.activate();
var sh = h.shaderPrograms.sum._program;
var sha = sh.attributes,
shu = sh.uniforms;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._framebuffer1.texture);
gl.uniform1i(shu.outside._pName, 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this._framebuffer0.texture);
gl.uniform1i(shu.inside._pName, 1);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, this._sourceTexture);
gl.uniform1i(shu.source._pName, 2);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.vertexAttribPointer(sha.aPos._pName, this._vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
return h.canvas;
}
};
export { SDFCreator };

View File

@ -0,0 +1,267 @@
/**
* @module og/utils/TerrainWorker
*/
'use sctrict';
import { QueueArray } from '../QueueArray.js';
class TerrainWorker {
constructor(numWorkers = 2) {
this._workerQueue = new QueueArray(numWorkers);
var elevationProgramm = new Blob([_programm], { type: 'application/javascript' });
for (let i = 0; i < numWorkers; i++) {
this._workerQueue.push(new Worker(URL.createObjectURL(elevationProgramm)));
}
this._pendingQueue = new QueueArray(512);
}
make(segment, elevations) {
if (segment.ready && segment.terrainIsLoading) {
var _elevations = new Float32Array(elevations.length);
_elevations.set(elevations);
if (this._workerQueue.length) {
var that = this;
var w = this._workerQueue.pop();
w.onmessage = function (e) {
segment._terrainWorkerCallback(e.data);
that._workerQueue.unshift(this);
if (that._pendingQueue.length) {
var p = that._pendingQueue.pop();
that.make(p.segment, p.elevations)
}
};
w.postMessage({
'elevations': _elevations,
'this_plainVertices': segment.plainVertices,
'this_plainNormals': segment.plainNormals,
'this_normalMapVertices': segment.normalMapVertices,
'this_normalMapNormals': segment.normalMapNormals,
'heightFactor': segment.planet._heightFactor,
'gridSize': segment.planet.terrain.gridSizeByZoom[segment.tileZoom]
}, [
_elevations.buffer,
segment.plainVertices.buffer,
segment.plainNormals.buffer,
segment.normalMapVertices.buffer,
segment.normalMapNormals.buffer
]);
} else {
this._pendingQueue.push({ 'segment': segment, 'elevations': _elevations });
}
}
}
};
const _programm =
'self.onmessage = function (e) {\n\
\n\
var Vector3 = function(x, y, z) {\n\
this.x = x;\n\
this.y = y;\n\
this.z = z;\n\
};\n\
Vector3.prototype.sub = function(v) {\n\
return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);\n\
};\n\
Vector3.prototype.add = function(v) {\n\
return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);\n\
};\n\
Vector3.prototype.cross = function(v) {\n\
return new Vector3(\n\
this.y * v.z - this.z * v.y,\n\
this.z * v.x - this.x * v.z,\n\
this.x * v.y - this.y * v.x\n\
);\n\
};\n\
Vector3.prototype.normalize = function(v) {\n\
var x = this.x, y = this.y, z = this.z;\n\
var length = 1.0 / Math.sqrt(x * x + y * y + z * z);\n\
this.x = x * length;\n\
this.y = y * length;\n\
this.z = z * length;\n\
return this;\n\
};\n\
\n\
var slice = function (t, h1, h0) {\n\
return t * (h1 - h0);\n\
};\n\
\n\
var elevations = e.data.elevations,\n\
this_plainVertices = e.data.this_plainVertices,\n\
this_plainNormals = e.data.this_plainNormals,\n\
this_normalMapVertices = e.data.this_normalMapVertices,\n\
this_normalMapNormals = e.data.this_normalMapNormals,\n\
heightFactor = e.data.heightFactor,\n\
//fileGridSize = e.data.fileGridSize,\n\
gridSize = e.data.gridSize;\n\
\n\
var xmin = 549755748352, xmax = -549755748352, ymin = 549755748352, ymax = -549755748352, zmin = 549755748352, zmax = -549755748352;\n\
\n\
fileGridSize = Math.sqrt(elevations.length) - 1;\n\
\n\
var fileGridSize_one = fileGridSize + 1,\n\
tgs = gridSize,\n\
dg = fileGridSize / tgs,\n\
gs = tgs + 1,\n\
hf = heightFactor;\n\
\n\
var nmvInd = 0;\n\
var vInd = 0;\n\
\n\
var terrainVertices = new Float32Array(gs * gs * 3);\n\
var normalMapNormals = new Float32Array(fileGridSize_one * fileGridSize_one * 3);\n\
var normalMapVertices = new Float32Array(fileGridSize_one * fileGridSize_one * 3);\n\
\n\
var nv = this_normalMapVertices,\n\
nn = this_normalMapNormals;\n\
\n\
if (fileGridSize >= tgs) {\n\
for (var i = 0; i < fileGridSize_one; i++) {\n\
for (var j = 0; j < fileGridSize_one; j++) {\n\
var hInd0 = i * fileGridSize_one + j;\n\
var vInd0 = hInd0 * 3;\n\
var h0 = hf * elevations[hInd0];\n\
var v0 = new Vector3(nv[vInd0] + h0 * nn[vInd0], nv[vInd0 + 1] + h0 * nn[vInd0 + 1], nv[vInd0 + 2] + h0 * nn[vInd0 + 2]);\n\
normalMapVertices[vInd0] = v0.x;\n\
normalMapVertices[vInd0 + 1] = v0.y;\n\
normalMapVertices[vInd0 + 2] = v0.z;\n\
\n\
if (i % dg == 0 && j % dg == 0) {\n\
terrainVertices[vInd++] = v0.x;\n\
terrainVertices[vInd++] = v0.y;\n\
terrainVertices[vInd++] = v0.z;\n\
\n\
if (v0.x < xmin) xmin = v0.x; if (v0.x > xmax) xmax = v0.x;\n\
if (v0.y < ymin) ymin = v0.y; if (v0.y > ymax) ymax = v0.y;\n\
if (v0.z < zmin) zmin = v0.z; if (v0.z > zmax) zmax = v0.z;\n\
}\n\
\n\
if (i != fileGridSize && j != fileGridSize) {\n\
var hInd1 = i * fileGridSize_one + j + 1;\n\
var vInd1 = hInd1 * 3;\n\
var h1 = hf * elevations[hInd1];\n\
var v1 = new Vector3(nv[vInd1] + h1 * nn[vInd1], nv[vInd1 + 1] + h1 * nn[vInd1 + 1], nv[vInd1 + 2] + h1 * nn[vInd1 + 2]);\n\
normalMapVertices[vInd1] = v1.x;\n\
normalMapVertices[vInd1 + 1] = v1.y;\n\
normalMapVertices[vInd1 + 2] = v1.z;\n\
\n\
var hInd2 = (i + 1) * fileGridSize_one + j;\n\
var vInd2 = hInd2 * 3;\n\
var h2 = hf * elevations[hInd2];\n\
var v2 = new Vector3(\n\
nv[vInd2] + h2 * nn[vInd2],\n\
nv[vInd2 + 1] + h2 * nn[vInd2 + 1],\n\
nv[vInd2 + 2] + h2 * nn[vInd2 + 2]);\n\
normalMapVertices[vInd2] = v2.x;\n\
normalMapVertices[vInd2 + 1] = v2.y;\n\
normalMapVertices[vInd2 + 2] = v2.z;\n\
\n\
var hInd3 = (i + 1) * fileGridSize_one + (j + 1);\n\
var vInd3 = hInd3 * 3;\n\
var h3 = hf * elevations[hInd3];\n\
var v3 = new Vector3(nv[vInd3] + h3 * nn[vInd3], nv[vInd3 + 1] + h3 * nn[vInd3 + 1], nv[vInd3 + 2] + h3 * nn[vInd3 + 2]);\n\
normalMapVertices[vInd3] = v3.x;\n\
normalMapVertices[vInd3 + 1] = v3.y;\n\
normalMapVertices[vInd3 + 2] = v3.z;\n\
\n\
var e10 = v1.sub(v0),\n\
e20 = v2.sub(v0),\n\
e30 = v3.sub(v0);\n\
var sw = e20.cross(e30).normalize();\n\
var ne = e30.cross(e10).normalize();\n\
var n0 = ne.add(sw).normalize();\n\
\n\
normalMapNormals[vInd0] += n0.x;\n\
normalMapNormals[vInd0 + 1] += n0.y;\n\
normalMapNormals[vInd0 + 2] += n0.z;\n\
\n\
normalMapNormals[vInd1] += ne.x;\n\
normalMapNormals[vInd1 + 1] += ne.y;\n\
normalMapNormals[vInd1 + 2] += ne.z;\n\
\n\
normalMapNormals[vInd2] += sw.x;\n\
normalMapNormals[vInd2 + 1] += sw.y;\n\
normalMapNormals[vInd2 + 2] += sw.z;\n\
\n\
normalMapNormals[vInd3] += n0.x;\n\
normalMapNormals[vInd3 + 1] += n0.y;\n\
normalMapNormals[vInd3 + 2] += n0.z;\n\
}\n\
}\n\
}\n\
\n\
} else {\n\
\n\
var plain_verts = this_plainVertices;\n\
var plainNormals = this_plainNormals;\n\
\n\
var oneSize = tgs / fileGridSize;\n\
var h, inside_i, inside_j, v_i, v_j;\n\
\n\
for (var i = 0; i < gs; i++) {\n\
if (i == gs - 1) {\n\
inside_i = oneSize;\n\
v_i = Math.floor(i / oneSize) - 1;\n\
} else {\n\
inside_i = i % oneSize;\n\
v_i = Math.floor(i / oneSize);\n\
}\n\
\n\
for (var j = 0; j < gs; j++) {\n\
if (j == gs - 1) {\n\
inside_j = oneSize;\n\
v_j = Math.floor(j / oneSize) - 1;\n\
} else {\n\
inside_j = j % oneSize;\n\
v_j = Math.floor(j / oneSize);\n\
}\n\
\n\
var hvlt = elevations[v_i * fileGridSize_one + v_j],\n\
hvrt = elevations[v_i * fileGridSize_one + v_j + 1],\n\
hvlb = elevations[(v_i + 1) * fileGridSize_one + v_j],\n\
hvrb = elevations[(v_i + 1) * fileGridSize_one + v_j + 1];\n\
\n\
if (inside_i + inside_j < oneSize) {\n\
h = hf * (hvlt + slice(inside_j / oneSize, hvrt, hvlt) + slice(inside_i / oneSize, hvlb, hvlt));\n\
} else {\n\
h = hf * (hvrb + slice((oneSize - inside_j) / oneSize, hvlb, hvrb) + slice((oneSize - inside_i) / oneSize, hvrt, hvrb));\n\
}\n\
\n\
var x = plain_verts[vInd] + h * plainNormals[vInd],\n\
y = plain_verts[vInd + 1] + h * plainNormals[vInd + 1],\n\
z = plain_verts[vInd + 2] + h * plainNormals[vInd + 2];\n\
\n\
terrainVertices[vInd] = x;\n\
terrainVertices[vInd + 1] = y;\n\
terrainVertices[vInd + 2] = z;\n\
\n\
vInd += 3;\n\
\n\
if (x < xmin) xmin = x; if (x > xmax) xmax = x;\n\
if (y < ymin) ymin = y; if (y > ymax) ymax = y;\n\
if (z < zmin) zmin = z; if (z > zmax) zmax = z;\n\
\n\
}\n\
}\n\
\n\
normalMapNormals = this_plainNormals;\n\
}\n\
self.postMessage({ \n\
normalMapNormals: normalMapNormals,\n\
normalMapVertices: normalMapVertices,\n\
terrainVertices: terrainVertices,\n\
bounds: [xmin, xmax, ymin, ymax, zmin, zmax],\n\
}, [normalMapNormals.buffer, normalMapVertices.buffer, terrainVertices.buffer]);\n\
}';
export { TerrainWorker };

View File

@ -0,0 +1,289 @@
/**
* @module og/utils/TextureAtlas
*/
'use strict';
import { ImageCanvas } from '../ImageCanvas.js';
import { Rectangle } from '../Rectangle.js';
import { ImagesCacheManager } from './ImagesCacheManager.js';
/**
* Border beetween stored images.
* @type {number}
* @const
*/
const BORDER_SIZE = 4;
/**
* Texture atlas stores images in one texture. Each image has texture
* coordinates returned with node creation by addImage function.
* @class
* @param {number} [width] - Texture atlas width, if it hasn't 1024 default.
* @param {number} [height] - Texture atlas height, if it hasn't 1024 default..
*/
class TextureAtlas {
constructor(width, height) {
/**
* Atlas nodes where input images store. It can be access by image.__nodeIndex.
* @public
* @type {Array.<og.utils.TextureAtlasNode >}
*/
this.nodes = [];
/**
* Created gl texture.
* @public
*/
this.texture = null;
/**
* Atlas canvas.
* @public
* @type {canvas}
*/
this.canvas = new ImageCanvas(width || 1024, height || 1024);
this.clearCanvas();
this._handler = null;
this._images = [];
this._btree = null;
this._imagesCacheManager = new ImagesCacheManager();
this.borderSize = 4;
}
/**
* Returns atlas javascript image object.
* @public
* @returns {Object}
*/
getImage() {
return this.canvas.getImage();
}
/**
* Returns canvas object.
* @public
* @retuns {Object}
*/
getCanvas() {
return this.canvas._canvas;
}
/**
* Clear atlas with black.
* @public
*/
clearCanvas() {
this.canvas.fillEmpty("black");
}
/**
* Sets openglobus gl handler that creates gl texture.
* @public
* @param {og.webgl.Handler} handler - WebGL handler.
*/
assignHandler(handler) {
this._handler = handler;
this.createTexture();
}
/**
* Returns image diagonal size.
* @param {Object} image - JavaSript image object.
* @returns {number}
*/
getDiagonal(image) {
var w = image.atlasWidth || image.width,
h = image.atlasHeight || image.height;
return Math.sqrt(w * w + h * h);
}
/**
* Adds image to the atlas and returns creted node with texture coordinates of the stored image.
* @public
* @param {Object} image - Input javascript image object.
* @param {boolean} [fastInsert] - If it's true atlas doesnt restore all images again
* and store image in the curent atlas sheme.
* @returns {og.utils.TextureAtlasNode}
*/
addImage(image, fastInsert) {
if (!(image.width && image.height)) {
return;
}
this._images.push(image);
this._makeAtlas(fastInsert);
return this.nodes[image.__nodeIndex];
}
/**
* Calculate texture coordianates and stores node.
* @private
*/
_completeNode(nodes, node) {
if (node) {
var w = this.canvas.getWidth(),
h = this.canvas.getHeight();
var im = node.image;
var r = node.rect;
var bs = Math.round(this.borderSize * 0.5);
this.canvas.drawImage(im, r.left + bs, r.top + bs, im.atlasWidth, im.atlasHeight);
var tc = node.texCoords;
tc[0] = (r.left + bs) / w;
tc[1] = (r.top + bs) / h;
tc[2] = (r.left + bs) / w;
tc[3] = (r.bottom - bs) / h;
tc[4] = (r.right - bs) / w;
tc[5] = (r.bottom - bs) / h;
tc[6] = (r.right - bs) / w;
tc[7] = (r.bottom - bs) / h;
tc[8] = (r.right - bs) / w;
tc[9] = (r.top + bs) / h;
tc[10] = (r.left + bs) / w;
tc[11] = (r.top + bs) / h;
nodes[im.__nodeIndex] = node;
}
}
/**
* Main atlas making function.
* @private
* @param {boolean} [fastInsert] - If it's true atlas doesnt restore all images again
* and store image in the curent atlas sheme.
*/
_makeAtlas(fastInsert) {
if (fastInsert && this._btree) {
var im = this._images[this._images.length - 1];
this._completeNode(this.nodes, this._btree.insert(im));
} else {
var im = this._images.slice(0);
im.sort(function (b, a) {
return ((a.atlasWidth || a.width) - (b.atlasWidth || b.width)) || ((a.atlasHeight || a.height) - (b.atlasHeight || b.height));
});
this._btree = new TextureAtlasNode(new Rectangle(0, 0, this.canvas.getWidth(), this.canvas.getHeight()));
this._btree.atlas = this;
this.clearCanvas();
var newNodes = [];
for (var i = 0; i < im.length; i++) {
this._completeNode(newNodes, this._btree.insert(im[i]));
}
this.nodes = [];
this.nodes = newNodes;
}
}
/**
* Creates atlas gl texture.
* @public
*/
createTexture() {
if (this._handler) {
this._handler.gl.deleteTexture(this.texture);
this.texture = this._handler.createTexture_l(this.canvas._canvas);
}
}
/**
* Image handler callback.
* @callback Object~successCallback
* @param {Image} img - Loaded image.
*/
/**
* Asynchronous function that loads and creates image to the image cache, and call success callback when it's done.
* @public
* @param {string} src - Image object src string.
* @param {Object~successCallback} success - The callback that handles the image loads done.
*/
loadImage(src, success) {
this._imagesCacheManager.load(src, success);
}
};
/**
* Atlas binary tree node.
* @class
* @prarm {og.Rectangle} rect - Node image rectangle.
*/
class TextureAtlasNode {
constructor(rect) {
this.childNodes = null;
this.image = null;
this.rect = rect;
this.texCoords = [];
this.atlas = null;
}
/**
* This algorithm has got from here:
* http://www.blackpawn.com/texts/lightmaps/default.html
*/
insert(img) {
if (this.childNodes) {
var newNode = this.childNodes[0].insert(img);
if (newNode != null)
return newNode;
return this.childNodes[1].insert(img);
} else {
if (this.image != null)
return null;
var rc = this.rect;
var w = (img.atlasWidth || img.width) + this.atlas.borderSize;
var h = (img.atlasHeight || img.height) + this.atlas.borderSize;
if (w > rc.getWidth() || h > rc.getHeight())
return null;
if (rc.fit(w, h)) {
this.image = img;
return this;
}
this.childNodes = new Array(2);
this.childNodes[0] = new TextureAtlasNode();
this.childNodes[0].atlas = this.atlas;
this.childNodes[1] = new TextureAtlasNode();
this.childNodes[1].atlas = this.atlas;
var dw = rc.getWidth() - w;
var dh = rc.getHeight() - h;
if (dw > dh) {
this.childNodes[0].rect = new Rectangle(rc.left, rc.top, rc.left + w, rc.bottom);
this.childNodes[1].rect = new Rectangle(rc.left + w, rc.top, rc.right, rc.bottom);
} else {
this.childNodes[0].rect = new Rectangle(rc.left, rc.top, rc.right, rc.top + h);
this.childNodes[1].rect = new Rectangle(rc.left, rc.top + h, rc.right, rc.bottom);
}
return this.childNodes[0].insert(img);
}
}
};
export { TextureAtlas };

View File

@ -0,0 +1,349 @@
/**
* @module og/utils/VectorTileCreator
*/
'use sctrict';
import * as quadTree from '../quadTree/quadTree.js';
import { Framebuffer } from '../webgl/Framebuffer.js';
import { ShaderProgram } from '../webgl/ShaderProgram.js';
import { types } from '../webgl/types.js';
const VectorTileCreator = function (planet, maxFrames, width, height) {
this._width = width || 256;
this._height = height || 256;
this._handler = planet.renderer.handler;
this._planet = planet;
this._framebuffer = null;
this.MAX_FRAMES = maxFrames || 5;
this._currentFrame = 0;
this._queue = [];
this._initialize();
};
VectorTileCreator.prototype._initialize = function () {
//Line
if (!this._handler.shaderPrograms.vectorTileLineRasterization) {
this._handler.addShaderProgram(new ShaderProgram("vectorTileLineRasterization", {
uniforms: {
'viewport': { type: types.VEC2 },
'thicknessOutline': { type: types.FLOAT },
'alpha': { type: types.FLOAT },
'extentParams': { type: types.VEC4 }
},
attributes: {
'prev': { type: types.VEC2 },
'current': { type: types.VEC2 },
'next': { type: types.VEC2 },
'order': { type: types.FLOAT },
'color': { type: types.VEC4 },
'thickness': { type: types.FLOAT }
},
vertexShader: 'attribute vec2 prev;\
attribute vec2 current;\
attribute vec2 next;\
attribute float order;\
attribute float thickness;\
attribute vec4 color;\
uniform float thicknessOutline;\
uniform vec2 viewport;\
uniform vec4 extentParams;\
varying vec4 vColor;\
\
vec2 proj(vec2 coordinates){\
return vec2(-1.0 + (coordinates - extentParams.xy) * extentParams.zw) * vec2(1.0, -1.0);\
}\
\
void main(){\
vColor = color;\
vec2 _next = next;\
vec2 _prev = prev;\
if(prev == current){\
if(next == current){\
_next = current + vec2(1.0, 0.0);\
_prev = current - next;\
}else{\
_prev = current + normalize(current - next);\
}\
}\
if(next == current){\
_next = current + normalize(current - _prev);\
}\
\
vec2 sNext = proj(_next),\
sCurrent = proj(current),\
sPrev = proj(_prev);\
vec2 dirNext = normalize(sNext - sCurrent);\
vec2 dirPrev = normalize(sPrev - sCurrent);\
float dotNP = dot(dirNext, dirPrev);\
\
vec2 normalNext = normalize(vec2(-dirNext.y, dirNext.x));\
vec2 normalPrev = normalize(vec2(dirPrev.y, -dirPrev.x));\
vec2 d = (thickness + thicknessOutline) * 0.5 * sign(order) / viewport;\
\
vec2 m;\
if(dotNP >= 0.99991){\
m = sCurrent - normalPrev * d;\
}else{\
vec2 dir = normalPrev + normalNext;\
m = sCurrent + dir * d / (dirNext.x * dir.y - dirNext.y * dir.x);\
\
if( dotNP > 0.5 && dot(dirNext + dirPrev, m - sCurrent) < 0.0 ){\
float occw = order * sign(dirNext.x * dirPrev.y - dirNext.y * dirPrev.x);\
if(occw == -1.0){\
m = sCurrent + normalPrev * d;\
}else if(occw == 1.0){\
m = sCurrent + normalNext * d;\
}else if(occw == -2.0){\
m = sCurrent + normalNext * d;\
}else if(occw == 2.0){\
m = sCurrent + normalPrev * d;\
}\
}else if(distance(sCurrent, m) > min(distance(sCurrent, sNext), distance(sCurrent, sPrev))){\
m = sCurrent + normalNext * d;\
}\
}\
gl_Position = vec4(m.x, m.y, 0.0, 1.0);\
}',
fragmentShader: 'precision highp float;\
uniform float alpha;\
varying vec4 vColor;\
void main() {\
gl_FragColor = vec4(vColor.rgb, alpha * vColor.a);\
}'
}));
}
//Polygon
if (!this._handler.shaderPrograms.vectorTilePolygonRasterization) {
this._handler.addShaderProgram(new ShaderProgram("vectorTilePolygonRasterization", {
uniforms: {
'extentParams': { type: types.VEC4 }
},
attributes: {
'coordinates': { type: types.VEC2 },
'colors': { type: types.VEC4 }
},
vertexShader: 'attribute vec2 coordinates; \
attribute vec4 colors; \
uniform vec4 extentParams; \
varying vec4 color;\
void main() { \
color = colors;\
gl_Position = vec4((-1.0 + (coordinates - extentParams.xy) * extentParams.zw) * vec2(1.0, -1.0), 0.0, 1.0); \
}',
fragmentShader: 'precision highp float;\
varying vec4 color;\
void main () { \
gl_FragColor = color; \
}'
}));
}
this._framebuffer = new Framebuffer(this._handler, {
width: this._width,
height: this._height,
useDepth: false
});
this._framebuffer.init();
};
VectorTileCreator.prototype.frame = function () {
if (this._planet.layerLock.isFree() && this._queue.length) {
var h = this._handler,
gl = h.gl;
gl.disable(gl.CULL_FACE);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
var hLine = h.shaderPrograms.vectorTileLineRasterization,
hPoly = h.shaderPrograms.vectorTilePolygonRasterization;
var width, height;
var pickingMask, texture;
var prevLayerId = -1;
var extentParams = new Array(4);
var f = this._framebuffer.activate();
var width2 = this._width * 2,
height2 = this._height * 2;
var deltaTime = 0,
startTime = window.performance.now();
while (this._planet.layerLock.isFree() && this._queue.length && deltaTime < 0.25) {
var material = this._queue.shift();
if (material.isLoading && material.segment.node.getState() === quadTree.RENDERING) {
if (material.segment.tileZoom <= 3) {
width = width2;
height = height2;
} else {
width = this._width;
height = this._height;
}
texture = material._updateTexture && material._updateTexture || h.createEmptyTexture_l(width, height);
f.setSize(width, height);
f.bindOutputTexture(texture);
gl.clearColor(1.0, 1.0, 1.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
var extent = material.segment.getExtentMerc();
extentParams[0] = extent.southWest.lon;
extentParams[1] = extent.southWest.lat;
extentParams[2] = 2.0 / extent.getWidth();
extentParams[3] = 2.0 / extent.getHeight();
hPoly.activate();
var sh = hPoly._program;
var sha = sh.attributes,
shu = sh.uniforms;
var geomHandler = material.layer._geometryHandler;
//=========================================
//Polygon rendering
//=========================================
gl.uniform4fv(shu.extentParams._pName, extentParams);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyVerticesBufferMerc);
gl.vertexAttribPointer(sha.coordinates._pName, geomHandler._polyVerticesBufferMerc.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyColorsBuffer);
gl.vertexAttribPointer(sha.colors._pName, geomHandler._polyColorsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geomHandler._polyIndexesBuffer);
gl.drawElements(gl.TRIANGLES, geomHandler._polyIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
//Polygon picking PASS
if (material.layer._pickingEnabled) {
if (!material.pickingReady) {
if (material._updatePickingMask) {
pickingMask = material._updatePickingMask;
} else {
pickingMask = h.createEmptyTexture_n(width, height);
}
f.bindOutputTexture(pickingMask);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._polyPickingColorsBuffer);
gl.vertexAttribPointer(sha.colors._pName, geomHandler._polyPickingColorsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawElements(gl.TRIANGLES, geomHandler._polyIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
} else {
pickingMask = material.pickingMask;
}
}
//=========================================
//Strokes and linestrings rendering
//=========================================
f.bindOutputTexture(texture);
hLine.activate();
sh = hLine._program;
sha = sh.attributes;
shu = sh.uniforms;
gl.uniform2fv(shu.viewport._pName, [width, height]);
gl.uniform4fv(shu.extentParams._pName, extentParams);
//vertex
var mb = geomHandler._lineVerticesBufferMerc;
gl.bindBuffer(gl.ARRAY_BUFFER, mb);
gl.vertexAttribPointer(sha.prev._pName, mb.itemSize, gl.FLOAT, false, 8, 0);
gl.vertexAttribPointer(sha.current._pName, mb.itemSize, gl.FLOAT, false, 8, 32);
gl.vertexAttribPointer(sha.next._pName, mb.itemSize, gl.FLOAT, false, 8, 64);
//order
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineOrdersBuffer);
gl.vertexAttribPointer(sha.order._pName, geomHandler._lineOrdersBuffer.itemSize, gl.FLOAT, false, 4, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geomHandler._lineIndexesBuffer);
//PASS - stroke
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineStrokesBuffer);
gl.vertexAttribPointer(sha.thickness._pName, geomHandler._lineStrokesBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineStrokeColorsBuffer);
gl.vertexAttribPointer(sha.color._pName, geomHandler._lineStrokeColorsBuffer.itemSize, gl.FLOAT, false, 0, 0);
//Antialias pass
gl.uniform1f(shu.thicknessOutline._pName, 2);
gl.uniform1f(shu.alpha._pName, 0.54);
gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
//
//Aliased pass
gl.uniform1f(shu.thicknessOutline._pName, 1);
gl.uniform1f(shu.alpha._pName, 1.0);
gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
//PASS - inside line
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineThicknessBuffer);
gl.vertexAttribPointer(sha.thickness._pName, geomHandler._lineThicknessBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._lineColorsBuffer);
gl.vertexAttribPointer(sha.color._pName, geomHandler._lineColorsBuffer.itemSize, gl.FLOAT, false, 0, 0);
//Antialias pass
gl.uniform1f(shu.thicknessOutline._pName, 2);
gl.uniform1f(shu.alpha._pName, 0.54);
gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
//
//Aliased pass
gl.uniform1f(shu.thicknessOutline._pName, 1);
gl.uniform1f(shu.alpha._pName, 1.0);
gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
if (material.layer._pickingEnabled && !material.pickingReady) {
f.bindOutputTexture(pickingMask);
gl.uniform1f(shu.thicknessOutline._pName, 8);
gl.bindBuffer(gl.ARRAY_BUFFER, geomHandler._linePickingColorsBuffer);
gl.vertexAttribPointer(sha.color._pName, geomHandler._linePickingColorsBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawElements(gl.TRIANGLE_STRIP, geomHandler._lineIndexesBuffer.numItems, gl.UNSIGNED_INT, 0);
}
material.applyTexture(texture, pickingMask);
} else {
material.isLoading = false;
}
deltaTime = window.performance.now() - startTime;
prevLayerId = material.layer._id;
}
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
f.deactivate();
}
};
VectorTileCreator.prototype.add = function (material) {
this._queue.push(material);
};
VectorTileCreator.prototype.remove = function (material) {
//...
};
export { VectorTileCreator };

251
src/og/webgl/Framebuffer.js Normal file
View File

@ -0,0 +1,251 @@
/**
* @module og/webgl/Framebuffer
*/
'use strict';
import { ImageCanvas } from '../ImageCanvas.js';
/**
* Class represents framebuffer.
* @class
* @param {og.webgl.Handler} handler - WebGL handler.
* @param {number} [width] - Framebuffer width. Default is handler canvas width.
* @param {number} [height] - Framebuffer height. Default is handler canvas height.
*/
const Framebuffer = function (handler, options) {
options = options || {};
/**
* WebGL handler.
* @public
* @type {og.webgl.Handler}
*/
this.handler = handler;
/**
* Framebuffer object.
* @private
* @type {Object}
*/
this._fbo = null;
/**
* Renderbuffer object.
* @private
* @type {Object}
*/
this._rbo = null;
/**
* Framebuffer width.
* @private
* @type {number}
*/
this._width = options.width || handler.canvas.width;
/**
* Framebuffer width.
* @private
* @type {number}
*/
this._height = options.height || handler.canvas.height;
this._useDepth = options.useDepth != undefined ? options.useDepth : true;
/**
* Framebuffer activity.
* @private
* @type {boolean}
*/
this._active = false;
/**
* Framebuffer texture.
* @public
* @type {number}
*/
this.texture = options.texture || null;
};
Framebuffer.prototype.destroy = function () {
var gl = this.handler.gl;
gl.deleteTexture(this.texture);
gl.deleteFramebuffer(this._fbo);
gl.deleteRenderbuffer(this._rbo);
this.texture = null;
this._rbo = null;
this._fbo = null;
this._active = false;
}
/**
* Framebuffer initialization.
* @private
*/
Framebuffer.prototype.init = function () {
var gl = this.handler.gl;
this._fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
!this.texture && this.bindOutputTexture(this.handler.createEmptyTexture_l(this._width, this._height));
if (this._useDepth) {
this._rbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this._rbo);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this._width, this._height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this._rbo);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
/**
* Bind buffer texture.
* @public
* @param{Object} texture - Output texture.
*/
Framebuffer.prototype.bindOutputTexture = function (texture) {
var gl = this.handler.gl;
this.texture = texture;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
gl.bindTexture(gl.TEXTURE_2D, null);
}
/**
* Sets framebuffer viewport size.
* @public
* @param {number} width - Framebuffer width.
* @param {number} height - Framebuffer height.
*/
Framebuffer.prototype.setSize = function (width, height) {
this._width = width;
this._height = height;
if (this._active) {
this.handler.gl.viewport(0, 0, this._width, this._height);
}
if (this._useDepth) {
this.destroy();
this.init();
}
};
/**
* Returns framebuffer completed.
* @public
* @returns {boolean}
*/
Framebuffer.prototype.isComplete = function () {
var gl = this.handler.gl;
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE)
return true;
return false;
}
/**
* Reads all pixels(RGBA colors) from framebuffer.
* @public
* @returns {Array.<number>}
*/
Framebuffer.prototype.readAllPixels = function () {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
var pixelValues = new Uint8Array(4 * this._width * this._height);
gl.readPixels(0, 0, this._width, this._height, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return pixelValues;
}
/**
* Gets pixel RBGA color from framebuffer by coordinates.
* @public
* @param {number} x - Normalized x - coordinate.
* @param {number} y - Normalized y - coordinate.
* @returns {Array.<number,number,number,number>}
*/
Framebuffer.prototype.readPixel = function (nx, ny) {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
var pixelValues = new Uint8Array(4);
gl.readPixels(nx * this._width, ny * this._height, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return pixelValues;
}
/**
* Activate framebuffer frame to draw.
* @public
* @returns {og.webgl.Framebuffer} Returns Current framebuffer.
*/
Framebuffer.prototype.activate = function () {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
gl.viewport(0, 0, this._width, this._height);
this._active = true;
var c = this.handler.framebufferStack.current().data;
c && (c._active = false);
this.handler.framebufferStack.push(this);
return this;
}
/**
* Deactivate framebuffer frame.
* @public
*/
Framebuffer.prototype.deactivate = function () {
var h = this.handler,
gl = h.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this._active = false;
var f = this.handler.framebufferStack.popPrev();
if (f) {
gl.bindFramebuffer(gl.FRAMEBUFFER, f._fbo);
gl.viewport(0, 0, f._width, f._height);
} else {
gl.viewport(0, 0, h.canvas.width, h.canvas.height);
}
}
/**
* Gets JavaScript image object that framebuffer has drawn.
* @public
* @returns {Object}
*/
Framebuffer.prototype.getImage = function () {
var data = this.readAllPixels();
var imageCanvas = new ImageCanvas(this._width, this._height);
imageCanvas.setData(data);
return imageCanvas.getImage();
}
/**
* Open dialog window with framebuffer image.
* @public
*/
Framebuffer.prototype.openImage = function () {
var img = this.getImage();
var dataUrl = img.src;
var windowContent = '<!DOCTYPE html>';
windowContent += '<html>'
windowContent += '<head><title>Print</title></head>';
windowContent += '<body>'
windowContent += '<img src="' + dataUrl + '">';
windowContent += '</body>';
windowContent += '</html>';
var printWin = window.open('', '', 'width=' + img.width + 'px ,height=' + img.height + 'px');
printWin.document.open();
printWin.document.write(windowContent);
printWin.document.close();
printWin.focus();
}
export { Framebuffer };

907
src/og/webgl/Handler.js Normal file
View File

@ -0,0 +1,907 @@
/**
* @module og/webgl/Handler
*/
'use strict';
import { cons } from '../cons.js';
import { Clock } from '../Clock.js';
import { ImageCanvas } from '../ImageCanvas.js';
import { ShaderController } from './ShaderController.js';
import { Stack } from '../Stack.js';
import { Vec2 } from '../math/Vec2.js';
/**
* Maximum texture image size.
* @const
* @type {number}
*/
const MAX_SIZE = 4096;
const vendorPrefixes = ["", "WEBKIT_", "MOZ_"];
/**
* A WebGL handler for accessing low-level WebGL capabilities.
* @class
* @param {string} id - Canvas element id that WebGL handler assing with. If it's null
* or undefined creates hidden canvas and handler bacomes hidden.
* @param {Object} [params] - Handler options:
* @param {number} [params.anisotropy] - Anisitropy filter degree. 8 is default.
* @param {number} [params.width] - Hidden handler width. 256 is default.
* @param {number} [params.height] - Hidden handler height. 256 is default.
* @param {Object} [param.scontext] - Native WebGL context attributes. See https://www.khronos.org/registry/webgl/specs/latest/1.0/#WEBGLCONTEXTATTRIBUTES
* @param {Array.<string>} [params.extensions] - Additional WebGL extension list. Available by default: OES_standard_derivatives, EXT_texture_filter_anisotropic.
*/
const Handler = function (id, params) {
/**
* Application default timer.
* @public
* @type {og.Clock}
*/
this.defaultClock = new Clock();
/**
* Custom timers.
* @protected
* @type{og.Clock[]}
*/
this._clocks = [];
/**
* Draw frame time in milliseconds.
* @public
* @readonly
* @type {number}
*/
this.deltaTime = 0;
/**
* WebGL rendering canvas element.
* @public
* @type {Object}
*/
this.canvas = null;
/**
* WebGL context.
* @public
* @type {Object}
*/
this.gl = null;
/**
* Shader program controller list.
* @public
* @type {Object.<og.webgl.ShaderController>}
*/
this.shaderPrograms = {};
/**
* Current active shader program controller.
* @public
* @type {og.webgl.ShaderController}
*/
this.activeShaderProgram = null;
/**
* Handler parameters.
* @private
* @type {Object}
*/
this._params = params || {};
this._params.anisotropy = this._params.anisotropy || 8;
var w = this._params.width;
if (w > MAX_SIZE) {
w = MAX_SIZE;
}
this._params.width = w || 256;
var h = this._params.height;
if (h > MAX_SIZE) {
h = MAX_SIZE;
}
this._params.height = h || 256;
this._params.context = this._params.context || {};
this._params.extensions = this._params.extensions || [];
this._oneByHeight = 1 / this._params.height;
/**
* Current WebGL extensions. Becomes here after context initialization.
* @public
* @type {Object}
*/
this.extensions = {};
/**
* HTML Canvas object id.
* @private
* @type {Object}
*/
this._id = id;
this._lastAnimationFrameTime = 0;
this._initialized = false;
/**
* Animation frame function assigned from outside(Ex. from Renderer).
* @private
* @type {frameCallback}
*/
this._frameCallback = function () { };
this.transparentTexture = null;
this.framebufferStack = new Stack();
if (params.autoActivate) {
this.initialize();
}
}
/**
* The return value is null if the extension is not supported, or an extension object otherwise.
* @param {Object} gl - WebGl context pointer.
* @param {String} name - Extension name.
* @returns {Object}
*/
Handler.getExtension = function (gl, name) {
var i, ext;
for (i in vendorPrefixes) {
ext = gl.getExtension(vendorPrefixes[i] + name);
if (ext) {
return ext;
}
}
return null;
}
/**
* Returns a drawing context on the canvas, or null if the context identifier is not supported.
* @param {Object} canvas - HTML canvas object.
* @params {Object} [contextAttributes] - See canvas.getContext contextAttributes.
* @returns {Object}
*/
Handler.getContext = function (canvas, contextAttributes) {
var ctx;
try {
ctx = canvas.getContext("webgl", contextAttributes) ||
canvas.getContext("experimental-webgl", contextAttributes);
//ctx.canvas = canvas;
}
catch (ex) {
cons.logErr("exception during the GL context initialization");
}
if (!ctx) {
cons.logErr("could not initialise WebGL");
}
return ctx;
}
/**
* Sets animation frame function.
* @public
* @param {callback} - Frame callback.
*/
Handler.prototype.setFrameCallback = function (callback) {
callback && (this._frameCallback = callback);
}
/**
* Creates NEAREST filter texture.
* @public
* @param {Object} image - Image or Canvas object.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createTexture_n = function (image) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates Empty half float texture.
* @public
* @param {number} width - Empty texture width.
* @param {number} height - Empty texture height.
* @returns {Object} - WebGL half float texture object.
*/
Handler.prototype.createEmptyTexture_hf = function (width, height) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.HALF_FLOAT_OES, null);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates Empty float texture.
* @public
* @param {number} width - Empty texture width.
* @param {number} height - Empty texture height.
* @returns {Object} - WebGL float texture object.
*/
Handler.prototype.createEmptyTexture_f = function (width, height) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
//gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates Empty NEAREST filtered texture.
* @public
* @param {number} width - Empty texture width.
* @param {number} height - Empty texture height.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createEmptyTexture_n = function (width, height) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates empty LINEAR filtered texture.
* @public
* @param {number} width - Empty texture width.
* @param {number} height - Empty texture height.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createEmptyTexture_l = function (width, height) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates LINEAR filter texture.
* @public
* @param {Object} image - Image or Canvas object.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createTexture_l = function (image) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates MIPMAP filter texture.
* @public
* @param {Object} image - Image or Canvas object.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createTexture_mm = function (image) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates ANISOTROPY filter texture.
* @public
* @param {Object} image - Image or Canvas object.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createTexture_a = function (image) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameterf(gl.TEXTURE_2D, this.extensions.EXT_texture_filter_anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this._params.anisotropy);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
/**
* Creates DEFAULT filter texture, ANISOTROPY is default.
* @public
* @param {Object} image - Image or Canvas object.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.createTexture = function (image) {
return this.createTexture_a(image)
}
/**
* Creates cube texture.
* @public
* @param {Object.<string>} params - Face image urls:
* @param {string} params.px - Positive X or right image url.
* @param {string} params.nx - Negative X or left image url.
* @param {string} params.py - Positive Y or up image url.
* @param {string} params.ny - Negative Y or bottom image url.
* @param {string} params.pz - Positive Z or face image url.
* @param {string} params.nz - Negative Z or back image url.
* @returns {Object} - WebGL texture object.
*/
Handler.prototype.loadCubeMapTexture = function (params) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
var faces = [[params.px, gl.TEXTURE_CUBE_MAP_POSITIVE_X],
[params.nx, gl.TEXTURE_CUBE_MAP_NEGATIVE_X],
[params.py, gl.TEXTURE_CUBE_MAP_POSITIVE_Y],
[params.ny, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y],
[params.pz, gl.TEXTURE_CUBE_MAP_POSITIVE_Z],
[params.nz, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]];
var imageCanvas = new ImageCanvas();
imageCanvas.fillEmpty();
var emptyImage = imageCanvas.getImage();
for (var i = 0; i < faces.length; i++) {
var face = faces[i][1];
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, emptyImage);
}
for (var i = 0; i < faces.length; i++) {
var face = faces[i][1];
var image = new Image();
image.crossOrigin = '';
image.onload = function (texture, face, image) {
return function () {
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
}(texture, face, image);
image.src = faces[i][0];
}
return texture;
}
/**
* Adds shader program to the handler.
* @public
* @param {og.shaderProgram.ShaderProgram} program - Shader program.
* @param {boolean} [notActivate] - If it's true program will not compile.
*/
Handler.prototype.addShaderProgram = function (program, notActivate) {
if (!this.shaderPrograms[program.name]) {
var sc = new ShaderController(this, program);
this.shaderPrograms[program.name] = sc;
this._initShaderController(sc);
if (notActivate)
sc._activated = false;
} else {
!COMPILED && cons.logWrn("og.webgl.Handler:284 - shader program: '" + program.name + "' is allready exists.");
}
return program;
}
/**
* Removes shader program from handler.
* @public
* @param {String} program - Shader program name.
*/
Handler.prototype.removeShaderProgram = function (name) {
this.shaderPrograms[name] && this.shaderPrograms[name].remove();
}
/**
* Adds shader programs to the handler.
* @public
* @param {Array.<og.shaderProgram.ShaderProgram>} programsArr - Shader program array.
*/
Handler.prototype.addShaderPrograms = function (programsArr) {
for (var i = 0; i < programsArr.length; i++) {
this.addShaderProgram(programsArr[i]);
}
}
/**
* Used in addShaderProgram
* @private
* @param {og.webgl.ShaderController}
*/
Handler.prototype._initShaderController = function (sc) {
if (this._initialized) {
sc.initialize();
if (!this.activeShaderProgram) {
this.activeShaderProgram = sc;
sc.activate();
} else {
sc.deactivate();
this.activeShaderProgram._program.enableAttribArrays();
this.activeShaderProgram._program.use();
}
}
}
/**
* Used in init function.
* @private
*/
Handler.prototype._initShaderPrograms = function () {
for (var p in this.shaderPrograms) {
this._initShaderController(this.shaderPrograms[p]);
}
}
/**
* Initialize additional WebGL extensions.
* @public
* @param {string} extensionStr - Extension name.
*/
Handler.prototype.initializeExtension = function (extensionStr, showLog) {
if (!(this.extensions && this.extensions[extensionStr])) {
var ext = Handler.getExtension(this.gl, extensionStr);
if (ext) {
this.extensions[extensionStr] = ext;
} else if (showLog) {
!COMPILED && cons.logWrn("og.webgl.Handler: extension '" + extensionStr + "' doesn't initialize.");
}
}
return this.extensions && this.extensions[extensionStr];
}
/**
* Main function that initialize handler.
* @public
*/
Handler.prototype.initialize = function () {
if (this._id) {
this.canvas = document.getElementById(this._id);
} else {
this.canvas = document.createElement("canvas");
this.canvas.width = this._params.width;
this.canvas.height = this._params.height;
}
this.gl = Handler.getContext(this.canvas, this._params.context);
this._initialized = true;
/** Sets deafult extensions */
this._params.extensions.push("OES_standard_derivatives");
this._params.extensions.push("EXT_texture_filter_anisotropic");
this._params.extensions.push("OES_element_index_uint");
//this._params.extensions.push('OES_texture_half_float');
//this._params.extensions.push('OES_texture_half_float_linear');
//this._params.extensions.push('OES_texture_float');
//this._params.extensions.push('OES_texture_float_linear');
var i = this._params.extensions.length;
while (i--) {
this.initializeExtension(this._params.extensions[i], true);
}
if (!this.extensions.EXT_texture_filter_anisotropic)
this.createTexture = this.createTexture_mm;
/** Initilalize shaders and rendering parameters*/
this._initShaderPrograms();
this._setDefaults();
}
/**
* Sets default gl render parameters. Used in init function.
* @private
*/
Handler.prototype._setDefaults = function () {
this.activateDepthTest();
this.setSize(this._params.width, this._params.height);
this.gl.frontFace(this.gl.CCW);
this.gl.cullFace(this.gl.BACK);
this.activateFaceCulling();
this.deactivateBlending();
var that = this;
this.createDefaultTexture({ color: "rgba(0,0,0,0.0)" }, function (t) {
that.transparentTexture = t;
});
}
/**
* Activate depth test.
* @public
*/
Handler.prototype.activateDepthTest = function () {
this.gl.enable(this.gl.DEPTH_TEST);
}
/**
* Deactivate depth test.
* @public
*/
Handler.prototype.deactivateDepthTest = function () {
this.gl.disable(this.gl.DEPTH_TEST);
}
/**
* Activate face culling.
* @public
*/
Handler.prototype.activateFaceCulling = function () {
this.gl.enable(this.gl.CULL_FACE);
}
/**
* Deactivate face cullting.
* @public
*/
Handler.prototype.deactivateFaceCulling = function () {
this.gl.disable(this.gl.CULL_FACE);
}
/**
* Activate blending.
* @public
*/
Handler.prototype.activateBlending = function () {
this.gl.enable(this.gl.BLEND);
}
/**
* Deactivate blending.
* @public
*/
Handler.prototype.deactivateBlending = function () {
this.gl.disable(this.gl.BLEND);
}
/**
* Creates ARRAY buffer.
* @public
* @param {Array.<number>} array - Input array.
* @param {number} itemSize - Array item size.
* @param {number} numItems - Items quantity.
* @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
* @return {Object}
*/
Handler.prototype.createArrayBuffer = function (array, itemSize, numItems, usage) {
var buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, array, usage || this.gl.STATIC_DRAW);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
buffer.itemSize = itemSize;
buffer.numItems = numItems;
return buffer;
}
/**
* Creates ELEMENT ARRAY buffer.
* @public
* @param {Array.<number>} array - Input array.
* @param {number} itemSize - Array item size.
* @param {number} numItems - Items quantity.
* @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
* @return {Object}
*/
Handler.prototype.createElementArrayBuffer = function (array, itemSize, numItems, usage) {
var buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, buffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, array, usage || this.gl.STATIC_DRAW);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
buffer.itemSize = itemSize;
buffer.numItems = numItems || array.length;
return buffer;
}
/**
* Sets handler canvas size.
* @public
* @param {number} width - Canvas width.
* @param {number} height - Canvas height.
*/
Handler.prototype.setSize = function (w, h) {
if (w > MAX_SIZE) {
w = MAX_SIZE;
}
if (h > MAX_SIZE) {
h = MAX_SIZE;
}
this._params.width = w;
this._params.height = h;
this.canvas.width = w;
this.canvas.height = h;
this._oneByHeight = 1 / h;
this.gl && this.gl.viewport(0, 0, w, h);
this.onCanvasResize && this.onCanvasResize(this.canvas);
}
/**
* Returns context screen width.
* @public
* @returns {number}
*/
Handler.prototype.getWidth = function () {
return this.canvas.width;
};
/**
* Returns context screen height.
* @public
* @returns {number}
*/
Handler.prototype.getHeight = function () {
return this.canvas.height;
}
/**
* Returns canvas aspect ratio.
* @public
* @returns {number}
*/
Handler.prototype.getClientAspect = function () {
return this.canvas.clientWidth / this.canvas.clientHeight;
}
/**
* Returns screen center coordinates.
* @public
* @returns {number}
*/
Handler.prototype.getCenter = function () {
var c = this.canvas;
return new Vec2(Math.round(c.width * 0.5), Math.round(c.height * 0.5));
}
/**
* Draw single frame.
* @public
* @param {number} now - Frame current time milliseconds.
*/
Handler.prototype.drawFrame = function () {
/** Calculate frame time */
var now = new Date().getTime();
this.deltaTime = now - this._lastAnimationFrameTime;
this._lastAnimationFrameTime = now;
this.defaultClock._tick(this.deltaTime);
for (var i = 0; i < this._clocks.length; i++) {
this._clocks[i]._tick(this.deltaTime);
}
/** Canvas resize checking */
var canvas = this.canvas;
if (canvas.clientWidth !== canvas.width ||
canvas.clientHeight !== canvas.height) {
this.setSize(canvas.clientWidth, canvas.clientHeight);
}
/** Draw frame */
this._frameCallback();
}
/**
* Clearing gl frame.
* @public
*/
Handler.prototype.clearFrame = function () {
var gl = this.gl;
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
/**
* Starts animation loop.
* @public
*/
Handler.prototype.start = function () {
if (!this._requestAnimationFrameId && this._initialized) {
var d = new Date();
this._lastAnimationFrameTime = d.getTime();
this.defaultClock.setDate(d);
this._animationFrameCallback();
}
}
Handler.prototype.stop = function () {
if (this._requestAnimationFrameId) {
window.cancelAnimationFrame(this._requestAnimationFrameId);
this._requestAnimationFrameId = null;
}
}
/**
* Make animation.
* @private
*/
Handler.prototype._animationFrameCallback = function () {
this._requestAnimationFrameId = window.requestAnimationFrame(() => {
this.drawFrame();
this._animationFrameCallback();
});
}
/**
* @public
* @param
*/
Handler.prototype.createDefaultTexture = function (params, success) {
var imgCnv;
var texture;
if (params && params.color) {
imgCnv = new ImageCanvas(2, 2);
imgCnv.fillColor(params.color);
texture = this.createTexture_n(imgCnv._canvas);
texture.default = true;
success(texture);
} else if (params && params.url) {
var img = new Image();
var that = this;
img.onload = function () {
texture = that.createTexture(this);
texture.default = true;
success(texture);
};
img.src = params.url;
} else {
imgCnv = new ImageCanvas(2, 2);
imgCnv.fillColor("#C5C5C5");
texture = this.createTexture_n(imgCnv._canvas);
texture.default = true;
success(texture);
}
}
/**
* @public
*/
Handler.prototype.destroy = function () {
var gl = this.gl;
this.stop();
for (var p in this.shaderPrograms) {
this.removeShaderProgram(p);
}
gl.deleteTexture(this.transparentTexture);
this.transparentTexture = null;
this.framebufferStack = null;
this.framebufferStack = new Stack();
if (this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
this.canvas.width = 1;
this.canvas.height = 1;
this.canvas = null;
var numAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
var tmp = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, tmp);
for (var ii = 0; ii < numAttribs; ++ii) {
gl.disableVertexAttribArray(ii);
gl.vertexAttribPointer(ii, 4, gl.FLOAT, false, 0, 0);
gl.vertexAttrib1f(ii, 0);
}
gl.deleteBuffer(tmp);
var numTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
for (var ii = 0; ii < numTextureUnits; ++ii) {
gl.activeTexture(gl.TEXTURE0 + ii);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
gl.bindTexture(gl.TEXTURE_2D, null);
}
gl.activeTexture(gl.TEXTURE0);
gl.useProgram(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.disable(gl.BLEND);
gl.disable(gl.CULL_FACE);
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.DITHER);
gl.disable(gl.SCISSOR_TEST);
gl.blendColor(0, 0, 0, 0);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ZERO);
gl.clearColor(0, 0, 0, 0);
gl.clearDepth(1);
gl.clearStencil(-1);
this.gl = null;
this._initialized = false;
}
Handler.prototype.addClock = function (clock) {
if (!clock.__handler) {
clock.__handler = this;
this._clocks.push(clock);
}
}
Handler.prototype.addClocks = function (clockArr) {
for (var i = 0; i < clockArr.length; i++) {
this.addClock(clockArr[i]);
}
}
Handler.prototype.removeClock = function (clock) {
if (clock.__handler) {
var c = this._clocks;
var i = c.length;
while (i--) {
if (c[i].equal(clock)) {
clock.__handler = null;
c.splice(i, 1);
break;
}
}
}
}
export { Handler };

View File

@ -0,0 +1,268 @@
/**
* @module og/webgl/Framebuffer
*/
'use strict';
import { ImageCanvas } from '../ImageCanvas.js';
/**
* Class represents multiple render framebuffer.
* @class
* @param {og.webgl.Handler} handler - WebGL handler.
*/
const MultiFramebuffer = function (handler, options) {
options = options || {};
/**
* WebGL handler.
* @public
* @type {og.webgl.Handler}
*/
this.handler = handler;
/**
* Framebuffer object.
* @private
* @type {Object}
*/
this._fbo = null;
/**
* Picking color framebuffers.
* @private
* @type {Object}
*/
this._pFbo = [];
/**
* Render buffer object.
* @private
*/
this._rbo = null;
this._size = options.size || 1;
/**
* Framebuffer width.
* @private
* @type {number}
*/
this._width = options.width || 256;
/**
* Framebuffer width.
* @private
* @type {number}
*/
this._height = options.height || 256;
/**
* Buffer textures.
* @public
*/
this.textures = [];
/**
* Framebuffer activity.
* @private
* @type {boolean}
*/
this._active = false;
}
/**
* Destroy framebuffer instance.
* @public
*/
MultiFramebuffer.prototype.destroy = function () {
var gl = this.handler.gl;
gl.deleteFramebuffer(this._fbo);
this._fbo = null;
gl.deleteRenderbuffer(this._rbo);
this._rbo = null;
for (var i = 0; i < this._size; i++) {
gl.deleteTexture(this.textures[i]);
gl.deleteFramebuffer(this._pFbo[i]);
}
this.textures.length = 0;
this.textures = [];
this._pFbo.length = 0;
this._pFbo = [];
}
/**
* Framebuffer initialization.
* @virtual
*/
MultiFramebuffer.prototype.init = function () {
var gl = this.handler.gl;
var ext = this.handler.extensions.WEBGL_draw_buffers;
this._fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
var fragDataArr = [];
for (var i = 0; i < this._size; i++) {
fragDataArr[i] = ext.COLOR_ATTACHMENT0_WEBGL + i;
}
ext.drawBuffersWEBGL(fragDataArr);
for (var i = 0; i < this._size; i++) {
this.textures[i] = this.handler.createEmptyTexture_l(this._width, this._height);
gl.bindTexture(gl.TEXTURE_2D, this.textures[i]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, ext.COLOR_ATTACHMENT0_WEBGL + i, gl.TEXTURE_2D, this.textures[i], 0);
gl.bindTexture(gl.TEXTURE_2D, null);
fragDataArr[i] = ext.COLOR_ATTACHMENT0_WEBGL + i;
}
this._rbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this._rbo);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this._width, this._height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this._rbo);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
for (var i = 0; i < this._size; i++) {
this._pFbo[i] = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this._pFbo[i]);
gl.bindTexture(gl.TEXTURE_2D, this.textures[i]);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.textures[i], 0);
gl.bindTexture(gl.TEXTURE_2D, null);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
/**
* Sets framebuffer size. Must be before the activate method.
* @public
* @param {number} width - Framebuffer width.
* @param {number} height - Framebuffer height.
*/
MultiFramebuffer.prototype.setSize = function (width, height) {
this._width = width;
this._height = height;
this.destroy();
this.init();
}
/**
* Reads all pixels(RGBA colors) from framebuffer.
* @public
* @returns {Array.<number>}
*/
MultiFramebuffer.prototype.readAllPixels = function (index) {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._pFbo[index || 0]);
var pixelValues = new Uint8Array(4 * this._width * this._height);
gl.readPixels(0, 0, this._width, this._height, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return pixelValues;
}
/**
* Gets pixel RBGA color from framebuffer by coordinates.
* @public
* @param {number} x - Normalized x - coordinate.
* @param {number} y - Normalized y - coordinate.
* @returns {Array.<number,number,number,number>}
*/
MultiFramebuffer.prototype.readPixel = function (nx, ny, index) {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._pFbo[index || 0]);
var pixelValues = new Uint8Array(4);
gl.readPixels(nx * this._width, ny * this._height, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return pixelValues;
}
/**
* Returns framebuffer completed.
* @public
* @returns {boolean}
*/
MultiFramebuffer.prototype.isComplete = function () {
var gl = this.handler.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return true;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return false;
}
/**
* Activate framebuffer frame to draw.
* @public
* @returns {og.webgl.MultiFramebuffer} Returns current framebuffer.
*/
MultiFramebuffer.prototype.activate = function () {
var h = this.handler,
gl = h.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
gl.viewport(0, 0, this._width, this._height);
this._active = true;
var c = this.handler.framebufferStack.current().data;
c && (c._active = false);
h.framebufferStack.push(this);
return this;
}
/**
* Deactivate framebuffer frame.
* @public
*/
MultiFramebuffer.prototype.deactivate = function () {
var h = this.handler,
gl = h.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, h.canvas.width, h.canvas.height);
this._active = false;
var f = h.framebufferStack.popPrev();
if (f) {
gl.bindFramebuffer(gl.FRAMEBUFFER, f._fbo);
gl.viewport(0, 0, f._width, f._height);
} else {
gl.viewport(0, 0, h.canvas.width, h.canvas.height);
}
}
/**
* Gets JavaScript image object that framebuffer has drawn.
* @public
* @returns {Object}
*/
MultiFramebuffer.prototype.getImage = function (index) {
var data = this.readAllPixels(index);
var imageCanvas = new ImageCanvas(this._width, this._height);
imageCanvas.setData(data);
return imageCanvas.getImage();
}
/**
* Open dialog window with framebuffer image.
* @public
*/
MultiFramebuffer.prototype.openImage = function (index) {
var img = this.getImage(index);
var dataUrl = img.src;
var windowContent = '<!DOCTYPE html>';
windowContent += '<html>'
windowContent += '<head><title>Print</title></head>';
windowContent += '<body>'
windowContent += '<img src="' + dataUrl + '">';
windowContent += '</body>';
windowContent += '</html>';
var printWin = window.open('', '', 'width=' + img.width + 'px ,height=' + img.height + 'px');
printWin.document.open();
printWin.document.write(windowContent);
printWin.document.close();
printWin.focus();
}
export { MultiFramebuffer };

View File

@ -0,0 +1,142 @@
/**
* @module og/webgl/ShaderController
*/
'use strict';
/**
* This is shader program controller that used by hadler object to access the shader
* program capabilities, like switching program during the rendering.
* Get access to the program from ...handler.shaderPrograms.<program name> etc.
* @class
* @param {og.webgl.Handler} handler - Handler.
* @param {og.shaderProgram.ShaderProgram} shaderProgram - Shader program.
*/
const ShaderController = function (handler, shaderProgram) {
/**
* Shader program.
* @private
* @type {og.shaderProgram.ShaderProgram}
*/
this._program = shaderProgram;
/**
* Handler.
* @private
* @type {og.webgl.Handler}
*/
this._handler = handler;
/**
* Program current frame activation flag.
* @private
* @type {boolean}
*/
this._activated = false;
};
/**
* Lazy create program call.
* @public
*/
ShaderController.prototype.initialize = function () {
this._program.createProgram(this._handler.gl);
}
/**
* Returns controller's shader program.
* @public
* @return {og.shaderProgram.ShaderProgram}
*/
ShaderController.prototype.getProgram = function () {
return this._program;
}
/**
* Activates current shader program.
* @public
*/
ShaderController.prototype.activate = function () {
if (!this._activated) {
this._handler.activeShaderProgram.deactivate();
this._handler.activeShaderProgram = this;
var p = this._program;
this._activated = true;
p.enableAttribArrays();
p.use();
}
return this;
}
/**
* Remove program from handler
* @public
*/
ShaderController.prototype.remove = function () {
var p = this._handler.shaderPrograms;
if (p[this._program.name]) {
if (this._activated) {
this.deactivate();
}
this._program.delete();
p[this._program.name] = null;
delete p[this._program.name];
}
}
/**
* Deactivate shader program. This is not necessary while activae function used.
* @public
*/
ShaderController.prototype.deactivate = function () {
this._program.disableAttribArrays();
this._activated = false;
}
/**
* Returns program activity.
* @public
* @return {boolean}
*/
ShaderController.prototype.isActive = function () {
return this._activated;
}
/**
* Sets program uniforms and attributes values and return controller instance.
* @public
* @param {Object} - Object with variable name and value like { value: 12, someArray:[1,2,3], uSampler: texture,... }
* @return {og.webgl.ShaderController}
*/
ShaderController.prototype.set = function (params) {
this.activate();
this._program.set(params);
return this;
}
/**
* Draw index buffer with this program.
* @public
* @param {number} mode - Gl draw mode
* @param {}
* @return {og.webgl.ShaderController} Returns current shader controller instance.
*/
ShaderController.prototype.drawIndexBuffer = function (mode, buffer) {
this._program.drawIndexBuffer(mode, buffer);
return this;
}
/**
* Calls Gl drawArray function.
* @param {number} - Gl draw mode.
* @param {number} - draw items count.
* @return {og.webgl.ShaderController} Returns current shader controller instance.
*/
ShaderController.prototype.drawArray = function (mode, numItems) {
this._program.drawArray(mode, numItems);
return this;
}
export { ShaderController };