/**
* A helper class wrapping a point cloud.
*
* @property {Object} opts An object containing options.
* @property {Number[]} indices Indices associated with the data.
* @property {Lore.Octree} octree The octree associated with the point cloud.
* @property {Object} filters A map mapping filter names to Lore.Filter instances associated with this helper class.
* @property {Number} pointSize The scaled and constrained point size of this data.
* @property {Number} pointScale The scale of the point size.
* @property {Number} rawPointSize The point size before scaling and constraints.
* @property {Object} dimensions An object with the properties min and max, each a 3D vector containing the extremes.
*/
Lore.PointHelper = class PointHelper extends Lore.HelperBase {
/**
* Creates an instance of PointHelper.
* @param {Lore.Renderer} renderer An instance of Lore.Renderer.
* @param {String} geometryName The name of this geometry.
* @param {String} shaderName The name of the shader used to render the geometry.
* @param {Object} options An object containing options.
*/
constructor(renderer, geometryName, shaderName, options) {
super(renderer, geometryName, shaderName);
let defaults = {
octree: true,
octreeThreshold: 500.0,
octreeMaxDepth: 8,
pointScale: 1.0,
maxPointSize: 100.0
};
this.opts = Lore.Utils.extend(true, defaults, options);
this.indices = null;
this.octree = null;
this.geometry.setMode(Lore.DrawModes.points);
this.initPointSize();
this.filters = {};
this.pointScale = this.opts.pointScale;
this.rawPointSize = 1.0;
this.pointSize = this.rawPointSize * this.pointScale;
this.dimensions = {
min: new Lore.Vector3f(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY),
max: new Lore.Vector3f(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY)
};
}
/**
* Get the max length of the length of three arrays.
*
* @param {Array} x
* @param {Array} y
* @param {Array} z
* @returns {Number} The length of the largest array.
*/
getMaxLength(x, y, z) {
return Math.max(x.length, Math.max(y.length, z.length));
}
/**
* Returns an object containing the dimensions of this point cloud.
*
* @returns {Object} An object with the properties min and max, each a 3D vector containing the extremes.
*/
getDimensions() {
return this.dimensions;
}
/**
* Get the center (average) of the point cloud.
*
* @returns {Lore.Vector3f} The center (average) of the point cloud.
*/
getCenter() {
return new Lore.Vector3f((this.dimensions.max.getX() + this.dimensions.min.getX()) / 2.0,
(this.dimensions.max.getY() + this.dimensions.min.getY()) / 2.0,
(this.dimensions.max.getZ() + this.dimensions.min.getZ()) / 2.0);
}
/**
* Set the positions of points in this point cloud.
*
* @param {TypedArray} positions The positions (linear array).
* @returns {Lore.PointHelper} Itself.
*/
setPositions(positions) {
// Min, max will NOT be calculated as of now!
// TODO?
this.setAttribute('position', positions);
return this;
}
/**
* Set the positions of points in this point clouds.
*
* @param {TypedArray} x An array containing the x components.
* @param {TypedArray} y An array containing the y components.
* @param {TypedArray} z An array containing the z components.
* @param {Number} length The length of the arrays.
* @returns {Lore.PointHelper} Itself.
*/
setPositionsXYZ(x, y, z, length) {
let positions = new Float32Array(length * 3);
for (var i = 0; i < length; i++) {
let j = 3 * i;
positions[j] = x[i] || 0;
positions[j + 1] = y[i] || 0;
positions[j + 2] = z[i] || 0;
if (x[i] > this.dimensions.max.getX()) {
this.dimensions.max.setX(x[i]);
}
if (x[i] < this.dimensions.min.getX()) {
this.dimensions.min.setX(x[i]);
}
if (y[i] > this.dimensions.max.getY()) {
this.dimensions.max.setY(y[i]);
}
if (y[i] < this.dimensions.min.getY()) {
this.dimensions.min.setY(y[i]);
}
if (z[i] > this.dimensions.max.getZ()) {
this.dimensions.max.setZ(z[i]);
}
if (z[i] < this.dimensions.min.getZ()) {
this.dimensions.min.setZ(z[i]);
}
}
if (this.opts.octree) {
let initialBounds = Lore.AABB.fromPoints(positions);
let indices = new Uint32Array(length);
for (var i = 0; i < length; i++) {
indices[i] = i;
}
this.octree = new Lore.Octree(this.opts.octreeThreshold, this.opts.octreeMaxDepth);
this.octree.build(indices, positions, initialBounds);
}
this.setAttribute('position', positions);
return this;
}
/**
* Set the positions and the HSS (Hue, Saturation, Size) values of the points in the point cloud.
*
* @param {TypedArray} x An array containing the x components.
* @param {TypedArray} y An array containing the y components.
* @param {TypedArray} z An array containing the z components.
* @param {TypedArray} hue An array containing the hues of the data points.
* @param {TypedArray} saturation An array containing the saturations of the data points.
* @param {TypedArray} size An array containing the sizes of the data points.
* @returns {Lore.PointHelper} Itself.
*/
setPositionsXYZHSS(x, y, z, hue, saturation, size) {
let length = this.getMaxLength(x, y, z);
this.setPositionsXYZ(x, y, z, length);
if (typeof hue === 'number' && typeof saturation === 'number' && typeof size === 'number') {
this.setHSS(hue, saturation, size, length);
} else if (typeof hue !== 'number' && typeof saturation !== 'number' && typeof size !== 'number') {
this.setHSSFromArrays(hue, saturation, size, length);
} else {
if (typeof hue === 'number') {
let hueTmp = new Float32Array(length);
hueTmp.fill(hue);
hue = hueTmp;
}
if (typeof saturation === 'number') {
let saturationTmp = new Float32Array(length);
saturationTmp.fill(saturation);
saturation = saturationTmp;
}
if (typeof size === 'number') {
let sizeTmp = new Float32Array(length);
sizeTmp.fill(size);
size = sizeTmp;
}
this.setHSSFromArrays(hue, saturation, size, length);
}
// TODO: Check why the projection matrix update is needed
this.renderer.camera.updateProjectionMatrix();
this.renderer.camera.updateViewMatrix();
return this;
}
/**
* Set the hue from an rgb values.
*
* @param {TypeArray} r An array containing the red components of the colors.
* @param {TypeArray} g An array containing the green components of the colors.
* @param {TypeArray} b An array containing the blue components of the colors.
* @returns {Lore.PointHelper} Itself.
*/
setRGB(r, g, b) {
let c = new Float32Array(r.length * 3);
let colors = this.getAttribute('color');
for (let i = 0; i < r.length; i++) {
let j = 3 * i;
c[j] = r[i];
c[j + 1] = g[i];
c[j + 2] = b[i];
}
// Convert to HOS (Hue, Saturation, Size)
for (let i = 0; i < c.length; i += 3) {
let r = c[i];
let g = c[i + 1];
let b = c[i + 2];
c[i] = Lore.Color.rgbToHsl(r, g, b)[0];
c[i + 1] = colors[i + 1];
c[i + 2] = colors[i + 2];
}
this.updateColors(c);
return this;
}
/**
* Set the colors (HSS) for the points.
*
* @param {TypedArray} colors An array containing the HSS values.
* @returns {Lore.PointHelper} Itself.
*/
setColors(colors) {
this.setAttribute('color', colors);
return this;
}
/**
* Update the colors (HSS) for the points.
*
* @param {TypedArray} colors An array containing the HSS values.
* @returns {Lore.PointHelper} Itself.
*/
updateColors(colors) {
this.updateAttributeAll('color', colors);
return this;
}
/**
* Update the color (HSS) at a specific index.
*
* @param {Number} index The index of the data point.
* @param {Lore.Color} color An instance of Lore.Color containing HSS values.
* @returns {Lore.PointHelper} Itself.
*/
updateColor(index, color) {
this.updateAttribute('color', index, color.components);
return this;
}
/**
* Set the global point size.
*
* @param {Number} size The global point size.
* @returns {Number} The threshold for the raycaster.
*/
setPointSize(size) {
this.rawPointSize = size;
this.updatePointSize();
let pointSize = this.rawPointSize * this.opts.pointScale;
if (pointSize > this.opts.maxPointSize) {
return 0.5 * (this.opts.maxPointSize / pointSize);
} else {
return 0.5;
}
}
/**
* Updates the displayed point size.
*/
updatePointSize() {
let pointSize = this.rawPointSize * this.opts.pointScale;
if (pointSize > this.opts.maxPointSize) {
this.pointSize = this.opts.maxPointSize;
} else {
this.pointSize = pointSize;
}
this.geometry.shader.uniforms.size.value = this.pointSize;
}
/**
* Get the global point size.
*
* @returns {Number} The global point size.
*/
getPointSize() {
return this.geometry.shader.uniforms.size.value;
}
/**
* Get the global point scale.
*
* @returns {Number} The global point size.
*/
getPointScale() {
return this.opts.pointScale;
}
/**
* Sets the global point scale.
*
* @param {Number} pointScale The global point size.
* @returns {Lore.PointHelper} Itself.
*/
setPointScale(pointScale) {
this.opts.pointScale = pointScale;
this.updatePointSize();
return this;
}
/**
* Sets the fog start and end distances, as seen from the camera.
*
* @param {Number} fogStart The start distance of the fog.
* @param {Number} fogEnd The end distance of the fog.
* @returns {Lore.PointHelper} Itself.
*/
setFogDistance(fogStart, fogEnd) {
this.geometry.shader.uniforms.fogStart.value = fogStart;
this.geometry.shader.uniforms.fogEnd.value = fogEnd;
return this;
}
/**
* Initialize the point size based on the current zoom.
*
* @returns {Lore.PointHelper} Itself.
*/
initPointSize() {
this.setPointSize(this.renderer.camera.zoom + 0.1);
return this;
}
/**
* Get the current cutoff value.
*
* @returns {Number} The current cutoff value.
*/
getCutoff() {
return this.geometry.shader.uniforms.cutoff.value;
}
/**
* Set the cutoff value.
*
* @param {Number} cutoff A cutoff value.
* @returns {Lore.PointHelper} Itself.
*/
setCutoff(cutoff) {
this.geometry.shader.uniforms.cutoff.value = cutoff;
return this;
}
/**
* Get the hue for a given index.
*
* @param {Number} index An index.
* @returns {Number} The hue of the specified index.
*/
getHue(index) {
let colors = this.getAttribute('color');
return colors[index * 3];
}
/**
* Get the saturation for a given index.
*
* @param {Number} index An index.
* @returns {Number} The saturation of the specified index.
*/
getSaturation(index) {
let colors = this.getAttribute('color');
return colors[index * 3 + 1];
}
/**
* Get the size for a given index.
*
* @param {Number} index An index.
* @returns {Number} The size of the specified index.
*/
getSize(index) {
let colors = this.getAttribute('color');
return colors[index * 3 + 2];
}
/**
* Get the position for a given index.
*
* @param {Number} index An index.
* @returns {Number} The position of the specified index.
*/
getPosition(index) {
let positions = this.getAttribute('position');
return new Lore.Vector3f(positions[index * 3], positions[index * 3 + 1],
positions[index * 3 + 2]);
}
/**
* Set the hue. If a number is supplied, all the hues are set to the supplied number.
*
* @param {TypedArray|Number} hue The hue to be set. If a number is supplied, all hues are set to its value.
*/
setHue(hue) {
let colors = this.getAttribute('color');
let c = null;
let index = 0;
if (typeof hue === 'number') {
let length = colors.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = hue;
c[i + 1] = colors[i + 1];
c[i + 2] = colors[i + 2];
}
} else {
let length = hue.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = hue[index++];
c[i + 1] = colors[i + 1];
c[i + 2] = colors[i + 2];
}
}
this.setColors(c);
}
/**
* Set the saturation. If a number is supplied, all the saturations are set to the supplied number.
*
* @param {TypedArray|Number} hue The saturation to be set. If a number is supplied, all saturations are set to its value.
*/
setSaturation(saturation) {
let colors = this.getAttribute('color');
let c = null;
let index = 0;
if (typeof saturation === 'number') {
let length = colors.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = colors[i];
c[i + 1] = saturation;
c[i + 2] = colors[i + 2];
}
} else {
let length = saturation.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = colors[i];
c[i + 1] = saturation[index++];
c[i + 2] = colors[i + 2];
}
}
this.setColors(c);
}
/**
* Set the size. If a number is supplied, all the sizes are set to the supplied number.
*
* @param {TypedArray|Number} hue The size to be set. If a number is supplied, all sizes are set to its value.
*/
setSize(size) {
let colors = this.getAttribute('color');
let c = null;
let index = 0;
if (typeof size === 'number') {
let length = colors.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = colors[i];
c[i + 1] = colors[i + 1];
c[i + 2] = size;
}
} else {
let length = size.length;
c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = colors[i];
c[i + 1] = colors[i + 1];
c[i + 2] = size[index++];
}
}
this.setColors(c);
}
/**
* Set the HSS values. Sets all indices to the same values.
*
* @param {Number} hue A hue value.
* @param {Number} saturation A saturation value.
* @param {Number} size A size value.
* @param {Number} length The length of the arrays.
*/
setHSS(hue, saturation, size, length) {
let c = new Float32Array(length * 3);
for (let i = 0; i < length * 3; i += 3) {
c[i] = hue;
c[i + 1] = saturation;
c[i + 2] = size;
}
this.setColors(c);
}
/**
* Set the HSS values.
*
* @param {TypedArray} hue An array of hue values.
* @param {TypedArray} saturation An array of saturation values.
* @param {TypedArray} size An array of size values.
* @param {Number} length The length of the arrays.
*/
setHSSFromArrays(hue, saturation, size, length) {
let c = new Float32Array(length * 3);
let index = 0;
if (hue.length !== length && saturation.length !== length && size.length !== length) {
throw 'Hue, saturation and size have to be arrays of length "length" (' + length +').';
}
for (let i = 0; i < length * 3; i += 3) {
c[i] = hue[index];
c[i + 1] = saturation[index];
c[i + 2] = size[index];
index++;
}
this.setColors(c);
}
/**
* Add a filter to this point helper.
*
* @param {String} name The name of the filter.
* @param {Lore.FilterBase} filter A filter instance.
* @returns {Lore.PointHelper} Itself.
*/
addFilter(name, filter) {
filter.setGeometry(this.geometry);
this.filters[name] = filter;
return this;
}
/**
* Remove a filter by name.
*
* @param {String} name The name of the filter to be removed.
* @returns {Lore.PointHelper} Itself.
*/
removeFilter(name) {
delete this.filters[name];
return this;
}
/**
* Get a filter by name.
*
* @param {String} name The name of a filter.
* @returns {Lore.FilterBase} A filter instance.
*/
getFilter(name) {
return this.filters[name];
}
}