blob: b4203b99260a0824f8bc2607e91cfdc102e36017 [file] [log] [blame]
/**
* @fileoverview Custom Texture that holds beacon vertex data for the line
* vertex shader.
*
* @license Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Texture requires THREE.js.
if (typeof THREE === 'undefined') {
throw Error('THREE.js is required by BeaconVertexTexture.');
}
var geovelo;
geovelo = geovelo || {};
/**
* Custom texture to hold beacon vertex data. Each pixel of the underlying image
* holds two longitude/latitude pairs, pre-converted to Web Mercator
* coordinates. This is accomplished by using floating point image data which
* gives us 32-bit floats per channel instead of the typical 8-bit integer.
*
* The Red and Green channels for a pixel hold one lon/lat pair, respectively,
* and the Blue and Alpha channels hold another.
*
* +---------+
* | RG : | R/G = Lon/Lat
* | : |
* | : BA | B/A = Lon/Lat
* +---------+
*
* The first half of the first column (index 0) of pixels is special, because it
* contains the (b)ase longitude and latitude for the beacon (its first reading)
* in its RG slots.
*
* Starting with the second column (index 1), each RG and BA pair holds the
* cumulative offset relative to the base. So for example, if the beacon didn't
* move at all from the initial day to the second, then the RG channels of the
* second column would be 0/0.
*
* | | | | | |
* +-------+ +-------+ +-------+ b = base
* +-------+ +-------+ +-------+ + med = medians
* | b : | | 1 : | | 3 : | | N = number of timesteps
* 1 | : | | : | | : | | ...
* | : 0 | | : 2 | | : 4 | |
* +-------+ +-------+ +-------+ +--- ---+
* +-------+ +-------+ +-------+ +--- ---+ +-------+
* | b : | | 1 : | | 3 : | | | |N-1: |
* 0 | : | | : | | : | | ... | | : |
* | : 0 | | : 2 | | : 4 | | | | : N |
* +-------+ +-------+ +-------+ +--- ---+ +-------+
* +-------+ +-------+ +-------+ +--- ---+ +-------+
* | : | | 1 : | | 3 : | | | |N-1: |
* med | : | | : | | : | | ... | | : |
* | : 0 | | : 2 | | : 4 | | | | : N |
* +-------+ +-------+ +-------+ +--- ---+ +-------+
* px 0 1 2 width-1
*
* The LineShaderMaterial is responsible for using a given vertex's timestamp
* and beaconIndex to look up its lon/lat pair from this texture at render time.
*
* If more data is needed per beacon/timestatmp in the future, and that data is
* needed by more than one vertex, then this would be the place to add it. For
* example, rather than packing two lon/lat pairs into a pixel, you could pack
* just one in RG, then use the BA slots for other information (like the
* magnitude of the movement). But do so carefully, since this would require
* likewise changes in the LineShaderMaterial.
*
* If additional information is needed per vertex, and that information is NOT
* needed at render time by other vertices, then it would be better to add that
* data to an attribute on the Line's BufferGeometry (see geovelo.Overlay).
*
* @param {number} beaconCount Number of beacons represented.
* @param {number} startTimestamp The earliest Unix timestamp of any data point.
* @param {number} endTimestamp The latest Unix timestamp of any data point.
*/
geovelo.BeaconVertexTexture =
function(beaconCount, startTimestamp, endTimestamp) {
THREE.Texture.call(this, null);
this.beaconCount = beaconCount;
this.startTimestamp = startTimestamp;
this.endTimestamp = endTimestamp;
this.timestampCount = (endTimestamp - startTimestamp) / 60 / 60 / 24 + 1;
// Each column represents two timestamps, plus a slot for for base positions.
this.width = Math.ceil((this.timestampCount + 1) / 2)
// One row per beacon, plus one for storing the medians.
this.height = beaconCount + 1;
this.data = new Float32Array(this.width * this.height * 4);
this.image = {
data: this.data,
width: this.width,
height: this.height
};
this.format = THREE.RGBAFormat;
this.type = THREE.FloatType;
this.magFilter = THREE.NearestFilter;
this.minFilter = THREE.NearestFilter;
this.flipY = false;
this.generateMipmaps = false;
};
geovelo.BeaconVertexTexture.prototype =
Object.create(THREE.DataTexture.prototype);
geovelo.BeaconVertexTexture.prototype.constructor = geovelo.BeaconVertexTexture;
/**
* Given a beacon index and time index, compute the offset into the data array
* where the longitude would be found (latitude will be one greater). This
* method does no bounds checking, and so it can be used to find the offset for
* the special beacon base column and the special medians row.
*
* @param {number} beaconIndex Index of the beacon, or -1 for the beacon's base.
* @param {number} timeIndex Index of timestamp or -1 for the median row.
*/
geovelo.BeaconVertexTexture.prototype.computeOffset =
function(beaconIndex, timeIndex) {
return (beaconIndex + 1) * this.width * 4 + (timeIndex + 1) * 2;
};
/**
* Given a timestamp, return the number of days since the start timestamp.
*
* @param {number} timestamp Timestamp to convert.
*/
geovelo.BeaconVertexTexture.prototype.getTimeIndex = function(timestamp) {
return (timestamp - this.startTimestamp) / 60 / 60 / 24;
}
/**
* Set the longitude and latitude values for a particular beacon at a particular
* timestamp.
*
* @param {number} beaconIndex Index of the beacon.
* @param {number} timestamp Timestamp to set.
* @param {number} lon The longitude diff in Web Mercator projected coordinates.
* @param {number} lat The latitude diff in Web Mercator projected coordinates.
*/
geovelo.BeaconVertexTexture.prototype.setBeaconLonLat =
function(beaconIndex, timestamp, lon, lat) {
if (beaconIndex >= this.beaconCount) {
throw Error('Beacon index out of bounds.');
}
if (timestamp < this.startTimestamp || timestamp > this.endTimestamp) {
throw Error('Timestamp out of bounds.');
}
var offset = this.computeOffset(beaconIndex, this.getTimeIndex(timestamp));
this.data[offset + 0] = lon;
this.data[offset + 1] = lat;
this.needsUpdate = true;
};
/**
* Set the cumulative median difference lon/lat for a particular timestamp.
*
* @param {number} timestamp Timestamp to set.
* @param {number} lon The longitude diff in Web Mercator projected coordinates.
* @param {number} lat The latitude diff in Web Mercator projected coordinates.
*/
geovelo.BeaconVertexTexture.prototype.setMedianLonLat =
function(timestamp, lon, lat) {
if (timestamp < this.startTimestamp || timestamp > this.endTimestamp) {
throw Error('Timestamp out of bounds.');
}
var offset = this.computeOffset(-1, this.getTimeIndex(timestamp));
this.data[offset + 0] = lon;
this.data[offset + 1] = lat;
this.needsUpdate = true;
};
/**
* Set the cumulative median difference lon/lat for a particular timestamp.
*
* @param {number} beaconIndex Index of the beacon.
* @param {number} lon The longitude diff in Web Mercator projected coordinates.
* @param {number} lat The latitude diff in Web Mercator projected coordinates.
*/
geovelo.BeaconVertexTexture.prototype.setBaseLonLat =
function(beaconIndex, lon, lat) {
if (beaconIndex >= this.beaconCount) {
throw Error('Beacon index out of bounds.');
}
var offset = this.computeOffset(beaconIndex, -1);
this.data[offset + 0] = lon;
this.data[offset + 1] = lat;
this.needsUpdate = true;
};