mirror of
https://github.com/NASAWorldWind/WebWorldWind.git
synced 2026-02-01 16:38:15 +00:00
Merge pull request #2 from NASAWorldWind/master
Import latest state from NASA repo
This commit is contained in:
commit
0bf46471c0
5
.idea/runConfigurations/AnalyticalSurface.xml
generated
Normal file
5
.idea/runConfigurations/AnalyticalSurface.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="AnalyticalSurface" type="JavascriptDebugType" factoryName="JavaScript Debug" uri="http://localhost:63342/WebWorldWind/examples/AnalyticalSurface.html">
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
7
.idea/runConfigurations/Annotations.xml
generated
Normal file
7
.idea/runConfigurations/Annotations.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Annotations" type="JavascriptDebugType" factoryName="JavaScript Debug" uri="http://localhost:63342/WebWorldWind/examples/Annotations.html">
|
||||
<RunnerSettings RunnerId="JavascriptDebugRunner" />
|
||||
<ConfigurationWrapper RunnerId="JavascriptDebugRunner" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
@ -1,4 +1,4 @@
|
||||
#Web World Wind#
|
||||
# Web World Wind #
|
||||
|
||||
Web World Wind is a 3D virtual globe API for JavaScript. You can use it to provide a geographic context, complete
|
||||
with terrain, for visualizing geographic or geo-located information. Web World Wind provides high-resolution terrain
|
||||
|
||||
168
apps/USGSSlabs/DataGrid.js
Normal file
168
apps/USGSSlabs/DataGrid.js
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2015 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
/**
|
||||
* @exports DataGrid
|
||||
*/
|
||||
define(function () {
|
||||
"use strict";
|
||||
|
||||
var DataGrid = function (data) {
|
||||
var latitude, longitude, altitude, firstLatitude = -1e5, lineCounter = 0, indexMap = [];
|
||||
|
||||
this.width = 0;
|
||||
this.positions = [];
|
||||
this.minLon = this.minLat = Number.MAX_VALUE;
|
||||
this.maxLon = this.maxLat = -Number.MAX_VALUE;
|
||||
|
||||
// Read the data but retain only the positions that have altitude values.
|
||||
var lines = data.split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var rawPosition = lines[i].trim().split("\t");
|
||||
if (rawPosition.length != 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
longitude = parseFloat(rawPosition[0]);
|
||||
latitude = parseFloat(rawPosition[1]);
|
||||
|
||||
if (longitude > 180) {
|
||||
longitude -= 360;
|
||||
}
|
||||
|
||||
if (longitude < this.minLon)
|
||||
this.minLon = longitude;
|
||||
if (longitude > this.maxLon)
|
||||
this.maxLon = longitude;
|
||||
if (latitude < this.minLat)
|
||||
this.minLat = latitude;
|
||||
if (latitude > this.maxLat)
|
||||
this.maxLat = latitude;
|
||||
|
||||
// Recognize when the first row of the grid ends and use that to calculate the grid width.
|
||||
if (firstLatitude === -1e5) { // identify the first latitude in the grid
|
||||
firstLatitude = latitude;
|
||||
} else if (latitude !== firstLatitude && this.width === 0) {
|
||||
// We've reached the first position of the second row, so now we know the grid width.
|
||||
this.width = lineCounter;
|
||||
}
|
||||
|
||||
if (rawPosition[2] != "NaN") {
|
||||
altitude = parseFloat(rawPosition[2]);
|
||||
// Keep a map that relates the original ordinal position in the data to the retained positions list.
|
||||
indexMap[lineCounter] = this.positions.length;
|
||||
this.positions.push(new WorldWind.Position(latitude, longitude, altitude * 1000));
|
||||
}
|
||||
|
||||
++lineCounter;
|
||||
}
|
||||
|
||||
this.height = lineCounter / this.width;
|
||||
this.deltaLat = (this.maxLat - this.minLat) / (this.height - 1);
|
||||
this.deltaLon = (this.maxLon - this.minLon) / (this.width - 1);
|
||||
this.indexMap = indexMap;
|
||||
};
|
||||
|
||||
DataGrid.prototype.makeGridIndices = function () {
|
||||
// Create all the triangles formed by the original grid.
|
||||
|
||||
var gridIndices = [], i = 0, width = this.width, height = this.height;
|
||||
|
||||
for (var r = 0; r < height - 1; r++) {
|
||||
for (var c = 0; c < width - 1; c++) {
|
||||
var k = r * width + c;
|
||||
|
||||
// lower left triangle
|
||||
gridIndices[i++] = k;
|
||||
gridIndices[i++] = k + 1;
|
||||
gridIndices[i++] = k + width;
|
||||
|
||||
// upper right triangle
|
||||
gridIndices[i++] = k + 1;
|
||||
gridIndices[i++] = k + 1 + width;
|
||||
gridIndices[i++] = k + width;
|
||||
}
|
||||
}
|
||||
|
||||
return gridIndices;
|
||||
};
|
||||
|
||||
DataGrid.prototype.findTriangles = function () {
|
||||
// Create triangles from the retained positions.
|
||||
|
||||
var gridIndices = this.makeGridIndices(), // get all the triangles in the original grid
|
||||
indexMap = this.indexMap, // maps original grid indices to the indices in the retained-positions array
|
||||
mappedIndices = [], // collects the triangle definitions
|
||||
ia, ib, ic, iaMapped, ibMapped, icMapped;
|
||||
|
||||
for (var i = 0; i < gridIndices.length; i += 3) { // for all original triangles
|
||||
// Determine the triangle's indices in the retained-positions array.
|
||||
ia = gridIndices[i];
|
||||
ib = gridIndices[i + 1];
|
||||
ic = gridIndices[i + 2];
|
||||
|
||||
// Determine whether those original positions had data associated with them. If they did not then
|
||||
// they will not have an entry in the index map.
|
||||
iaMapped = indexMap[ia];
|
||||
ibMapped = indexMap[ib];
|
||||
icMapped = indexMap[ic];
|
||||
|
||||
// If all three triangle vertices have data associated, save the triangle, using the mapped indices.
|
||||
if (iaMapped && ibMapped && icMapped) {
|
||||
mappedIndices.push(iaMapped);
|
||||
mappedIndices.push(ibMapped);
|
||||
mappedIndices.push(icMapped);
|
||||
}
|
||||
}
|
||||
|
||||
return mappedIndices;
|
||||
};
|
||||
|
||||
DataGrid.prototype.lookupValue = function (latitude, longitude) {
|
||||
// Look up a value from the grid using bilinear interpolation.
|
||||
|
||||
// Determine the four corner indices of the original grid.
|
||||
var colNW = Math.floor((this.width - 1) * (longitude - this.minLon) / (this.maxLon - this.minLon)),
|
||||
rowNW = Math.floor((this.height - 1) * (this.maxLat - latitude) / (this.maxLat - this.minLat));
|
||||
|
||||
if (colNW === this.width - 1) {
|
||||
--colNW;
|
||||
}
|
||||
if (rowNW === this.height - 1) {
|
||||
--rowNW;
|
||||
}
|
||||
|
||||
var nwIndex = rowNW * this.width + colNW,
|
||||
neIndex = nwIndex + 1,
|
||||
swIndex = nwIndex + this.width,
|
||||
seIndex = swIndex + 1;
|
||||
|
||||
// Map the grid indices to the retained-positions indices.
|
||||
var indexMap = this.indexMap;
|
||||
swIndex = indexMap[swIndex];
|
||||
seIndex = indexMap[seIndex];
|
||||
nwIndex = indexMap[nwIndex];
|
||||
neIndex = indexMap[neIndex];
|
||||
|
||||
// If all four corners have values, interpolate the values over the grid cell.
|
||||
if (swIndex && seIndex && nwIndex && neIndex) {
|
||||
var a = this.positions[swIndex], aa = a.altitude,
|
||||
b = this.positions[seIndex], bb = b.altitude,
|
||||
c = this.positions[nwIndex], cc = c.altitude,
|
||||
d = this.positions[neIndex], dd = d.altitude;
|
||||
|
||||
var s = (longitude - a.longitude) / this.deltaLon,
|
||||
t = (latitude - a.latitude) / this.deltaLat;
|
||||
var lower = (1 - s) * aa + s * bb,
|
||||
upper = (1 - s) * cc + s * dd,
|
||||
result = (1 - t) * lower + t * upper;
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return DataGrid;
|
||||
});
|
||||
@ -9,12 +9,14 @@ define(['../../src/WorldWind',
|
||||
'../util/GoToBox',
|
||||
'../util/LayersPanel',
|
||||
'../util/ProjectionMenu',
|
||||
'../util/TerrainOpacityController'],
|
||||
'../util/TerrainOpacityController',
|
||||
'DataGrid'],
|
||||
function (ww,
|
||||
GoToBox,
|
||||
LayersPanel,
|
||||
ProjectionMenu,
|
||||
TerrainOpacityController) {
|
||||
TerrainOpacityController,
|
||||
DataGrid) {
|
||||
"use strict";
|
||||
|
||||
var USGSSlabs = function () {
|
||||
@ -43,6 +45,10 @@ define(['../../src/WorldWind',
|
||||
|
||||
// Enable sub-surface rendering for the World Window.
|
||||
this.wwd.subsurfaceMode = true;
|
||||
// Enable deep picking in order to detect the sub-surface shapes.
|
||||
this.wwd.deepPicking = true;
|
||||
|
||||
this.wwd.surfaceOpacity = 0.7;
|
||||
|
||||
// Start the view pointing to a longitude within the current time zone.
|
||||
this.wwd.navigator.lookAtLocation.latitude = 30;
|
||||
@ -53,12 +59,60 @@ define(['../../src/WorldWind',
|
||||
this.projectionMenu = new ProjectionMenu(this.wwd);
|
||||
this.terrainOpacityController = new TerrainOpacityController(this.wwd);
|
||||
|
||||
this.screenText = new WorldWind.ScreenText(
|
||||
new WorldWind.Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), "Upper Left");
|
||||
var textAttributes = new WorldWind.TextAttributes(textAttributes);
|
||||
// Use offset to position the lower left corner of the text string at the shape's screen location.
|
||||
textAttributes.offset = new WorldWind.Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, 0);
|
||||
this.screenText.attributes = textAttributes;
|
||||
this.textLayer = new WorldWind.RenderableLayer();
|
||||
this.textLayer.hide = true;
|
||||
this.textLayer.enabled = false;
|
||||
this.textLayer.addRenderable(this.screenText);
|
||||
this.wwd.addLayer(this.textLayer);
|
||||
|
||||
this.layersPanel.synchronizeLayerList();
|
||||
|
||||
this.loadSlabData("CAS", "cascadia_slab1.0_clip.xyz", 401, WorldWind.Color.YELLOW);
|
||||
this.loadSlabData("SOL", "sol_slab1.0_clip.xyz", 1001, WorldWind.Color.YELLOW);
|
||||
this.loadSlabData("MEX", "mex_slab1.0_clip.xyz", 1251, WorldWind.Color.CYAN);
|
||||
this.loadSlabData("ALU", "alu_slab1.0_clip.xyz", 2451, WorldWind.Color.MAGENTA);
|
||||
//this.loadSlabData("ALU", "alu_slab1.0_clip.xyz", 2451, WorldWind.Color.MAGENTA);
|
||||
|
||||
var handlePick = (function (o) {
|
||||
var pickPoint = this.wwd.canvasCoordinates(o.clientX, o.clientY);
|
||||
|
||||
this.textLayer.enabled = false;
|
||||
this.wwd.redraw();
|
||||
|
||||
var pickList = this.wwd.pick(pickPoint);
|
||||
if (pickList.objects.length > 0) {
|
||||
for (var p = 0; p < pickList.objects.length; p++) {
|
||||
var pickedObject = pickList.objects[p];
|
||||
if (pickedObject.userObject instanceof WorldWind.TriangleMesh) {
|
||||
if (pickedObject.position) {
|
||||
var latitude = pickedObject.position.latitude,
|
||||
longitude = pickedObject.position.longitude,
|
||||
altitude = pickedObject.userObject.dataGrid.lookupValue(latitude, longitude);
|
||||
if (altitude !== null) {
|
||||
this.screenText.screenOffset.x = pickPoint[0];
|
||||
this.screenText.screenOffset.y = this.wwd.viewport.width - pickPoint[1];
|
||||
this.screenText.text = Math.floor(Math.abs(altitude) / 1000).toString() + " Km";
|
||||
this.textLayer.enabled = true;
|
||||
}
|
||||
//console.log("PO: " + pickedObject.position.toString() + " " + pickedObject.isOnTop);
|
||||
//console.log("TN: " + pickList.terrainObject().position.toString() +
|
||||
// " " + pickList.terrainObject().isOnTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
// Listen for mouse moves and highlight the placemarks that the cursor rolls over.
|
||||
this.wwd.addEventListener("mousemove", handlePick);
|
||||
|
||||
// Listen for taps on mobile devices and highlight the placemarks that the user taps.
|
||||
var tapRecognizer = new WorldWind.TapRecognizer(this.wwd, handlePick);
|
||||
};
|
||||
|
||||
USGSSlabs.prototype.loadSlabData = function (name, dataFile, width, color) {
|
||||
@ -71,7 +125,8 @@ define(['../../src/WorldWind',
|
||||
xhr.onreadystatechange = (function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
this.parse(name, width, color, xhr.responseText);
|
||||
var dataGrid = new DataGrid(xhr.responseText);
|
||||
this.addGridToWorldWindow(name, dataGrid, color);
|
||||
}
|
||||
else {
|
||||
Logger.log(Logger.LEVEL_WARNING,
|
||||
@ -91,9 +146,7 @@ define(['../../src/WorldWind',
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
USGSSlabs.prototype.parse = function (name, width, color, responseText) {
|
||||
var lines = responseText.split("\n");
|
||||
|
||||
USGSSlabs.prototype.addGridToWorldWindow = function (name, dataGrid, color) {
|
||||
var meshLayer = new WorldWind.RenderableLayer();
|
||||
meshLayer.displayName = name;
|
||||
this.wwd.addLayer(meshLayer);
|
||||
@ -107,15 +160,14 @@ define(['../../src/WorldWind',
|
||||
var highlightAttributes = new WorldWind.ShapeAttributes(meshAttributes);
|
||||
highlightAttributes.outlineColor = WorldWind.Color.WHITE;
|
||||
|
||||
var positions = this.makePositionList(lines);
|
||||
var gridIndices = this.makeGridIndices(width, positions.numOriginalPositions / width);
|
||||
var indices = this.findTriangles(gridIndices, positions.indexMap);
|
||||
var splitShapes = WorldWind.TriangleMesh.split(positions.positions, indices, null, null);
|
||||
var indices = dataGrid.findTriangles();
|
||||
var splitShapes = WorldWind.TriangleMesh.split(dataGrid.positions, indices, null, null);
|
||||
|
||||
for (var i = 0; i < splitShapes.length; i++) {
|
||||
var mesh = new WorldWind.TriangleMesh(splitShapes[i].positions, splitShapes[i].indices, meshAttributes);
|
||||
mesh.altitudeScale = 100;
|
||||
mesh.altitudeMode = WorldWind.ABSOLUTE;
|
||||
mesh.highlightAttributes = highlightAttributes;
|
||||
mesh.dataGrid = dataGrid;
|
||||
meshLayer.addRenderable(mesh);
|
||||
}
|
||||
|
||||
@ -123,79 +175,5 @@ define(['../../src/WorldWind',
|
||||
this.wwd.redraw();
|
||||
};
|
||||
|
||||
USGSSlabs.prototype.makeGridIndices = function (width, height) {
|
||||
var indices = [], i = 0;
|
||||
|
||||
for (var r = 0; r < height - 1; r++) {
|
||||
for (var c = 0; c < width - 1; c++) {
|
||||
var k = r * width + c;
|
||||
|
||||
indices[i++] = k;
|
||||
indices[i++] = k + 1;
|
||||
indices[i++] = k + width;
|
||||
indices[i++] = k + 1;
|
||||
indices[i++] = k + 1 + width;
|
||||
indices[i++] = k + width;
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
};
|
||||
|
||||
USGSSlabs.prototype.makePositionList = function (lines) {
|
||||
var positions = [],
|
||||
indices = [],
|
||||
originalIndex = 0,
|
||||
latitude, longitude, altitude;
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var rawPosition = lines[i].trim().split("\t");
|
||||
if (rawPosition.length != 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rawPosition[2] != "NaN") {
|
||||
indices[originalIndex] = positions.length;
|
||||
|
||||
longitude = parseFloat(rawPosition[0]);
|
||||
latitude = parseFloat(rawPosition[1]);
|
||||
altitude = parseFloat(rawPosition[2]);
|
||||
|
||||
if (longitude > 180) {
|
||||
longitude -= 360;
|
||||
}
|
||||
|
||||
positions.push(new WorldWind.Position(latitude, longitude, altitude));
|
||||
}
|
||||
|
||||
++originalIndex;
|
||||
}
|
||||
|
||||
return {positions: positions, indexMap: indices, numOriginalPositions: originalIndex};
|
||||
};
|
||||
|
||||
USGSSlabs.prototype.findTriangles = function (gridIndices, indexMap) {
|
||||
var mappedIndices = [],
|
||||
ia, ib, ic, iaMapped, ibMapped, icMapped;
|
||||
|
||||
for (var i = 0; i < gridIndices.length; i += 3) {
|
||||
ia = gridIndices[i];
|
||||
ib = gridIndices[i + 1];
|
||||
ic = gridIndices[i + 2];
|
||||
|
||||
iaMapped = indexMap[ia];
|
||||
ibMapped = indexMap[ib];
|
||||
icMapped = indexMap[ic];
|
||||
|
||||
if (iaMapped && ibMapped && icMapped) {
|
||||
mappedIndices.push(iaMapped);
|
||||
mappedIndices.push(ibMapped);
|
||||
mappedIndices.push(icMapped);
|
||||
}
|
||||
}
|
||||
|
||||
return mappedIndices;
|
||||
};
|
||||
|
||||
return USGSSlabs;
|
||||
});
|
||||
@ -22,7 +22,7 @@ define(function () {
|
||||
this.wwd = worldWindow;
|
||||
|
||||
this.slider = $("#terrainOpacitySlider").slider({
|
||||
value: 100,
|
||||
value: this.wwd.surfaceOpacity * 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
animate: true,
|
||||
|
||||
8
build.js
8
build.js
@ -1,10 +1,10 @@
|
||||
{
|
||||
({
|
||||
baseUrl: 'src',
|
||||
name: '../tools/almond',
|
||||
include: ['WorldWind'],
|
||||
out: 'worldwindlib.js',
|
||||
wrap: {
|
||||
startFile: 'tools/wrap.start',
|
||||
endFile: 'tools/wrap.end'
|
||||
}
|
||||
}
|
||||
endFile: 'tools/wrap.end'
|
||||
}
|
||||
})
|
||||
294
examples/AnnotationController.js
Normal file
294
examples/AnnotationController.js
Normal file
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
define(function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs an annotation controller for a specified {@link WorldWindow}.
|
||||
* @alias AnnotationController
|
||||
* @constructor
|
||||
* @classdesc Provides an annotation controller to interactively update DOM elements corresponding to a
|
||||
* specific annotation
|
||||
* @param {WorldWindow} worldWindow The World Window to associate this annotation controller with. Used
|
||||
* mainly for redrawing the wwd globe after changing settings.
|
||||
*/
|
||||
var AnnotationController = function (worldWindow) {
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* The World Window associated with this annotation controller.
|
||||
* @type {WorldWindow}
|
||||
*/
|
||||
this.worldWindow = worldWindow;
|
||||
|
||||
// Store the loaded annotation so we may read/modify
|
||||
// it/s settings
|
||||
this.currentAnnotation = null;
|
||||
|
||||
//Store DOM slider elements
|
||||
this.opacitySlider = $("#opacitySlider");
|
||||
this.scaleSlider = $("#scaleSlider");
|
||||
this.cornerSlider = $("#cornerSlider");
|
||||
this.backgroundR = $("#bgR");
|
||||
this.backgroundG = $("#bgG");
|
||||
this.backgroundB = $("#bgB");
|
||||
this.textR = $("#textR");
|
||||
this.textG = $("#textG");
|
||||
this.textB = $("#textB");
|
||||
|
||||
// Store DOM spinner elements for the insets
|
||||
this.spinnerLeft = $("#spinnerLeft");
|
||||
this.spinnerRight = $("#spinnerRight");
|
||||
this.spinnerTop = $("#spinnerTop");
|
||||
this.spinnerBottom = $("#spinnerBottom");
|
||||
|
||||
// Store DOM input elements
|
||||
this.text = $("#annotationText");
|
||||
|
||||
// Store DOM label elements
|
||||
this.bgColorLabel = $("#bgColor");
|
||||
this.textColorLabel = $("#textColor");
|
||||
this.opacityLabel = $("#opacity");
|
||||
this.cornerRadiusLabel = $("#cornerRadius");
|
||||
|
||||
// Create an event for the textbox so that we may update the
|
||||
// annotations text as this one's text changes
|
||||
this.text.on('input', function (e) {
|
||||
self.currentAnnotation.text = this.value;
|
||||
self.worldWindow.redraw();
|
||||
});
|
||||
|
||||
this.opacitySlider.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
$("#opacity").html(ui.value);
|
||||
self.currentAnnotation.attributes.opacity = ui.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.scaleSlider.slider({
|
||||
value: 1,
|
||||
min: 1,
|
||||
max: 2,
|
||||
step: 0.1,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
$("#scale").html(ui.value);
|
||||
self.currentAnnotation.attributes.scale = ui.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.cornerSlider.slider({
|
||||
value: 1,
|
||||
min: 0,
|
||||
max: 20,
|
||||
step: 1,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
$("#cornerRadius").html(ui.value);
|
||||
self.currentAnnotation.attributes.cornerRadius = ui.value;
|
||||
}
|
||||
});
|
||||
|
||||
// Red value of the background color
|
||||
this.backgroundR.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
step: 1,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeBackgroundColor(
|
||||
self.backgroundR.slider('value'),
|
||||
self.backgroundG.slider('value'),
|
||||
self.backgroundB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Green value of the background color
|
||||
this.backgroundG.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeBackgroundColor(
|
||||
self.backgroundR.slider('value'),
|
||||
self.backgroundG.slider('value'),
|
||||
self.backgroundB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Blue value of the background color
|
||||
this.backgroundB.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeBackgroundColor(
|
||||
self.backgroundR.slider('value'),
|
||||
self.backgroundG.slider('value'),
|
||||
self.backgroundB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Red value of the text color
|
||||
this.textR.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeTextColor(
|
||||
self.textR.slider('value'),
|
||||
self.textG.slider('value'),
|
||||
self.textB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Green value of the text color
|
||||
this.textG.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeTextColor(
|
||||
self.textR.slider('value'),
|
||||
self.textG.slider('value'),
|
||||
self.textB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Blue value of the text color
|
||||
this.textB.slider({
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 255,
|
||||
animate: true,
|
||||
slide: function (event, ui) {
|
||||
self.changeTextColor(
|
||||
self.textR.slider('value'),
|
||||
self.textG.slider('value'),
|
||||
self.textB.slider('value'));
|
||||
}
|
||||
});
|
||||
|
||||
// Left inset spinner
|
||||
this.spinnerLeft.spinner({
|
||||
min: 0,
|
||||
max: 100,
|
||||
spin: function (event, ui) {
|
||||
var insets = self.currentAnnotation.attributes.insets.clone();
|
||||
insets.left = ui.value;
|
||||
self.currentAnnotation.attributes.insets = insets;
|
||||
self.worldWindow.redraw();
|
||||
}
|
||||
});
|
||||
|
||||
// Right inset spinner
|
||||
this.spinnerRight.spinner({
|
||||
min: 0,
|
||||
max: 100,
|
||||
spin: function (event, ui) {
|
||||
var insets = self.currentAnnotation.attributes.insets.clone();
|
||||
insets.right = ui.value;
|
||||
self.currentAnnotation.attributes.insets = insets;
|
||||
self.worldWindow.redraw();
|
||||
}
|
||||
});
|
||||
|
||||
// Top inset spinner
|
||||
this.spinnerTop.spinner({
|
||||
min: 0,
|
||||
max: 100,
|
||||
spin: function (event, ui) {
|
||||
var insets = self.currentAnnotation.attributes.insets.clone();
|
||||
insets.top = ui.value;
|
||||
self.currentAnnotation.attributes.insets = insets;
|
||||
self.worldWindow.redraw();
|
||||
}
|
||||
});
|
||||
|
||||
// Bottom inset spinner
|
||||
this.spinnerBottom.spinner({
|
||||
min: 0,
|
||||
max: 100,
|
||||
spin: function (event, ui) {
|
||||
var insets = self.currentAnnotation.attributes.insets.clone();
|
||||
insets.bottom = ui.value;
|
||||
self.currentAnnotation.attributes.insets = insets;
|
||||
self.worldWindow.redraw();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Internal
|
||||
AnnotationController.prototype.changeTextColor = function(r, g, b) {
|
||||
this.textColorLabel.html("RGB(" + r + "," + g + "," + b + ")");
|
||||
this.currentAnnotation.attributes.textAttributes.color = WorldWind.Color.colorFromBytes(r, g, b, 255);
|
||||
this.worldWindow.redraw();
|
||||
};
|
||||
|
||||
// Internal
|
||||
AnnotationController.prototype.changeBackgroundColor = function(r, g, b) {
|
||||
this.bgColorLabel.html("RGB(" + r + "," + g + "," + b + ")");
|
||||
this.currentAnnotation.attributes.backgroundColor = WorldWind.Color.colorFromBytes(r, g, b, 255);
|
||||
this.worldWindow.redraw();
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads an annotations and adjusts ui controls based on it's settings
|
||||
* @param annotation
|
||||
*/
|
||||
AnnotationController.prototype.load = function (annotation) {
|
||||
|
||||
this.currentAnnotation = annotation;
|
||||
|
||||
var bgRed = annotation.attributes.backgroundColor.red * 255,
|
||||
bgGreen = annotation.attributes.backgroundColor.green * 255,
|
||||
bgBlue = annotation.attributes.backgroundColor.blue * 255,
|
||||
textRed = annotation.attributes.textAttributes.color.red * 255,
|
||||
textGreen = annotation.attributes.textAttributes.color.green * 255,
|
||||
textBlue = annotation.attributes.textAttributes.color.blue * 255;
|
||||
|
||||
// Load background RGB sliders and format label based on values
|
||||
this.backgroundR.slider('value', bgRed);
|
||||
this.backgroundG.slider('value', bgGreen);
|
||||
this.backgroundB.slider('value', bgBlue);
|
||||
this.bgColorLabel.html("RGB(" + bgRed + "," + bgGreen + "," + bgBlue + ")");
|
||||
|
||||
// Load text RGB sliders and format label based on values
|
||||
this.textR.slider('value', textRed);
|
||||
this.textG.slider('value', textGreen);
|
||||
this.textB.slider('value', textBlue);
|
||||
this.textColorLabel.html("RGB(" + textRed + "," + textGreen + "," + textBlue + ")");
|
||||
|
||||
// Load sliders settings and adjusts labels with their values
|
||||
this.opacitySlider.slider('value', annotation.attributes.opacity);
|
||||
this.scaleSlider.slider('value', annotation.attributes.scale);
|
||||
this.cornerSlider.slider('value', annotation.attributes.cornerRadius);
|
||||
this.opacityLabel.html(annotation.attributes.opacity);
|
||||
this.cornerRadiusLabel.html(annotation.attributes.cornerRadius);
|
||||
|
||||
// Load insets values into the spinners
|
||||
this.spinnerBottom.val(annotation.attributes.insets.bottom);
|
||||
this.spinnerTop.val(annotation.attributes.insets.top);
|
||||
this.spinnerLeft.val(annotation.attributes.insets.left);
|
||||
this.spinnerRight.val(annotation.attributes.insets.right);
|
||||
|
||||
//Load and display the text
|
||||
this.text.val(annotation.text);
|
||||
|
||||
};
|
||||
|
||||
return AnnotationController;
|
||||
});
|
||||
70
examples/Annotations.html
Normal file
70
examples/Annotations.html
Normal file
@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.min.css">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" type="text/javascript"></script>
|
||||
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
||||
<script data-main="Annotations"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.17/require.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="jumbotron hidden-xs">
|
||||
<h1 style="text-align:center">World Wind Annotations</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<h4>Projection</h4>
|
||||
<div class="dropdown" id="projectionDropdown">
|
||||
</div>
|
||||
<br>
|
||||
<div class="list-group" id="annotationList">
|
||||
<div class="input-group" id="annotationBox">
|
||||
<textarea name="Text1" cols="30" rows="5" id="annotationText"></textarea>
|
||||
<h4>Opacity<span id="opacity" class="pull-right"></span></h4>
|
||||
<div id="opacitySlider"></div>
|
||||
<br>
|
||||
<h4>Scale<span id="opacity" class="pull-right"></span></h4>
|
||||
<div id="scaleSlider"></div>
|
||||
<br>
|
||||
<h4>Corner radius<span id="cornerRadius" class="pull-right"></span></h4>
|
||||
<div id="cornerSlider"></div>
|
||||
<br>
|
||||
<h4>Bg color<span id="bgColor" class="pull-right"></span></h4>
|
||||
<div id="bgR"></div>
|
||||
<br>
|
||||
<div id="bgG"></div>
|
||||
<br>
|
||||
<div id="bgB"></div>
|
||||
<br>
|
||||
<h4>Text color<span id="textColor" class="pull-right"></span></h4>
|
||||
<div id="textR"></div>
|
||||
<br>
|
||||
<div id="textG"></div>
|
||||
<br>
|
||||
<div id="textB"></div>
|
||||
<h4>Insets<span id="textInsets" class="pull-right"></span></h4>
|
||||
<div class="row">
|
||||
<div class="col-xs-6"><input id="spinnerLeft" name="value" style="width:30px"></div>
|
||||
<div class="col-xs-6"><input id="spinnerRight" name="value" style="width:30px"></div>
|
||||
<div class="col-xs-6"><input id="spinnerTop" name="value" style="width:30px"></div>
|
||||
<div class="col-xs-6"><input id="spinnerBottom" name="value" style="width:30px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-9" id="globe">
|
||||
<canvas id="canvasOne" width="1000" height="1000" style="width: 100%; height: auto">
|
||||
Your browser does not support HTML5 Canvas.
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
142
examples/Annotations.js
Normal file
142
examples/Annotations.js
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
requirejs(['../src/WorldWind',
|
||||
'./LayerManager',
|
||||
'./AnnotationController'],
|
||||
function (ww,
|
||||
LayerManager,
|
||||
AnnotationController) {
|
||||
"use strict";
|
||||
|
||||
// Tell World Wind to log only warnings.
|
||||
WorldWind.Logger.setLoggingLevel(WorldWind.Logger.LEVEL_WARNING);
|
||||
|
||||
// Create the World Window.
|
||||
var wwd = new WorldWind.WorldWindow("canvasOne");
|
||||
|
||||
var layers = [
|
||||
{layer: new WorldWind.BMNGLayer(), enabled: true},
|
||||
{layer: new WorldWind.BMNGLandsatLayer(), enabled: false},
|
||||
{layer: new WorldWind.BingAerialWithLabelsLayer(null), enabled: true},
|
||||
{layer: new WorldWind.OpenStreetMapImageLayer(null), enabled: false},
|
||||
{layer: new WorldWind.CompassLayer(), enabled: true},
|
||||
{layer: new WorldWind.CoordinatesDisplayLayer(wwd), enabled: true},
|
||||
{layer: new WorldWind.ViewControlsLayer(wwd), enabled: true}
|
||||
];
|
||||
|
||||
for (var l = 0; l < layers.length; l++) {
|
||||
layers[l].layer.enabled = layers[l].enabled;
|
||||
wwd.addLayer(layers[l].layer);
|
||||
}
|
||||
|
||||
var annotationsLayer = new WorldWind.RenderableLayer("Annotations");
|
||||
|
||||
var annotationController = new AnnotationController(wwd);
|
||||
|
||||
var layerManger = new LayerManager(wwd);
|
||||
|
||||
var locations = [
|
||||
new WorldWind.Position(45.759506, 21.227948, 1e2),
|
||||
new WorldWind.Position(39.238384, 58.331522, 1e2),
|
||||
new WorldWind.Position(62.905780, 93.247174, 1e2),
|
||||
new WorldWind.Position(54.560028, -102.221517, 1e2),
|
||||
new WorldWind.Position(40.964231, -103.627767, 1e2),
|
||||
new WorldWind.Position(72.913535, -41.752785, 1e2),
|
||||
new WorldWind.Position(-22.061476, 133.611391, 1e2),
|
||||
new WorldWind.Position(-11.820326, -66.076097, 1e2),
|
||||
new WorldWind.Position(7.061353, 10.212961, 1e2)
|
||||
];
|
||||
|
||||
var annotations = [],
|
||||
annotation,
|
||||
annotationAttributes,
|
||||
insets;
|
||||
|
||||
var backgroundColors = [
|
||||
WorldWind.Color.RED,
|
||||
WorldWind.Color.GREEN,
|
||||
WorldWind.Color.MAGENTA,
|
||||
WorldWind.Color.BLUE,
|
||||
WorldWind.Color.DARK_GRAY,
|
||||
WorldWind.Color.BLACK,
|
||||
WorldWind.Color.BLACK,
|
||||
WorldWind.Color.RED,
|
||||
WorldWind.Color.BLACK,
|
||||
WorldWind.Color.BLACK,
|
||||
WorldWind.Color.BLACK];
|
||||
|
||||
for (var z = 0; z < locations.length; z++) {
|
||||
annotationAttributes = new WorldWind.AnnotationAttributes(null);
|
||||
annotationAttributes.cornerRadius = 14;
|
||||
annotationAttributes.backgroundColor = backgroundColors[z];
|
||||
annotationAttributes.textColor = new WorldWind.Color(1, 1, 1, 1);
|
||||
annotationAttributes.drawLeader = true;
|
||||
annotationAttributes.leaderGapWidth = 40;
|
||||
annotationAttributes.leaderGapHeight = 30;
|
||||
annotationAttributes.opacity = 1;
|
||||
annotationAttributes.scale = 1;
|
||||
annotationAttributes.width = 200;
|
||||
annotationAttributes.height = 100;
|
||||
annotationAttributes.textAttributes.color = WorldWind.Color.WHITE;
|
||||
annotationAttributes.insets = new WorldWind.Insets(10, 10, 10, 10);
|
||||
|
||||
annotation = new WorldWind.Annotation(locations[z], annotationAttributes);
|
||||
annotation.label = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";
|
||||
annotations.push(annotation);
|
||||
annotationsLayer.addRenderable(annotation);
|
||||
}
|
||||
|
||||
// Add the annotations layer to the World Window's layer list.
|
||||
wwd.addLayer(annotationsLayer);
|
||||
|
||||
var highlightedItems = [];
|
||||
|
||||
// The common pick-handling function.
|
||||
var handlePick = function (o) {
|
||||
// The input argument is either an Event or a TapRecognizer. Both have the same properties for determining
|
||||
// the mouse or tap location.
|
||||
var x = o.clientX,
|
||||
y = o.clientY;
|
||||
|
||||
var redrawRequired = highlightedItems.length > 0; // must redraw if we de-highlight previously picked items
|
||||
|
||||
for (var h = 0; h < highlightedItems.length; h++) {
|
||||
highlightedItems[h].highlighted = false;
|
||||
}
|
||||
|
||||
highlightedItems = [];
|
||||
|
||||
// Perform the pick. Must first convert from window coordinates to canvas coordinates, which are
|
||||
// relative to the upper left corner of the canvas rather than the upper left corner of the page.
|
||||
var pickList = wwd.pick(wwd.canvasCoordinates(x, y));
|
||||
if (pickList.objects.length > 0) {
|
||||
redrawRequired = true;
|
||||
}
|
||||
|
||||
// Highlight the items picked by simply setting their highlight flag to true.
|
||||
if (pickList.objects.length > 0) {
|
||||
|
||||
for (var p = 0; p < pickList.objects.length; p++) {
|
||||
|
||||
if (!(pickList.objects[p].userObject instanceof WorldWind.Annotation)) continue;
|
||||
|
||||
pickList.objects[p].userObject.highlighted = true;
|
||||
|
||||
annotationController.load(pickList.objects[p].userObject);
|
||||
|
||||
// Keep track of highlighted items in order to de-highlight them later.
|
||||
highlightedItems.push(pickList.objects[p].userObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the window if we changed anything.
|
||||
if (redrawRequired) {
|
||||
wwd.redraw();
|
||||
}
|
||||
};
|
||||
|
||||
new WorldWind.ClickRecognizer(wwd, handlePick);
|
||||
new WorldWind.TapRecognizer(wwd, handlePick);
|
||||
});
|
||||
@ -8,6 +8,7 @@
|
||||
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
|
||||
<script data-main="BlueMarbleTimeSeries" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.17/require.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
*/
|
||||
/**
|
||||
* Displays a time series of the 12 months of Blue Marble imagery.
|
||||
*
|
||||
* @version $Id: BlueMarbleTimeSeries.js 3320 2015-07-15 20:53:05Z dcollins $
|
||||
*/
|
||||
|
||||
requirejs(['../src/WorldWind',
|
||||
@ -18,8 +16,15 @@ requirejs(['../src/WorldWind',
|
||||
|
||||
var wwd = new WorldWind.WorldWindow("canvasOne");
|
||||
|
||||
// Create the Blue Marble layer and add it to the World Window's layer list.
|
||||
var backgroundLayer = new WorldWind.BMNGOneImageLayer();
|
||||
backgroundLayer.hide = true; // Don't show it in the layer manager.
|
||||
wwd.addLayer(backgroundLayer);
|
||||
|
||||
// Create the Blue Marble layer and add it to the World Window's layer list. Disable it until its images
|
||||
// are preloaded, which is initiated below.
|
||||
var blueMarbleLayer = new WorldWind.BlueMarbleLayer(null, WorldWind.BlueMarbleLayer.availableTimes[0]);
|
||||
blueMarbleLayer.enabled = false;
|
||||
blueMarbleLayer.showSpinner = true;
|
||||
wwd.addLayer(blueMarbleLayer);
|
||||
|
||||
// Create a compass and view controls.
|
||||
@ -30,11 +35,36 @@ requirejs(['../src/WorldWind',
|
||||
// Create a layer manager for controlling layer visibility.
|
||||
var layerManger = new LayerManager(wwd);
|
||||
|
||||
// Increment the Blue Marble layer's time at a specified frequency.
|
||||
var currentIndex = 0;
|
||||
window.setInterval(function (){
|
||||
currentIndex = ++currentIndex % WorldWind.BlueMarbleLayer.availableTimes.length;
|
||||
blueMarbleLayer.time = WorldWind.BlueMarbleLayer.availableTimes[currentIndex];
|
||||
wwd.redraw();
|
||||
// Ensure that the background and other control layers are displayed while the blue marble layer is
|
||||
// being pre-populated.
|
||||
wwd.redraw();
|
||||
|
||||
// Wait for the layer to pre-populate all its sub-layers before enabling it.
|
||||
var prePopulateInterval = window.setInterval(function () {
|
||||
if (!this.prePopulate) {
|
||||
// Pre-populate the layer's sub-layers so that we don't see flashing of their image tiles as they're
|
||||
// loaded.
|
||||
blueMarbleLayer.prePopulate(wwd);
|
||||
this.prePopulate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// See if the layer is pre-populated now. If so, enable it.
|
||||
if (blueMarbleLayer.isPrePopulated(wwd)) {
|
||||
blueMarbleLayer.enabled = true;
|
||||
blueMarbleLayer.showSpinner = false;
|
||||
window.clearInterval(prePopulateInterval);
|
||||
layerManger.synchronizeLayerList();
|
||||
|
||||
// Increment the Blue Marble layer's time at a specified frequency.
|
||||
var currentIndex = 0;
|
||||
window.setInterval(function () {
|
||||
if (blueMarbleLayer.enabled) {
|
||||
currentIndex = ++currentIndex % WorldWind.BlueMarbleLayer.availableTimes.length;
|
||||
blueMarbleLayer.time = WorldWind.BlueMarbleLayer.availableTimes[currentIndex];
|
||||
wwd.redraw();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
69
examples/CanvasWorker.js
Normal file
69
examples/CanvasWorker.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// Set RGBA values for a pixel
|
||||
function setPixel(imageData, x, y, r, g, b, a) {
|
||||
var index = (x + y * imageData.width) * 4;
|
||||
imageData.data[index+0] = r;
|
||||
imageData.data[index+1] = g;
|
||||
imageData.data[index+2] = b;
|
||||
imageData.data[index+3] = a;
|
||||
}
|
||||
|
||||
// Set ImageData for the canvas
|
||||
function setImageData(dataArray, imageData, colorPaletteArray, regionName, filename, noDataValue)
|
||||
{
|
||||
var newDataArray = [];
|
||||
for (var rowNo = 0; rowNo < dataArray.length; rowNo++)
|
||||
{
|
||||
var col = dataArray[rowNo].trim().split(' ');
|
||||
if(col.length > 1){
|
||||
for (var colNo = 0; colNo < col.length; colNo++){
|
||||
newDataArray.push(col[colNo]);
|
||||
var r, g, b, a;
|
||||
if (col[colNo] == noDataValue || col[colNo] == 255){
|
||||
r = colorPaletteArray[2] * 255;
|
||||
g = colorPaletteArray[3] * 255;
|
||||
b = colorPaletteArray[4] * 255;
|
||||
a = colorPaletteArray[5] * 255;
|
||||
setPixel(imageData, colNo, rowNo, r, g, b, a);
|
||||
}
|
||||
else{
|
||||
for (var k = 0; k < colorPaletteArray.length; k+=6){
|
||||
if (col[colNo] >= colorPaletteArray[k] && col[colNo] <= colorPaletteArray[k+1])
|
||||
{
|
||||
r = colorPaletteArray[k+2] * 255;
|
||||
g = colorPaletteArray[k+3] * 255;
|
||||
b = colorPaletteArray[k+4] * 255;
|
||||
a = colorPaletteArray[k+5] * 255;
|
||||
setPixel(imageData, colNo, rowNo, r, g, b, a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage(
|
||||
{
|
||||
'imageData' : imageData,
|
||||
'regionName' : regionName,
|
||||
'filename' : filename,
|
||||
'dataArray' : newDataArray
|
||||
});
|
||||
|
||||
imageData = null;
|
||||
}
|
||||
|
||||
self.onmessage = function(e) {
|
||||
var imageData = e.data.imageData;
|
||||
var dataArray = e.data.dataArray;
|
||||
var colorPaletteArray = e.data.colorPaletteArray;
|
||||
var regionName = e.data.regionName;
|
||||
var filename = e.data.filename;
|
||||
var noDataValue = e.data.noDataValue;
|
||||
setImageData(dataArray, imageData, colorPaletteArray, regionName, filename, noDataValue);
|
||||
}
|
||||
@ -79,53 +79,53 @@ requirejs(['../src/WorldWind',
|
||||
return configuration;
|
||||
};
|
||||
|
||||
var resourcesUrl = "./geojson-data/";
|
||||
var resourcesUrl = "http://worldwindserver.net/webworldwind/data/geojson-data/";
|
||||
|
||||
// Polygon test
|
||||
var polygonLayer = new WorldWind.RenderableLayer("Polygon");
|
||||
var polygonGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/PolygonTest.geojson");
|
||||
var polygonGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "PolygonTest.geojson");
|
||||
polygonGeoJSON.load(shapeConfigurationCallback, polygonLayer);
|
||||
wwd.addLayer(polygonLayer);
|
||||
|
||||
// MultiPolygon test
|
||||
var multiPolygonLayer = new WorldWind.RenderableLayer("MultiPolygon");
|
||||
var multiPolygonGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/MultiPolygonTest.geojson");
|
||||
var multiPolygonGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "MultiPolygonTest.geojson");
|
||||
multiPolygonGeoJSON.load(shapeConfigurationCallback, multiPolygonLayer);
|
||||
wwd.addLayer(multiPolygonLayer);
|
||||
|
||||
//Point test
|
||||
var pointLayer = new WorldWind.RenderableLayer("Point");
|
||||
var pointGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/PointTest.geojson");
|
||||
var pointGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "PointTest.geojson");
|
||||
pointGeoJSON.load(shapeConfigurationCallback, pointLayer);
|
||||
wwd.addLayer(pointLayer);
|
||||
|
||||
//MultiPoint test
|
||||
var multiPointLayer = new WorldWind.RenderableLayer("MultiPoint");
|
||||
var multiPointGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/MultiPointTest.geojson");
|
||||
var multiPointGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "MultiPointTest.geojson");
|
||||
multiPointGeoJSON.load(shapeConfigurationCallback, multiPointLayer);
|
||||
wwd.addLayer(multiPointLayer);
|
||||
|
||||
//LineString test
|
||||
var lineStringLayer = new WorldWind.RenderableLayer("LineString");
|
||||
var lineStringGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/LineStringTest.geojson");
|
||||
var lineStringGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "LineStringTest.geojson");
|
||||
lineStringGeoJSON.load(shapeConfigurationCallback, lineStringLayer);
|
||||
wwd.addLayer(lineStringLayer);
|
||||
|
||||
//MultiLineString test
|
||||
var multiLineStringLayer = new WorldWind.RenderableLayer("MultiLineString");
|
||||
var multiLineStringGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/MultiLineStringTest.geojson");
|
||||
var multiLineStringGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "MultiLineStringTest.geojson");
|
||||
multiLineStringGeoJSON.load(shapeConfigurationCallback, multiLineStringLayer);
|
||||
wwd.addLayer(multiLineStringLayer);
|
||||
|
||||
// GeometryCollection test
|
||||
var geometryCollectionLayer = new WorldWind.RenderableLayer("GeometryCollection");
|
||||
var geometryCollectionGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/GeometryCollectionFeatureTest.geojson");
|
||||
var geometryCollectionGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "GeometryCollectionFeatureTest.geojson");
|
||||
geometryCollectionGeoJSON.load(shapeConfigurationCallback, geometryCollectionLayer);
|
||||
wwd.addLayer(geometryCollectionLayer);
|
||||
|
||||
// Feature test
|
||||
var featureLayer = new WorldWind.RenderableLayer("Feature - USA");
|
||||
var featureGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/FeatureTest.geojson");
|
||||
var featureGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "FeatureTest.geojson");
|
||||
featureGeoJSON.load(shapeConfigurationCallback, featureLayer);
|
||||
wwd.addLayer(featureLayer);
|
||||
|
||||
@ -133,19 +133,19 @@ requirejs(['../src/WorldWind',
|
||||
|
||||
//World Borders
|
||||
var worldBordersLayer = new WorldWind.RenderableLayer("World Borders");
|
||||
var worldBordersGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/world_borders.geojson");
|
||||
var worldBordersGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "world_borders.geojson");
|
||||
worldBordersGeoJSON.load(shapeConfigurationCallback, worldBordersLayer);
|
||||
wwd.addLayer(worldBordersLayer);
|
||||
|
||||
//World Main Cities
|
||||
var worldMainCitiesLayer = new WorldWind.RenderableLayer("World Main Cities");
|
||||
var worldMainCitiesGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/world_main_cities.geojson");
|
||||
var worldMainCitiesGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "world_main_cities.geojson");
|
||||
worldMainCitiesGeoJSON.load(shapeConfigurationCallback, worldMainCitiesLayer);
|
||||
wwd.addLayer(worldMainCitiesLayer);
|
||||
|
||||
//World Rivers
|
||||
var worldRiversLayer = new WorldWind.RenderableLayer("World Rivers");
|
||||
var worldRiversGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/world_rivers.geojson");
|
||||
var worldRiversGeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "world_rivers.geojson");
|
||||
worldRiversGeoJSON.load(shapeConfigurationCallback, worldRiversLayer);
|
||||
wwd.addLayer(worldRiversLayer);
|
||||
|
||||
@ -153,19 +153,19 @@ requirejs(['../src/WorldWind',
|
||||
|
||||
//USA EPSG:3857 named
|
||||
var usa3857Layer = new WorldWind.RenderableLayer("USA 3857-named");
|
||||
var usa3857GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/usa_epsg3857_named.geojson");
|
||||
var usa3857GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "usa_epsg3857_named.geojson");
|
||||
usa3857GeoJSON.load(shapeConfigurationCallback, usa3857Layer);
|
||||
wwd.addLayer(usa3857Layer);
|
||||
|
||||
//USA EPSG:3857 linked
|
||||
var usa3857Layer = new WorldWind.RenderableLayer("USA 3857-linked");
|
||||
var usa3857GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/usa_epsg3857_linked.geojson");
|
||||
var usa3857GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "usa_epsg3857_linked.geojson");
|
||||
usa3857GeoJSON.load(shapeConfigurationCallback, usa3857Layer);
|
||||
wwd.addLayer(usa3857Layer);
|
||||
|
||||
//USA EPSG:4326
|
||||
var usa4326Layer = new WorldWind.RenderableLayer("USA-4326");
|
||||
var usa4326GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "/usa_epsg4326.geojson");
|
||||
var usa4326GeoJSON = new WorldWind.GeoJSONParser(resourcesUrl + "usa_epsg4326.geojson");
|
||||
usa4326GeoJSON.load(shapeConfigurationCallback, usa4326Layer);
|
||||
wwd.addLayer(usa4326Layer);
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
/**
|
||||
* @exports LayerManager
|
||||
* @version $Id: LayerManager.js 3313 2015-07-10 17:59:29Z dcollins $
|
||||
*/
|
||||
define(function () {
|
||||
"use strict";
|
||||
@ -30,10 +29,6 @@ define(function () {
|
||||
|
||||
this.synchronizeLayerList();
|
||||
|
||||
$("#layerList").find("button").on("click", function (e) {
|
||||
thisExplorer.onLayerClick($(this));
|
||||
});
|
||||
|
||||
$("#searchBox").find("button").on("click", function (e) {
|
||||
thisExplorer.onSearchButton(e);
|
||||
});
|
||||
@ -113,6 +108,7 @@ define(function () {
|
||||
layerButton.removeClass("active");
|
||||
}
|
||||
this.wwd.redraw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -132,13 +128,25 @@ define(function () {
|
||||
var layerItem = $('<button class="list-group-item btn btn-block">' + layer.displayName + '</button>');
|
||||
layerListItem.append(layerItem);
|
||||
|
||||
if (layer.showSpinner && Spinner) {
|
||||
var opts = {
|
||||
scale: 0.9,
|
||||
};
|
||||
var spinner = new Spinner(opts).spin();
|
||||
layerItem.append(spinner.el);
|
||||
}
|
||||
|
||||
if (layer.enabled) {
|
||||
layerItem.addClass("active");
|
||||
} else {
|
||||
layerItem.removeClass("active");
|
||||
}
|
||||
this.wwd.redraw();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
layerListItem.find("button").on("click", function (e) {
|
||||
self.onLayerClick($(this));
|
||||
});
|
||||
};
|
||||
//
|
||||
//LayerManager.prototype.updateVisibilityState = function (worldWindow) {
|
||||
|
||||
182
examples/NDVIViewer.html
Normal file
182
examples/NDVIViewer.html
Normal file
@ -0,0 +1,182 @@
|
||||
<!DOCTYPE html>
|
||||
<!--@version $Id: GeographicMeshes.html 3320 2015-07-15 20:53:05Z dcollins $-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--NOTE: Most Web World Wind examples use jquery, Bootstrap and requirejs but those technologies are NOT-->
|
||||
<!--required by Web World Wind. See SimplestExample.html for an example of using Web World Wind without them.-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
|
||||
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
|
||||
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
|
||||
<script data-main="NDVIViewer" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.17/require.min.js"></script>
|
||||
|
||||
<style>
|
||||
table tr td:empty {
|
||||
width: 5px;
|
||||
}
|
||||
table tr th:empty {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
/* tell the SVG path to be a thin blue line without any area fill */
|
||||
path {
|
||||
stroke: steelblue;
|
||||
stroke-width: 1;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.axis {
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.x.axis line {
|
||||
stroke: lightgrey;
|
||||
}
|
||||
|
||||
.x.axis .minor {
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
.x.axis path {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.y.axis line, .y.axis path {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="jumbotron hidden-xs">
|
||||
<h1 style="text-align:center">World Wind NDVI Viewer</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<h4>Projection</h4>
|
||||
<div class="dropdown" id="projectionDropdown">
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<h4>Layers</h4>
|
||||
<div class="list-group" id="layerList">
|
||||
</div>
|
||||
<br>
|
||||
<div id="surfacesStatus">
|
||||
<h4>NDVI</h4>
|
||||
</div>
|
||||
<h5>Select region</h5>
|
||||
<select id="select-region">
|
||||
</select>
|
||||
<h5>Change datasource date</h5>
|
||||
<div id="scene-slider" style="height:11px;width:200px"></div>
|
||||
<p>
|
||||
<label>Datasource date:</label>
|
||||
<input type="text" id="scene-date" readonly style="border:0; color:#f6931f; font-weight:bold;">
|
||||
</p>
|
||||
<h5>Change surface transparency</h5>
|
||||
<div id="transparency-slider" style="height:11px;width:200px"></div>
|
||||
<p>
|
||||
<label>Opacity:</label>
|
||||
<input type="text" id="transparency" readonly style="border:0; color:#f6931f; font-weight:bold;">
|
||||
</p>
|
||||
<div>
|
||||
<h4>NDVI legend</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<td bgcolor="#B3EBFF"></td>
|
||||
<td></td>
|
||||
<td>0.0  -  25.5 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#FFFFF0"></td>
|
||||
<td></td>
|
||||
<td>25.5  -  51.0</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#FFFFE5"></td>
|
||||
<td></td>
|
||||
<td>51.0  -  76.5 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#F6FBB9"></td>
|
||||
<td></td>
|
||||
<td>76.5  -  102.0 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="D9F0A2"></td>
|
||||
<td></td>
|
||||
<td>102.0  -  127.5 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#ACDD8E"></td>
|
||||
<td></td>
|
||||
<td>127.5  -  153.0</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#40AB5C"></td>
|
||||
<td></td>
|
||||
<td>153.0  -  178.5 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#238342"></td>
|
||||
<td></td>
|
||||
<td>175.5  -  204.0 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#005929"></td>
|
||||
<td></td>
|
||||
<td>204.0  -  229.5 </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td bgcolor="#004529"></td>
|
||||
<td></td>
|
||||
<td>229.5  -  255.0</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<h4>Destination</h4>
|
||||
<div class="input-group" id="searchBox">
|
||||
<input type="text" class="form-control" placeholder="GoTo" id="searchText"/>
|
||||
<span class="input-group-btn">
|
||||
<button id="searchButton" class="btn btn-primary" type="button">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-9" id="globe">
|
||||
<canvas id="canvasOne" width="1000" height="1000" style="width: 100%; height: auto">
|
||||
Your browser does not support HTML5 Canvas.
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
<div id="graph" class="col-sm-9"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
544
examples/NDVIViewer.js
Normal file
544
examples/NDVIViewer.js
Normal file
@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
requirejs([
|
||||
'../src/WorldWind',
|
||||
'./LayerManager'],
|
||||
function (ww,
|
||||
LayerManager) {
|
||||
|
||||
// Tell World Wind to log only warnings and errors.
|
||||
WorldWind.Logger.setLoggingLevel(WorldWind.Logger.LEVEL_WARNING);
|
||||
|
||||
// Create the World Window.
|
||||
var wwd = new WorldWind.WorldWindow("canvasOne");
|
||||
var layerManger;
|
||||
|
||||
// Add imagery layers
|
||||
var layers = [
|
||||
{layer: new WorldWind.BingAerialWithLabelsLayer(null), enabled: true},
|
||||
{layer: new WorldWind.OpenStreetMapImageLayer(null), enabled: false},
|
||||
{layer: new WorldWind.CoordinatesDisplayLayer(wwd), enabled: true},
|
||||
{layer: new WorldWind.ViewControlsLayer(wwd), enabled: true}
|
||||
];
|
||||
|
||||
for (var l = 0; l < layers.length; l++) {
|
||||
layers[l].layer.enabled = layers[l].enabled;
|
||||
wwd.addLayer(layers[l].layer);
|
||||
}
|
||||
|
||||
var dataJsonUrl = './data/analyticalSurfaces.json';
|
||||
var colorPaletteArray = [
|
||||
0.0,25.5,0.701,0.921,1.0, 1.0,
|
||||
25.5,51.0,1.0000,1.0000,0.9412, 1.0,
|
||||
51.0,76.5,1.0000,1.0000,0.8980, 1.0,
|
||||
76.5,102.0,0.9686,0.9882,0.7255, 1.0,
|
||||
102.0,127.5,0.8510,0.9412,0.6392, 1.0,
|
||||
127.5,153.0,0.6784,0.8667,0.5569, 1.0,
|
||||
153.0,175.5,0.2549,0.6706,0.3647, 1.0,
|
||||
175.5,204.0,0.1373,0.5176,0.2627, 1.0,
|
||||
204.0,229.5,0.0000,0.3529,0.1608, 1.0,
|
||||
229.5,255.0,0.0000,0.2706,0.1608, 1.0
|
||||
];
|
||||
|
||||
var sceneCounter = 0,
|
||||
loadingCounter = 0,
|
||||
analyticalSurfaceObjectArray = [],
|
||||
surfaceLayerArrayGlobal = [],
|
||||
isFirstSceneDraw = false,
|
||||
analyticalSurfaceDataObject;
|
||||
|
||||
getDataJSON(dataJsonUrl);
|
||||
|
||||
function getDataJSON(url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", url, true);
|
||||
xhr.onreadystatechange = (function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
analyticalSurfaceDataObject = JSON.parse(xhr.response);
|
||||
|
||||
$( "#scene-date" ).val(getStringDateFormat(analyticalSurfaceDataObject[0].filenameList[0]));
|
||||
|
||||
//console.log(analyticalSurfaceDataObject);
|
||||
//show globe after loading first scene of each region (synchronous)
|
||||
for(var i=0; i < analyticalSurfaceDataObject.length; i++) {
|
||||
analyticalSurfaceDataObject[i].paragraphId = "p" + analyticalSurfaceDataObject[i].regionName;
|
||||
analyticalSurfaceDataObject[i].loadingStatus = 0;
|
||||
addLoadingStatusParagraph(
|
||||
analyticalSurfaceDataObject[i].paragraphId,
|
||||
analyticalSurfaceDataObject[i].regionName,
|
||||
analyticalSurfaceDataObject[i].loadingStatus,
|
||||
analyticalSurfaceDataObject[i].filenameList.length
|
||||
|
||||
);
|
||||
getNDVIData(
|
||||
analyticalSurfaceDataObject[i].url,
|
||||
analyticalSurfaceDataObject[i].regionName,
|
||||
analyticalSurfaceDataObject[i].filenameList[0],
|
||||
true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('retrieve failed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhr.onerror = function () {
|
||||
console.log('error');
|
||||
};
|
||||
|
||||
xhr.ontimeout = function () {
|
||||
console.log('timeout');
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
function addLoadingStatusParagraph(paragraphId, regionName, loadingStatus, totalNoOfScenes){
|
||||
var newParagraph = document.createElement('p');
|
||||
newParagraph.setAttribute("id", paragraphId)
|
||||
newParagraph.textContent = "Loading " + regionName + ": " + loadingStatus + "/" + totalNoOfScenes;
|
||||
document.getElementById("surfacesStatus").appendChild(newParagraph);
|
||||
}
|
||||
|
||||
function updateLoadingStatus(paragraphId, regionName, loadingStatus, totalNoOfScenes){
|
||||
$("#" + paragraphId).text("Loading " + regionName + ": " + loadingStatus + "/" + totalNoOfScenes);
|
||||
}
|
||||
|
||||
function getNDVIData(url, regionName, filename, isFirstScene) {
|
||||
var url = url + filename;
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (isFirstScene){
|
||||
xhr.open("GET", url, false);
|
||||
}
|
||||
else{
|
||||
xhr.open("GET", url, true);
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = (function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
var dataArray = xhr.response.split('\n');
|
||||
|
||||
if (analyticalSurfaceObjectArray.filter(
|
||||
function(e) {
|
||||
return e.regionName === regionName}
|
||||
).length === 0)
|
||||
{
|
||||
// Parse ESRI Grid file
|
||||
var ncols = Number(dataArray.shift().replace(/ +/g, ' ').split(' ')[1]);
|
||||
var nrows = Number(dataArray.shift().replace(/ +/g, ' ').split(' ')[1]);
|
||||
var xllcorner = Number(dataArray.shift().replace(/ +/g, ' ').split(' ')[1]);
|
||||
var yllcorner = Number(dataArray.shift().replace(/ +/g, ' ').split(' ')[1]);
|
||||
var cellsize = Number(dataArray.shift().replace(/ +/g, ' ').split(' ')[1]);
|
||||
var ytlcorner = Number(yllcorner + nrows * cellsize);
|
||||
var NODATA_value = dataArray.shift().replace(/ +/g, ' ').split(' ')[1];
|
||||
|
||||
// Create 2d context for images
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = ncols;
|
||||
canvas.height = nrows;
|
||||
var context2d = canvas.getContext("2d");
|
||||
var imageData = context2d.createImageData(canvas.width, canvas.height);
|
||||
|
||||
analyticalSurfaceObjectArray.push(
|
||||
{
|
||||
"regionName" : regionName,
|
||||
"width" : ncols,
|
||||
"height" : nrows,
|
||||
"referenceX" : xllcorner,
|
||||
"referenceY" : ytlcorner,
|
||||
"cellSize" : cellsize,
|
||||
"noDataValue" : NODATA_value,
|
||||
"imageList" : [
|
||||
{
|
||||
"filename" : filename,
|
||||
"dataArray" : dataArray,
|
||||
"imageData" : imageData
|
||||
}],
|
||||
"canvas" : canvas,
|
||||
"context2d" : context2d,
|
||||
"loadingStatus:" : 0
|
||||
}
|
||||
);
|
||||
|
||||
getImage(
|
||||
dataArray,
|
||||
regionName,
|
||||
filename,
|
||||
context2d,
|
||||
imageData,
|
||||
NODATA_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataArray.splice(0, 6);
|
||||
|
||||
var index = getIndexOfObjectsArrayByAttribute(
|
||||
analyticalSurfaceObjectArray,
|
||||
"regionName",
|
||||
regionName);
|
||||
|
||||
var imageData = analyticalSurfaceObjectArray[index].context2d.createImageData(
|
||||
analyticalSurfaceObjectArray[index].canvas.width,
|
||||
analyticalSurfaceObjectArray[index].canvas.height);
|
||||
|
||||
analyticalSurfaceObjectArray[index].imageList.push(
|
||||
{
|
||||
"filename" : filename,
|
||||
"dataArray" : dataArray,
|
||||
"imageData" : imageData
|
||||
}
|
||||
);
|
||||
|
||||
getImage(
|
||||
dataArray,
|
||||
regionName,
|
||||
filename,
|
||||
analyticalSurfaceObjectArray[index].context2d,
|
||||
analyticalSurfaceObjectArray[index].imageList[
|
||||
analyticalSurfaceObjectArray[index].imageList.length - 1].imageData,
|
||||
analyticalSurfaceObjectArray[index].noDataValue);
|
||||
|
||||
analyticalSurfaceObjectArray[index].imageList.sort(compareFilename);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('retrieve failed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhr.onerror = function () {
|
||||
console.log('error');
|
||||
};
|
||||
|
||||
xhr.ontimeout = function () {
|
||||
console.log('timeout');
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
function compareFilename(a,b) {
|
||||
if (a.filename < b.filename)
|
||||
return -1;
|
||||
if (a.filename > b.filename)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function createAnalyticalSurfaceLayer(analyticalSurfaceArray) {
|
||||
var surfaceLayerArray = [];
|
||||
for (var i = 0; i < analyticalSurfaceArray.length; i++){
|
||||
var analyticalSurfaceLayer = new WorldWind.RenderableLayer();
|
||||
analyticalSurfaceLayer.displayName = analyticalSurfaceArray[i].regionName;
|
||||
|
||||
var surfaceImage = new WorldWind.SurfaceImage(
|
||||
new WorldWind.Sector(
|
||||
analyticalSurfaceArray[i].referenceY - analyticalSurfaceArray[i].height *
|
||||
analyticalSurfaceArray[i].cellSize,
|
||||
analyticalSurfaceArray[i].referenceY,
|
||||
analyticalSurfaceArray[i].referenceX,
|
||||
analyticalSurfaceArray[i].referenceX + analyticalSurfaceArray[i].width *
|
||||
analyticalSurfaceArray[i].cellSize),
|
||||
new WorldWind.ImageSource(analyticalSurfaceArray[i].canvas)
|
||||
);
|
||||
surfaceImage.displayName = analyticalSurfaceArray[i].regionName;
|
||||
analyticalSurfaceLayer.addRenderable(surfaceImage);
|
||||
surfaceLayerArray.push(analyticalSurfaceLayer);
|
||||
}
|
||||
return surfaceLayerArray;
|
||||
}
|
||||
|
||||
function doGetNDVIDataAsync(j, k){
|
||||
setTimeout(function() {
|
||||
getNDVIData(
|
||||
analyticalSurfaceDataObject[j].url,
|
||||
analyticalSurfaceDataObject[j].regionName,
|
||||
analyticalSurfaceDataObject[j].filenameList[k],
|
||||
false);
|
||||
}, loadingCounter++ * 1000);
|
||||
}
|
||||
|
||||
function getImage(dataArray, regionName, filename, context2d, imageData, noDataValue) {
|
||||
var worker = new Worker('CanvasWorker.js');
|
||||
|
||||
worker.onmessage = function(e) {
|
||||
|
||||
sceneCounter++;
|
||||
|
||||
var regionIndex = getIndexOfObjectsArrayByAttribute(
|
||||
analyticalSurfaceObjectArray,
|
||||
"regionName",
|
||||
regionName);
|
||||
var filenameIndex = getIndexOfObjectsArrayByAttribute(
|
||||
analyticalSurfaceObjectArray[regionIndex].imageList,
|
||||
"filename",
|
||||
filename);
|
||||
analyticalSurfaceObjectArray[regionIndex].imageList[filenameIndex].imageData = e.data.imageData;
|
||||
analyticalSurfaceObjectArray[regionIndex].imageList[filenameIndex].dataArray = e.data.dataArray;
|
||||
|
||||
analyticalSurfaceDataObject[regionIndex].loadingStatus++;
|
||||
|
||||
//$( "#scene-slider" ).slider(
|
||||
// "option",
|
||||
// "max",
|
||||
// analyticalSurfaceDataObject[regionIndex].loadingStatus - 1);
|
||||
|
||||
updateLoadingStatus(
|
||||
analyticalSurfaceDataObject[regionIndex].paragraphId,
|
||||
analyticalSurfaceDataObject[regionIndex].regionName,
|
||||
analyticalSurfaceDataObject[regionIndex].loadingStatus,
|
||||
analyticalSurfaceDataObject[regionIndex].filenameList.length
|
||||
);
|
||||
|
||||
if (sceneCounter === analyticalSurfaceDataObject.length && !isFirstSceneDraw){
|
||||
context2d.putImageData(e.data.imageData, 0, 0);
|
||||
|
||||
surfaceLayerArrayGlobal = createAnalyticalSurfaceLayer(analyticalSurfaceObjectArray);
|
||||
|
||||
//add regions to select control
|
||||
for(var i=0; i< surfaceLayerArrayGlobal.length; i++){
|
||||
$('#select-region').append($('<option>', {
|
||||
value: surfaceLayerArrayGlobal[i].displayName,
|
||||
text: surfaceLayerArrayGlobal[i].displayName
|
||||
}));
|
||||
wwd.addLayer(surfaceLayerArrayGlobal[i]);
|
||||
}
|
||||
|
||||
layerManger = new LayerManager(wwd);
|
||||
layerManger.goToAnimator.goTo(new WorldWind.Position(41.77, 12.77, 40000));
|
||||
isFirstSceneDraw = true;
|
||||
|
||||
//start load async other scenes
|
||||
for(var j = 0; j < analyticalSurfaceDataObject.length; j++){
|
||||
for(var k = 1; k < analyticalSurfaceDataObject[j].filenameList.length; k++){
|
||||
doGetNDVIDataAsync(j, k);
|
||||
}
|
||||
}
|
||||
}else if (sceneCounter < analyticalSurfaceDataObject.length && !isFirstSceneDraw){
|
||||
context2d.putImageData(e.data.imageData, 0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = function(e) {
|
||||
alert('Error: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
|
||||
};
|
||||
|
||||
//start the worker
|
||||
worker.postMessage({
|
||||
'imageData': imageData,
|
||||
'dataArray': dataArray,
|
||||
'colorPaletteArray': colorPaletteArray,
|
||||
'regionName' : regionName,
|
||||
'filename' : filename,
|
||||
'noDataValue' : noDataValue
|
||||
});
|
||||
}
|
||||
|
||||
function getStringDateFormat(filename){
|
||||
var dataField = filename.split("_")[4];
|
||||
return dataField.substring(0,4) + "/" + dataField.substring(4,6) + "/" + dataField.substring(6,8);
|
||||
}
|
||||
|
||||
function changeTransparencyValue(value){
|
||||
for (var i=0; i < wwd.layers.length; i++ ){
|
||||
if (wwd.layers[i].displayName === ($( "#select-region" ).val())) {
|
||||
for(var j=0; j < wwd.layers[i].renderables.length; j++){
|
||||
wwd.layers[i].renderables[j].opacity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$( "#transparency" ).val( value );
|
||||
}
|
||||
|
||||
function getIndexOfObjectsArrayByAttribute(array, attribute, attributeValue){
|
||||
return array.map(function(e) { return e[attribute]; }).indexOf(attributeValue);
|
||||
}
|
||||
|
||||
function changeParamValues(value){
|
||||
for (var i=0; i < wwd.layers.length; i++ ){
|
||||
if (wwd.layers[i].displayName === ($( "#select-region" ).val())) {
|
||||
var regionIndex = getIndexOfObjectsArrayByAttribute(
|
||||
analyticalSurfaceObjectArray,
|
||||
"regionName",
|
||||
wwd.layers[i].displayName);
|
||||
analyticalSurfaceObjectArray[regionIndex].context2d.putImageData(
|
||||
analyticalSurfaceObjectArray[regionIndex].imageList[value].imageData,
|
||||
0,
|
||||
0);
|
||||
wwd.layers[i].renderables[0].imageSource = new WorldWind.ImageSource(
|
||||
analyticalSurfaceObjectArray[regionIndex].canvas);
|
||||
|
||||
var filename = analyticalSurfaceObjectArray[regionIndex].imageList[value].filename;
|
||||
$( "#scene-date" ).val( getStringDateFormat(filename));
|
||||
|
||||
wwd.redraw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The common pick-handling function.
|
||||
var handlePick = function (o) {
|
||||
// The input argument is either an Event or a TapRecognizer. Both have the same properties for determining
|
||||
// the mouse or tap location.
|
||||
var x = o.clientX,
|
||||
y = o.clientY;
|
||||
|
||||
// Perform the pick. Must first convert from window coordinates to canvas coordinates, which are
|
||||
// relative to the upper left corner of the canvas rather than the upper left corner of the page.
|
||||
var pickPoint = wwd.canvasCoordinates(x, y);
|
||||
var pickList = wwd.pick(pickPoint);
|
||||
|
||||
if (pickList.objects.length > 0) {
|
||||
for (var p = 0; p < pickList.objects.length; p++) {
|
||||
if (pickList.objects[p].userObject instanceof WorldWind.SurfaceImage) {
|
||||
if (pickList.objects[p].userObject.displayName === ($( "#select-region" ).val())){
|
||||
var index = getIndexOfObjectsArrayByAttribute(
|
||||
analyticalSurfaceObjectArray,
|
||||
"regionName",
|
||||
pickList.objects[p].userObject.displayName);
|
||||
var refLat = analyticalSurfaceObjectArray[index].referenceY;
|
||||
var refLong = analyticalSurfaceObjectArray[index].referenceX;
|
||||
var resolution = analyticalSurfaceObjectArray[index].cellSize;
|
||||
var numColumns = analyticalSurfaceObjectArray[index].width;
|
||||
|
||||
var terrainObject = wwd.pickTerrain(pickPoint).terrainObject();
|
||||
var pickLat = terrainObject.position.latitude;
|
||||
var pickLong = terrainObject.position.longitude;
|
||||
|
||||
var deltaY = refLat - pickLat;
|
||||
var deltaX = pickLong - refLong;
|
||||
|
||||
var colNo = Math.round(deltaX / resolution);
|
||||
var rowNo = Math.round(deltaY / resolution);
|
||||
|
||||
var indexParam = rowNo * numColumns + colNo;
|
||||
|
||||
var data = [];
|
||||
var xLabels = [];
|
||||
|
||||
for(var i=0; i < analyticalSurfaceObjectArray[index].imageList.length;i++ ){
|
||||
if (analyticalSurfaceObjectArray[index].imageList[i].dataArray[indexParam] ===
|
||||
analyticalSurfaceObjectArray[index].noDataValue){
|
||||
data.push(0);
|
||||
}
|
||||
else{
|
||||
data.push(analyticalSurfaceObjectArray[index].imageList[i].dataArray[indexParam]);
|
||||
}
|
||||
xLabels.push(
|
||||
getStringDateFormat(analyticalSurfaceObjectArray[index].imageList[i].filename));
|
||||
}
|
||||
|
||||
var m = [10, 60, 80, 60]; // margins
|
||||
var w = 1000 - m[1] - m[3]; // width
|
||||
var h = 300 - m[0] - m[2]; // height
|
||||
|
||||
var step = w / data.length;
|
||||
|
||||
var domainArray = [];
|
||||
for (var i = 0; i < data.length; i ++){
|
||||
domainArray.push(i * step);
|
||||
}
|
||||
|
||||
var x = d3.scale.ordinal()
|
||||
.domain(xLabels).range(domainArray);
|
||||
var y = d3.scale.linear().domain([0, 255]).range([h, 0]);
|
||||
|
||||
var line = d3.svg.line()
|
||||
.x(function(d,i) {
|
||||
return x(i);
|
||||
})
|
||||
.y(function(d) {
|
||||
return y(d);
|
||||
});
|
||||
|
||||
d3.select("svg").remove();
|
||||
|
||||
var svg = d3.select("#graph").append("svg")
|
||||
.attr("width", w + m[1] + m[3])
|
||||
.attr("height", h + m[0] + m[2])
|
||||
.append("svg:g")
|
||||
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
||||
|
||||
var xAxis = d3.svg.axis().scale(x).tickSize(-h).tickSubdivide(true);
|
||||
|
||||
svg.append("svg:g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + h + ")")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "end")
|
||||
.attr("dx", "-.8em")
|
||||
.attr("dy", ".15em")
|
||||
.attr("transform", "rotate(-65)" );;
|
||||
|
||||
var yAxisLeft = d3.svg.axis().scale(y).ticks(4).orient("left");
|
||||
svg.append("svg:g")
|
||||
.attr("class", "y axis")
|
||||
.attr("transform", "translate(-25,0)")
|
||||
.call(yAxisLeft);
|
||||
|
||||
svg.append("svg:path").attr("d", line(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for mouse moves and highlight the placemarks that the cursor rolls over.
|
||||
wwd.addEventListener("click", handlePick);
|
||||
|
||||
// Listen for taps on mobile devices and highlight the placemarks that the user taps.
|
||||
var tapRecognizer = new WorldWind.TapRecognizer(wwd, handlePick);
|
||||
|
||||
$( "#scene-slider" ).slider({
|
||||
orientation: "horizontal",
|
||||
range: "min",
|
||||
min: 0,
|
||||
max: 12,
|
||||
value: 0,
|
||||
slide: function( event, ui ) {
|
||||
changeParamValues(ui.value);
|
||||
}
|
||||
});
|
||||
|
||||
$( "#transparency-slider" ).slider({
|
||||
orientation: "horizontal",
|
||||
range: "min",
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
value: 1.0,
|
||||
slide: function( event, ui ) {
|
||||
changeTransparencyValue(ui.value);
|
||||
}
|
||||
});
|
||||
$( "#transparency" ).val( 1.0 );
|
||||
|
||||
$( "#select-region" ).change(function() {
|
||||
var str = "";
|
||||
$( "select option:selected" ).each(function() {
|
||||
str += $( this ).text();
|
||||
});
|
||||
|
||||
if (str === "Sicily"){
|
||||
layerManger.goToAnimator.goTo(new WorldWind.Position(37.51, 14.00, 400000));
|
||||
}else if (str === "Rome"){
|
||||
layerManger.goToAnimator.goTo(new WorldWind.Position(41.77, 12.77, 40000));
|
||||
}
|
||||
|
||||
for (var i=0; i < analyticalSurfaceObjectArray.length; i++){
|
||||
if (analyticalSurfaceObjectArray[i].regionName === str){
|
||||
$( "#scene-slider" ).slider("option", "max", analyticalSurfaceObjectArray[i].imageList.length - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -66,6 +66,7 @@ requirejs(['../src/WorldWind',
|
||||
polygonAttributes.outlineColor = WorldWind.Color.BLUE;
|
||||
polygonAttributes.interiorColor = new WorldWind.Color(0, 1, 1, 0.5);
|
||||
polygonAttributes.drawVerticals = polygon.extrude;
|
||||
polygonAttributes.applyLighting = true;
|
||||
polygon.attributes = polygonAttributes;
|
||||
|
||||
// Create and assign the polygon's highlight attributes.
|
||||
@ -106,6 +107,7 @@ requirejs(['../src/WorldWind',
|
||||
polygonAttributes.outlineColor = WorldWind.Color.BLUE;
|
||||
polygonAttributes.interiorColor = WorldWind.Color.WHITE;
|
||||
polygonAttributes.drawVerticals = polygon.extrude;
|
||||
polygonAttributes.applyLighting = true;
|
||||
polygon.attributes = polygonAttributes;
|
||||
highlightAttributes = new WorldWind.ShapeAttributes(polygonAttributes);
|
||||
highlightAttributes.outlineColor = WorldWind.Color.RED;
|
||||
@ -142,6 +144,7 @@ requirejs(['../src/WorldWind',
|
||||
polygonAttributes.outlineColor = WorldWind.Color.BLUE;
|
||||
polygonAttributes.interiorColor = WorldWind.Color.WHITE;
|
||||
polygonAttributes.drawVerticals = polygon.extrude;
|
||||
polygonAttributes.applyLighting = true;
|
||||
polygon.attributes = polygonAttributes;
|
||||
highlightAttributes = new WorldWind.ShapeAttributes(polygonAttributes);
|
||||
highlightAttributes.outlineColor = WorldWind.Color.RED;
|
||||
@ -200,6 +203,7 @@ requirejs(['../src/WorldWind',
|
||||
polygonAttributes.outlineColor = WorldWind.Color.BLUE;
|
||||
polygonAttributes.interiorColor = WorldWind.Color.WHITE;
|
||||
polygonAttributes.drawVerticals = polygon.extrude;
|
||||
polygonAttributes.applyLighting = true;
|
||||
polygon.attributes = polygonAttributes;
|
||||
highlightAttributes = new WorldWind.ShapeAttributes(polygonAttributes);
|
||||
highlightAttributes.outlineColor = WorldWind.Color.RED;
|
||||
|
||||
@ -91,4 +91,27 @@ requirejs(['../src/WorldWind',
|
||||
|
||||
// Create a layer manager for controlling layer visibility.
|
||||
var layerManger = new LayerManager(wwd);
|
||||
|
||||
// Set up to handle picking.
|
||||
var handlePick = (function (o) {
|
||||
var pickPoint = wwd.canvasCoordinates(o.clientX, o.clientY);
|
||||
|
||||
var pickList = wwd.pick(pickPoint);
|
||||
if (pickList.objects.length > 0) {
|
||||
for (var p = 0; p < pickList.objects.length; p++) {
|
||||
var pickedObject = pickList.objects[p];
|
||||
if (!pickedObject.isTerrain) {
|
||||
if (pickedObject.userObject instanceof WorldWind.ScreenText) {
|
||||
console.log(pickedObject.userObject.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
// Listen for mouse moves and highlight text that the cursor rolls over.
|
||||
wwd.addEventListener("mousemove", handlePick);
|
||||
|
||||
// Listen for taps on mobile devices and highlight text that the user taps.
|
||||
var tapRecognizer = new WorldWind.TapRecognizer(wwd, handlePick);
|
||||
});
|
||||
@ -44,7 +44,7 @@
|
||||
var wwd = new WorldWind.WorldWindow("canvasOne", elevationModel);
|
||||
|
||||
// Add some REST image layers to the World Window's globe.
|
||||
wwd.addLayer(new WorldWind.BMNGRestLayer(null, "../standalonedata/Earth/BMNG256-200404", "Blue Marble"));
|
||||
wwd.addLayer(new WorldWind.RestTiledImageLayer(null, "../standalonedata/Earth/BlueMarble256/BlueMarble-200404", "Blue Marble"));
|
||||
wwd.addLayer(new WorldWind.LandsatRestLayer(null, "../standalonedata/Earth/Landsat", "LandSat"));
|
||||
|
||||
// Add a compass, a coordinates display and some view controls to the World Window.
|
||||
|
||||
64
examples/data/analyticalSurfaces.json
Normal file
64
examples/data/analyticalSurfaces.json
Normal file
@ -0,0 +1,64 @@
|
||||
[
|
||||
{
|
||||
"url":"http://worldwindserver.net/webworldwind/data/proba_v_out_italy/",
|
||||
"regionName":"Rome",
|
||||
"filenameList":["probav_s5_toc_x19y03_20140606_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20140706_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20140806_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20140906_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20141006_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20141106_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20141206_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150106_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150206_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150306_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150406_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150506_100m_ndvi_v001.txt",
|
||||
"probav_s5_toc_x19y03_20150606_100m_ndvi_v001.txt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url":"http://worldwindserver.net/webworldwind/data/proba_v_italy_sicily/",
|
||||
"regionName":"Sicily",
|
||||
"filenameList":["probav_s10_toc_x19y03_20140601_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140611_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140621_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140701_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140711_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140721_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140801_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140811_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140821_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140901_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140911_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20140921_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141001_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141011_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141021_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141101_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141111_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141121_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141201_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141211_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20141221_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150101_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150111_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150121_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150201_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150211_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150221_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150301_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150311_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150321_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150401_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150411_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150421_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150501_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150511_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150521_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150601_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150611_333m_ndvi_v001.txt",
|
||||
"probav_s10_toc_x19y03_20150621_333m_ndvi_v001.txt"
|
||||
]
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{ "type": "LineString", "coordinates": [[28.609974323244046, 44.202662372914631] , [ 26.098000795350401, 44.435317663494573]] }
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{ "type": "MultiPoint", "coordinates": [[ 28.609974323244046, 44.202662372914631 , 5000], [ 26.098000795350401, 44.435317663494573]] }
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{ "type": "Point", "coordinates": [ 27.574947060870272, 47.168346983929098 ] }
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -8,6 +8,8 @@
|
||||
define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not directory name).
|
||||
'./error/AbstractError',
|
||||
'./geom/Angle',
|
||||
'./shapes/Annotation',
|
||||
'./shapes/AnnotationAttributes',
|
||||
'./error/ArgumentError',
|
||||
'./shaders/BasicProgram',
|
||||
'./shaders/BasicTextureProgram',
|
||||
@ -19,7 +21,6 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
'./layer/BMNGLandsatLayer',
|
||||
'./layer/BMNGLayer',
|
||||
'./layer/BMNGOneImageLayer',
|
||||
'./layer/BMNGRestLayer',
|
||||
'./geom/BoundingBox',
|
||||
'./gesture/ClickRecognizer',
|
||||
'./util/Color',
|
||||
@ -53,6 +54,7 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
'./util/HighlightController',
|
||||
'./util/ImageSource',
|
||||
'./render/ImageTile',
|
||||
'./util/Insets',
|
||||
'./layer/LandsatRestLayer',
|
||||
'./layer/Layer',
|
||||
'./util/Level',
|
||||
@ -88,9 +90,11 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
'./projections/ProjectionMercator',
|
||||
'./projections/ProjectionPolarEquidistant',
|
||||
'./projections/ProjectionUPS',
|
||||
'./projections/ProjectionWgs84',
|
||||
'./geom/Rectangle',
|
||||
'./render/Renderable',
|
||||
'./layer/RenderableLayer',
|
||||
'./layer/RestTiledImageLayer',
|
||||
'./gesture/RotationRecognizer',
|
||||
'./shapes/ScreenImage',
|
||||
'./shapes/ScreenText',
|
||||
@ -148,6 +152,8 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
'./globe/ZeroElevationModel'],
|
||||
function (AbstractError,
|
||||
Angle,
|
||||
Annotation,
|
||||
AnnotationAttributes,
|
||||
ArgumentError,
|
||||
BasicProgram,
|
||||
BasicTextureProgram,
|
||||
@ -159,7 +165,6 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
BMNGLandsatLayer,
|
||||
BMNGLayer,
|
||||
BMNGOneImageLayer,
|
||||
BMNGRestLayer,
|
||||
BoundingBox,
|
||||
ClickRecognizer,
|
||||
Color,
|
||||
@ -193,6 +198,7 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
HighlightController,
|
||||
ImageSource,
|
||||
ImageTile,
|
||||
Insets,
|
||||
LandsatRestLayer,
|
||||
Layer,
|
||||
Level,
|
||||
@ -228,9 +234,11 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
ProjectionMercator,
|
||||
ProjectionPolarEquidistant,
|
||||
ProjectionUPS,
|
||||
ProjectionWgs84,
|
||||
Rectangle,
|
||||
Renderable,
|
||||
RenderableLayer,
|
||||
RestTiledImageLayer,
|
||||
RotationRecognizer,
|
||||
ScreenImage,
|
||||
ScreenText,
|
||||
@ -497,6 +505,8 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
|
||||
WorldWind['AbstractError'] = AbstractError;
|
||||
WorldWind['Angle'] = Angle;
|
||||
WorldWind['Annotation'] = Annotation;
|
||||
WorldWind['AnnotationAttributes'] = AnnotationAttributes;
|
||||
WorldWind['ArgumentError'] = ArgumentError;
|
||||
WorldWind['BasicProgram'] = BasicProgram;
|
||||
WorldWind['BasicTextureProgram'] = BasicTextureProgram;
|
||||
@ -508,7 +518,6 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
WorldWind['BMNGLandsatLayer'] = BMNGLandsatLayer;
|
||||
WorldWind['BMNGLayer'] = BMNGLayer;
|
||||
WorldWind['BMNGOneImageLayer'] = BMNGOneImageLayer;
|
||||
WorldWind['BMNGRestLayer'] = BMNGRestLayer;
|
||||
WorldWind['BoundingBox'] = BoundingBox;
|
||||
WorldWind['ClickRecognizer'] = ClickRecognizer;
|
||||
WorldWind['Color'] = Color;
|
||||
@ -542,6 +551,7 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
WorldWind['HighlightController'] = HighlightController;
|
||||
WorldWind['ImageSource'] = ImageSource;
|
||||
WorldWind['ImageTile'] = ImageTile;
|
||||
WorldWind['Insets'] = Insets;
|
||||
WorldWind['LandsatRestLayer'] = LandsatRestLayer;
|
||||
WorldWind['Layer'] = Layer;
|
||||
WorldWind['Level'] = Level;
|
||||
@ -577,9 +587,11 @@ define([ // PLEASE KEEP ALL THIS IN ALPHABETICAL ORDER BY MODULE NAME (not direc
|
||||
WorldWind['ProjectionMercator'] = ProjectionMercator;
|
||||
WorldWind['ProjectionPolarEquidistant'] = ProjectionPolarEquidistant;
|
||||
WorldWind['ProjectionUPS'] = ProjectionUPS;
|
||||
WorldWind['ProjectionWgs84'] = ProjectionWgs84;
|
||||
WorldWind['Rectangle'] = Rectangle;
|
||||
WorldWind['Renderable'] = Renderable;
|
||||
WorldWind['RenderableLayer'] = RenderableLayer;
|
||||
WorldWind['RestTiledImageLayer'] = RestTiledImageLayer;
|
||||
WorldWind['RotationRecognizer'] = RotationRecognizer;
|
||||
WorldWind['ScreenText'] = ScreenText;
|
||||
WorldWind['ScreenImage'] = ScreenImage;
|
||||
|
||||
@ -51,7 +51,7 @@ define([
|
||||
* @constructor
|
||||
* @classdesc Represents a World Wind window for an HTML canvas.
|
||||
* @param {String} canvasName The name assigned to the HTML canvas in the document.
|
||||
* @param {ElevationModel} An optional argument indicating the elevation model to use for the World
|
||||
* @param {ElevationModel} elevationModel An optional argument indicating the elevation model to use for the World
|
||||
* Window. If missing or null, a default elevation model is used.
|
||||
* @throws {ArgumentError} If there is no HTML element with the specified name in the document, or if the
|
||||
* HTML canvas does not support WebGL.
|
||||
@ -644,14 +644,17 @@ define([
|
||||
WorldWindow.prototype.doNormalRepaint = function () {
|
||||
this.createTerrain();
|
||||
this.clearFrame();
|
||||
this.deferOrderedRendering = false;
|
||||
if (this.drawContext.pickingMode) {
|
||||
if (this.drawContext.makePickFrustum()) {
|
||||
this.doPick();
|
||||
this.resolvePick();
|
||||
}
|
||||
} else {
|
||||
this.doDraw();
|
||||
if (this.subsurfaceMode && this.hasStencilBuffer) {
|
||||
this.redrawSurface();
|
||||
this.drawScreenRenderables();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -662,12 +665,23 @@ define([
|
||||
if (this.drawContext.pickingMode) {
|
||||
if (this.drawContext.makePickFrustum()) {
|
||||
this.pick2DContiguous();
|
||||
this.resolvePick();
|
||||
}
|
||||
} else {
|
||||
this.draw2DContiguous();
|
||||
}
|
||||
};
|
||||
|
||||
WorldWindow.prototype.resolvePick = function () {
|
||||
if (this.drawContext.pickTerrainOnly) {
|
||||
this.resolveTerrainPick();
|
||||
} else if (this.drawContext.regionPicking) {
|
||||
this.resolveRegionPick();
|
||||
} else {
|
||||
this.resolveTopPick();
|
||||
}
|
||||
};
|
||||
|
||||
// Internal function. Intentionally not documented.
|
||||
WorldWindow.prototype.beginFrame = function () {
|
||||
var gl = this.drawContext.currentGlContext;
|
||||
@ -742,6 +756,7 @@ define([
|
||||
|
||||
if (!this.deferOrderedRendering) {
|
||||
this.drawOrderedRenderables();
|
||||
this.drawScreenRenderables();
|
||||
}
|
||||
|
||||
this.drawContext.screenCreditController.drawCredits(this.drawContext);
|
||||
@ -751,6 +766,7 @@ define([
|
||||
WorldWindow.prototype.redrawSurface = function () {
|
||||
// Draw the terrain and surface shapes but only where the current stencil buffer is non-zero.
|
||||
// The non-zero fragments are from drawing the ordered renderables previously.
|
||||
this.drawContext.currentGlContext.enable(this.drawContext.currentGlContext.STENCIL_TEST);
|
||||
this.drawContext.currentGlContext.stencilFunc(this.drawContext.currentGlContext.EQUAL, 1, 1);
|
||||
this.drawContext.currentGlContext.stencilOp(
|
||||
this.drawContext.currentGlContext.KEEP, this.drawContext.currentGlContext.KEEP, this.drawContext.currentGlContext.KEEP);
|
||||
@ -758,6 +774,7 @@ define([
|
||||
this.drawLayers(false);
|
||||
this.drawSurfaceRenderables();
|
||||
this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);
|
||||
this.drawContext.currentGlContext.disable(this.drawContext.currentGlContext.STENCIL_TEST);
|
||||
};
|
||||
|
||||
// Internal function. Intentionally not documented.
|
||||
@ -767,25 +784,44 @@ define([
|
||||
}
|
||||
|
||||
if (!this.drawContext.pickTerrainOnly) {
|
||||
this.drawContext.surfaceShapeTileBuilder.clear();
|
||||
if (this.subsurfaceMode && this.hasStencilBuffer) {
|
||||
// Draw the surface and collect the ordered renderables.
|
||||
this.drawContext.currentGlContext.disable(this.drawContext.currentGlContext.STENCIL_TEST);
|
||||
this.drawContext.surfaceShapeTileBuilder.clear();
|
||||
this.drawLayers(true);
|
||||
this.drawSurfaceRenderables();
|
||||
this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);
|
||||
|
||||
this.drawLayers(true);
|
||||
this.drawSurfaceRenderables();
|
||||
if (!this.deferOrderedRendering) {
|
||||
// Clear the depth and stencil buffers prior to rendering the ordered renderables. This allows
|
||||
// sub-surface renderables to be drawn beneath the terrain. Turn on stenciling to capture the
|
||||
// fragments that ordered renderables draw. The terrain and surface shapes will be subsequently
|
||||
// drawn again, and the stencil buffer will ensure that they are drawn only where they overlap
|
||||
// the fragments drawn by the ordered renderables.
|
||||
this.drawContext.currentGlContext.clear(
|
||||
this.drawContext.currentGlContext.DEPTH_BUFFER_BIT | this.drawContext.currentGlContext.STENCIL_BUFFER_BIT);
|
||||
this.drawContext.currentGlContext.enable(this.drawContext.currentGlContext.STENCIL_TEST);
|
||||
this.drawContext.currentGlContext.stencilFunc(this.drawContext.currentGlContext.ALWAYS, 1, 1);
|
||||
this.drawContext.currentGlContext.stencilOp(
|
||||
this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE, this.drawContext.currentGlContext.REPLACE);
|
||||
this.drawOrderedRenderables();
|
||||
this.drawContext.terrain.pick(this.drawContext);
|
||||
this.drawScreenRenderables();
|
||||
}
|
||||
} else {
|
||||
this.drawContext.surfaceShapeTileBuilder.clear();
|
||||
|
||||
this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);
|
||||
this.drawLayers(true);
|
||||
this.drawSurfaceRenderables();
|
||||
|
||||
if (!this.deferOrderedRendering) {
|
||||
this.drawOrderedRenderables();
|
||||
this.drawContext.surfaceShapeTileBuilder.doRender(this.drawContext);
|
||||
|
||||
if (!this.deferOrderedRendering) {
|
||||
this.drawOrderedRenderables();
|
||||
this.drawScreenRenderables();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.drawContext.pickTerrainOnly) {
|
||||
this.resolveTerrainPick();
|
||||
} else if (this.drawContext.regionPicking) {
|
||||
this.resolveRegionPick();
|
||||
} else {
|
||||
this.resolveTopPick();
|
||||
}
|
||||
};
|
||||
|
||||
// Internal function. Intentionally not documented.
|
||||
@ -888,6 +924,8 @@ define([
|
||||
this.redrawSurface();
|
||||
}
|
||||
}
|
||||
|
||||
this.drawScreenRenderables();
|
||||
};
|
||||
|
||||
WorldWindow.prototype.pick2DContiguous = function () {
|
||||
@ -1032,6 +1070,21 @@ define([
|
||||
dc.frameStatistics.orderedRenderingTime = Date.now() - beginTime;
|
||||
};
|
||||
|
||||
WorldWindow.prototype.drawScreenRenderables = function () {
|
||||
var dc = this.drawContext,
|
||||
or;
|
||||
|
||||
while (or = dc.nextScreenRenderable()) {
|
||||
try {
|
||||
or.renderOrdered(dc);
|
||||
} catch (e) {
|
||||
Logger.logMessage(Logger.LEVEL_WARNING, "WorldWindow", "drawOrderedRenderables",
|
||||
"Error while rendering a screen renderable.\n" + e.message);
|
||||
// Keep going. Render the rest of the screen renderables.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Internal function. Intentionally not documented.
|
||||
WorldWindow.prototype.resolveTopPick = function () {
|
||||
if (this.drawContext.objectsAtPickPoint.objects.length == 0) {
|
||||
|
||||
@ -496,6 +496,8 @@ define(['../../error/ArgumentError',
|
||||
multiPolygonGeometry,
|
||||
properties ? properties : null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,22 +9,26 @@
|
||||
define([
|
||||
'../geom/Angle',
|
||||
'../error/ArgumentError',
|
||||
'../geom/BoundingBox',
|
||||
'../globe/ElevationModel',
|
||||
'../geom/Line',
|
||||
'../geom/Location',
|
||||
'../util/Logger',
|
||||
'../geom/Position',
|
||||
'../projections/ProjectionWgs84',
|
||||
'../geom/Sector',
|
||||
'../globe/Tessellator',
|
||||
'../geom/Vec3',
|
||||
'../util/WWMath'],
|
||||
function (Angle,
|
||||
ArgumentError,
|
||||
BoundingBox,
|
||||
ElevationModel,
|
||||
Line,
|
||||
Location,
|
||||
Logger,
|
||||
Position,
|
||||
ProjectionWgs84,
|
||||
Sector,
|
||||
Tessellator,
|
||||
Vec3,
|
||||
@ -47,9 +51,11 @@ define([
|
||||
* All Cartesian coordinates and elevations are in meters.
|
||||
|
||||
* @param {ElevationModel} elevationModel The elevation model to use for this globe.
|
||||
* @param {GeographicProjection} projection The projection to apply to the globe. May be null or undefined,
|
||||
* in which case no projection is applied and the globe is a WGS84 ellipsoid.
|
||||
* @throws {ArgumentError} If the specified elevation model is null or undefined.
|
||||
*/
|
||||
var Globe = function (elevationModel) {
|
||||
var Globe = function (elevationModel, projection) {
|
||||
if (!elevationModel) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
|
||||
"constructor", "Elevation model is null or undefined."));
|
||||
@ -87,9 +93,14 @@ define([
|
||||
*/
|
||||
this.tessellator = new Tessellator();
|
||||
|
||||
// Used internally to eliminate temporary allocations for certain calculations.
|
||||
this.scratchPosition = new Position(0, 0, 0);
|
||||
this.scratchPoint = new Vec3(0, 0, 0);
|
||||
// Internal. Intentionally not documented.
|
||||
this._projection = projection || new ProjectionWgs84();
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
this._offset = 0;
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
this.offsetVector = new Vec3(0, 0, 0);
|
||||
|
||||
// A unique ID for this globe. Intentionally not documented.
|
||||
this.id = ++Globe.idPool;
|
||||
@ -110,7 +121,72 @@ define([
|
||||
*/
|
||||
stateKey: {
|
||||
get: function () {
|
||||
return this._stateKey + this.elevationModel.stateKey;
|
||||
return this._stateKey + this.elevationModel.stateKey + "offset " + this.offset.toString() + " "
|
||||
+ this.projection.stateKey;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether this globe is 2D and continuous with itself -- that it should scroll continuously
|
||||
* horizontally.
|
||||
* @memberof Globe.prototype
|
||||
* @readonly
|
||||
* @type {Boolean}
|
||||
*/
|
||||
continuous: {
|
||||
get: function () {
|
||||
return this.projection.continuous;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The projection used by this globe.
|
||||
* @memberof Globe.prototype
|
||||
* @default {@link ProjectionWgs84}
|
||||
* @type {GeographicProjection}
|
||||
*/
|
||||
projection: {
|
||||
get: function () {
|
||||
return this._projection;
|
||||
},
|
||||
set: function (projection) {
|
||||
if (!projection) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe",
|
||||
"projection", "missingProjection"));
|
||||
}
|
||||
|
||||
if (this.projection != projection) {
|
||||
this.tessellator = new Tessellator();
|
||||
}
|
||||
this._projection = projection;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The projection limits of the associated projection.
|
||||
* @memberof Globe.prototype
|
||||
* @type {Sector}
|
||||
*/
|
||||
projectionLimits: {
|
||||
get: function () {
|
||||
return this._projection.projectionLimits;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An offset to apply to this globe when translating between Geographic positions and Cartesian points.
|
||||
* Used during scrolling to position points appropriately.
|
||||
* Applications typically do not access this property. It is used by the associated globe.
|
||||
* @memberof Globe.prototype
|
||||
* @type {Number}
|
||||
*/
|
||||
offset: {
|
||||
get: function () {
|
||||
return this._offset;
|
||||
},
|
||||
set: function (offset) {
|
||||
this._offset = offset;
|
||||
this.offsetVector[0] = offset * 2 * Math.PI * this.equatorialRadius;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -120,7 +196,7 @@ define([
|
||||
* @returns {Boolean} true if this is a 2D globe, otherwise false.
|
||||
*/
|
||||
Globe.prototype.is2D = function () {
|
||||
return false;
|
||||
return this.projection.is2D;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -140,17 +216,7 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
rpm = this.equatorialRadius / Math.sqrt(1.0 - this.eccentricitySquared * sinLat * sinLat);
|
||||
|
||||
result[0] = (rpm + altitude) * cosLat * sinLon;
|
||||
result[1] = (rpm * (1.0 - this.eccentricitySquared) + altitude) * sinLat;
|
||||
result[2] = (rpm + altitude) * cosLat * cosLon;
|
||||
|
||||
return result;
|
||||
return this.projection.geographicToCartesian(this, latitude, longitude, altitude, this.offsetVector, result);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -220,51 +286,8 @@ define([
|
||||
"Result array is null, undefined or insufficient length."));
|
||||
}
|
||||
|
||||
var minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
|
||||
maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
|
||||
minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
|
||||
maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
|
||||
deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
|
||||
deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
|
||||
refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
|
||||
latIndex, lonIndex,
|
||||
elevIndex = 0, resultIndex = 0,
|
||||
lat, lon, rpm, elev,
|
||||
cosLat, sinLat,
|
||||
cosLon = new Float64Array(numLon), sinLon = new Float64Array(numLon);
|
||||
|
||||
// Compute and save values that are a function of each unique longitude value in the specified sector. This
|
||||
// eliminates the need to re-compute these values for each column of constant longitude.
|
||||
for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
|
||||
if (lonIndex === numLon - 1) {
|
||||
lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
|
||||
}
|
||||
|
||||
cosLon[lonIndex] = Math.cos(lon);
|
||||
sinLon[lonIndex] = Math.sin(lon);
|
||||
}
|
||||
|
||||
// Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian
|
||||
// point corresponding to each latitude and longitude.
|
||||
for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
|
||||
if (latIndex === numLat - 1) {
|
||||
lat = maxLat; // explicitly set the last lat to the max longitude to ensure alignment
|
||||
}
|
||||
|
||||
// Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
|
||||
cosLat = Math.cos(lat);
|
||||
sinLat = Math.sin(lat);
|
||||
rpm = this.equatorialRadius / Math.sqrt(1.0 - this.eccentricitySquared * sinLat * sinLat);
|
||||
|
||||
for (lonIndex = 0; lonIndex < numLon; lonIndex++) {
|
||||
elev = elevations[elevIndex++];
|
||||
result[resultIndex++] = (rpm + elev) * cosLat * sinLon[lonIndex] - refCenter[0];
|
||||
result[resultIndex++] = (rpm * (1.0 - this.eccentricitySquared) + elev) * sinLat - refCenter[1];
|
||||
result[resultIndex++] = (rpm + elev) * cosLat * cosLon[lonIndex] - refCenter[2];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return this.projection.geographicToCartesianGrid(this, sector, numLat, numLon, elevations, referencePoint,
|
||||
this.offsetVector, result);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -285,105 +308,16 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
// Contributed by Nathan Kronenfeld. Updated on 1/24/2011. Brings this calculation in line with Vermeille's most
|
||||
// recent update.
|
||||
this.projection.cartesianToGeographic(this, x, y, z, this.offsetVector, result);
|
||||
|
||||
// According to H. Vermeille, "An analytical method to transform geocentric into geodetic coordinates"
|
||||
// http://www.springerlink.com/content/3t6837t27t351227/fulltext.pdf
|
||||
// Journal of Geodesy, accepted 10/2010, not yet published
|
||||
var X = z,
|
||||
Y = x,
|
||||
Z = y,
|
||||
XXpYY = X * X + Y * Y,
|
||||
sqrtXXpYY = Math.sqrt(XXpYY),
|
||||
a = this.equatorialRadius,
|
||||
ra2 = 1 / (a * a),
|
||||
e2 = this.eccentricitySquared,
|
||||
e4 = e2 * e2,
|
||||
p = XXpYY * ra2,
|
||||
q = Z * Z * (1 - e2) * ra2,
|
||||
r = (p + q - e4) / 6,
|
||||
h,
|
||||
phi,
|
||||
u,
|
||||
evoluteBorderTest = 8 * r * r * r + e4 * p * q,
|
||||
rad1,
|
||||
rad2,
|
||||
rad3,
|
||||
atan,
|
||||
v,
|
||||
w,
|
||||
k,
|
||||
D,
|
||||
sqrtDDpZZ,
|
||||
e,
|
||||
lambda,
|
||||
s2;
|
||||
|
||||
if (evoluteBorderTest > 0 || q != 0) {
|
||||
if (evoluteBorderTest > 0) {
|
||||
// Step 2: general case
|
||||
rad1 = Math.sqrt(evoluteBorderTest);
|
||||
rad2 = Math.sqrt(e4 * p * q);
|
||||
|
||||
// 10*e2 is my arbitrary decision of what Vermeille means by "near... the cusps of the evolute".
|
||||
if (evoluteBorderTest > 10 * e2) {
|
||||
rad3 = WWMath.cbrt((rad1 + rad2) * (rad1 + rad2));
|
||||
u = r + 0.5 * rad3 + 2 * r * r / rad3;
|
||||
}
|
||||
else {
|
||||
u = r + 0.5 * WWMath.cbrt((rad1 + rad2) * (rad1 + rad2))
|
||||
+ 0.5 * WWMath.cbrt((rad1 - rad2) * (rad1 - rad2));
|
||||
}
|
||||
// Wrap if the globe is continuous.
|
||||
if (this.continuous) {
|
||||
if (result.longitude < -180) {
|
||||
result.longitude += 360;
|
||||
} else if (result.longitude > 180) {
|
||||
result.longitude -= 360;
|
||||
}
|
||||
else {
|
||||
// Step 3: near evolute
|
||||
rad1 = Math.sqrt(-evoluteBorderTest);
|
||||
rad2 = Math.sqrt(-8 * r * r * r);
|
||||
rad3 = Math.sqrt(e4 * p * q);
|
||||
atan = 2 * Math.atan2(rad3, rad1 + rad2) / 3;
|
||||
|
||||
u = -4 * r * Math.sin(atan) * Math.cos(Math.PI / 6 + atan);
|
||||
}
|
||||
|
||||
v = Math.sqrt(u * u + e4 * q);
|
||||
w = e2 * (u + v - q) / (2 * v);
|
||||
k = (u + v) / (Math.sqrt(w * w + u + v) + w);
|
||||
D = k * sqrtXXpYY / (k + e2);
|
||||
sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
|
||||
|
||||
h = (k + e2 - 1) * sqrtDDpZZ / k;
|
||||
phi = 2 * Math.atan2(Z, sqrtDDpZZ + D);
|
||||
}
|
||||
else {
|
||||
// Step 4: singular disk
|
||||
rad1 = Math.sqrt(1 - e2);
|
||||
rad2 = Math.sqrt(e2 - p);
|
||||
e = Math.sqrt(e2);
|
||||
|
||||
h = -a * rad1 * rad2 / e;
|
||||
phi = rad2 / (e * rad2 + rad1 * Math.sqrt(p));
|
||||
}
|
||||
|
||||
// Compute lambda
|
||||
s2 = Math.sqrt(2);
|
||||
if ((s2 - 1) * Y < sqrtXXpYY + X) {
|
||||
// case 1 - -135deg < lambda < 135deg
|
||||
lambda = 2 * Math.atan2(Y, sqrtXXpYY + X);
|
||||
}
|
||||
else if (sqrtXXpYY + Y < (s2 + 1) * X) {
|
||||
// case 2 - -225deg < lambda < 45deg
|
||||
lambda = -Math.PI * 0.5 + 2 * Math.atan2(X, sqrtXXpYY - Y);
|
||||
}
|
||||
else {
|
||||
// if (sqrtXXpYY-Y<(s2=1)*X) { // is the test, if needed, but it's not
|
||||
// case 3: - -45deg < lambda < 225deg
|
||||
lambda = Math.PI * 0.5 - 2 * Math.atan2(X, sqrtXXpYY + Y);
|
||||
}
|
||||
|
||||
result.latitude = Angle.RADIANS_TO_DEGREES * phi;
|
||||
result.longitude = Angle.RADIANS_TO_DEGREES * lambda;
|
||||
result.altitude = h;
|
||||
|
||||
return result;
|
||||
};
|
||||
@ -416,6 +350,14 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
if (this.is2D()) {
|
||||
result[0] = 0;
|
||||
result[1] = 0;
|
||||
result[2] = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
@ -446,6 +388,20 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
// For backwards compatibility, check whether the projection defines a surfaceNormalAtPoint function
|
||||
// before calling it. If it's not available, use the old code to compute the normal.
|
||||
if (this.projection.surfaceNormalAtPoint) {
|
||||
return this.projection.surfaceNormalAtPoint(this, x, y, z, result);
|
||||
}
|
||||
|
||||
if (this.is2D()) {
|
||||
result[0] = 0;
|
||||
result[1] = 0;
|
||||
result[2] = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var eSquared = this.equatorialRadius * this.equatorialRadius,
|
||||
polSquared = this.polarRadius * this.polarRadius;
|
||||
|
||||
@ -471,29 +427,7 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
// The north-pointing tangent is derived by rotating the vector (0, 1, 0) about the Y-axis by longitude degrees,
|
||||
// then rotating it about the X-axis by -latitude degrees. The latitude angle must be inverted because latitude
|
||||
// is a clockwise rotation about the X-axis, and standard rotation matrices assume counter-clockwise rotation.
|
||||
// The combined rotation can be represented by a combining two rotation matrices Rlat, and Rlon, then
|
||||
// transforming the vector (0, 1, 0) by the combined transform:
|
||||
//
|
||||
// NorthTangent = (Rlon * Rlat) * (0, 1, 0)
|
||||
//
|
||||
// This computation can be simplified and encoded inline by making two observations:
|
||||
// - The vector's X and Z coordinates are always 0, and its Y coordinate is always 1.
|
||||
// - Inverting the latitude rotation angle is equivalent to inverting sinLat. We know this by the
|
||||
// trigonometric identities cos(-x) = cos(x), and sin(-x) = -sin(x).
|
||||
|
||||
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
|
||||
|
||||
result[0] = -sinLat * sinLon;
|
||||
result[1] = cosLat;
|
||||
result[2] = -sinLat * cosLon;
|
||||
|
||||
return result.normalize();
|
||||
return this.projection.northTangentAtLocation(this, latitude, longitude, result);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -512,9 +446,7 @@ define([
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
this.computePositionFromPoint(x, y, z, this.scratchPosition);
|
||||
|
||||
return this.northTangentAtLocation(this.scratchPosition.latitude, this.scratchPosition.longitude, result);
|
||||
return this.projection.northTangentAtPoint(this, x, y, z, this.offsetVector, result);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -529,6 +461,14 @@ define([
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsFrustum", "missingFrustum"));
|
||||
}
|
||||
|
||||
if (this.is2D()) {
|
||||
var bbox = new BoundingBox();
|
||||
bbox.setToSector(Sector.FULL_SPHERE, this, this.elevationModel.minElevation,
|
||||
this.elevationModel.maxElevation);
|
||||
|
||||
return bbox.intersectsFrustum(frustum);
|
||||
}
|
||||
|
||||
if (frustum.far.distance <= this.equatorialRadius)
|
||||
return false;
|
||||
if (frustum.left.distance <= this.equatorialRadius)
|
||||
@ -563,6 +503,31 @@ define([
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsLine", "missingResult"));
|
||||
}
|
||||
|
||||
if (this.is2D()) {
|
||||
var vx = line.direction[0],
|
||||
vy = line.direction[1],
|
||||
vz = line.direction[2],
|
||||
sx = line.origin[0],
|
||||
sy = line.origin[1],
|
||||
sz = line.origin[2],
|
||||
t;
|
||||
|
||||
if (vz == 0 && sz != 0) { // ray is parallel to and not coincident with the XY plane
|
||||
return false;
|
||||
}
|
||||
|
||||
t = -sz / vz; // intersection distance, simplified for the XY plane
|
||||
if (t < 0) { // intersection is behind the ray's origin
|
||||
return false;
|
||||
}
|
||||
|
||||
result[0] = sx + vx * t;
|
||||
result[1] = sy + vy * t;
|
||||
result[2] = sz + vz * t;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return WWMath.computeEllipsoidalGlobeIntersection(line, this.equatorialRadius, this.polarRadius, result);
|
||||
};
|
||||
|
||||
|
||||
@ -7,28 +7,12 @@
|
||||
* @version $Id: Globe2D.js 3205 2015-06-17 18:05:23Z tgaskins $
|
||||
*/
|
||||
define([
|
||||
'../geom/Angle',
|
||||
'../error/ArgumentError',
|
||||
'../geom/BoundingBox',
|
||||
'../projections/GeographicProjection',
|
||||
'../globe/Globe',
|
||||
'../util/Logger',
|
||||
'../projections/ProjectionEquirectangular',
|
||||
'../geom/Sector',
|
||||
'../globe/Tessellator',
|
||||
'../geom/Vec3',
|
||||
'../globe/ZeroElevationModel'
|
||||
],
|
||||
function (Angle,
|
||||
ArgumentError,
|
||||
BoundingBox,
|
||||
GeographicProjection,
|
||||
Globe,
|
||||
Logger,
|
||||
function (Globe,
|
||||
ProjectionEquirectangular,
|
||||
Sector,
|
||||
Tessellator,
|
||||
Vec3,
|
||||
ZeroElevationModel) {
|
||||
"use strict";
|
||||
|
||||
@ -38,260 +22,14 @@ define([
|
||||
* @alias Globe2D
|
||||
* @constructor
|
||||
* @augments Globe
|
||||
* @classdesc Represents a 2D flat globe with a configurable projection. Rectangular projections
|
||||
* are continuously scrolling longitudinally.
|
||||
* @classdesc Represents a 2D flat globe with a configurable projection.
|
||||
* The default rectangular projection scrolls longitudinally.
|
||||
*/
|
||||
var Globe2D = function () {
|
||||
Globe.call(this, new ZeroElevationModel());
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
this._projection = new ProjectionEquirectangular();
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
this._offset = 0;
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
this.offsetVector = new Vec3(0, 0, 0);
|
||||
Globe.call(this, new ZeroElevationModel(), new ProjectionEquirectangular());
|
||||
};
|
||||
|
||||
Globe2D.prototype = Object.create(Globe.prototype);
|
||||
|
||||
Object.defineProperties(Globe2D.prototype, {
|
||||
/**
|
||||
* Indicates whether this projection is continuous with itself -- that it should scroll continuously
|
||||
* horizontally.
|
||||
* @memberof Globe2D.prototype
|
||||
* @readonly
|
||||
* @type {Boolean}
|
||||
*/
|
||||
continuous: {
|
||||
get: function () {
|
||||
return this.projection.continuous;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The projection used by this 2D globe.
|
||||
* @memberof Globe2D.prototype
|
||||
* @default {@link ProjectionEquirectangular}
|
||||
* @type {GeographicProjection}
|
||||
*/
|
||||
projection: {
|
||||
get: function () {
|
||||
return this._projection;
|
||||
},
|
||||
set: function (projection) {
|
||||
if (!projection) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D",
|
||||
"projection", "missingProjection"));
|
||||
}
|
||||
|
||||
if (this.projection != projection) {
|
||||
this.tessellator = new Tessellator();
|
||||
}
|
||||
this._projection = projection;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The projection limits of the associated projection.
|
||||
* @memberof Globe2D.prototype
|
||||
* @type {Sector}
|
||||
*/
|
||||
projectionLimits: {
|
||||
get: function () {
|
||||
return this._projection.projectionLimits;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An offset to apply to this globe when translating between Geographic positions and Cartesian points.
|
||||
* Used during scrolling to position points appropriately.
|
||||
* Applications typically do not access this property. It is used by the associated globe.
|
||||
* @memberof Globe2D.prototype
|
||||
* @type {Number}
|
||||
*/
|
||||
offset: {
|
||||
get: function () {
|
||||
return this._offset;
|
||||
},
|
||||
set: function (offset) {
|
||||
this._offset = offset;
|
||||
this.offsetVector[0] = offset * 2 * Math.PI * this.equatorialRadius;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A string identifying this globe's current state. Used to compare states during rendering to
|
||||
* determine whether globe-state dependent cached values must be updated. Applications typically do not
|
||||
* interact with this property.
|
||||
* @memberof Globe2D.prototype
|
||||
* @readonly
|
||||
* @type {String}
|
||||
*/
|
||||
stateKey: {
|
||||
get: function () {
|
||||
return this._stateKey + this.elevationModel.stateKey + "offset " + this.offset.toString() + " "
|
||||
+ this.projection.stateKey;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.is2D = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes a Cartesian point from a specified position and using the current projection.
|
||||
* @param {Number} latitude The position's latitude.
|
||||
* @param {Number} longitude The position's longitude.
|
||||
* @param {Number} altitude The position's altitude.
|
||||
* @param {Vec3} result A reference to a pre-allocated {@link Vec3} in which to return the computed X,
|
||||
* Y and Z Cartesian coordinates.
|
||||
* @returns {Vec3} The result argument.
|
||||
* @throws {ArgumentError} If the specified result argument is null or undefined.
|
||||
*/
|
||||
Globe2D.prototype.computePointFromPosition = function (latitude, longitude, altitude, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D", "computePointFromPosition",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
return this.projection.geographicToCartesian(this, latitude, longitude, altitude, this.offsetVector, result);
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.computePointsForGrid = function (sector, numLat, numLon, elevations, referencePoint, result) {
|
||||
|
||||
return this.projection.geographicToCartesianGrid(this, sector, numLat, numLon, elevations, referencePoint,
|
||||
this.offsetVector, result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes a geographic position from a specified Cartesian point and using the current projection.
|
||||
*
|
||||
* @param {Number} x The X coordinate.
|
||||
* @param {Number} y The Y coordinate.
|
||||
* @param {Number} z The Z coordinate.
|
||||
* @param {Position} result A pre-allocated {@link Position} instance in which to return the computed position.
|
||||
* @returns {Position} The specified result position.
|
||||
* @throws {ArgumentError} If the specified result argument is null or undefined.
|
||||
*/
|
||||
Globe2D.prototype.computePositionFromPoint = function (x, y, z, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D", "computePositionFromPoint",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
this.projection.cartesianToGeographic(this, x, y, z, this.offsetVector, result);
|
||||
|
||||
// Wrap if the globe is continuous.
|
||||
if (result.longitude < -180) {
|
||||
result.longitude += 360;
|
||||
} else if (result.longitude > 180) {
|
||||
result.longitude -= 360;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.surfaceNormalAtLocation = function (latitude, longitude, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D", "surfaceNormalAtLocation",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
result[0] = 0;
|
||||
result[1] = 0;
|
||||
result[2] = 1;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.surfaceNormalAtPoint = function (x, y, z, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "surfaceNormalAtPoint",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
result[0] = 0;
|
||||
result[1] = 0;
|
||||
result[2] = 1;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.northTangentAtLocation = function (latitude, longitude, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "northTangentAtLocation",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
return this.projection.northTangentAtLocation(this, latitude, longitude, result);
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.northTangentAtPoint = function (x, y, z, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D", "northTangentAtPoint",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
return this.projection.northTangentAtPoint(this, x, y, z, this.offsetVector, result);
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.intersectsFrustum = function (frustum) {
|
||||
if (!frustum) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe2D",
|
||||
"intersectsFrustum", "missingFrustum"));
|
||||
}
|
||||
|
||||
var bbox = new BoundingBox();
|
||||
bbox.setToSector(Sector.FULL_SPHERE, this, this.elevationModel.minElevation,
|
||||
this.elevationModel.maxElevation);
|
||||
|
||||
return bbox.intersectsFrustum(frustum);
|
||||
};
|
||||
|
||||
// Documented in superclass.
|
||||
Globe2D.prototype.intersectsLine = function (line, result) {
|
||||
if (!line) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectWithRay", "missingLine"));
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Globe", "intersectsLine", "missingResult"));
|
||||
}
|
||||
|
||||
var vx = line.direction[0],
|
||||
vy = line.direction[1],
|
||||
vz = line.direction[2],
|
||||
sx = line.origin[0],
|
||||
sy = line.origin[1],
|
||||
sz = line.origin[2],
|
||||
t;
|
||||
|
||||
if (vz == 0 && sz != 0) { // ray is parallel to and not coincident with the XY plane
|
||||
return false;
|
||||
}
|
||||
|
||||
t = -sz / vz; // intersection distance, simplified for the XY plane
|
||||
if (t < 0) { // intersection is behind the ray's origin
|
||||
return false;
|
||||
}
|
||||
|
||||
result[0] = sx + vx * t;
|
||||
result[1] = sy + vy * t;
|
||||
result[2] = sz + vz * t;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return Globe2D;
|
||||
});
|
||||
@ -29,7 +29,7 @@ define([
|
||||
RenderableLayer.call(this, "Blue Marble Image");
|
||||
|
||||
var surfaceImage = new SurfaceImage(Sector.FULL_SPHERE,
|
||||
WWUtil.currentUrlSansFilePart() + "/../images/BMNG_world.topo.bathy.200405.3.2048x1024.jpg");
|
||||
WorldWind.configuration.baseUrl + "images/BMNG_world.topo.bathy.200405.3.2048x1024.jpg");
|
||||
|
||||
this.addRenderable(surfaceImage);
|
||||
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
/**
|
||||
* @exports BMNGRestLayer
|
||||
* @version $Id: BMNGRestLayer.js 2939 2015-03-30 16:50:49Z tgaskins $
|
||||
*/
|
||||
define([
|
||||
'../error/ArgumentError',
|
||||
'../geom/Location',
|
||||
'../util/Logger',
|
||||
'../geom/Sector',
|
||||
'../layer/TiledImageLayer',
|
||||
'../util/LevelRowColumnUrlBuilder',
|
||||
'../util/WWUtil'
|
||||
],
|
||||
function (ArgumentError,
|
||||
Location,
|
||||
Logger,
|
||||
Sector,
|
||||
TiledImageLayer,
|
||||
LevelRowColumnUrlBuilder,
|
||||
WWUtil) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs a Blue Marble image layer that uses a REST interface to retrieve its imagery.
|
||||
* @alias BMNGRestLayer
|
||||
* @constructor
|
||||
* @augments TiledImageLayer
|
||||
* @classdesc Displays a Blue Marble image layer that spans the entire globe. The imagery is obtained from a
|
||||
* specified REST tile service.
|
||||
* See [LevelRowColumnUrlBuilder]{@link LevelRowColumnUrlBuilder} for a description of the REST interface.
|
||||
* @param {String} serverAddress The server address of the tile service. May be null, in which case the
|
||||
* current origin is used (see window.location).
|
||||
* @param {String} pathToData The path to the data directory relative to the specified server address.
|
||||
* May be null, in which case the server address is assumed to be the full path to the data directory.
|
||||
* @param {String} displayName The display name to associate with this layer.
|
||||
*/
|
||||
var BMNGRestLayer = function (serverAddress, pathToData, displayName) {
|
||||
var cachePath = WWUtil.urlPath(serverAddress + "/" + pathToData);
|
||||
|
||||
TiledImageLayer.call(this, Sector.FULL_SPHERE, new Location(45, 45), 5, "image/jpeg", cachePath, 256, 256);
|
||||
|
||||
this.displayName = displayName;
|
||||
this.pickEnabled = false;
|
||||
this.urlBuilder = new LevelRowColumnUrlBuilder(serverAddress, pathToData);
|
||||
};
|
||||
|
||||
BMNGRestLayer.prototype = Object.create(TiledImageLayer.prototype);
|
||||
|
||||
return BMNGRestLayer;
|
||||
});
|
||||
@ -4,14 +4,13 @@
|
||||
*/
|
||||
/**
|
||||
* @exports BlueMarbleLayer
|
||||
* @version $Id: BlueMarbleLayer.js 3126 2015-05-29 14:48:36Z tgaskins $
|
||||
*/
|
||||
define([
|
||||
'../layer/BMNGLayer',
|
||||
'../layer/Layer'
|
||||
'../layer/Layer',
|
||||
'../layer/RestTiledImageLayer'
|
||||
],
|
||||
function (BMNGLayer,
|
||||
Layer) {
|
||||
function (Layer,
|
||||
RestTiledImageLayer) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -26,8 +25,11 @@ define([
|
||||
* undefined.
|
||||
* @param {Date} initialTime A date value indicating the month to display. The nearest month to the specified
|
||||
* time is displayed. January is displayed if this argument is null or undefined, i.e., new Date("2004-01");
|
||||
* @param {{}} configuration An optional object with properties defining the layer configuration.
|
||||
* See {@link RestTiledImageLayer} for a description of its contents. May be null, in which case default
|
||||
* values are used.
|
||||
*/
|
||||
var BlueMarbleLayer = function (displayName, initialTime) {
|
||||
var BlueMarbleLayer = function (displayName, initialTime, configuration) {
|
||||
Layer.call(this, displayName || "Blue Marble");
|
||||
|
||||
/**
|
||||
@ -37,6 +39,8 @@ define([
|
||||
*/
|
||||
this.time = initialTime || new Date("2004-01");
|
||||
|
||||
this.configuration = configuration;
|
||||
|
||||
this.pickEnabled = false;
|
||||
|
||||
// Intentionally not documented.
|
||||
@ -57,6 +61,9 @@ define([
|
||||
{month: "BlueMarble-200411", time: BlueMarbleLayer.availableTimes[10]},
|
||||
{month: "BlueMarble-200412", time: BlueMarbleLayer.availableTimes[11]}
|
||||
];
|
||||
|
||||
this.serverAddress = null;
|
||||
this.pathToData = "../standalonedata/Earth/BlueMarble256/";
|
||||
};
|
||||
|
||||
BlueMarbleLayer.prototype = Object.create(Layer.prototype);
|
||||
@ -78,9 +85,55 @@ define([
|
||||
new Date("2004-09"),
|
||||
new Date("2004-10"),
|
||||
new Date("2004-11"),
|
||||
new Date("2004-12"),
|
||||
new Date("2004-12")
|
||||
];
|
||||
|
||||
/**
|
||||
* Initiates retrieval of this layer's level 0 images for all sub-layers. Use
|
||||
* [isPrePopulated]{@link TiledImageLayer#isPrePopulated} to determine when the images have been retrieved
|
||||
* and associated with the level 0 tiles.
|
||||
* Pre-populating is not required. It is used to eliminate the visual effect of loading tiles incrementally,
|
||||
* but only for level 0 tiles. An application might pre-populate a layer in order to delay displaying it
|
||||
* within a time series until all the level 0 images have been retrieved and added to memory.
|
||||
* @param {WorldWindow} wwd The world window for which to pre-populate this layer.
|
||||
* @throws {ArgumentError} If the specified world window is null or undefined.
|
||||
*/
|
||||
BlueMarbleLayer.prototype.prePopulate = function (wwd) {
|
||||
if (!wwd) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "BlueMarbleLayer", "prePopulate", "missingWorldWindow"));
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.layerNames.length; i++) {
|
||||
var layerName = this.layerNames[i].month;
|
||||
|
||||
if (!this.layers[layerName]) {
|
||||
this.createSubLayer(layerName);
|
||||
}
|
||||
|
||||
this.layers[layerName].prePopulate(wwd);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether this layer's level 0 tile images for all sub-layers have been retrieved and associated
|
||||
* with the tiles.
|
||||
* Use [prePopulate]{@link TiledImageLayer#prePopulate} to initiate retrieval of level 0 images.
|
||||
* @param {WorldWindow} wwd The world window associated with this layer.
|
||||
* @returns {Boolean} true if all level 0 images have been retrieved, otherwise false.
|
||||
* @throws {ArgumentError} If the specified world window is null or undefined.
|
||||
*/
|
||||
BlueMarbleLayer.prototype.isPrePopulated = function (wwd) {
|
||||
for (var i = 0; i < this.layerNames.length; i++) {
|
||||
var layer = this.layers[this.layerNames[i].month];
|
||||
if (!layer || !layer.isPrePopulated(wwd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
BlueMarbleLayer.prototype.doRender = function (dc) {
|
||||
var layer = this.nearestLayer(this.time);
|
||||
layer.opacity = this.opacity;
|
||||
@ -95,12 +148,18 @@ define([
|
||||
var nearestName = this.nearestLayerName(time);
|
||||
|
||||
if (!this.layers[nearestName]) {
|
||||
this.layers[nearestName] = new BMNGLayer(nearestName);
|
||||
this.createSubLayer(nearestName);
|
||||
}
|
||||
|
||||
return this.layers[nearestName];
|
||||
};
|
||||
|
||||
BlueMarbleLayer.prototype.createSubLayer = function (layerName) {
|
||||
var dataPath = this.pathToData + layerName;
|
||||
this.layers[layerName] = new RestTiledImageLayer(this.serverAddress, dataPath, this.displayName,
|
||||
this.configuration);
|
||||
};
|
||||
|
||||
// Intentionally not documented.
|
||||
BlueMarbleLayer.prototype.nearestLayerName = function (time) {
|
||||
var milliseconds = time.getTime();
|
||||
@ -117,7 +176,7 @@ define([
|
||||
var leftTime = this.layerNames[i].time.getTime(),
|
||||
rightTime = this.layerNames[i + 1].time.getTime();
|
||||
|
||||
if (milliseconds >= leftTime && milliseconds <= rightTime) {
|
||||
if (milliseconds >= leftTime && milliseconds <= rightTime) {
|
||||
var dLeft = milliseconds - leftTime,
|
||||
dRight = rightTime - milliseconds;
|
||||
|
||||
|
||||
@ -70,6 +70,13 @@ define([
|
||||
* @readonly
|
||||
*/
|
||||
this.inCurrentFrame = false;
|
||||
|
||||
/**
|
||||
* The time to display. This property selects the layer contents that represents the specified time.
|
||||
* If null, layer-type dependent contents are displayed.
|
||||
* @type {Date}
|
||||
*/
|
||||
this.time = null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
69
src/layer/RestTiledImageLayer.js
Normal file
69
src/layer/RestTiledImageLayer.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
/**
|
||||
* @exports RestTiledImageLayer
|
||||
*/
|
||||
define([
|
||||
'../error/ArgumentError',
|
||||
'../geom/Location',
|
||||
'../util/Logger',
|
||||
'../geom/Sector',
|
||||
'../layer/TiledImageLayer',
|
||||
'../util/LevelRowColumnUrlBuilder',
|
||||
'../util/WWUtil'
|
||||
],
|
||||
function (ArgumentError,
|
||||
Location,
|
||||
Logger,
|
||||
Sector,
|
||||
TiledImageLayer,
|
||||
LevelRowColumnUrlBuilder,
|
||||
WWUtil) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs a tiled image layer that uses a REST interface to retrieve its imagery.
|
||||
* @alias RestTiledImageLayer
|
||||
* @constructor
|
||||
* @augments TiledImageLayer
|
||||
* @classdesc Displays a layer whose imagery is retrieved using a REST interface.
|
||||
* See [LevelRowColumnUrlBuilder]{@link LevelRowColumnUrlBuilder} for a description of the REST interface.
|
||||
* @param {String} serverAddress The server address of the tile service. May be null, in which case the
|
||||
* current origin is used (see window.location).
|
||||
* @param {String} pathToData The path to the data directory relative to the specified server address.
|
||||
* May be null, in which case the server address is assumed to be the full path to the data directory.
|
||||
* @param {String} displayName The display name to associate with this layer.
|
||||
* @param {{}} configuration The tiled image layer configuration. May have the following properties:
|
||||
* <ul>
|
||||
* <li>sector {Sector}, default is full sphere</li>
|
||||
* <li>levelZerotTileDelta {Location}, default is 45, 45</li>
|
||||
* <li>numLevels {Number}, default is 5</li>
|
||||
* <li>imageFormat {String}, default is image/jpeg</li>
|
||||
* <li>tileWidth {Number}, default is 256</li>
|
||||
* <li>tileHeight {Number}, default is 256</li>
|
||||
* </ul>
|
||||
* The specified default is used for any property not specified.
|
||||
*/
|
||||
var RestTiledImageLayer = function (serverAddress, pathToData, displayName, configuration) {
|
||||
var cachePath = WWUtil.urlPath(serverAddress + "/" + pathToData);
|
||||
|
||||
TiledImageLayer.call(this,
|
||||
(configuration && configuration.sector) || Sector.FULL_SPHERE,
|
||||
(configuration && configuration.levelZeroTileDelta) || new Location(45, 45),
|
||||
(configuration && configuration.numLevels) || 5,
|
||||
(configuration && configuration.imageFormat) || "image/jpeg",
|
||||
cachePath,
|
||||
(configuration && configuration.tileWidth) || 256,
|
||||
(configuration && configuration.tileHeight) || 256);
|
||||
|
||||
this.displayName = displayName;
|
||||
this.pickEnabled = false;
|
||||
this.urlBuilder = new LevelRowColumnUrlBuilder(serverAddress, pathToData);
|
||||
};
|
||||
|
||||
RestTiledImageLayer.prototype = Object.create(TiledImageLayer.prototype);
|
||||
|
||||
return RestTiledImageLayer;
|
||||
});
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
/**
|
||||
* @exports TiledImageLayer
|
||||
* @version $Id: TiledImageLayer.js 3414 2015-08-20 19:09:19Z tgaskins $
|
||||
*/
|
||||
define([
|
||||
'../util/AbsentResourceList',
|
||||
@ -141,6 +140,59 @@ define([
|
||||
this.currentTilesInvalid = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates retrieval of this layer's level 0 images. Use
|
||||
* [isPrePopulated]{@link TiledImageLayer#isPrePopulated} to determine when the images have been retrieved
|
||||
* and associated with the level 0 tiles.
|
||||
* Pre-populating is not required. It is used to eliminate the visual effect of loading tiles incrementally,
|
||||
* but only for level 0 tiles. An application might pre-populate a layer in order to delay displaying it
|
||||
* within a time series until all the level 0 images have been retrieved and added to memory.
|
||||
* @param {WorldWindow} wwd The world window for which to pre-populate this layer.
|
||||
* @throws {ArgumentError} If the specified world window is null or undefined.
|
||||
*/
|
||||
TiledImageLayer.prototype.prePopulate = function (wwd) {
|
||||
if (!wwd) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "prePopulate", "missingWorldWindow"));
|
||||
}
|
||||
|
||||
var dc = wwd.drawContext;
|
||||
|
||||
if (!this.topLevelTiles || (this.topLevelTiles.length === 0)) {
|
||||
this.createTopLevelTiles(dc);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.topLevelTiles.length; i++) {
|
||||
var tile = this.topLevelTiles[i];
|
||||
|
||||
if (!this.isTileTextureInMemory(dc, tile)) {
|
||||
this.retrieveTileImage(dc, tile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether this layer's level 0 tile images have been retrieved and associated with the tiles.
|
||||
* Use [prePopulate]{@link TiledImageLayer#prePopulate} to initiate retrieval of level 0 images.
|
||||
* @param {WorldWindow} wwd The world window associated with this layer.
|
||||
* @returns {Boolean} true if all level 0 images have been retrieved, otherwise false.
|
||||
* @throws {ArgumentError} If the specified world window is null or undefined.
|
||||
*/
|
||||
TiledImageLayer.prototype.isPrePopulated = function (wwd) {
|
||||
if (!wwd) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "TiledImageLayer", "isPrePopulated", "missingWorldWindow"));
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.topLevelTiles.length; i++) {
|
||||
if (!this.isTileTextureInMemory(wwd.drawContext, this.topLevelTiles[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Intentionally not documented.
|
||||
TiledImageLayer.prototype.createTile = function (sector, level, row, column) {
|
||||
var path = this.cachePath + "-layer/" + level.levelNumber + "/" + row + "/" + row + "_" + column + "."
|
||||
|
||||
@ -46,13 +46,6 @@ define([
|
||||
*/
|
||||
this.config = config;
|
||||
|
||||
/**
|
||||
* The time to display. This property selects the sub-layer that represents the specified time.
|
||||
* If null, no sub-layer is displayed.
|
||||
* @type {Date}
|
||||
*/
|
||||
this.time = null;
|
||||
|
||||
// Intentionally not documented.
|
||||
this.displayName = config.title;
|
||||
this.pickEnabled = false;
|
||||
|
||||
@ -49,11 +49,26 @@ define([],
|
||||
|
||||
/**
|
||||
* Adds a picked object to this list.
|
||||
* If the picked object is a terrain object and the list already contains a terrain object, the terrain
|
||||
* object in the list is replaced by the specified one.
|
||||
* @param {PickedObject} pickedObject The picked object to add. If null, this list remains unchanged.
|
||||
*/
|
||||
PickedObjectList.prototype.add = function (pickedObject) {
|
||||
if (pickedObject) {
|
||||
this.objects.push(pickedObject);
|
||||
if (pickedObject.isTerrain) {
|
||||
var terrainObjectIndex = this.objects.length;
|
||||
|
||||
for (var i = 0, len = this.objects.length; i < len; i++) {
|
||||
if (this.objects[i].isTerrain) {
|
||||
terrainObjectIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.objects[terrainObjectIndex] = pickedObject;
|
||||
} else {
|
||||
this.objects.push(pickedObject);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -58,6 +58,13 @@ define([
|
||||
* @readonly
|
||||
*/
|
||||
this.projectionLimits = projectionLimits;
|
||||
|
||||
/**
|
||||
* Indicates whether this projection is a 2D projection.
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
this.is2D = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -192,5 +199,30 @@ define([
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the Cartesian surface normal vector at a specified Cartesian point.
|
||||
*
|
||||
* @param {Globe} globe The globe this projection is applied to.
|
||||
* @param {number} x The X component of the Cartesian point.
|
||||
* @param {number} y The Y component of the Cartesian point.
|
||||
* @param {number} z The Z component of the Cartesian point.
|
||||
* @param {Vec3} result A variable in which to return the computed vector.
|
||||
*
|
||||
* @returns{Vec3} The specified result argument containing the computed vector.
|
||||
* @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
|
||||
*/
|
||||
GeographicProjection.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
|
||||
if (!result) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "surfaceNormalAtPoint",
|
||||
"missingResult"));
|
||||
}
|
||||
|
||||
result[0] = 0;
|
||||
result[1] = 0;
|
||||
result[2] = 1;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return GeographicProjection;
|
||||
});
|
||||
292
src/projections/ProjectionWgs84.js
Normal file
292
src/projections/ProjectionWgs84.js
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (C) 2015 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
/**
|
||||
* @exports ProjectionWgs84
|
||||
*/
|
||||
define([
|
||||
'../geom/Angle',
|
||||
'../error/ArgumentError',
|
||||
'../projections/GeographicProjection',
|
||||
'../util/Logger',
|
||||
'../geom/Position',
|
||||
'../geom/Vec3',
|
||||
'../util/WWMath'
|
||||
],
|
||||
function (Angle,
|
||||
ArgumentError,
|
||||
GeographicProjection,
|
||||
Logger,
|
||||
Position,
|
||||
Vec3,
|
||||
WWMath) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs a WGS84 ellipsoid
|
||||
* @alias ProjectionWgs84
|
||||
* @constructor
|
||||
* @augments GeographicProjection
|
||||
* @classdesc Represents a WGS84 ellipsoid.
|
||||
*/
|
||||
var ProjectionWgs84 = function () {
|
||||
|
||||
GeographicProjection.call(this, "WGS84", false, null);
|
||||
|
||||
this.is2D = false;
|
||||
|
||||
this.scratchPosition = new Position(0, 0, 0);
|
||||
};
|
||||
|
||||
ProjectionWgs84.prototype = Object.create(GeographicProjection.prototype);
|
||||
|
||||
Object.defineProperties(ProjectionWgs84.prototype, {
|
||||
/**
|
||||
* A string identifying this projection's current state. Used to compare states during rendering to
|
||||
* determine whether globe-state dependent cached values must be updated. Applications typically do not
|
||||
* interact with this property.
|
||||
* @memberof ProjectionEquirectangular.prototype
|
||||
* @readonly
|
||||
* @type {String}
|
||||
*/
|
||||
stateKey: {
|
||||
get: function () {
|
||||
return "projection wgs84 ";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Documented in base class.
|
||||
ProjectionWgs84.prototype.geographicToCartesian = function (globe, latitude, longitude, altitude, offset,
|
||||
result) {
|
||||
if (!globe) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
|
||||
"geographicToCartesian", "missingGlobe"));
|
||||
}
|
||||
|
||||
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);
|
||||
|
||||
result[0] = (rpm + altitude) * cosLat * sinLon;
|
||||
result[1] = (rpm * (1.0 - globe.eccentricitySquared) + altitude) * sinLat;
|
||||
result[2] = (rpm + altitude) * cosLat * cosLon;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Documented in base class.
|
||||
ProjectionWgs84.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
|
||||
referencePoint, offset, result) {
|
||||
if (!globe) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
|
||||
"geographicToCartesianGrid", "missingGlobe"));
|
||||
}
|
||||
|
||||
var minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
|
||||
maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
|
||||
minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
|
||||
maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
|
||||
deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
|
||||
deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
|
||||
refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
|
||||
latIndex, lonIndex,
|
||||
elevIndex = 0, resultIndex = 0,
|
||||
lat, lon, rpm, elev,
|
||||
cosLat, sinLat,
|
||||
cosLon = new Float64Array(numLon), sinLon = new Float64Array(numLon);
|
||||
|
||||
// Compute and save values that are a function of each unique longitude value in the specified sector. This
|
||||
// eliminates the need to re-compute these values for each column of constant longitude.
|
||||
for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
|
||||
if (lonIndex === numLon - 1) {
|
||||
lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
|
||||
}
|
||||
|
||||
cosLon[lonIndex] = Math.cos(lon);
|
||||
sinLon[lonIndex] = Math.sin(lon);
|
||||
}
|
||||
|
||||
// Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian
|
||||
// point corresponding to each latitude and longitude.
|
||||
for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
|
||||
if (latIndex === numLat - 1) {
|
||||
lat = maxLat; // explicitly set the last lat to the max longitude to ensure alignment
|
||||
}
|
||||
|
||||
// Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
|
||||
cosLat = Math.cos(lat);
|
||||
sinLat = Math.sin(lat);
|
||||
rpm = globe.equatorialRadius / Math.sqrt(1.0 - globe.eccentricitySquared * sinLat * sinLat);
|
||||
|
||||
for (lonIndex = 0; lonIndex < numLon; lonIndex++) {
|
||||
elev = elevations[elevIndex++];
|
||||
result[resultIndex++] = (rpm + elev) * cosLat * sinLon[lonIndex] - refCenter[0];
|
||||
result[resultIndex++] = (rpm * (1.0 - globe.eccentricitySquared) + elev) * sinLat - refCenter[1];
|
||||
result[resultIndex++] = (rpm + elev) * cosLat * cosLon[lonIndex] - refCenter[2];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Documented in base class.
|
||||
ProjectionWgs84.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
|
||||
if (!globe) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
|
||||
"cartesianToGeographic", "missingGlobe"));
|
||||
}
|
||||
|
||||
// According to H. Vermeille, "An analytical method to transform geocentric into geodetic coordinates"
|
||||
// http://www.springerlink.com/content/3t6837t27t351227/fulltext.pdf
|
||||
// Journal of Geodesy, accepted 10/2010, not yet published
|
||||
var X = z,
|
||||
Y = x,
|
||||
Z = y,
|
||||
XXpYY = X * X + Y * Y,
|
||||
sqrtXXpYY = Math.sqrt(XXpYY),
|
||||
a = globe.equatorialRadius,
|
||||
ra2 = 1 / (a * a),
|
||||
e2 = globe.eccentricitySquared,
|
||||
e4 = e2 * e2,
|
||||
p = XXpYY * ra2,
|
||||
q = Z * Z * (1 - e2) * ra2,
|
||||
r = (p + q - e4) / 6,
|
||||
h,
|
||||
phi,
|
||||
u,
|
||||
evoluteBorderTest = 8 * r * r * r + e4 * p * q,
|
||||
rad1,
|
||||
rad2,
|
||||
rad3,
|
||||
atan,
|
||||
v,
|
||||
w,
|
||||
k,
|
||||
D,
|
||||
sqrtDDpZZ,
|
||||
e,
|
||||
lambda,
|
||||
s2;
|
||||
|
||||
if (evoluteBorderTest > 0 || q != 0) {
|
||||
if (evoluteBorderTest > 0) {
|
||||
// Step 2: general case
|
||||
rad1 = Math.sqrt(evoluteBorderTest);
|
||||
rad2 = Math.sqrt(e4 * p * q);
|
||||
|
||||
// 10*e2 is my arbitrary decision of what Vermeille means by "near... the cusps of the evolute".
|
||||
if (evoluteBorderTest > 10 * e2) {
|
||||
rad3 = WWMath.cbrt((rad1 + rad2) * (rad1 + rad2));
|
||||
u = r + 0.5 * rad3 + 2 * r * r / rad3;
|
||||
}
|
||||
else {
|
||||
u = r + 0.5 * WWMath.cbrt((rad1 + rad2) * (rad1 + rad2))
|
||||
+ 0.5 * WWMath.cbrt((rad1 - rad2) * (rad1 - rad2));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Step 3: near evolute
|
||||
rad1 = Math.sqrt(-evoluteBorderTest);
|
||||
rad2 = Math.sqrt(-8 * r * r * r);
|
||||
rad3 = Math.sqrt(e4 * p * q);
|
||||
atan = 2 * Math.atan2(rad3, rad1 + rad2) / 3;
|
||||
|
||||
u = -4 * r * Math.sin(atan) * Math.cos(Math.PI / 6 + atan);
|
||||
}
|
||||
|
||||
v = Math.sqrt(u * u + e4 * q);
|
||||
w = e2 * (u + v - q) / (2 * v);
|
||||
k = (u + v) / (Math.sqrt(w * w + u + v) + w);
|
||||
D = k * sqrtXXpYY / (k + e2);
|
||||
sqrtDDpZZ = Math.sqrt(D * D + Z * Z);
|
||||
|
||||
h = (k + e2 - 1) * sqrtDDpZZ / k;
|
||||
phi = 2 * Math.atan2(Z, sqrtDDpZZ + D);
|
||||
}
|
||||
else {
|
||||
// Step 4: singular disk
|
||||
rad1 = Math.sqrt(1 - e2);
|
||||
rad2 = Math.sqrt(e2 - p);
|
||||
e = Math.sqrt(e2);
|
||||
|
||||
h = -a * rad1 * rad2 / e;
|
||||
phi = rad2 / (e * rad2 + rad1 * Math.sqrt(p));
|
||||
}
|
||||
|
||||
// Compute lambda
|
||||
s2 = Math.sqrt(2);
|
||||
if ((s2 - 1) * Y < sqrtXXpYY + X) {
|
||||
// case 1 - -135deg < lambda < 135deg
|
||||
lambda = 2 * Math.atan2(Y, sqrtXXpYY + X);
|
||||
}
|
||||
else if (sqrtXXpYY + Y < (s2 + 1) * X) {
|
||||
// case 2 - -225deg < lambda < 45deg
|
||||
lambda = -Math.PI * 0.5 + 2 * Math.atan2(X, sqrtXXpYY - Y);
|
||||
}
|
||||
else {
|
||||
// if (sqrtXXpYY-Y<(s2=1)*X) { // is the test, if needed, but it's not
|
||||
// case 3: - -45deg < lambda < 225deg
|
||||
lambda = Math.PI * 0.5 - 2 * Math.atan2(X, sqrtXXpYY + Y);
|
||||
}
|
||||
|
||||
result.latitude = Angle.RADIANS_TO_DEGREES * phi;
|
||||
result.longitude = Angle.RADIANS_TO_DEGREES * lambda;
|
||||
result.altitude = h;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ProjectionWgs84.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
|
||||
// The north-pointing tangent is derived by rotating the vector (0, 1, 0) about the Y-axis by longitude degrees,
|
||||
// then rotating it about the X-axis by -latitude degrees. The latitude angle must be inverted because latitude
|
||||
// is a clockwise rotation about the X-axis, and standard rotation matrices assume counter-clockwise rotation.
|
||||
// The combined rotation can be represented by a combining two rotation matrices Rlat, and Rlon, then
|
||||
// transforming the vector (0, 1, 0) by the combined transform:
|
||||
//
|
||||
// NorthTangent = (Rlon * Rlat) * (0, 1, 0)
|
||||
//
|
||||
// This computation can be simplified and encoded inline by making two observations:
|
||||
// - The vector's X and Z coordinates are always 0, and its Y coordinate is always 1.
|
||||
// - Inverting the latitude rotation angle is equivalent to inverting sinLat. We know this by the
|
||||
// trigonometric identities cos(-x) = cos(x), and sin(-x) = -sin(x).
|
||||
|
||||
var cosLat = Math.cos(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
cosLon = Math.cos(longitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
|
||||
sinLon = Math.sin(longitude * Angle.DEGREES_TO_RADIANS);
|
||||
|
||||
result[0] = -sinLat * sinLon;
|
||||
result[1] = cosLat;
|
||||
result[2] = -sinLat * cosLon;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ProjectionWgs84.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
|
||||
this.cartesianToGeographic(globe, x, y, z, Vec3.ZERO, this.scratchPosition);
|
||||
|
||||
return this.northTangentAtLocation(globe, this.scratchPosition.latitude, this.scratchPosition.longitude, result);
|
||||
};
|
||||
|
||||
ProjectionWgs84.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
|
||||
if (!globe) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionWgs84",
|
||||
"surfaceNormalAtPoint", "missingGlobe"));
|
||||
}
|
||||
|
||||
var eSquared = globe.equatorialRadius * globe.equatorialRadius,
|
||||
polSquared = globe.polarRadius * globe.polarRadius;
|
||||
|
||||
result[0] = x / eSquared;
|
||||
result[1] = y / polSquared;
|
||||
result[2] = z / eSquared;
|
||||
|
||||
return result.normalize();
|
||||
};
|
||||
|
||||
return ProjectionWgs84;
|
||||
});
|
||||
@ -170,6 +170,12 @@ define([
|
||||
*/
|
||||
this.orderedRenderables = [];
|
||||
|
||||
/**
|
||||
* The list of screen renderables.
|
||||
* @type {Array}
|
||||
*/
|
||||
this.screeRenderables = [];
|
||||
|
||||
// Internal. Intentionally not documented. Provides ordinal IDs to ordered renderables.
|
||||
this.orderedRenderablesCounter = 0; // Number
|
||||
|
||||
@ -361,6 +367,7 @@ define([
|
||||
this.surfaceRenderables = []; // clears the surface renderables array
|
||||
this.orderedRenderingMode = false;
|
||||
this.orderedRenderables = []; // clears the ordered renderables array
|
||||
this.screenRenderables = [];
|
||||
this.orderedRenderablesCounter = 0;
|
||||
|
||||
// Advance the per-frame timestamp.
|
||||
@ -535,13 +542,15 @@ define([
|
||||
* Adds an ordered renderable to this draw context's ordered renderable list.
|
||||
* @param {OrderedRenderable} orderedRenderable The ordered renderable to add. May be null, in which case the
|
||||
* current ordered renderable list remains unchanged.
|
||||
* @param {Number} eyeDistance An optional argument indicating the ordered renderable's eye distance.
|
||||
* If this parameter is not specified then the ordered renderable must have an eyeDistance property.
|
||||
*/
|
||||
DrawContext.prototype.addOrderedRenderable = function (orderedRenderable) {
|
||||
DrawContext.prototype.addOrderedRenderable = function (orderedRenderable, eyeDistance) {
|
||||
if (orderedRenderable) {
|
||||
var ore = {
|
||||
orderedRenderable: orderedRenderable,
|
||||
insertionOrder: this.orderedRenderablesCounter++,
|
||||
eyeDistance: orderedRenderable.eyeDistance,
|
||||
eyeDistance: eyeDistance || orderedRenderable.eyeDistance,
|
||||
globeStateKey: this.globeStateKey
|
||||
};
|
||||
|
||||
@ -549,7 +558,11 @@ define([
|
||||
ore.globeOffset = this.globe.offset;
|
||||
}
|
||||
|
||||
this.orderedRenderables.push(ore);
|
||||
if (ore.eyeDistance === 0) {
|
||||
this.screenRenderables.push(ore);
|
||||
} else {
|
||||
this.orderedRenderables.push(ore);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -610,6 +623,27 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the ordered renderable at the head of the ordered renderable list and removes it from the list.
|
||||
* @returns {OrderedRenderable} The first ordered renderable in this draw context's ordered renderable list, or
|
||||
* null if the ordered renderable list is empty.
|
||||
*/
|
||||
DrawContext.prototype.nextScreenRenderable = function () {
|
||||
if (this.screenRenderables.length > 0) {
|
||||
var ore = this.screenRenderables.shift();
|
||||
this.globeStateKey = ore.globeStateKey;
|
||||
|
||||
if (this.globe.continuous) {
|
||||
// Restore the globe state to that when the ordered renderable was created.
|
||||
this.globe.offset = ore.globeOffset;
|
||||
}
|
||||
|
||||
return ore.orderedRenderable;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts the ordered renderable list from nearest to the eye point to farthest from the eye point.
|
||||
*/
|
||||
|
||||
@ -130,5 +130,153 @@ define([
|
||||
return new Texture(gl, canvas2D);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates maximum line height based on a font
|
||||
* @param {Font} font The font to use.
|
||||
* @returns {Vec2} A vector indicating the text's width and height, respectively, in pixels based on the passed font.
|
||||
*/
|
||||
TextSupport.prototype.getMaxLineHeight = function(font)
|
||||
{
|
||||
// Check underscore + capital E with acute accent
|
||||
return this.textSize("_\u00c9", font, 0)[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps the text based on width and height using new linew delimiter
|
||||
* @param {String} text The text to wrap.
|
||||
* @param {Number} width The width in pixels.
|
||||
* @param {Number} height The height in pixels.
|
||||
* @param {Font} font The font to use.
|
||||
* @returns {String} The wrapped text.
|
||||
*/
|
||||
TextSupport.prototype.wrap = function(text, width, height, font)
|
||||
{
|
||||
if (!text) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.WARNING, "TextSupport", "wrap", "missing text"));
|
||||
}
|
||||
|
||||
var i;
|
||||
|
||||
var lines = text.split("\n");
|
||||
var wrappedText = "";
|
||||
|
||||
// Wrap each line
|
||||
for (i = 0; i < lines.length; i++)
|
||||
{
|
||||
lines[i] = this.wrapLine(lines[i], width, font);
|
||||
}
|
||||
// Concatenate all lines in one string with new line separators
|
||||
// between lines - not at the end
|
||||
// Checks for height limit.
|
||||
var currentHeight = 0;
|
||||
var heightExceeded = false;
|
||||
var maxLineHeight = this.getMaxLineHeight(font);
|
||||
for (i = 0; i < lines.length && !heightExceeded; i++)
|
||||
{
|
||||
var subLines = lines[i].split("\n");
|
||||
for (var j = 0; j < subLines.length && !heightExceeded; j++)
|
||||
{
|
||||
if (height <= 0 || currentHeight + maxLineHeight <= height)
|
||||
{
|
||||
wrappedText += subLines[j];
|
||||
currentHeight += maxLineHeight + this.lineSpacing;
|
||||
if (j < subLines.length - 1) {
|
||||
wrappedText += '\n';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
heightExceeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < lines.length - 1 && !heightExceeded) {
|
||||
wrappedText += '\n';
|
||||
}
|
||||
}
|
||||
// Add continuation string if text truncated
|
||||
if (heightExceeded)
|
||||
{
|
||||
if (wrappedText.length > 0) {
|
||||
wrappedText = wrappedText.substring(0, wrappedText.length - 1);
|
||||
}
|
||||
|
||||
wrappedText += "...";
|
||||
}
|
||||
|
||||
return wrappedText;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a line of text based on width and height
|
||||
* @param {String} text The text to wrap.
|
||||
* @param {Number} width The width in pixels.
|
||||
* @param {Font} font The font to use.
|
||||
* @returns {String} The wrapped text.
|
||||
*/
|
||||
TextSupport.prototype.wrapLine = function(text, width, font)
|
||||
{
|
||||
var wrappedText = "";
|
||||
|
||||
// Single line - trim leading and trailing spaces
|
||||
var source = text.trim();
|
||||
var lineBounds = this.textSize(source, font, 0);
|
||||
if (lineBounds[0] > width)
|
||||
{
|
||||
// Split single line to fit preferred width
|
||||
var line = "";
|
||||
var start = 0;
|
||||
var end = source.indexOf(' ', start + 1);
|
||||
while (start < source.length)
|
||||
{
|
||||
if (end == -1) {
|
||||
end = source.length; // last word
|
||||
}
|
||||
|
||||
// Extract a 'word' which is in fact a space and a word
|
||||
var word = source.substring(start, end);
|
||||
var linePlusWord = line + word;
|
||||
if (this.textSize(linePlusWord, font, 0)[0] <= width)
|
||||
{
|
||||
// Keep adding to the current line
|
||||
line += word;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Width exceeded
|
||||
if (line.length != 0)
|
||||
{
|
||||
// Finish current line and start new one
|
||||
wrappedText += line;
|
||||
wrappedText += '\n';
|
||||
line = "";
|
||||
line += word.trim(); // get read of leading space(s)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Line is empty, force at least one word
|
||||
line += word.trim();
|
||||
}
|
||||
}
|
||||
// Move forward in source string
|
||||
start = end;
|
||||
if (start < source.length - 1)
|
||||
{
|
||||
end = source.indexOf(' ', start + 1);
|
||||
}
|
||||
}
|
||||
// Gather last line
|
||||
wrappedText += line;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Line doesn't need to be wrapped
|
||||
wrappedText += source;
|
||||
}
|
||||
|
||||
return wrappedText;
|
||||
};
|
||||
|
||||
return TextSupport;
|
||||
});
|
||||
@ -12,6 +12,7 @@ define([
|
||||
'../geom/BoundingBox',
|
||||
'../util/Color',
|
||||
'../util/ImageSource',
|
||||
'../geom/Line',
|
||||
'../geom/Location',
|
||||
'../util/Logger',
|
||||
'../geom/Matrix',
|
||||
@ -20,6 +21,7 @@ define([
|
||||
'../shapes/ShapeAttributes',
|
||||
'../geom/Vec2',
|
||||
'../geom/Vec3',
|
||||
'../util/WWMath'
|
||||
],
|
||||
function (AbstractShape,
|
||||
ArgumentError,
|
||||
@ -27,6 +29,7 @@ define([
|
||||
BoundingBox,
|
||||
Color,
|
||||
ImageSource,
|
||||
Line,
|
||||
Location,
|
||||
Logger,
|
||||
Matrix,
|
||||
@ -34,7 +37,8 @@ define([
|
||||
Position,
|
||||
ShapeAttributes,
|
||||
Vec2,
|
||||
Vec3) {
|
||||
Vec3,
|
||||
WWMath) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -122,7 +126,7 @@ define([
|
||||
// Set the transformation matrix to correspond to the reference position.
|
||||
var refPt = currentData.referencePoint;
|
||||
dc.surfacePointForMode(this.referencePosition.latitude, this.referencePosition.longitude,
|
||||
this.referencePosition.altitude, this._altitudeMode, refPt);
|
||||
this.referencePosition.altitude * this._altitudeScale, this._altitudeMode, refPt);
|
||||
currentData.transformationMatrix.setToTranslation(refPt[0], refPt[1], refPt[2]);
|
||||
|
||||
// Convert the geographic coordinates to the Cartesian coordinates that will be rendered.
|
||||
@ -468,12 +472,53 @@ define([
|
||||
}
|
||||
|
||||
if (dc.pickingMode) {
|
||||
var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, null,
|
||||
var pickPosition = this.computePickPosition(dc);
|
||||
var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, pickPosition,
|
||||
dc.currentLayer, false);
|
||||
dc.resolvePick(po);
|
||||
}
|
||||
};
|
||||
|
||||
AbstractMesh.prototype.computePickPosition = function (dc) {
|
||||
var currentData = this.currentData,
|
||||
line = dc.navigatorState.rayFromScreenPoint(dc.pickPoint),
|
||||
localLineOrigin = new Vec3(line.origin[0], line.origin[1], line.origin[2]).subtract(
|
||||
currentData.referencePoint),
|
||||
localLine = new Line(localLineOrigin, line.direction),
|
||||
intersectionPoints = [];
|
||||
|
||||
if (WWMath.computeIndexedTrianglesIntersection(localLine, currentData.meshPoints, this.meshIndices,
|
||||
intersectionPoints)) {
|
||||
var iPoint = intersectionPoints[0];
|
||||
|
||||
if (intersectionPoints.length > 1) {
|
||||
// Find the intersection nearest the eye point.
|
||||
var distance2 = iPoint.distanceToSquared(dc.navigatorState.eyePoint);
|
||||
|
||||
for (var i = 1; i < intersectionPoints.length; i++) {
|
||||
var d2 = intersectionPoints[i].distanceToSquared(dc.navigatorState.eyePoint);
|
||||
if (d2 < distance2) {
|
||||
distance2 = d2;
|
||||
iPoint = intersectionPoints[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pos = new Position(0, 0, 0);
|
||||
dc.globe.computePositionFromPoint(
|
||||
iPoint[0] + currentData.referencePoint[0],
|
||||
iPoint[1] + currentData.referencePoint[1],
|
||||
iPoint[2] + currentData.referencePoint[2],
|
||||
pos);
|
||||
|
||||
pos.altitude /= this._altitudeScale;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Overridden from AbstractShape base class.
|
||||
AbstractMesh.prototype.beginDrawing = function (dc) {
|
||||
var gl = dc.currentGlContext;
|
||||
|
||||
@ -250,7 +250,7 @@ define([
|
||||
}
|
||||
|
||||
orderedRenderable.layer = dc.currentLayer;
|
||||
dc.addOrderedRenderable(orderedRenderable);
|
||||
dc.addOrderedRenderable(orderedRenderable, this.currentData.eyeDistance);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
526
src/shapes/Annotation.js
Normal file
526
src/shapes/Annotation.js
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
define([
|
||||
'../shapes/AnnotationAttributes',
|
||||
'../error/ArgumentError',
|
||||
'../shaders/BasicTextureProgram',
|
||||
'../util/Color',
|
||||
'../util/Font',
|
||||
'../util/Insets',
|
||||
'../util/Logger',
|
||||
'../geom/Matrix',
|
||||
'../util/Offset',
|
||||
'../pick/PickedObject',
|
||||
'../render/Renderable',
|
||||
'../shapes/TextAttributes',
|
||||
'../geom/Vec2',
|
||||
'../geom/Vec3',
|
||||
'../util/WWMath'
|
||||
],
|
||||
function (AnnotationAttributes,
|
||||
ArgumentError,
|
||||
BasicTextureProgram,
|
||||
Color,
|
||||
Font,
|
||||
Insets,
|
||||
Logger,
|
||||
Matrix,
|
||||
Offset,
|
||||
PickedObject,
|
||||
Renderable,
|
||||
TextAttributes,
|
||||
Vec2,
|
||||
Vec3,
|
||||
WWMath) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs an annotation.
|
||||
* @alias Annotation
|
||||
* @constructor
|
||||
* @augments Renderable
|
||||
* @classdesc Represents an Annotation shape. An annotation displays a callout, a text and a leader pointing
|
||||
* the annotation's geographic position to the ground.
|
||||
* @param {Position} position The annotations's geographic position.
|
||||
* @param {AnnotationAttributes} attributes The attributes to associate with this annotation.
|
||||
* @throws {ArgumentError} If the specified position is null or undefined.
|
||||
*/
|
||||
var Annotation = function (position, attributes) {
|
||||
|
||||
if (!position) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "Annotation", "constructor", "missingPosition"));
|
||||
}
|
||||
|
||||
Renderable.call(this);
|
||||
|
||||
/**
|
||||
* This annotation's geographic position.
|
||||
* @type {Position}
|
||||
*/
|
||||
this.position = position;
|
||||
|
||||
/**
|
||||
* The annotation's attributes.
|
||||
* @type {AnnotationAttributes}
|
||||
* @default see [AnnotationAttributes]{@link AnnotationAttributes}
|
||||
*/
|
||||
this.attributes = attributes ? attributes : new AnnotationAttributes(null);
|
||||
|
||||
/**
|
||||
* This annotation's altitude mode. May be one of
|
||||
* <ul>
|
||||
* <li>[WorldWind.ABSOLUTE]{@link WorldWind#ABSOLUTE}</li>
|
||||
* <li>[WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND}</li>
|
||||
* <li>[WorldWind.CLAMP_TO_GROUND]{@link WorldWind#CLAMP_TO_GROUND}</li>
|
||||
* </ul>
|
||||
* @default WorldWind.ABSOLUTE
|
||||
*/
|
||||
this.altitudeMode = WorldWind.ABSOLUTE;
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.layer = null;
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.lastStateKey = null;
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.calloutTransform = Matrix.fromIdentity();
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.calloutOffset = new WorldWind.Offset(
|
||||
WorldWind.OFFSET_FRACTION, 0.5,
|
||||
WorldWind.OFFSET_FRACTION, 0);
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.label = "";
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.labelTexture = null;
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.labelTransform = Matrix.fromIdentity();
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.placePoint = new Vec3(0, 0, 0);
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.depthOffset = -2.05;
|
||||
|
||||
// Internal use only. Intentionally not documented.
|
||||
this.calloutPoints = null;
|
||||
};
|
||||
|
||||
Annotation.matrix = Matrix.fromIdentity();
|
||||
Annotation.screenPoint = new Vec3(0, 0, 0);
|
||||
Annotation.scratchPoint = new Vec3(0, 0, 0);
|
||||
|
||||
Annotation.prototype = Object.create(Renderable.prototype);
|
||||
|
||||
Object.defineProperties(Annotation.prototype, {
|
||||
|
||||
/**
|
||||
* The text for this annotation.
|
||||
* @type {String}
|
||||
* @memberof Annotation.prototype
|
||||
*/
|
||||
text: {
|
||||
get: function () {
|
||||
return this.label;
|
||||
},
|
||||
set: function (value) {
|
||||
this.label = value;
|
||||
this.lastStateKey = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Draws this shape as an ordered renderable. Applications do not call this function. It is called by
|
||||
* [WorldWindow]{@link WorldWindow} during rendering.
|
||||
* @param {DrawContext} dc The current draw context.
|
||||
*/
|
||||
Annotation.prototype.renderOrdered = function (dc) {
|
||||
|
||||
this.drawOrderedAnnotation(dc);
|
||||
|
||||
if (dc.pickingMode) {
|
||||
|
||||
var po = new PickedObject(this.pickColor.clone(), this,
|
||||
this.position, this.layer, false);
|
||||
|
||||
if (dc.pickPoint) {
|
||||
if (this.labelBounds.containsPoint(
|
||||
dc.navigatorState.convertPointToViewport(dc.pickPoint, Annotation.scratchPoint))) {
|
||||
po.labelPicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
dc.resolvePick(po);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new annotation that is a copy of this annotation.
|
||||
* @returns {Annotation} The new annotation.
|
||||
*/
|
||||
Annotation.prototype.clone = function () {
|
||||
var clone = new Annotation(this.position);
|
||||
|
||||
clone.copy(this);
|
||||
clone.pickDelegate = this.pickDelegate ? this.pickDelegate : this;
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copies the contents of a specified annotation to this annotation.
|
||||
* @param {Annotation} that The Annotation to copy.
|
||||
*/
|
||||
Annotation.prototype.copy = function (that) {
|
||||
this.position = that.position;
|
||||
this.enabled = that.enabled;
|
||||
this.attributes = that.attributes;
|
||||
this.label = that.label;
|
||||
this.altitudeMode = that.altitudeMode;
|
||||
this.pickDelegate = that.pickDelegate;
|
||||
this.depthOffset = that.depthOffset;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders this annotation. This method is typically not called by applications but is called by
|
||||
* {@link RenderableLayer} during rendering. For this shape this method creates and
|
||||
* enques an ordered renderable with the draw context and does not actually draw the annotation.
|
||||
* @param {DrawContext} dc The current draw context.
|
||||
*/
|
||||
Annotation.prototype.render = function (dc) {
|
||||
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dc.accumulateOrderedRenderables) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dc.globe.projectionLimits
|
||||
&& !dc.globe.projectionLimits.containsLocation(this.position.latitude, this.position.longitude)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var orderedAnnotation;
|
||||
if (this.lastFrameTime !== dc.timestamp) {
|
||||
orderedAnnotation = this.makeOrderedRenderable(dc);
|
||||
} else {
|
||||
var annotationCopy = this.clone();
|
||||
orderedAnnotation = annotationCopy.makeOrderedRenderable(dc);
|
||||
}
|
||||
|
||||
if (!orderedAnnotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
orderedAnnotation.layer = dc.currentLayer;
|
||||
|
||||
this.lastFrameTime = dc.timestamp;
|
||||
dc.addOrderedRenderable(orderedAnnotation);
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.drawOrderedAnnotation = function (dc) {
|
||||
this.beginDrawing(dc);
|
||||
|
||||
try {
|
||||
this.doDrawOrderedAnnotation(dc);
|
||||
} finally {
|
||||
this.endDrawing(dc);
|
||||
}
|
||||
};
|
||||
|
||||
/* Intentionally not documented
|
||||
* Creates an ordered renderable for this shape.
|
||||
* @protected
|
||||
* @param {DrawContext} dc The current draw context.
|
||||
* @returns {OrderedRenderable} The ordered renderable. May be null, in which case an ordered renderable
|
||||
* cannot be created or should not be created at the time this method is called.
|
||||
*/
|
||||
Annotation.prototype.makeOrderedRenderable = function (dc) {
|
||||
|
||||
var w, h, s, iLeft, iRight, iTop, iBottom,
|
||||
offset, leaderGapHeight;
|
||||
|
||||
// Wraps the text based and the width and height that were set for the
|
||||
// annotation
|
||||
this.label = dc.textSupport.wrap(
|
||||
this.label,
|
||||
this.attributes.width,this.attributes.height,
|
||||
this.attributes.textAttributes.font);
|
||||
|
||||
// Compute the annotation's model point.
|
||||
dc.surfacePointForMode(this.position.latitude, this.position.longitude, this.position.altitude,
|
||||
this.altitudeMode, this.placePoint);
|
||||
|
||||
this.eyeDistance = dc.navigatorState.eyePoint.distanceTo(this.placePoint);
|
||||
|
||||
// Compute the annotation's screen point in the OpenGL coordinate system of the WorldWindow
|
||||
// by projecting its model coordinate point onto the viewport. Apply a depth offset in order
|
||||
// to cause the annotation to appear above nearby terrain.
|
||||
if (!dc.navigatorState.projectWithDepth(this.placePoint, this.depthOffset, Annotation.screenPoint)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var labelFont = this.attributes.textAttributes.font;
|
||||
var labelKey = this.label + labelFont.toString();
|
||||
|
||||
this.labelTexture = dc.gpuResourceCache.resourceForKey(labelKey);
|
||||
|
||||
if (!this.labelTexture) {
|
||||
this.labelTexture = dc.textSupport.createTexture(dc, this.label, labelFont, false);
|
||||
dc.gpuResourceCache.putResource(labelKey, this.labelTexture, this.labelTexture.size);
|
||||
}
|
||||
|
||||
w = this.labelTexture.imageWidth;
|
||||
h = this.labelTexture.imageHeight;
|
||||
s = this.attributes.scale;
|
||||
iLeft = this.attributes.insets.left;
|
||||
iRight = this.attributes.insets.right;
|
||||
iTop = this.attributes.insets.top;
|
||||
iBottom = this.attributes.insets.bottom;
|
||||
leaderGapHeight = this.attributes.leaderGapHeight;
|
||||
|
||||
offset = this.calloutOffset.offsetForSize((w + iLeft + iRight) * s, (h + iTop + iBottom) * s);
|
||||
|
||||
this.calloutTransform.setTranslation(
|
||||
Annotation.screenPoint[0] - offset[0],
|
||||
Annotation.screenPoint[1] + leaderGapHeight,
|
||||
Annotation.screenPoint[2]);
|
||||
|
||||
this.labelTransform.setTranslation(
|
||||
Annotation.screenPoint[0] - offset[0] + iLeft * s,
|
||||
Annotation.screenPoint[1] + leaderGapHeight + iBottom * s,
|
||||
Annotation.screenPoint[2]);
|
||||
|
||||
this.labelTransform.setScale(w * s, h * s, 1);
|
||||
|
||||
this.labelBounds = WWMath.boundingRectForUnitQuad(this.labelTransform);
|
||||
|
||||
// Compute dimensions of the callout taking in consideration the insets
|
||||
var width = (w + iLeft + iRight) * s;
|
||||
var height = (h + iTop + iBottom) * s;
|
||||
|
||||
var leaderOffsetX = (width / 2);
|
||||
|
||||
var leaderOffsetY = -leaderGapHeight;
|
||||
|
||||
if (!this.attributes.drawLeader) {
|
||||
leaderOffsetY = 0;
|
||||
}
|
||||
|
||||
if (this.attributes.stateKey != this.lastStateKey){
|
||||
this.calloutPoints = this.createCallout(
|
||||
width, height,
|
||||
leaderOffsetX, leaderOffsetY,
|
||||
this.attributes.leaderGapWidth, this.attributes.cornerRadius);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.beginDrawing = function (dc) {
|
||||
var gl = dc.currentGlContext,
|
||||
program;
|
||||
|
||||
dc.findAndBindProgram(BasicTextureProgram);
|
||||
|
||||
program = dc.currentProgram;
|
||||
|
||||
gl.enableVertexAttribArray(program.vertexPointLocation);
|
||||
gl.enableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
|
||||
program.loadModulateColor(gl, dc.pickingMode);
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.endDrawing = function (dc) {
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram;
|
||||
|
||||
// Clear the vertex attribute state.
|
||||
gl.disableVertexAttribArray(program.vertexPointLocation);
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
|
||||
// Clear GL bindings.
|
||||
dc.bindProgram(null);
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.drawCorner = function (x0, y0, cornerRadius, start, end, steps, buffer, startIdx) {
|
||||
if (cornerRadius < 1) {
|
||||
return startIdx;
|
||||
}
|
||||
|
||||
var step = (end - start) / (steps - 1);
|
||||
for (var i = 1; i < steps - 1; i++) {
|
||||
var a = start + step * i;
|
||||
var x = x0 + Math.cos(a) * cornerRadius;
|
||||
var y = y0 + Math.sin(a) * cornerRadius;
|
||||
buffer[startIdx++] = x;
|
||||
buffer[startIdx++] = y;
|
||||
}
|
||||
|
||||
return startIdx;
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.createCallout = function (width, height, leaderOffsetX, leaderOffsetY, leaderGapWidth,
|
||||
cornerRadius) {
|
||||
|
||||
var cornerSteps = 16;
|
||||
|
||||
var numVertices = 2 * (12 + (cornerRadius < 1 ? 0 : 4 * (cornerSteps - 2)));
|
||||
|
||||
var buffer = new Float32Array(numVertices);
|
||||
|
||||
var idx = 0;
|
||||
|
||||
//Bottom right
|
||||
buffer[idx++] = width / 2 + leaderGapWidth / 2;
|
||||
buffer[idx++] = 0;
|
||||
buffer[idx++] = width - cornerRadius;
|
||||
buffer[idx++] = 0;
|
||||
idx = this.drawCorner(width - cornerRadius, cornerRadius, cornerRadius, -Math.PI / 2, 0,
|
||||
cornerSteps, buffer, idx);
|
||||
|
||||
//Right
|
||||
buffer[idx++] = width;
|
||||
buffer[idx++] = cornerRadius;
|
||||
buffer[idx++] = width;
|
||||
buffer[idx++] = height - cornerRadius;
|
||||
idx = this.drawCorner(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2,
|
||||
cornerSteps, buffer, idx);
|
||||
|
||||
//Top
|
||||
buffer[idx++] = width - cornerRadius;
|
||||
buffer[idx++] = height;
|
||||
buffer[idx++] = cornerRadius;
|
||||
buffer[idx++] = height;
|
||||
idx = this.drawCorner(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI,
|
||||
cornerSteps, buffer, idx);
|
||||
|
||||
//Left
|
||||
buffer[idx++] = 0;
|
||||
buffer[idx++] = height - cornerRadius;
|
||||
buffer[idx++] = 0;
|
||||
buffer[idx++] = cornerRadius;
|
||||
idx = this.drawCorner(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 1.5,
|
||||
cornerSteps, buffer, idx);
|
||||
|
||||
//Bottom left
|
||||
buffer[idx++] = cornerRadius;
|
||||
buffer[idx++] = 0;
|
||||
buffer[idx++] = width / 2 - leaderGapWidth / 2;
|
||||
buffer[idx++] = 0;
|
||||
|
||||
//Draw leader
|
||||
buffer[idx++] = leaderOffsetX;
|
||||
buffer[idx++] = leaderOffsetY;
|
||||
|
||||
buffer[idx++] = width / 2 + leaderGapWidth / 2;
|
||||
buffer[idx] = 0;
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
// Internal. Intentionally not documented.
|
||||
Annotation.prototype.doDrawOrderedAnnotation = function (dc) {
|
||||
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram,
|
||||
textureBound;
|
||||
|
||||
var refreshBuffers = false;
|
||||
|
||||
if (dc.pickingMode) {
|
||||
this.pickColor = dc.uniquePickColor();
|
||||
}
|
||||
|
||||
program.loadOpacity(gl, this.attributes.opacity);
|
||||
|
||||
// Attributes have changed. We need to track this because the callout vbo data may
|
||||
// have changed if scaled or text wrapping changes callout dimensions
|
||||
var calloutAttributesChanged = (this.attributes.stateKey != this.lastStateKey);
|
||||
|
||||
// Create new cache key if callout drawing points have changed
|
||||
if (!this.calloutCacheKey || calloutAttributesChanged) {
|
||||
this.calloutCacheKey = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
var calloutVboId = dc.gpuResourceCache.resourceForKey(this.calloutCacheKey);
|
||||
|
||||
if (!calloutVboId) {
|
||||
calloutVboId = gl.createBuffer();
|
||||
dc.gpuResourceCache.putResource(this.calloutCacheKey, calloutVboId,
|
||||
this.calloutPoints.length * 4);
|
||||
|
||||
refreshBuffers = true;
|
||||
}
|
||||
|
||||
// Remove the last generated vbo data if attributes changed
|
||||
if (calloutAttributesChanged && this.calloutCacheKey){
|
||||
dc.gpuResourceCache.removeResource(this.calloutCacheKey);
|
||||
}
|
||||
|
||||
// Store current statekey because we are no longer using it
|
||||
// in this iteration
|
||||
this.lastStateKey = this.attributes.stateKey;
|
||||
|
||||
// Compute and specify the MVP matrix.
|
||||
Annotation.matrix.copy(dc.screenProjection);
|
||||
Annotation.matrix.multiplyMatrix(this.calloutTransform);
|
||||
program.loadModelviewProjection(gl, Annotation.matrix);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, calloutVboId);
|
||||
|
||||
if (refreshBuffers) {
|
||||
gl.bufferData(gl.ARRAY_BUFFER,
|
||||
this.calloutPoints, gl.STATIC_DRAW);
|
||||
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
}
|
||||
|
||||
program.loadColor(gl, dc.pickingMode ? this.pickColor : this.attributes.backgroundColor);
|
||||
program.loadTextureEnabled(gl, false);
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_FAN, 0, this.calloutPoints.length / 2);
|
||||
|
||||
// Draw text
|
||||
Annotation.matrix.copy(dc.screenProjection);
|
||||
Annotation.matrix.multiplyMatrix(this.labelTransform);
|
||||
program.loadModelviewProjection(gl, Annotation.matrix);
|
||||
|
||||
program.loadColor(gl, dc.pickingMode ? this.pickColor : this.attributes.textAttributes.color);
|
||||
textureBound = this.labelTexture.bind(dc);
|
||||
program.loadTextureEnabled(gl, textureBound);
|
||||
|
||||
// Configure GL to use the draw context's unit quad VBOs for both model coordinates and texture coordinates.
|
||||
// Most browsers can share the same buffer for vertex and texture coordinates, but Internet Explorer requires
|
||||
// that they be in separate buffers, so the code below uses the 3D buffer for vertex coords and the 2D
|
||||
// buffer for texture coords.
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer3());
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, dc.unitQuadBuffer());
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
};
|
||||
|
||||
return Annotation;
|
||||
});
|
||||
273
src/shapes/AnnotationAttributes.js
Normal file
273
src/shapes/AnnotationAttributes.js
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
define([
|
||||
'../util/Color',
|
||||
'../util/Font',
|
||||
'../util/Insets',
|
||||
'../shapes/TextAttributes'
|
||||
],
|
||||
function (Color,
|
||||
Font,
|
||||
Insets,
|
||||
TextAttributes) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs an annotation attributes bundle.
|
||||
* @alias AnnotationAttributes
|
||||
* @constructor
|
||||
* @classdesc Holds attributes applied to {@link Annotation} shapes.
|
||||
* @param {AnnotationAttributes} attributes Attributes to initialize this attributes instance to. May be null,
|
||||
* in which case the new instance contains default attributes.
|
||||
*/
|
||||
var AnnotationAttributes = function (attributes) {
|
||||
|
||||
// These are all documented with their property accessors below.
|
||||
this._cornerRadius = attributes ? attributes._cornerRadius : 0;
|
||||
this._insets = attributes ? attributes._insets : new Insets(0, 0, 0, 0);
|
||||
this._backgroundColor = attributes ? attributes._backgroundColor : Color.WHITE;
|
||||
this._leaderGapWidth = attributes ? attributes._leaderGapWidth : 40;
|
||||
this._leaderGapHeight = attributes ? attributes._leaderGapHeight : 30;
|
||||
this._opacity = attributes ? attributes._opacity : 1;
|
||||
this._scale = attributes ? attributes._scale : 1;
|
||||
this._drawLeader = attributes ? attributes._drawLeader : true;
|
||||
this._width = attributes ? attributes._width : 200;
|
||||
this._height = attributes ? attributes._height : 100;
|
||||
this._textAttributes = attributes ? attributes._textAttributes : new TextAttributes(null);
|
||||
|
||||
/**
|
||||
* Indicates whether this object's state key is invalid. Subclasses must set this value to true when their
|
||||
* attributes change. The state key will be automatically computed the next time it's requested. This flag
|
||||
* will be set to false when that occurs.
|
||||
* @type {Boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.stateKeyInvalid = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the state key for this attributes object. Subclasses that define additional attributes must
|
||||
* override this method, call it from that method, and append the state of their attributes to its
|
||||
* return value.
|
||||
* @returns {String} The state key for this object.
|
||||
* @protected
|
||||
*/
|
||||
AnnotationAttributes.prototype.computeStateKey = function () {
|
||||
return "wi " + this._width
|
||||
+ " he " + this._height
|
||||
+ " cr " + this._cornerRadius
|
||||
+ " in " + this._insets.toString()
|
||||
+ " bg " + this.backgroundColor.toHexString(true)
|
||||
+ " dl " + this.drawLeader
|
||||
+ " lgw " + this.leaderGapWidth
|
||||
+ " lgh " + this.leaderGapHeight
|
||||
+ " op " + this.opacity
|
||||
+ " ta " + this._textAttributes.stateKey
|
||||
+ " sc " + this.scale;
|
||||
};
|
||||
|
||||
Object.defineProperties(AnnotationAttributes.prototype, {
|
||||
|
||||
/**
|
||||
* Indicates the width of the callout.
|
||||
* @type {Number}
|
||||
* @default 200
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
width: {
|
||||
get: function () {
|
||||
return this._width;
|
||||
},
|
||||
set: function (value) {
|
||||
this._width = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates height of the callout.
|
||||
* @type {Number}
|
||||
* @default 100
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
height: {
|
||||
get: function () {
|
||||
return this._height;
|
||||
},
|
||||
set: function (value) {
|
||||
this._height = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the radius for the corners.
|
||||
* @type {Number}
|
||||
* @default 0
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
cornerRadius: {
|
||||
get: function () {
|
||||
return this._cornerRadius;
|
||||
},
|
||||
set: function (value) {
|
||||
this._cornerRadius = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the insets instance of this object.
|
||||
* Insets adjusts top, bottom, left, right padding for the text.
|
||||
* @type {Insets}
|
||||
* @default 0, 0, 0, 0
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
insets: {
|
||||
get: function () {
|
||||
return this._insets;
|
||||
},
|
||||
set: function (value) {
|
||||
this._insets = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the background color of the callout.
|
||||
* @type {Color}
|
||||
* @default White
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
backgroundColor: {
|
||||
get: function () {
|
||||
return this._backgroundColor;
|
||||
},
|
||||
set: function (value) {
|
||||
this._backgroundColor = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the attributes to apply to the annotation's text.
|
||||
* @type {TextAttributes}
|
||||
* @default The defaults of {@link TextAttributes}.
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
textAttributes: {
|
||||
get: function () {
|
||||
return this._textAttributes;
|
||||
},
|
||||
set: function (value) {
|
||||
this._textAttributes = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether to draw a leader pointing to the annotation's geographic position.
|
||||
* @type {Boolean}
|
||||
* @default true
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
drawLeader: {
|
||||
get: function () {
|
||||
return this._drawLeader;
|
||||
},
|
||||
set: function (value) {
|
||||
this._drawLeader = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the gap width of the leader in pixels.
|
||||
* @type {Number}
|
||||
* @default 40
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
leaderGapWidth: {
|
||||
get: function () {
|
||||
return this._leaderGapWidth;
|
||||
},
|
||||
set: function (value) {
|
||||
this._leaderGapWidth = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the gap height of the leader in pixels.
|
||||
* @type {Number}
|
||||
* @default 30
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
leaderGapHeight: {
|
||||
get: function () {
|
||||
return this._leaderGapHeight;
|
||||
},
|
||||
set: function (value) {
|
||||
this._leaderGapHeight = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the opacity of the annotation.
|
||||
* The value ranges from 0 to 1.
|
||||
* Opacity affects both callout and text.
|
||||
* @type {Number}
|
||||
* @default 1
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
opacity: {
|
||||
get: function () {
|
||||
return this._opacity;
|
||||
},
|
||||
set: function (value) {
|
||||
this._opacity = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the scale multiplier.
|
||||
* @type {Number}
|
||||
* @default 1
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
scale: {
|
||||
get: function () {
|
||||
return this._scale;
|
||||
},
|
||||
set: function (value) {
|
||||
this._scale = value;
|
||||
this.stateKeyInvalid = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A string identifying the state of this attributes object. The string encodes the current values of all
|
||||
* this object's properties. It's typically used to validate cached representations of shapes associated
|
||||
* with this attributes object.
|
||||
* @type {String}
|
||||
* @readonly
|
||||
* @memberof AnnotationAttributes.prototype
|
||||
*/
|
||||
stateKey: {
|
||||
get: function () {
|
||||
if (this.stateKeyInvalid) {
|
||||
this._stateKey = this.computeStateKey();
|
||||
this.stateKeyInvalid = false;
|
||||
}
|
||||
return this._stateKey;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return AnnotationAttributes;
|
||||
});
|
||||
@ -242,8 +242,8 @@ define([
|
||||
// Internal. Indicates whether this polygon has side textures defined.
|
||||
Polygon.prototype.hasSideTextures = function () {
|
||||
return this.activeAttributes.imageSource &&
|
||||
Array.isArray(this.activeAttributes.imageSource)
|
||||
&& this.activeAttributes.imageSource.length > 1;
|
||||
Array.isArray(this.activeAttributes.imageSource) &&
|
||||
this.activeAttributes.imageSource.length > 1;
|
||||
};
|
||||
|
||||
// Internal. Determines the side texture for a specified side. See the class description above for the policy.
|
||||
@ -445,6 +445,7 @@ define([
|
||||
normal);
|
||||
}
|
||||
this.polygonTessellator.gluTessNormal(normal[0], normal[1], normal[2]);
|
||||
this.currentData.capNormal = normal;
|
||||
|
||||
// Tessellate the polygon.
|
||||
this.polygonTessellator.gluTessBeginPolygon(triangles);
|
||||
@ -483,244 +484,28 @@ define([
|
||||
|
||||
// Overridden from AbstractShape base class.
|
||||
Polygon.prototype.doRenderOrdered = function (dc) {
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram,
|
||||
currentData = this.currentData,
|
||||
refreshBuffers = currentData.refreshBuffers,
|
||||
hasCapTexture = this.hasCapTexture(),
|
||||
hasSideTextures = this.hasSideTextures(),
|
||||
numBoundaryPoints, vboId, opacity, color, pickColor, stride, nPts, textureBound;
|
||||
var currentData = this.currentData,
|
||||
pickColor;
|
||||
|
||||
if (dc.pickingMode) {
|
||||
pickColor = dc.uniquePickColor();
|
||||
}
|
||||
|
||||
// Assume no cap or side textures.
|
||||
program.loadTextureEnabled(gl, false);
|
||||
|
||||
// Draw the cap if the interior requested and we were able to tessellate the polygon.
|
||||
if (this.activeAttributes.drawInterior && currentData.capTriangles && currentData.capTriangles.length > 0) {
|
||||
this.applyMvpMatrix(dc);
|
||||
|
||||
if (!currentData.capVboCacheKey) {
|
||||
currentData.capVboCacheKey = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
vboId = dc.gpuResourceCache.resourceForKey(currentData.capVboCacheKey);
|
||||
if (!vboId) {
|
||||
vboId = gl.createBuffer();
|
||||
dc.gpuResourceCache.putResource(currentData.capVboCacheKey, vboId,
|
||||
currentData.capTriangles.length * 4);
|
||||
refreshBuffers = true;
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
if (refreshBuffers) {
|
||||
gl.bufferData(gl.ARRAY_BUFFER, currentData.capTriangles,
|
||||
gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
}
|
||||
|
||||
color = this.activeAttributes.interiorColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
|
||||
|
||||
stride = hasCapTexture ? 20 : 12;
|
||||
|
||||
if (hasCapTexture && !dc.pickingMode) {
|
||||
this.activeTexture = dc.gpuResourceCache.resourceForKey(this.capImageSource());
|
||||
if (!this.activeTexture) {
|
||||
this.activeTexture =
|
||||
dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.capImageSource());
|
||||
}
|
||||
|
||||
textureBound = this.activeTexture && this.activeTexture.bind(dc);
|
||||
if (textureBound) {
|
||||
program.loadTextureEnabled(gl, true);
|
||||
gl.enableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT,
|
||||
false, stride, 12);
|
||||
program.loadTextureUnit(gl, gl.TEXTURE0);
|
||||
program.loadModulateColor(gl, dc.pickingMode);
|
||||
}
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 4 * (currentData.capTriangles.length / stride));
|
||||
this.drawCap(dc, pickColor);
|
||||
}
|
||||
|
||||
// Draw the un-textured extruded boundaries and/or the outline.
|
||||
if ((this._extrude && this.activeAttributes.drawInterior) || this.activeAttributes.drawOutline) {
|
||||
if (!currentData.boundaryVboCacheKeys) {
|
||||
this.currentData.boundaryVboCacheKeys = [];
|
||||
}
|
||||
|
||||
program.loadTextureEnabled(gl, false);
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation); // we're not texturing in this clause
|
||||
|
||||
for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary
|
||||
// The sides and outline use the same vertices, those of the individual boundaries.
|
||||
// Set up that data here for common use below.
|
||||
|
||||
numBoundaryPoints = currentData.boundaryPoints[b].length / 3;
|
||||
|
||||
if (!currentData.boundaryVboCacheKeys[b]) {
|
||||
currentData.boundaryVboCacheKeys[b] = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
vboId = dc.gpuResourceCache.resourceForKey(currentData.boundaryVboCacheKeys[b]);
|
||||
if (!vboId) {
|
||||
vboId = gl.createBuffer();
|
||||
dc.gpuResourceCache.putResource(currentData.boundaryVboCacheKeys[b], vboId, numBoundaryPoints * 12);
|
||||
refreshBuffers = true;
|
||||
}
|
||||
|
||||
// Bind and if necessary fill the VBO. We fill the VBO here rather than in doMakeOrderedRenderable
|
||||
// so that there's no possibility of the VBO being ejected from the cache between the time it's
|
||||
// filled and the time it's used.
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
if (refreshBuffers) {
|
||||
gl.bufferData(gl.ARRAY_BUFFER, currentData.boundaryPoints[b],
|
||||
gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
}
|
||||
|
||||
// Draw the extruded boundary.
|
||||
if (this.activeAttributes.drawInterior && this._extrude && (!hasSideTextures || dc.pickingMode)) {
|
||||
this.applyMvpMatrix(dc);
|
||||
|
||||
color = this.activeAttributes.interiorColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the interior is
|
||||
// semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
|
||||
|
||||
// Draw the extruded boundary as one tri-strip.
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, numBoundaryPoints);
|
||||
}
|
||||
|
||||
// Draw the outline for this boundary.
|
||||
if (this.activeAttributes.drawOutline) {
|
||||
// Make the outline stand out from the interior.
|
||||
this.applyMvpMatrixForOutline(dc);
|
||||
program.loadTextureEnabled(gl, false);
|
||||
|
||||
color = this.activeAttributes.outlineColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the interior is
|
||||
// semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? 1 : opacity);
|
||||
|
||||
gl.lineWidth(this.activeAttributes.outlineWidth);
|
||||
|
||||
if (this._extrude) {
|
||||
stride = 24;
|
||||
nPts = numBoundaryPoints / 2;
|
||||
} else {
|
||||
stride = 12;
|
||||
nPts = numBoundaryPoints;
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false,
|
||||
stride, 0);
|
||||
gl.drawArrays(gl.LINE_STRIP, 0, nPts);
|
||||
|
||||
if (this.mustDrawVerticals(dc)) {
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false,
|
||||
0, 0);
|
||||
gl.drawArrays(gl.LINES, 0, numBoundaryPoints - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentData.refreshBuffers = false;
|
||||
|
||||
// If the extruded boundaries are textured, draw them here. This is a separate block because the
|
||||
// operation must create its own vertex VBO in order to include texture coordinates. It can't simply
|
||||
// use the previously computed boundary-points VBO.
|
||||
if (hasSideTextures && this.activeAttributes.drawInterior && this._extrude && !dc.pickingMode) {
|
||||
this.applyMvpMatrix(dc);
|
||||
|
||||
color = this.activeAttributes.interiorColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
|
||||
|
||||
// Create and bind a temporary VBO to hold the boundary vertices and texture coordinates.
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
|
||||
var boundaryVertices = new Float32Array(4 * 5); // 4 vertices of x, y, z, s, t
|
||||
|
||||
for (b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary
|
||||
numBoundaryPoints = currentData.boundaryPoints[b].length / 3;
|
||||
var numSides = (currentData.boundaryPoints[b].length) / 6 - 1,
|
||||
boundaryPoints = currentData.boundaryPoints[b];
|
||||
|
||||
for (var side = 0; side < numSides; side++) {
|
||||
var sideImageSource = this.sideImageSource(side),
|
||||
sideTexture = dc.gpuResourceCache.resourceForKey(sideImageSource);
|
||||
|
||||
if (sideImageSource && !sideTexture) {
|
||||
sideTexture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, sideImageSource);
|
||||
}
|
||||
|
||||
textureBound = sideTexture && sideTexture.bind(dc);
|
||||
if (textureBound) {
|
||||
program.loadTextureEnabled(gl, true);
|
||||
program.loadTextureUnit(gl, gl.TEXTURE0);
|
||||
gl.enableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
} else {
|
||||
program.loadTextureEnabled(gl, false);
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
}
|
||||
|
||||
// Make a 4-vertex tri-strip from consecutive boundary segments.
|
||||
|
||||
boundaryVertices[0] = boundaryPoints[side * 6];
|
||||
boundaryVertices[1] = boundaryPoints[side * 6 + 1];
|
||||
boundaryVertices[2] = boundaryPoints[side * 6 + 2];
|
||||
boundaryVertices[3] = 0; // upper left texture coordinates
|
||||
boundaryVertices[4] = 1;
|
||||
|
||||
boundaryVertices[5] = boundaryPoints[side * 6 + 3];
|
||||
boundaryVertices[6] = boundaryPoints[side * 6 + 4];
|
||||
boundaryVertices[7] = boundaryPoints[side * 6 + 5];
|
||||
boundaryVertices[8] = 0; // lower left texture coordinates
|
||||
boundaryVertices[9] = 0;
|
||||
|
||||
boundaryVertices[10] = boundaryPoints[side * 6 + 6];
|
||||
boundaryVertices[11] = boundaryPoints[side * 6 + 7];
|
||||
boundaryVertices[12] = boundaryPoints[side * 6 + 8];
|
||||
boundaryVertices[13] = 1; // upper right texture coordinates
|
||||
boundaryVertices[14] = 1;
|
||||
|
||||
boundaryVertices[15] = boundaryPoints[side * 6 + 9];
|
||||
boundaryVertices[16] = boundaryPoints[side * 6 + 10];
|
||||
boundaryVertices[17] = boundaryPoints[side * 6 + 11];
|
||||
boundaryVertices[18] = 1; // lower right texture coordinates
|
||||
boundaryVertices[19] = 0;
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, boundaryVertices,
|
||||
gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT,
|
||||
false, 20, 12);
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT,
|
||||
false, 20, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this._extrude && this.activeAttributes.drawInterior) {
|
||||
this.drawSides(dc, pickColor);
|
||||
}
|
||||
|
||||
if (this.activeAttributes.drawOutline) {
|
||||
this.drawOutline(dc, pickColor);
|
||||
}
|
||||
|
||||
currentData.refreshBuffers = false;
|
||||
|
||||
if (dc.pickingMode) {
|
||||
var po = new PickedObject(pickColor, this.pickDelegate ? this.pickDelegate : this, null,
|
||||
dc.currentLayer, false);
|
||||
@ -728,6 +513,370 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
Polygon.prototype.drawCap = function (dc, pickColor) {
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram,
|
||||
currentData = this.currentData,
|
||||
refreshBuffers = currentData.refreshBuffers,
|
||||
hasCapTexture = !!this.hasCapTexture(),
|
||||
applyLighting = this.activeAttributes.applyLighting,
|
||||
numCapVertices = currentData.capTriangles.length / (hasCapTexture ? 5 : 3),
|
||||
vboId, opacity, color, stride, textureBound, capBuffer;
|
||||
|
||||
// Assume no cap texture.
|
||||
program.loadTextureEnabled(gl, false);
|
||||
|
||||
this.applyMvpMatrix(dc);
|
||||
|
||||
if (!currentData.capVboCacheKey) {
|
||||
currentData.capVboCacheKey = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
vboId = dc.gpuResourceCache.resourceForKey(currentData.capVboCacheKey);
|
||||
if (!vboId) {
|
||||
vboId = gl.createBuffer();
|
||||
dc.gpuResourceCache.putResource(currentData.capVboCacheKey, vboId, currentData.capTriangles.length * 4);
|
||||
refreshBuffers = true;
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
if (refreshBuffers) {
|
||||
capBuffer = applyLighting ? this.makeCapBufferWithNormals() : currentData.capTriangles;
|
||||
gl.bufferData(gl.ARRAY_BUFFER, capBuffer, gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
}
|
||||
|
||||
color = this.activeAttributes.interiorColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
|
||||
|
||||
stride = 12 + (hasCapTexture ? 8 : 0) + (applyLighting ? 12 : 0);
|
||||
|
||||
if (hasCapTexture && !dc.pickingMode) {
|
||||
this.activeTexture = dc.gpuResourceCache.resourceForKey(this.capImageSource());
|
||||
if (!this.activeTexture) {
|
||||
this.activeTexture =
|
||||
dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.capImageSource());
|
||||
}
|
||||
|
||||
textureBound = this.activeTexture && this.activeTexture.bind(dc);
|
||||
if (textureBound) {
|
||||
program.loadTextureEnabled(gl, true);
|
||||
gl.enableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, stride, 12);
|
||||
program.loadTextureUnit(gl, gl.TEXTURE0);
|
||||
program.loadModulateColor(gl, dc.pickingMode);
|
||||
}
|
||||
}
|
||||
|
||||
if (applyLighting && !dc.pickingMode) {
|
||||
program.loadApplyLighting(gl, true);
|
||||
gl.enableVertexAttribArray(program.normalVectorLocation);
|
||||
gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, stride, stride - 12);
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, numCapVertices);
|
||||
};
|
||||
|
||||
Polygon.prototype.makeCapBufferWithNormals = function () {
|
||||
var currentData = this.currentData,
|
||||
normal = currentData.capNormal,
|
||||
numFloatsIn = this.hasCapTexture() ? 5 : 3,
|
||||
numFloatsOut = numFloatsIn + 3,
|
||||
numVertices = currentData.capTriangles.length / numFloatsIn,
|
||||
bufferIn = currentData.capTriangles,
|
||||
bufferOut = new Float32Array(numVertices * numFloatsOut),
|
||||
k = 0;
|
||||
|
||||
for (var i = 0; i < numVertices; i++) {
|
||||
for (var j = 0; j < numFloatsIn; j++) {
|
||||
bufferOut[k++] = bufferIn[i * numFloatsIn + j];
|
||||
}
|
||||
|
||||
bufferOut[k++] = normal[0];
|
||||
bufferOut[k++] = normal[1];
|
||||
bufferOut[k++] = normal[2];
|
||||
}
|
||||
|
||||
return bufferOut;
|
||||
};
|
||||
|
||||
Polygon.prototype.drawSides = function (dc, pickColor) {
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram,
|
||||
currentData = this.currentData,
|
||||
refreshBuffers = currentData.refreshBuffers,
|
||||
hasSideTextures = this.hasSideTextures(),
|
||||
applyLighting = this.activeAttributes.applyLighting,
|
||||
numFloatsPerVertex = 3 + (hasSideTextures ? 2 : 0) + (applyLighting ? 3 : 0),
|
||||
numBytesPerVertex = 4 * numFloatsPerVertex,
|
||||
vboId, opacity, color, textureBound, sidesBuffer, numSides;
|
||||
|
||||
numSides = 0;
|
||||
for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
|
||||
numSides += (currentData.boundaryPoints[b].length / 6) - 1; // 6 floats per boundary point: top + bottom
|
||||
}
|
||||
|
||||
if (!currentData.sidesVboCacheKey) {
|
||||
currentData.sidesVboCacheKey = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
vboId = dc.gpuResourceCache.resourceForKey(currentData.sidesVboCacheKey);
|
||||
if (!vboId || refreshBuffers) {
|
||||
sidesBuffer = this.makeSidesBuffer(numSides);
|
||||
currentData.numSideVertices = sidesBuffer.length / numFloatsPerVertex;
|
||||
|
||||
if (!vboId) {
|
||||
vboId = gl.createBuffer();
|
||||
}
|
||||
|
||||
dc.gpuResourceCache.putResource(currentData.sidesVboCacheKey, vboId, sidesBuffer.length * 4);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, sidesBuffer, gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
} else {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
}
|
||||
|
||||
color = this.activeAttributes.interiorColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the interior is semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? (opacity > 0 ? 1 : 0) : opacity);
|
||||
|
||||
if (hasSideTextures && !dc.pickingMode) {
|
||||
this.activeTexture = dc.gpuResourceCache.resourceForKey(this.capImageSource());
|
||||
if (!this.activeTexture) {
|
||||
this.activeTexture =
|
||||
dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, this.capImageSource());
|
||||
}
|
||||
|
||||
if (applyLighting) {
|
||||
program.loadApplyLighting(gl, true);
|
||||
gl.enableVertexAttribArray(program.normalVectorLocation);
|
||||
} else {
|
||||
program.loadApplyLighting(gl, false);
|
||||
}
|
||||
|
||||
// Step through the sides buffer rendering each side independently but from the same buffer.
|
||||
for (var side = 0; side < numSides; side++) {
|
||||
var sideImageSource = this.sideImageSource(side),
|
||||
sideTexture = dc.gpuResourceCache.resourceForKey(sideImageSource),
|
||||
coordByteOffset = side * 6 * numBytesPerVertex; // 6 vertices (2 triangles) per side
|
||||
|
||||
if (sideImageSource && !sideTexture) {
|
||||
sideTexture = dc.gpuResourceCache.retrieveTexture(dc.currentGlContext, sideImageSource);
|
||||
}
|
||||
|
||||
textureBound = sideTexture && sideTexture.bind(dc);
|
||||
if (textureBound) {
|
||||
program.loadTextureEnabled(gl, true);
|
||||
program.loadTextureUnit(gl, gl.TEXTURE0);
|
||||
gl.enableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
gl.vertexAttribPointer(program.vertexTexCoordLocation, 2, gl.FLOAT, false, numBytesPerVertex,
|
||||
coordByteOffset + 12);
|
||||
} else {
|
||||
program.loadTextureEnabled(gl, false);
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
}
|
||||
|
||||
if (applyLighting) {
|
||||
gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, numBytesPerVertex,
|
||||
coordByteOffset + 20);
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, numBytesPerVertex,
|
||||
coordByteOffset);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6); // 6 vertices per side
|
||||
}
|
||||
} else {
|
||||
program.loadTextureEnabled(gl, false);
|
||||
|
||||
if (applyLighting && !dc.pickingMode) {
|
||||
program.loadApplyLighting(gl, true);
|
||||
gl.enableVertexAttribArray(program.normalVectorLocation);
|
||||
gl.vertexAttribPointer(program.normalVectorLocation, 3, gl.FLOAT, false, numBytesPerVertex,
|
||||
numBytesPerVertex - 12);
|
||||
} else {
|
||||
program.loadApplyLighting(gl, false);
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, numBytesPerVertex, 0);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, currentData.numSideVertices);
|
||||
}
|
||||
};
|
||||
|
||||
Polygon.prototype.makeSidesBuffer = function (numSides) {
|
||||
var currentData = this.currentData,
|
||||
hasSideTextures = this.hasSideTextures(),
|
||||
applyLighting = this.activeAttributes.applyLighting,
|
||||
numFloatsPerVertex = 3 + (hasSideTextures ? 2 : 0) + (applyLighting ? 3 : 0),
|
||||
sidesBuffer, sidesBufferIndex, numBufferFloats, v0, v1, v2, v3, t0, t1, t2, t3;
|
||||
|
||||
numBufferFloats = numSides * 2 * 3 * numFloatsPerVertex; // 2 triangles per side, 3 vertices per triangle
|
||||
sidesBuffer = new Float32Array(numBufferFloats);
|
||||
sidesBufferIndex = 0;
|
||||
|
||||
v0 = new Vec3(0, 0, 0);
|
||||
v1 = new Vec3(0, 0, 0);
|
||||
v2 = new Vec3(0, 0, 0);
|
||||
v3 = new Vec3(0, 0, 0);
|
||||
|
||||
if (hasSideTextures) {
|
||||
t0 = new Vec2(0, 1);
|
||||
t1 = new Vec2(0, 0);
|
||||
t2 = new Vec2(1, 1);
|
||||
t3 = new Vec2(1, 0);
|
||||
} else {
|
||||
t0 = t1 = t2 = t3 = null;
|
||||
}
|
||||
|
||||
for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
|
||||
var boundaryPoints = currentData.boundaryPoints[b],
|
||||
sideNormal;
|
||||
|
||||
for (var i = 0; i < boundaryPoints.length - 6; i += 6) {
|
||||
v0[0] = boundaryPoints[i];
|
||||
v0[1] = boundaryPoints[i + 1];
|
||||
v0[2] = boundaryPoints[i + 2];
|
||||
|
||||
v1[0] = boundaryPoints[i + 3];
|
||||
v1[1] = boundaryPoints[i + 4];
|
||||
v1[2] = boundaryPoints[i + 5];
|
||||
|
||||
v2[0] = boundaryPoints[i + 6];
|
||||
v2[1] = boundaryPoints[i + 7];
|
||||
v2[2] = boundaryPoints[i + 8];
|
||||
|
||||
v3[0] = boundaryPoints[i + 9];
|
||||
v3[1] = boundaryPoints[i + 10];
|
||||
v3[2] = boundaryPoints[i + 11];
|
||||
|
||||
sideNormal = applyLighting ? Vec3.computeTriangleNormal(v0, v1, v2) : null;
|
||||
|
||||
// First triangle.
|
||||
this.addVertexToBuffer(v0, t0, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
|
||||
this.addVertexToBuffer(v1, t1, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
|
||||
this.addVertexToBuffer(v2, t2, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
|
||||
// Second triangle.
|
||||
this.addVertexToBuffer(v1, t1, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
|
||||
this.addVertexToBuffer(v3, t3, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
|
||||
this.addVertexToBuffer(v2, t2, sideNormal, sidesBuffer, sidesBufferIndex);
|
||||
sidesBufferIndex += numFloatsPerVertex;
|
||||
}
|
||||
}
|
||||
|
||||
return sidesBuffer;
|
||||
};
|
||||
|
||||
Polygon.prototype.addVertexToBuffer = function (v, texCoord, normal, buffer, bufferIndex) {
|
||||
buffer[bufferIndex++] = v[0];
|
||||
buffer[bufferIndex++] = v[1];
|
||||
buffer[bufferIndex++] = v[2];
|
||||
|
||||
if (texCoord) {
|
||||
buffer[bufferIndex++] = texCoord[0];
|
||||
buffer[bufferIndex++] = texCoord[1];
|
||||
}
|
||||
|
||||
if (normal) {
|
||||
buffer[bufferIndex++] = normal[0];
|
||||
buffer[bufferIndex++] = normal[1];
|
||||
buffer[bufferIndex] = normal[2];
|
||||
}
|
||||
};
|
||||
|
||||
Polygon.prototype.drawOutline = function (dc, pickColor) {
|
||||
var gl = dc.currentGlContext,
|
||||
program = dc.currentProgram,
|
||||
currentData = this.currentData,
|
||||
refreshBuffers = currentData.refreshBuffers,
|
||||
numBoundaryPoints, vboId, opacity, color, stride, nPts, textureBound;
|
||||
|
||||
program.loadTextureEnabled(gl, false);
|
||||
program.loadApplyLighting(gl, false);
|
||||
|
||||
if (this.hasCapTexture()) {
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation); // we're not texturing the outline
|
||||
}
|
||||
|
||||
if (this.activeAttributes.applyLighting) {
|
||||
gl.disableVertexAttribArray(program.normalVectorLocation); // we're not lighting the outline
|
||||
}
|
||||
|
||||
if (!currentData.boundaryVboCacheKeys) {
|
||||
this.currentData.boundaryVboCacheKeys = [];
|
||||
}
|
||||
|
||||
// Make the outline stand out from the interior.
|
||||
this.applyMvpMatrixForOutline(dc);
|
||||
|
||||
program.loadTextureEnabled(gl, false);
|
||||
gl.disableVertexAttribArray(program.vertexTexCoordLocation);
|
||||
|
||||
for (var b = 0; b < currentData.boundaryPoints.length; b++) { // for each boundary}
|
||||
numBoundaryPoints = currentData.boundaryPoints[b].length / 3;
|
||||
|
||||
if (!currentData.boundaryVboCacheKeys[b]) {
|
||||
currentData.boundaryVboCacheKeys[b] = dc.gpuResourceCache.generateCacheKey();
|
||||
}
|
||||
|
||||
vboId = dc.gpuResourceCache.resourceForKey(currentData.boundaryVboCacheKeys[b]);
|
||||
if (!vboId) {
|
||||
vboId = gl.createBuffer();
|
||||
dc.gpuResourceCache.putResource(currentData.boundaryVboCacheKeys[b], vboId, numBoundaryPoints * 12);
|
||||
refreshBuffers = true;
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vboId);
|
||||
if (refreshBuffers) {
|
||||
gl.bufferData(gl.ARRAY_BUFFER, currentData.boundaryPoints[b], gl.STATIC_DRAW);
|
||||
dc.frameStatistics.incrementVboLoadCount(1);
|
||||
}
|
||||
|
||||
color = this.activeAttributes.outlineColor;
|
||||
opacity = color.alpha * dc.currentLayer.opacity;
|
||||
// Disable writing the shape's fragments to the depth buffer when the outline is
|
||||
// semi-transparent.
|
||||
gl.depthMask(opacity >= 1 || dc.pickingMode);
|
||||
program.loadColor(gl, dc.pickingMode ? pickColor : color);
|
||||
program.loadOpacity(gl, dc.pickingMode ? 1 : opacity);
|
||||
|
||||
gl.lineWidth(this.activeAttributes.outlineWidth);
|
||||
|
||||
if (this._extrude) {
|
||||
stride = 24;
|
||||
nPts = numBoundaryPoints / 2;
|
||||
} else {
|
||||
stride = 12;
|
||||
nPts = numBoundaryPoints;
|
||||
}
|
||||
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, stride, 0);
|
||||
gl.drawArrays(gl.LINE_STRIP, 0, nPts);
|
||||
|
||||
if (this.mustDrawVerticals(dc)) {
|
||||
gl.vertexAttribPointer(program.vertexPointLocation, 3, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.LINES, 0, numBoundaryPoints - 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Overridden from AbstractShape base class.
|
||||
Polygon.prototype.beginDrawing = function (dc) {
|
||||
var gl = dc.currentGlContext;
|
||||
@ -738,6 +887,11 @@ define([
|
||||
|
||||
dc.findAndBindProgram(BasicTextureProgram);
|
||||
gl.enableVertexAttribArray(dc.currentProgram.vertexPointLocation);
|
||||
|
||||
var applyLighting = !dc.pickMode && this.activeAttributes.applyLighting;
|
||||
if (applyLighting) {
|
||||
dc.currentProgram.loadModelviewInverse(gl, dc.navigatorState.modelviewNormalTransform);
|
||||
}
|
||||
};
|
||||
|
||||
// Overridden from AbstractShape base class.
|
||||
@ -745,12 +899,11 @@ define([
|
||||
var gl = dc.currentGlContext;
|
||||
|
||||
gl.disableVertexAttribArray(dc.currentProgram.vertexPointLocation);
|
||||
gl.disableVertexAttribArray(dc.currentProgram.normalVectorLocation);
|
||||
gl.depthMask(true);
|
||||
gl.lineWidth(1);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
};
|
||||
|
||||
return Polygon;
|
||||
}
|
||||
)
|
||||
;
|
||||
});
|
||||
@ -562,7 +562,7 @@ define([
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
if (!this.activeAttributes.depthTest) {
|
||||
// Turn depth testing back on.
|
||||
gl.disable(gl.DEPTH_TEST, true);
|
||||
gl.enable(gl.DEPTH_TEST, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -135,7 +135,7 @@ define([
|
||||
}
|
||||
|
||||
this._positions = positions;
|
||||
this.referencePosition = this.determineReferencePosition(this._positions);
|
||||
this.referencePosition = this._positions[0];
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
||||
128
src/util/Insets.js
Normal file
128
src/util/Insets.js
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2014 United States Government as represented by the Administrator of the
|
||||
* National Aeronautics and Space Administration. All Rights Reserved.
|
||||
*/
|
||||
define(['../error/ArgumentError',
|
||||
'../util/Logger'
|
||||
],
|
||||
function (ArgumentError,
|
||||
Logger) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Constructs an Insets object that is a representation of the borders of a container.
|
||||
* It specifies the space that a container must leave at each of its edges.
|
||||
* @alias Insets
|
||||
* @param {Number} top The inset from the top.
|
||||
* @param {Number} left The inset from the left.
|
||||
* @param {Number} bottom The inset from the bottom.
|
||||
* @param {Number} right The inset from the right.
|
||||
* @constructor
|
||||
*/
|
||||
var Insets = function (top, left, bottom, right) {
|
||||
|
||||
if (arguments.length !== 4) {
|
||||
throw new ArgumentError(
|
||||
Logger.logMessage(Logger.LEVEL_SEVERE, "Insets", "constructor", "invalidArgumentCount"));
|
||||
}
|
||||
|
||||
// These are all documented with their property accessors below.
|
||||
this._top = top;
|
||||
this._left = left;
|
||||
this._bottom = bottom;
|
||||
this._right = right;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set top, left, bottom, and right to the specified values.
|
||||
* @param {Number} top The inset from the top.
|
||||
* @param {Number} left The inset from the left.
|
||||
* @param {Number} bottom The inset from the bottom.
|
||||
* @param {Number} right The inset from the right.
|
||||
*/
|
||||
Insets.prototype.set = function (top, left, bottom, right) {
|
||||
this._top = top;
|
||||
this._left = left;
|
||||
this._bottom = bottom;
|
||||
this._right = right;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new copy of this insets with identical property values.
|
||||
* @returns {Insets} A new insets instance with its property values the same as this one's.
|
||||
*/
|
||||
Insets.prototype.clone = function () {
|
||||
return new Insets(this._top, this._left, this._bottom, this._right);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
* @returns {String} A string representation of this object.
|
||||
*/
|
||||
Insets.prototype.toString = function () {
|
||||
return this._top + " " + this._left + " " + this._bottom + " " + this._right;
|
||||
};
|
||||
|
||||
Object.defineProperties(Insets.prototype, {
|
||||
|
||||
/**
|
||||
* Indicates the the inset from the top.
|
||||
* @type {Number}
|
||||
* @memberof Insets.prototype
|
||||
*/
|
||||
top: {
|
||||
get: function () {
|
||||
return this._top;
|
||||
},
|
||||
set: function (value) {
|
||||
this._top = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the the inset from the left.
|
||||
* @type {Number}
|
||||
* @memberof Insets.prototype
|
||||
*/
|
||||
left: {
|
||||
get: function () {
|
||||
return this._left;
|
||||
},
|
||||
set: function (value) {
|
||||
this._left = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the the inset from the bottom.
|
||||
* @type {Number}
|
||||
* @memberof Insets.prototype
|
||||
*/
|
||||
bottom: {
|
||||
get: function () {
|
||||
return this._bottom;
|
||||
},
|
||||
set: function (value) {
|
||||
this._bottom = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates the the inset from the right.
|
||||
* @type {Number}
|
||||
* @memberof Insets.prototype
|
||||
*/
|
||||
right: {
|
||||
get: function () {
|
||||
return this._right;
|
||||
},
|
||||
set: function (value) {
|
||||
this._right = value;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return Insets;
|
||||
}
|
||||
);
|
||||
@ -208,6 +208,58 @@ define([
|
||||
}
|
||||
},
|
||||
|
||||
computeIndexedTrianglesIntersection: function (line, points, indices, results) {
|
||||
if (!line) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
|
||||
"computeIndexedTrianglesIntersection", "missingLine"));
|
||||
}
|
||||
|
||||
if (!points) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
|
||||
"computeIndexedTrianglesIntersection", "missingPoints"));
|
||||
}
|
||||
|
||||
if (!indices) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
|
||||
"computeIndexedTrianglesIntersection", "missingIndices"));
|
||||
}
|
||||
|
||||
if (!results) {
|
||||
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "WWMath",
|
||||
"computeIndexedTrianglesIntersection", "missingResults"));
|
||||
}
|
||||
|
||||
var v0 = new Vec3(0, 0, 0),
|
||||
v1 = new Vec3(0, 0, 0),
|
||||
v2 = new Vec3(0, 0, 0),
|
||||
iPoint = new Vec3(0, 0, 0);
|
||||
|
||||
for (var i = 0, len = indices.length; i < len; i += 3) {
|
||||
var i0 = 3 * indices[i],
|
||||
i1 = 3 * indices[i + 1],
|
||||
i2 = 3 * indices[i + 2];
|
||||
|
||||
v0[0] = points[i0];
|
||||
v0[1] = points[i0 + 1];
|
||||
v0[2] = points[i0 + 2];
|
||||
|
||||
v1[0] = points[i1];
|
||||
v1[1] = points[i1 + 1];
|
||||
v1[2] = points[i1 + 2];
|
||||
|
||||
v2[0] = points[i2];
|
||||
v2[1] = points[i2 + 1];
|
||||
v2[2] = points[i2 + 2];
|
||||
|
||||
if (WWMath.computeTriangleIntersection(line, v0, v1, v2, iPoint)) {
|
||||
results.push(iPoint);
|
||||
iPoint = new Vec3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return results.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Computes the Cartesian intersection points of a specified line with a triangle strip. The triangle strip
|
||||
* is specified by a list of vertex points and a list of indices indicating the triangle strip tessellation
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user