diff --git a/package.json b/package.json
index 2beeb4b8..3ac7bc84 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vts-browser-js",
- "version": "2.15.17",
+ "version": "2.16.0",
"description": "JavaScript WebGL 3D maps rendering engine",
"main": "src/browser/index.js",
"scripts": {
diff --git a/src/browser/browser.css b/src/browser/browser.css
index 4340ad05..df33fbfc 100755
--- a/src/browser/browser.css
+++ b/src/browser/browser.css
@@ -522,6 +522,17 @@
/*background-color: rgba(255, 255, 255, 0.9);*/
}
+.vts-measure-tools div {
+ margin-right: 2px;
+}
+
+.vts-measure-tools {
+ font-family: Verdana, Tahoma, Geneva, Arial, Sans-serif;
+ font-size: 12px;
+ margin-top: 2px;
+ /*background-color: rgba(255, 255, 255, 0.9);*/
+}
+
.vts-measure-tools-button {
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.7);
@@ -531,11 +542,34 @@
cursor: pointer;
}
-.vts-measure-tools-button:hover {
+.vts-measure-tools-button-selected {
+ border-radius: 2px;
+ display: inline-block;
+ padding: 0px 5px;
+ background-color: #fff;
+ cursor: pointer;
color: rgba(0, 102, 255, 1);
border: 1px solid rgba(0, 102, 255, 1);
}
+.vts-measure-compute {
+ position: absolute;
+ font-family: Verdana, Tahoma, Geneva, Arial, Sans-serif;
+ font-size: 12px;
+ top: 0px;
+ left: 0px;
+ display: none;
+ border-radius: 2px;
+ background-color: #aaa;
+ border: solid 1px #000;
+ white-space: nowrap;
+ padding: 3px 1px 3px 3px;
+}
+
+.vts-measure-compute div {
+ margin-right: 2px;
+}
+
/*
* FULLSCREEN
*/
diff --git a/src/browser/browser.js b/src/browser/browser.js
index 6190fcba..4db9c115 100755
--- a/src/browser/browser.js
+++ b/src/browser/browser.js
@@ -108,7 +108,7 @@ Browser.prototype.getControlMode = function() {
Browser.prototype.on = function(name, listener) {
- this.core.on(name, listener);
+ return this.core.on(name, listener);
};
@@ -310,6 +310,7 @@ Browser.prototype.initConfig = function() {
controlSearchUrl : null,
controlSearchFilter : true,
controlMeasure : false,
+ controlMeasureLite : false,
controlLink : false,
controlGithub : false,
controlScale : true,
@@ -391,13 +392,14 @@ Browser.prototype.setConfigParam = function(key, value, ignoreCore) {
case 'controlSearchFilter': this.config.controlSearchFilter = utils.validateBool(value, true); break;
case 'controlSearchElement': this.config.controlSearchElement = value; this.updateUI(key); break;
case 'controlSearchValue': this.config.controlSearchValue = /*utils.validateString(*/value/*, null)*/; this.updateUI(key); break;
- case 'controlLink': this.config.controlLink = utils.validateBool(value, false); this.updateUI(key); break;
- case 'controlGithub': this.config.controlGithub = utils.validateBool(value, false); this.updateUI(key); break;
- case 'controlMeasure': this.config.controlMeasure = utils.validateBool(value, false); this.updateUI(key); break;
- case 'controlLogo': this.config.controlLogo = utils.validateBool(value, false); this.updateUI(key); break;
- case 'controlFullscreen': this.config.controlFullscreen = utils.validateBool(value, true); this.updateUI(key); break;
- case 'controlCredits': this.config.controlCredits = utils.validateBool(value, true); this.updateUI(key); break;
- case 'controlLoading': this.config.controlLoading = utils.validateBool(value, true); this.updateUI(key); break;
+ case 'controlLink': this.config.controlLink = utils.validateBool(value, false); this.updateUI(key); break;
+ case 'controlGithub': this.config.controlGithub = utils.validateBool(value, false); this.updateUI(key); break;
+ case 'controlMeasure': this.config.controlMeasure = utils.validateBool(value, false); this.updateUI(key); break;
+ case 'controlMeasureLite': this.config.controlMeasureLite = utils.validateBool(value, false); this.updateUI(key); break;
+ case 'controlLogo': this.config.controlLogo = utils.validateBool(value, false); this.updateUI(key); break;
+ case 'controlFullscreen': this.config.controlFullscreen = utils.validateBool(value, true); this.updateUI(key); break;
+ case 'controlCredits': this.config.controlCredits = utils.validateBool(value, true); this.updateUI(key); break;
+ case 'controlLoading': this.config.controlLoading = utils.validateBool(value, true); this.updateUI(key); break;
case 'minViewExtent': this.config.minViewExtent = utils.validateNumber(value, 0.01, Number.MAXINTEGER, 100); break;
case 'maxViewExtent': this.config.maxViewExtent = utils.validateNumber(value, 0.01, Number.MAXINTEGER, Number.MAXINTEGER); break;
case 'sensitivity': this.config.sensitivity = utils.validateNumberArray(value, 3, [0,0,0], [10, 10, 10], [1, 0.12, 0.05]); break;
@@ -486,6 +488,7 @@ Browser.prototype.getConfigParam = function(key) {
case 'controlLink': return this.config.controlLink;
case 'controlGithub': return this.config.controlGithub;
case 'controlMeasure': return this.config.controlMeasure;
+ case 'controlMeasureLite': return this.config.controlMeasureLite;
case 'controlLogo': return this.config.controlLogo;
case 'controlFullscreen': return this.config.controlFullscreen;
case 'controlCredits': return this.config.controlCredits;
diff --git a/src/browser/interface.js b/src/browser/interface.js
index cc57a856..8eafc31e 100755
--- a/src/browser/interface.js
+++ b/src/browser/interface.js
@@ -95,8 +95,8 @@ BrowserInterface.prototype.destroyMap = function() {
BrowserInterface.prototype.on = function(eventName, call) {
if (this.killed) return;
- this.core.on(eventName, call);
- return this;
+ return this.core.on(eventName, call);
+ //return this;
};
diff --git a/src/browser/ui/control/loading.js b/src/browser/ui/control/loading.js
index efaab249..9563e662 100755
--- a/src/browser/ui/control/loading.js
+++ b/src/browser/ui/control/loading.js
@@ -39,6 +39,7 @@ UIControlLoading.prototype.show = function() {
this.ui.setControlVisible('link', false);
this.ui.setControlVisible('github', false);
this.ui.setControlVisible('measure', false);
+ this.ui.setControlVisible('measure2', false);
this.ui.setControlVisible('fullscreen', false);
this.ui.setControlVisible('credits', false);
this.ui.setControlVisible('loading', true);
@@ -73,6 +74,7 @@ UIControlLoading.prototype.hide = function() {
this.ui.setControlVisible('link', this.ui.config.controlLink, false);
this.ui.setControlVisible('github', this.ui.config.controlGithub, false);
this.ui.setControlVisible('measure', this.ui.config.controlMeasure, false);
+ this.ui.setControlVisible('measure2', this.ui.config.controlMeasureLite, false);
this.ui.setControlVisible('fullscreen', this.ui.config.controlFullscreen, false);
this.ui.setControlVisible('credits', this.ui.config.controlCredits, false);
this.ui.setControlVisible('loading', false);
diff --git a/src/browser/ui/control/measure-lite.js b/src/browser/ui/control/measure-lite.js
new file mode 100644
index 00000000..a9dfdda8
--- /dev/null
+++ b/src/browser/ui/control/measure-lite.js
@@ -0,0 +1,236 @@
+
+import Dom_ from '../../utility/dom';
+import {UIControlMeasureIcon as UIControlMeasureIcon_, UIControlMeasureIcon2 as UIControlMeasureIcon2_} from './measure';
+
+//get rid of compiler mess
+var dom = Dom_,
+ UIControlMeasureIcon = UIControlMeasureIcon_,
+ UIControlMeasureIcon2 = UIControlMeasureIcon2_;
+
+
+var UIControlMeasureLite = function(ui, visible, visibleLock) {
+ this.ui = ui;
+ this.browser = ui.browser;
+ this.control = this.ui.addControl('measure2',
+ '
'
+
+ + '
'
+
+ + '
'
+
+ + '
'
+ + '
'
+ + '
'
+ + ''
+ + '
'
+ + '
'
+ + '
'
+ + '
'
+
+ + '
'
+ + '
'
+
+ + '
', visible, visibleLock);
+
+ this.div = this.control.getElement('vts-measure');
+
+ this.buttonOff = this.control.getElement('vts-measure-button');
+ this.buttonOff.on('click', this.onSwitch.bind(this));
+ this.buttonOff.on('dblclick', this.onDoNothing.bind(this));
+
+ this.buttonOn = this.control.getElement('vts-measure-button2');
+ this.buttonOn.on('click', this.onSwitch.bind(this));
+ this.buttonOn.on('dblclick', this.onDoNothing.bind(this));
+
+ this.info = this.control.getElement('vts-measure-info');
+
+ var clearButton = this.control.getElement('vts-measure-clear');
+ clearButton.on('click', this.onClear.bind(this));
+ clearButton.on('dblclick', this.onDoNothing.bind(this));
+
+ this.measuring = false;
+ this.counter = 1;
+ this.lastCoords = null;
+
+ this.listPanel = this.control.getElement('vts-measure-text-holder');
+ this.list = this.control.getElement('vts-measure-text-input');
+
+ if (this.measuring) {
+ this.buttonOn.setStyle('display', 'block');
+ this.buttonOff.setStyle('display', 'none');
+ } else {
+ this.buttonOn.setStyle('display', 'none');
+ this.buttonOff.setStyle('display', 'block');
+ }
+
+ this.onMouseMoveCall = this.onMouseMove.bind(this);
+ this.onMouseLeaveCall = this.onMouseLeave.bind(this);
+ this.onMouseClickCall = this.onMouseClick.bind(this);
+
+ this.update();
+};
+
+
+UIControlMeasureLite.prototype.onDoNothing = function(event) {
+ dom.stopPropagation(event);
+};
+
+UIControlMeasureLite.prototype.onMouseLeave = function(event) {
+ this.info.setStyle('display', 'none');
+};
+
+
+UIControlMeasureLite.prototype.onMouseClick = function(event) {
+ var map = this.browser.getMap();
+ if (!map) {
+ return;
+ }
+
+ var mapElement = this.ui.getMapElement();
+ var state = mapElement.getDraggingState();
+
+ //if (state['dragging']) { //TODO: why does not work this parameter? Fix it once you have time
+ // return;
+ //}
+ var delta = state['absMoved'];
+
+ if ((delta[0]+delta[1]) > 0) {
+ return;
+ }
+
+ var coords = event.getMouseCoords();
+ var clickCoords = map.getHitCoords(coords[0], coords[1], 'fix');
+
+ if (!clickCoords) {
+ return;
+ }
+
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+
+ var str = '#' + this.counter + ' ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + clickCoords[2].toFixed(2) + 'm';
+
+ if (this.lastCoords) {
+ var res = map.getDistance(this.lastCoords, clickCoords, false, true);
+ var space = '\n ';
+
+ for (var i = 0, li = ('' + this.counter).length; i < li; i++) {
+ space += ' ';
+ }
+
+ str += space + 'great-circle distance: ';
+
+ if (res[0] > 100000) {
+ str += '' + (res[0]*0.001).toFixed(2) + 'km';
+ } else {
+ str += '' + res[0].toFixed(2) + 'm';
+ }
+
+ str += space + 'elevation difference: ' + (clickCoords[2] - this.lastCoords[2]).toFixed(2) + 'm';
+ str += space + 'euclidean distance: ';
+
+ if (res[2] > 100000) {
+ str += '' + (res[2]*0.001).toFixed(2) + 'km';
+ } else {
+ str += '' + res[2].toFixed(2) + 'm';
+ }
+ }
+
+ this.counter++;
+ this.lastCoords = clickCoords;
+
+ var listElement = this.list.getElement();
+ listElement.value += str + '\n';
+ listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+};
+
+UIControlMeasureLite.prototype.onMouseMove = function(event) {
+ var map = this.browser.getMap();
+ if (!map) {
+ return;
+ }
+
+ var coords = event.getMouseCoords();
+ var clickCoords = map.getHitCoords(coords[0], coords[1], 'fix');
+
+ if (!clickCoords) {
+ this.info.setStyle('display', 'none');
+ return;
+ }
+
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+
+ var str = clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + clickCoords[2].toFixed(2) + 'm';
+
+ coords[0] -= this.divRect.left;
+ coords[1] -= this.divRect.top;
+
+ this.info.setStyle('display', 'block');
+ this.info.setStyle('left', (coords[0]+20)+'px');
+ this.info.setStyle('top', (coords[1]+10)+'px');
+ this.info.setHtml(str);
+};
+
+UIControlMeasureLite.prototype.onSwitch = function() {
+ this.measuring = !this.measuring;
+
+ var mapElement = this.ui.getMapElement();
+
+ if (this.measuring) {
+ this.buttonOn.setStyle('display', 'block');
+ this.buttonOff.setStyle('display', 'none');
+
+ this.divRect = this.div.getRect();
+
+ mapElement.on('mousemove', this.onMouseMoveCall);
+ mapElement.on('mouseleave', this.onMouseLeaveCall);
+ mapElement.on('click', this.onMouseClickCall);
+
+ } else {
+ this.buttonOn.setStyle('display', 'none');
+ this.buttonOff.setStyle('display', 'block');
+
+ mapElement.off('mousemove', this.onMouseMoveCall);
+ mapElement.off('mouseleave', this.onMouseLeaveCall);
+ mapElement.off('click', this.onMouseClickCall);
+ }
+
+ this.updateLink();
+ this.update();
+};
+
+UIControlMeasureLite.prototype.onClear = function() {
+ this.counter = 1;
+ this.lastCoords = null;
+
+ var listElement = this.list.getElement();
+ listElement.value = '';
+ listElement.scrollTop = 0;
+};
+
+UIControlMeasureLite.prototype.update = function() {
+ //var button = this.control.getElement('vts-measure-button');
+
+ var left = 10 + (this.ui.config.controlZoom ? 70 : 0) +
+ (this.ui.config.controlSpace ? 35 : 0);
+
+ this.div.setStyle('left', left + 'px');
+ this.listPanel.setStyle('display', this.measuring ? 'block' : 'none');
+};
+
+
+UIControlMeasureLite.prototype.updateLink = function() {
+ /*
+ var linkValue = this.browser.getLinkWithCurrentPos();
+ if (this.list.getElement().value != linkValue) {
+ this.list.getElement().value = linkValue;
+ }*/
+};
+
+
+export default UIControlMeasureLite;
diff --git a/src/browser/ui/control/measure.js b/src/browser/ui/control/measure.js
index aee2306b..102c8130 100644
--- a/src/browser/ui/control/measure.js
+++ b/src/browser/ui/control/measure.js
@@ -1,8 +1,14 @@
import Dom_ from '../../utility/dom';
+import {utils as utils_} from '../../../core/utils/utils';
+import {vec3 as vec3_} from '../../../core/utils/matrix';
//get rid of compiler mess
-var dom = Dom_;
+var dom = Dom_,
+ utils = utils_,
+ vec3 = vec3_,
+ UIControlMeasureIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2lpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpkNjI1MjFjMi1mYzE5LTcyNDUtOTI5My1kNTU3MmE5N2E1MjgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODJGRUI2NzE2NzkwMTFFN0EzRUZFNzQ1NEFCMkVFQUQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODJGRUI2NzA2NzkwMTFFN0EzRUZFNzQ1NEFCMkVFQUQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRBMjkwN0JENjc4QzExRTc5QTQwRjk4NjQzOEI4RDczIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRBMjkwN0JFNjc4QzExRTc5QTQwRjk4NjQzOEI4RDczIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k3ySjQAAAdJJREFUeNrclk0oBGEYgHemjZOLm8tSyt9BqT2I1O7JSdSSkpsDxZGLi5MTjn5K4SDh4OfiaPfkIDebOPiPUqKUiLKet96pzzT7MztxMPX0le+b95n3Z2ZZmUwm9NuXHfqLK1smsVhsED5hKkhswc4mYJmHVxgNIvIslyE4hnLYDSqycwiaoCGVSnUFFdk5BH2wKHtBRbYKqlVwbQhGoNU5GETkZHKpggjMqaBFhaGgIltv/mKphRuQss1oZkPuG4oRWTLHlmU5fSlhOYNKSEK7xIUugj+6hmSHpROm2RvL9Q7arqf80IykdDEdgll4IWi/Bm+ECT8Z2R7l+DBKV6N/PoRnEbBuwIKf0nm+8S7RGuzDLaxLMuw/5OuR+bn60ROPt9/pUUQnUEa6A56SyeSWeTYejzs9GkC8ZIpySjxEy1APbdCM6MAlGmeZdIahYIlLVAU9kIA7KZFxrJrA5+bU8RBjBf+eGD26gk0oI4AItiU4dMOqHh+GTylbQT3JU7oj2IMLmNBJrIATKIUoD5L2LfEQJbRPvXAP0iPZj5J92vNl9Fk6mSCZsBV4dwuyflb8XJrRqX6C3iDsFhSdiXOzZlSnwxD2yuBHJv/iX6JvAQYAPSICqA82OnoAAAAASUVORK5CYII=',
+ UIControlMeasureIcon2 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2Nzg3NzczMzY3OEMxMUU3QjU2QUUxNTNCNzc4MzVBQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2Nzg3NzczNDY3OEMxMUU3QjU2QUUxNTNCNzc4MzVBQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY3ODc3NzMxNjc4QzExRTdCNTZBRTE1M0I3NzgzNUFCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY3ODc3NzMyNjc4QzExRTdCNTZBRTE1M0I3NzgzNUFCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+owD8TQAAAepJREFUeNrcls8rBGEYx993lIMfxR4kF/+AKMqBg7K1ajelLEWK025tKOTnjVykuAklNgcHuzmQg4MiTtz4Aza3TeGActDr+45nZt41s7tmBwdbn2l35933M9/3eead5UII9tsvjf3FK2uSiGgBKdDtZW6JllXA2DG4A3teRM7LZQl2QRuY9irScghGQRXb5KteRVoOQSMY0895FGkk8JHgSBH0gllzpAeRkeSRBAEwQIIZUJMxukARly3GOZdpivA5DnpAM2iQ5zFx3KE55DIugT6cT+a7PTTlKt9xHAT74AzU6vWJiEVQ4iWR9uXHhkgu3QhYB/JK3zBZE6WoBkE3Ivt9YolOQBd9uwJedQFjC+A8XyJ1J3G+4zNFV+CCmmMejINnY8sQG0wVzZnFlnW2Fd55ezGaIUAtPgzqwAsmv8mYMCLW8DaqdyY1g73w+RMFQRh0glse1RtDHRvDcdJp6XInsScKU3u3gnukSfCoOcoH0YPa3jif/P7zxEqUoPqUkWBZ398YqwdTNHqLdu+YuyTONToEl+AaTIAhUE61qwTtuJC0e4klkssQAn7QTy3+BA5AhRQgfdoovHvJp6iYli5EdTqlBKZA7a7CJFaiHdABUqBUFfyMxBJt06PBrwpskn/xl+hDgAEAH0j0b9rsgVUAAAAASUVORK5CYII=';
var UIControlMeasure = function(ui, visible, visibleLock) {
@@ -13,25 +19,36 @@ var UIControlMeasure = function(ui, visible, visibleLock) {
+ ''
+ + ' src="' + UIControlMeasureIcon + '">'
+ ''
+ + ' src="' + UIControlMeasureIcon2 + '">'
+ ''
+ '
'
+ '
'
- + ''
+ + ''
+ '
'
+ '
'
+ '
'
+ '
'
+ ''
+ '
'
+
+ + ''
+ ' ', visible, visibleLock);
@@ -46,14 +63,55 @@ var UIControlMeasure = function(ui, visible, visibleLock) {
this.buttonOn.on('dblclick', this.onDoNothing.bind(this));
this.info = this.control.getElement('vts-measure-info');
+ this.compute = this.control.getElement('vts-measure-buttons');
+
+ var computeButton = this.control.getElement('vts-measure-undo');
+ computeButton.on('click', this.onCompute.bind(this, 0));
+ computeButton.on('dblclick', this.onDoNothing.bind(this));
+ computeButton = this.control.getElement('vts-measure-compute');
+ computeButton.on('click', this.onCompute.bind(this, 1));
+ computeButton.on('dblclick', this.onDoNothing.bind(this));
var clearButton = this.control.getElement('vts-measure-clear');
clearButton.on('click', this.onClear.bind(this));
clearButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons = [];
+ var toolButton = this.control.getElement('vts-measure-position');
+ toolButton.on('click', this.onTool.bind(this, 0));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ toolButton = this.control.getElement('vts-measure-length');
+ toolButton.on('click', this.onTool.bind(this, 1));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ toolButton = this.control.getElement('vts-measure-track');
+ toolButton.on('click', this.onTool.bind(this, 2));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ toolButton = this.control.getElement('vts-measure-area');
+ toolButton.on('click', this.onTool.bind(this, 3));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ toolButton = this.control.getElement('vts-measure-volume');
+ toolButton.on('click', this.onTool.bind(this, 4));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ toolButton = this.control.getElement('vts-measure-metric');
+ toolButton.on('click', this.onTool.bind(this, 5));
+ toolButton.on('dblclick', this.onDoNothing.bind(this));
+ this.toolButtons.push(toolButton);
+ this.metricButton = toolButton;
+
+
this.measuring = false;
this.counter = 1;
+ this.renderCounter = 1;
this.lastCoords = null;
+ this.navCoords = null;
+ this.tool = 0;
+ this.metric = true;
+ this.mapUpdateDestructor = null;
this.listPanel = this.control.getElement('vts-measure-text-holder');
this.list = this.control.getElement('vts-measure-text-input');
@@ -69,6 +127,7 @@ var UIControlMeasure = function(ui, visible, visibleLock) {
this.onMouseMoveCall = this.onMouseMove.bind(this);
this.onMouseLeaveCall = this.onMouseLeave.bind(this);
this.onMouseClickCall = this.onMouseClick.bind(this);
+ this.onMapUpdateCall = this.onMapUpdate.bind(this);
this.update();
};
@@ -78,6 +137,7 @@ UIControlMeasure.prototype.onDoNothing = function(event) {
dom.stopPropagation(event);
};
+
UIControlMeasure.prototype.onMouseLeave = function(event) {
this.info.setStyle('display', 'none');
};
@@ -104,48 +164,99 @@ UIControlMeasure.prototype.onMouseClick = function(event) {
var coords = event.getMouseCoords();
var clickCoords = map.getHitCoords(coords[0], coords[1], 'fix');
+ map.redraw();
+
if (!clickCoords) {
return;
}
- clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+ var i, li, res, str;
+ var space = ' ';
- var str = '#' + this.counter + ' ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + clickCoords[2].toFixed(2) + 'm';
+ for (i = 0, li = ('' + this.counter).length; i < li; i++) {
+ space += ' ';
+ }
- if (this.lastCoords) {
- var res = map.getDistance(this.lastCoords, clickCoords, false, true);
- var space = '\n ';
+ if (this.tool == 0) {
+ this.navCoords = clickCoords;
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
- for (var i = 0, li = ('' + this.counter).length; i < li; i++) {
- space += ' ';
- }
+ str = '------------------------------------------------------\n';
+ str += '#' + this.counter + ' Position: ';
+ str += '\n' + space + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
+ this.counter++;
- str += space + 'great-circle distance: ';
+ } else if (this.tool == 1) {
+ if (!this.navCoords || this.navCoords.length == 2) {
+ this.navCoords = [clickCoords];
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
- if (res[0] > 100000) {
- str += '' + (res[0]*0.001).toFixed(2) + 'km';
+ str = '------------------------------------------------------\n';
+ str += '#' + this.counter + ' Length: ';
+ str += '\n' + space + 'p1: ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
} else {
- str += '' + res[0].toFixed(2) + 'm';
+ this.navCoords.push(clickCoords);
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+
+ str = space + 'p2: ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
+ str += '\n' + space + '------------------------';
+
+ res = map.getDistance(this.lastCoords, clickCoords, false, true);
+ str += '\n' + space + 'great-circle distance: ' + this.getTextNumber(res[0]);
+ str += '\n' + space + 'euclidean distance: ' + this.getTextNumber(res[2]);
+ str += '\n' + space + 'elevation difference: ' + this.getTextNumber(clickCoords[2] - this.lastCoords[2]);
+ str += '\n' + space + 'azimuth: ' + res[1].toFixed(2) + ' deg';
+
+ this.counter++;
+ }
+ } else if (this.tool == 2) {
+ if (this.renderCounter != this.counter) {
+ this.renderCounter = this.counter;
+ this.onTool(2);
}
- str += space + 'elevation difference: ' + (clickCoords[2] - this.lastCoords[2]).toFixed(2) + 'm';
- str += space + 'euclidean distance: ';
+ if (!this.navCoords) {
+ this.navCoords = [clickCoords];
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+ str = '------------------------------------------------------\n';
+ str += '#' + this.counter + ' Track Length: ';
+ str += '\n' + space + 'p1: ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
+ } else {
+ this.navCoords.push(clickCoords);
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
- if (res[2] > 100000) {
- str += '' + (res[2]*0.001).toFixed(2) + 'km';
+ str = space + 'p' + this.navCoords.length + ': ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
+ }
+ } else if (this.tool == 3 || this.tool == 4) {
+ if (this.renderCounter != this.counter) {
+ this.renderCounter = this.counter;
+ this.onTool(this.tool);
+ }
+
+ if (!this.navCoords) {
+ this.navCoords = [clickCoords];
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+ str = '------------------------------------------------------\n';
+ str += '#' + this.counter + ((this.tool == 3) ? ' Area: ' : ' Volume: ');
+ str += '\n' + space + 'p1: ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
} else {
- str += '' + res[2].toFixed(2) + 'm';
+ this.navCoords.push(clickCoords);
+ clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
+
+ str = space + 'p' + this.navCoords.length + ': ' + clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
}
}
- this.counter++;
this.lastCoords = clickCoords;
- var listElement = this.list.getElement();
- listElement.value += str + '\n';
- listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+ if (str) {
+ var listElement = this.list.getElement();
+ listElement.value += str + '\n';
+ listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+ }
};
+
UIControlMeasure.prototype.onMouseMove = function(event) {
var map = this.browser.getMap();
if (!map) {
@@ -162,7 +273,7 @@ UIControlMeasure.prototype.onMouseMove = function(event) {
clickCoords = map.convertCoordsFromNavToPublic(clickCoords, 'fix');
- var str = clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + clickCoords[2].toFixed(2) + 'm';
+ var str = clickCoords[0].toFixed(7) + ', ' + clickCoords[1].toFixed(7) + ', ' + this.getTextNumber(clickCoords[2]);
coords[0] -= this.divRect.left;
coords[1] -= this.divRect.top;
@@ -173,6 +284,7 @@ UIControlMeasure.prototype.onMouseMove = function(event) {
this.info.setHtml(str);
};
+
UIControlMeasure.prototype.onSwitch = function() {
this.measuring = !this.measuring;
@@ -187,6 +299,7 @@ UIControlMeasure.prototype.onSwitch = function() {
mapElement.on('mousemove', this.onMouseMoveCall);
mapElement.on('mouseleave', this.onMouseLeaveCall);
mapElement.on('click', this.onMouseClickCall);
+ this.mapUpdateDestructor = this.browser.on('map-update', this.onMapUpdateCall);
} else {
this.buttonOn.setStyle('display', 'none');
@@ -195,21 +308,423 @@ UIControlMeasure.prototype.onSwitch = function() {
mapElement.off('mousemove', this.onMouseMoveCall);
mapElement.off('mouseleave', this.onMouseLeaveCall);
mapElement.off('click', this.onMouseClickCall);
+
+ if (this.mapUpdateDestructor) {
+ this.mapUpdateDestructor();
+ }
+ }
+
+ this.onTool(this.tool);
+
+ this.compute.setStyle('display', 'none');
+
+ var map = this.browser.getMap();
+ if (map) {
+ map.redraw();
}
- this.updateLink();
this.update();
};
+
+UIControlMeasure.prototype.onTool = function(tool) {
+ if (tool == 5) {
+ this.metric = !this.metric;
+ this.metricButton.setHtml(this.metric ? 'Units: Meters' : 'Units: Feets');
+ return;
+ }
+
+ this.tool = tool;
+ this.navCoords = null;
+
+ this.compute.setStyle('display', 'none');
+
+ for (var i = 0; i < 5; i++) {
+ this.toolButtons[i].setClass('vts-measure-tools-button');
+ }
+
+ this.toolButtons[tool].setClass('vts-measure-tools-button-selected');
+
+ var map = this.browser.getMap();
+ if (map) {
+ map.redraw();
+ }
+};
+
+
+UIControlMeasure.prototype.onCompute = function(button) {
+ if (!this.navCoords) {
+ return;
+ }
+
+ var map = this.browser.getMap();
+ if (!map) {
+ return;
+ }
+
+ var str, i, li, space;
+ var listElement = this.list.getElement();
+
+ if (button == 0) { //undo button
+ this.navCoords.pop();
+ } else { //compute button
+ if (this.tool == 2) {
+
+ var distance = 0;
+ var distance2 = 0, coords, coords2, res;
+ var emin = Number.POSITIVE_INFINITY;
+ var emax = Number.NEGATIVE_INFINITY;
+
+ space = ' ';
+
+ for (i = 0, li = ('' + this.counter).length; i < li; i++) {
+ space += ' ';
+ }
+
+ str = space + '------------------------';
+
+ for (i = 0, li = this.navCoords.length; i < li; i++) {
+ coords = map.convertCoordsFromNavToPublic(this.navCoords[i], 'fix');
+
+ if (coords[2] > emax) {
+ emax = coords[2];
+ }
+
+ if (coords[2] < emin) {
+ emin = coords[2];
+ }
+ }
+
+ for (i = 0, li = this.navCoords.length - 1; i < li; i++) {
+ coords = map.convertCoordsFromNavToPublic(this.navCoords[i], 'fix');
+ coords2 = map.convertCoordsFromNavToPublic(this.navCoords[i+1], 'fix');
+ res = map.getDistance(coords, coords2, false, true);
+ distance += res[0];
+ distance2 += res[2];
+ }
+
+ str += '\n' + space + 'great-circle distance: ' + this.getTextNumber(distance);
+ str += '\n' + space + 'euclidean distance: ' + this.getTextNumber(distance2);
+ str += '\n' + space + 'max elevation: ' + this.getTextNumber(emax);
+ str += '\n' + space + 'min elevation: ' + this.getTextNumber(emin);
+ str += '\n' + space + 'elevation difference: ' + this.getTextNumber(emax - emin);
+
+ this.counter++;
+ }
+
+ if (this.tool == 3) {
+ var geodata = map.createGeodata();
+ geodata.addPolygon3(this.navCoords, [], null, 'fix', {}, 'tmp-polygon');
+ geodata.processHeights('node-by-lod', 62, (function(){
+
+ space = ' ';
+
+ for (i = 0, li = ('' + this.counter).length; i < li; i++) {
+ space += ' ';
+ }
+
+ str = space + '------------------------';
+
+ var poly = geodata.extractGeometry('tmp-polygon');
+
+ var area = poly.getSurfaceArea()
+
+ if (this.metric) {
+ str += '\n' + space + 'area: ' + area.toFixed(2) + ' m\u00B2';
+
+ if (area > 100) {
+ str += '\n' + space + ' ' + (area/100).toFixed(2) + ' ares';
+ }
+
+ if (area > 10000) {
+ str += '\n' + space + ' ' + (area/10000).toFixed(2) + ' hectares';
+ }
+
+ if (area > 1000000) {
+ str += '\n' + space + ' ' + (area/1000000).toFixed(2) + ' km\u00B2';
+ }
+ } else {
+ //str += '\n' + space + 'area: ' + (area / 0.09290304).toFixed(2) + ' ft²';
+ str += '\n' + space + 'area: ' + (area / 0.83612736).toFixed(2) + ' yd\u00B2';
+
+ if ((area / 4046.8564224) >= 1) {
+ str += '\n' + space + ' ' + (area / 4046.8564224).toFixed(2) + ' acres';
+ }
+
+ if ((area / 2589988.110346) >= 1) {
+ str += '\n' + space + ' ' + (area / 2589988.110346).toFixed(2) + ' mi\u00B2';
+ }
+ }
+
+ this.counter++;
+
+ }).bind(this));
+ }
+
+ if (this.tool == 4) {
+
+ var center = [0,0,0];
+
+ for (i = 0, li = this.navCoords.length; i < li; i++) {
+ coords = map.convertCoordsFromNavToPhys(this.navCoords[i], 'fix');
+ center[0] += coords[0];
+ center[1] += coords[1];
+ center[2] += coords[2];
+ }
+
+ center[0] /= li;
+ center[1] /= li;
+ center[2] /= li;
+
+ var radius = 0, dx, dy, dz, distance;
+
+ for (i = 0, li = this.navCoords.length; i < li; i++) {
+ coords = map.convertCoordsFromNavToPhys(this.navCoords[i], 'fix');
+ dx = (center[0] - coords[0]);
+ dy = (center[1] - coords[1]);
+ dz = (center[2] - coords[2]);
+ distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
+
+ if (distance > radius) {
+ radius = distance;
+ }
+ }
+
+ var geodata = map.createGeodata();
+
+ if (radius > 30000) {
+ geodata.addPolygon3(this.navCoords, [], null, 'fix', {}, 'tmp-polygon');
+ } else {
+ geodata.addPolygon(this.navCoords, [], null, 'fix', {}, 'tmp-polygon');
+ }
+
+ geodata.processHeights('node-by-lod', 62, (function(){
+
+ if (this.navCoords.length) {
+
+ space = ' ';
+
+ for (i = 0, li = ('' + this.counter).length; i < li; i++) {
+ space += ' ';
+ }
+
+ str = space + '------------------------';
+
+ var poly = geodata.extractGeometry('tmp-polygon');
+
+ var faces = new Array(poly.getElements());
+
+ for (i = 0, li = faces.length; i < li; i++) {
+ faces[i] = poly.getElement(i);
+ }
+
+ var renderer = this.browser.getRenderer();
+ var x, y, north, east;
+
+ coords = map.convertCoordsFromPhysToNav(center, 'fix');
+
+ var texelSize = radius * 0.0030; //0.15 texel size for 100m diameter
+ var core = this.browser.getCore();
+
+ var traceVolumeCall = (function(terrain){
+
+ str = listElement.value;
+ str = str.substr(0, str.lastIndexOf('loading data ...'));
+ str += 'computation progress: 0%';
+
+ if (!terrain) {
+ str += '\n some error ocurred. Try it again.';
+ return;
+ }
+
+ var octree = renderer.buildOctreeFromGeometry(terrain);
+
+ var ned = map.getNED(coords, false);
+ north = ned.direction;
+ east = ned.east;
+
+ var steps = 25, l, sx, sy, res2, dir = [0,0,0], delta;
+ var sampleArea = (1.0 / steps) * radius;
+ var volumeAbove = 0;
+ var volumeBelow = 0;
+
+ sampleArea *= sampleArea;
+
+ var y = -steps;
+
+ var traceVolumeLine = (function(){
+
+ for (x = -steps; x <= steps; x++) {
+
+ sx = (1.0 / steps) * x * radius;
+ sy = (1.0 / steps) * y * radius;
+ coords[0] = center[0] * 1.0001 + north[0] * sy + east[0] * sx;
+ coords[1] = center[1] * 1.0001 + north[1] * sy + east[1] * sx;
+ coords[2] = center[2] * 1.0001 + north[2] * sy + east[2] * sx;
+
+ vec3.normalize(coords, dir); // TODO: add support for projected systems
+ dir[0] = -dir[0];
+ dir[1] = -dir[1];
+ dir[2] = -dir[2];
+
+ res = this.hitFaces(coords, dir, faces);
+
+ if (res[0]) {
+ res2 = renderer.raycastOctreeGeometry(octree, coords, dir);
+
+ if (res2.length > 0) {
+ delta = (res[1] - res2[0]) * sampleArea;
+
+ if (delta >= 0) {
+ volumeAbove += delta;
+ } else {
+ volumeBelow += -delta;
+ }
+
+ ///console.log("T" + JSON.stringify(res2));
+ }
+ }
+ }
+
+ if (y < steps) {
+ str = str.substr(0, str.lastIndexOf('computation progress:'));
+ str += 'computation progress: ' + (((y + steps) / (steps*2))*100).toFixed(1) + ' %';
+ } else {
+ str = str.substr(0, str.lastIndexOf('computation progress:'));
+
+ if (this.metric) {
+ str += 'volume above: ' + volumeAbove.toFixed(2) + ' m\u00B3';
+ str += '\n' + space + 'volume below: ' + volumeBelow.toFixed(2) + ' m\u00B3';
+ str += '\n' + space + 'volume combined: ' + (volumeAbove + volumeBelow).toFixed(2) + ' m\u00B3' + '\n';
+ } else {
+ var yd2m = 0.764554857984;
+ str += 'volume above: ' + (volumeAbove/yd2m).toFixed(2) + ' yd\u00B3';
+ str += '\n' + space + 'volume below: ' + (volumeBelow/yd2m).toFixed(2) + ' yd\u00B3';
+ str += '\n' + space + 'volume combined: ' + ((volumeAbove + volumeBelow)/yd2m).toFixed(2) + ' yd\u00B3' + '\n';
+ }
+ }
+
+ if (str) {
+ listElement.value = str;
+ listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+ }
+
+ if (y < steps) {
+ core.once('tick', traceVolumeLine, 1);
+ }
+
+ y++;
+
+ //console.log("*");
+ }).bind(this);
+
+ core.once('tick', traceVolumeLine, 1);
+
+ }).bind(this);
+
+ str += '\n' + space + 'loading data ...';
+ listElement.value += str;
+ listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+ str = null;
+
+ var destructor = map.getSurfaceAreaGeometry(coords, radius, 'texelSize', texelSize, traceVolumeCall, false);
+
+ }
+
+ }).bind(this));
+
+ this.counter++;
+ }
+ }
+
+ if (str) {
+ listElement.value += str + '\n';
+ listElement.scrollTop = listElement.scrollHeight; //scroll list to the last line
+ }
+
+ map.redraw();
+};
+
+UIControlMeasure.prototype.hitFace = function(origin, dir, face) {
+ var EPSILON = 0.0000001;
+ var v1 = face[0];
+ var v2 = face[1];
+ var v3 = face[2];
+
+ var h = [0,0,0], q = [0,0,0], s;
+ var a,f,u,v;
+ var edge1 = [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]];
+ var edge2 = [v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]];
+
+ vec3.cross(dir, edge2, h);
+ a = vec3.dot(edge1, h);
+
+ if (a > -EPSILON && a < EPSILON) {
+ return [false];
+ }
+
+ f = 1/a;
+ s = [origin[0] - v1[0], origin[1] - v1[1], origin[2] - v1[2]];
+ u = f * (vec3.dot(s, h));
+
+ if (u < 0.0 || u > 1.0) {
+ return [false];
+ }
+
+ q = vec3.cross(s, edge1);
+ v = f * vec3.dot(dir, q);
+ if (v < 0.0 || u + v > 1.0) {
+ return [false];
+ }
+
+ // At this stage we can compute t to find out where the intersection point is on the line.
+ var t = f * vec3.dot(edge2, q);
+ //if (t > EPSILON) { // ray intersection
+ return [true, t]; //[origin[0] + dir[0] * t, origin[1] + dir[1] * t, origin[2] + dir[2] * t ]];
+ //} else { // This means that there is a line intersection but not a ray intersection.
+ // return [false];
+ //}
+};
+
+
+UIControlMeasure.prototype.hitFaces = function(coords, dir, faces) {
+ var hit = false, t = Number.POSITIVE_INFINITY;
+
+ for (var i = 0, li = faces.length; i < li; i++) {
+ var res = this.hitFace(coords, dir, faces[i]);
+
+ if (res[0]) {
+ hit = true;
+
+ if (res[1] < t) {
+ t = res[1];
+ }
+ }
+ }
+
+ //console.log(hit ? ("" + t.toFixed(2)) : ("N"));
+ return [hit, t];
+};
+
+
UIControlMeasure.prototype.onClear = function() {
this.counter = 1;
this.lastCoords = null;
+ this.navCoords = null;
var listElement = this.list.getElement();
listElement.value = '';
listElement.scrollTop = 0;
+
+ this.compute.setStyle('display', 'none');
+
+ var map = this.browser.getMap();
+ if (map) {
+ map.redraw();
+ }
};
+
UIControlMeasure.prototype.update = function() {
//var button = this.control.getElement('vts-measure-button');
@@ -221,14 +736,142 @@ UIControlMeasure.prototype.update = function() {
};
-UIControlMeasure.prototype.updateLink = function() {
- /*
- var linkValue = this.browser.getLinkWithCurrentPos();
- if (this.list.getElement().value != linkValue) {
- this.list.getElement().value = linkValue;
- }*/
+UIControlMeasure.prototype.onMapUpdate = function() {
+ var map = this.browser.getMap();
+ if (!map) {
+ return;
+ }
+
+ var renderer = this.browser.getRenderer();
+
+ if (!this.circleImage) {
+ this.circleImage = utils.loadImage(
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAACW9GRnMAAAAgAAAA4ACD+EAUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA/UlEQVRYw+2VPwqDMBTG3dz1Am56EnH2XLroETxGuwc3Z7cOdhY8QJpfSUBspUvStJAPPggvD973/uQligICAgL+DKViqygUV02hbaXLwJlio7gpyhNu2idzEXwwgfI8H+u6vnZdN/V9P3EuimLcCRlsiyArGcfxjWDLsmzyAGzc4aNFNDZ7/iw7AeQH4LNrh5WZYLgkJTaZCyHuVVVdkiSZ0zSdOWMzlaBFWkRrQ4A4Zk/A4wBie1MFYUMAz0wybCYAmR8FUAlzj6+2r18TgM2VAO8tOB1Cyk7mrofQ+zP0voheVjHtIBjDxjrmvCu7k1Xs/TP6ie84ICDAGR5uCYdPo0MWiAAAAABJRU5ErkJggg==',
+ //"http://maps.google.com/mapfiles/kml/shapes/placemarkcircle.png",
+ (function(){
+ this.circleTexture = renderer.createTexture({ 'source': this.circleImage });
+ }).bind(this)
+ );
+ }
+
+ if (!this.circleTexture) {
+ return;
+ }
+
+ var i, li, coords, points;
+
+ switch(this.tool) {
+ case 0: //point
+
+ if (this.navCoords) {
+ coords = map.convertCoordsFromNavToCanvas(this.navCoords, "fix");
+
+ renderer.drawImage({
+ rect : [coords[0]-12, coords[1]-12, 24, 24],
+ texture : this.circleTexture,
+ color : [255,0,0,255], //white point is multiplied by red color so resulting point will be red
+ depth : coords[2],
+ depthTest : false,
+ blend : true //point texture has alpha channel so blend is needed
+ });
+ }
+
+ break;
+
+ case 1: //line
+ case 2: //track
+ case 3: //area
+ case 4: //volume
+
+ if (this.navCoords) {
+ points = [];
+
+ for (i = 0, li = this.navCoords.length; i < li; i++) {
+ points.push(map.convertCoordsFromNavToCanvas(this.navCoords[i], "fix"));
+ }
+
+ if (li > 1) {
+
+ var points2 = [], points3 = [], tmp;
+
+ for (i = 0, li = this.navCoords.length - 1; i < li; i++) {
+ tmp = map.getGeodesicLinePoints(this.navCoords[i], this.navCoords[i+1]);
+ points2 = points2.concat(tmp);
+ }
+
+ for (i = 0, li = points2.length; i < li; i++) {
+ points3.push(map.convertCoordsFromNavToCanvas(points2[i], "fix"));
+ }
+
+ if (this.tool == 3 || this.tool == 4) {
+ points3.push(map.convertCoordsFromNavToCanvas(points2[0], "fix"));
+ }
+
+ renderer.drawLineString({
+ points : points3,
+ size : 5.0,
+ color : [0,0,0,255],
+ depthTest : false,
+ //depthTest : true,
+ //depthOffset : [-0.01,0,0],
+ blend : false
+ });
+
+ renderer.drawLineString({
+ points : points3,
+ size : 2.0,
+ color : [255,0,0,255],
+ depthTest : false,
+ //depthTest : true,
+ //depthOffset : [-0.01,0,0],
+ blend : false
+ });
+ }
+
+ for (i = 0, li = points.length; i < li; i++) {
+ coords = points[i];
+
+ renderer.drawImage({
+ rect : [coords[0]-12, coords[1]-12, 24, 24],
+ texture : this.circleTexture,
+ color : [255,0,0,255], //white point is multiplied by red color so resulting point will be red
+ depth : coords[2],
+ depthTest : false,
+ blend : true //point texture has alpha channel so blend is needed
+ });
+ }
+ }
+
+
+ }
+
+
+ if ((this.tool == 2 || this.tool == 3 || this.tool == 4) && points) {
+ if (points.length < 2 || this.renderCounter != this.counter) {
+ this.compute.setStyle('display', 'none');
+ return;
+ }
+
+ coords = points[points.length - 1];
+ coords[0] -= this.divRect.left;
+ coords[1] -= this.divRect.top;
+
+ this.compute.setStyle('display', 'block');
+ this.compute.setStyle('left', (coords[0]+20)+'px');
+ this.compute.setStyle('top', (coords[1]+10)+'px');
+ }
+
};
+UIControlMeasure.prototype.getTextNumber = function(value) {
+ var m2ft = 3.28084, km2mi = 0.621371;
+
+ if (value >= 100000) {
+ return (this.metric) ? (value*0.001).toFixed(2) + 'km' : (value*0.001*km2mi).toFixed(2) + 'mi';
+ } else {
+ return (this.metric) ? (value).toFixed(2) + 'm' : (value*m2ft).toFixed(2) + 'ft';
+ }
+};
-export default UIControlMeasure;
+export {UIControlMeasure , UIControlMeasureIcon, UIControlMeasureIcon2};
diff --git a/src/browser/ui/ui.js b/src/browser/ui/ui.js
index ada14aa7..d45f4469 100755
--- a/src/browser/ui/ui.js
+++ b/src/browser/ui/ui.js
@@ -19,11 +19,12 @@ import UIControlSpace_ from './control/space';
import UIControlSearch_ from './control/search';
import UIControlLink_ from './control/link';
import UIControlGithub_ from './control/github';
-import UIControlMeasure_ from './control/measure';
import UIControlLayers_ from './control/layers';
import UIControlFallback_ from './control/fallback';
import UIControlPopup_ from './control/popup';
import UIControlLoading_ from './control/loading';
+import { UIControlMeasure as UIControlMeasure_ } from './control/measure';
+import UIControlMeasureLite_ from './control/measure-lite';
//get rid of compiler mess
var UIControlCompass = UIControlCompass_;
@@ -35,6 +36,7 @@ var UIControlSearch = UIControlSearch_;
var UIControlLink = UIControlLink_;
var UIControlGithub = UIControlGithub_;
var UIControlMeasure = UIControlMeasure_;
+var UIControlMeasureLite = UIControlMeasureLite_;
var UIControlLayers = UIControlLayers_;
var UIControlFallback = UIControlFallback_;
var UIControlPopup = UIControlPopup_;
@@ -81,6 +83,7 @@ UI.prototype.init = function() {
this.link = new UIControlLink(this, (!loading && this.config.controlLink), loading);
this.github = new UIControlGithub(this, (!loading && this.config.controlGithub), loading);
this.measure = new UIControlMeasure(this, (!loading && this.config.controlMeasure), loading);
+ this.measure2 = new UIControlMeasureLite(this, (!loading && this.config.controlMeasureLite), loading);
//this.navigator = new UIControlNavigation(this, this.config.controlNavigator);
this.layers = new UIControlLayers(this, (!loading && this.config.controlLayers), loading);
this.fallback = new UIControlFallback(this);
diff --git a/src/core/core.js b/src/core/core.js
index 5a401f83..d7762e06 100755
--- a/src/core/core.js
+++ b/src/core/core.js
@@ -339,7 +339,7 @@ Core.prototype.setOption = function(/*key, value*/) {
};
-Core.prototype.on = function(name, listener) {
+Core.prototype.on = function(name, listener, wait, once) {
if (this.killed) { // || this.renderer == null) {
return;
}
@@ -349,17 +349,32 @@ Core.prototype.on = function(name, listener) {
}
this.listenerCounter++;
- this.listeners.push({ name : name, listener : listener, id : this.listenerCounter });
+ this.listeners.push({ name : name, listener : listener, id : this.listenerCounter, once: once, wait: wait ? wait : 0 });
return (function(id){ this.removeListener(id); }).bind(this, this.listenerCounter);
};
+Core.prototype.once = function(name, listener, wait) {
+ this.on(name, listener, wait, true);
+};
+
+
// private
Core.prototype.callListener = function(name, event, log) {
for (var i = 0; i < this.listeners.length; i++) {
if (this.listeners[i].name == name) {
- this.listeners[i].listener(event);
+ var listener = this.listeners[i];
+
+ if (listener.wait > 0) {
+ listener.wait--;
+ } else {
+ listener.listener(event);
+ if (listener.once) {
+ this.listeners.splice(i, 1);
+ i--;
+ }
+ }
}
}
@@ -508,7 +523,7 @@ string getCoreVersion()
*/
function getCoreVersion(full) {
- return (full ? 'Core: ' : '') + '2.15.17';
+ return (full ? 'Core: ' : '') + '2.16.0';
}
diff --git a/src/core/interface.js b/src/core/interface.js
index 42ae10ed..eb8b8b8a 100755
--- a/src/core/interface.js
+++ b/src/core/interface.js
@@ -81,7 +81,12 @@ CoreInterface.prototype.getProj4 = function() {
CoreInterface.prototype.on = function(eventName, call) {
if (!this.core) { return null; }
- this.core.on(eventName, call);
+ return this.core.on(eventName, call);
+};
+
+CoreInterface.prototype.once = function(eventName, call, wait) {
+ if (!this.core) { return null; }
+ return this.core.once(eventName, call, wait);
};
CoreInterface.prototype.callListener = function(name, event) {
diff --git a/src/core/map/convert.js b/src/core/map/convert.js
index 2e490433..59c2fa3d 100755
--- a/src/core/map/convert.js
+++ b/src/core/map/convert.js
@@ -34,8 +34,7 @@ MapConvert.prototype.movePositionCoordsTo = function(position, azimuth, distance
position.setCoords2([coords[0] + (forward[0]*distance),
coords[1] + (forward[1]*distance)]);
} else {
- var geod = new GeographicLib.Geodesic.Geodesic(navigationSrsInfo['a'],
- (navigationSrsInfo['a'] / navigationSrsInfo['b']) - 1.0);
+ var geod = this.measure.getGeodesic();
var r = geod.Direct(coords[1], coords[0], azimuth, distance);
position.setCoords2([r.lon2, r.lat2]);
@@ -296,5 +295,41 @@ MapConvert.prototype.convertCoordsFromPhysToNav = function(coords, mode, lod) {
return coords;
};
+MapConvert.prototype.getGeodesicLinePoints = function(coords, coords2, height, density) {
+ var geod, r, length, azimuth, minStep, d;
+ var navigationSrsInfo = this.measure.navigationSrsInfo;
+ var dx = coords2[0] - coords[0];
+ var dy = coords2[1] - coords[1];
+ var dz = coords2[2] - coords[2];
+
+ if (this.isProjected) {
+ length = Math.sqrt(dx*dx + dy*dy + dz*dz);
+ minStep = 1000000; //just big number
+ } else {
+ geod = this.measure.getGeodesic();
+ r = geod.Inverse(coords[1], coords[0], coords2[1], coords2[0]);
+ length = r.s12;
+ azimuth = r.azi1;
+ minStep = 10 * ((navigationSrsInfo['a'] * 2 * Math.PI) / 4007.5); //aprox 100km for earth
+ }
+
+ var points = [coords];
+ var distance = minStep;
+
+ for (;distance < length; distance += minStep) {
+ d = distance / length;
+
+ if (this.isProjected) {
+ points.push([ coords[0] + dx * d, coords[1] + dy * d, coords[2] + dz * d ]);
+ } else {
+ r = geod.Direct(coords[1], coords[0], azimuth, distance);
+ points.push([r.lon2, r.lat2, coords[2] + dz * d]);
+ }
+ }
+
+ points.push(coords2);
+
+ return points;
+};
export default MapConvert;
diff --git a/src/core/map/draw-tiles.js b/src/core/map/draw-tiles.js
index 2b6b13e5..50d556ba 100755
--- a/src/core/map/draw-tiles.js
+++ b/src/core/map/draw-tiles.js
@@ -736,6 +736,10 @@ MapDrawTiles.prototype.updateTileSurfaceBounds = function(tile, submesh, surface
// tile = tile;
//}
+ if (this.config.mapNoTextures) {
+ return;
+ }
+
//search map view
if (surface.boundLayerSequence.length > 0) {
if (fullUpdate) {
diff --git a/src/core/map/draw.js b/src/core/map/draw.js
index 47fa8afe..2f4fca26 100755
--- a/src/core/map/draw.js
+++ b/src/core/map/draw.js
@@ -357,7 +357,7 @@ MapDraw.prototype.drawMap = function(skipFreeLayers) {
}
if (this.tree.surfaceSequence.length > 0) {
- this.tree.draw(camInfo);
+ this.tree.draw();
}
if (replay.storeTiles) { //used only in inspectors
@@ -404,7 +404,7 @@ MapDraw.prototype.drawMap = function(skipFreeLayers) {
if (layer.type == 'geodata') {
this.drawMonoliticGeodata(layer);
} else {
- layer.tree.draw(camInfo);
+ layer.tree.draw();
}
this.zbufferOffset = null;
diff --git a/src/core/map/geodata-builder.js b/src/core/map/geodata-builder.js
index a8684581..cf39b579 100644
--- a/src/core/map/geodata-builder.js
+++ b/src/core/map/geodata-builder.js
@@ -1043,7 +1043,7 @@ MapGeodataBuilder.prototype.addPolygon3 = function(shape, holes, middle, heightM
' p6:' + (Array.isArray(sbuffer2[l+5][1]) ? 'a' : '') + sbuffer2[l+5][1]);*/
} else if (l2 == l) {
- console.log('l2');
+ //console.log('l2');
//add new vertices to the buffer
vbuffer[mm] = p5[0];
@@ -1463,7 +1463,8 @@ MapGeodataBuilder.prototype.extractGeometry = function(id) {
var group = this.groups[i];
var groupPoints = group.points;
- var groupLines = group.lines, j, lj;
+ var groupLines = group.lines;
+ var groupPolygons = group.polygons, j, lj;
//get group bbox
for (j = 0, lj = groupPoints.length; j < lj; j++) {
@@ -1477,6 +1478,12 @@ MapGeodataBuilder.prototype.extractGeometry = function(id) {
feature = groupLines[j];
}
}
+
+ for (j = 0, lj = groupPolygons.length; j < lj; j++) {
+ if (groupPolygons[j].id == id) {
+ feature = groupPolygons[j];
+ }
+ }
}
if (feature) {
@@ -1529,6 +1536,13 @@ MapGeodataBuilder.prototype.extractGeometry = function(id) {
}
return new MapGeodataGeometry(this.map, {'type': 'line-geometry', 'id':feature.id, 'geometryBuffer': vertexBuffer, 'indicesBuffer': indexBuffer });
+ } else if (feature.vertices) {
+
+ /*feature.vertices = featureVertices;
+ feature.surface = featureSurface;
+ feature.borders = featureBorders;*/
+
+ return new MapGeodataGeometry(this.map, {'type': 'polygon-geometry', 'id':feature.id, 'geometryBuffer': feature.vertices, 'surface': feature.surface });
}
return;
diff --git a/src/core/map/geodata-geometry.js b/src/core/map/geodata-geometry.js
index 1ba2adc2..df3ceb3b 100644
--- a/src/core/map/geodata-geometry.js
+++ b/src/core/map/geodata-geometry.js
@@ -25,6 +25,9 @@ var MapGeodataGeometry = function(map, data) {
break;
case 'polygon-geometry':
this.type = 3;
+ this.vertexBuffer = this.data.geometryBuffer;
+ this.surface = this.data.surface;
+ this.borders = this.data.borders;
break;
}
};
@@ -42,12 +45,17 @@ MapGeodataGeometry.prototype.getElement = function(index) {
switch(this.type) {
case 1: return [v[i], v[i+1], v[i+2]]; //point
case 2: return [[v[i], v[i+1], v[i+2]], [v[i+3], v[i+4], v[i+5]]]; //line
+ case 3:
+ var s = this.surface;
+ var i1 = s[i], i2 = s[i+1], i3 = s[i+2];
+ return [[v[i1][0], v[i1][1], v[i1][2]], [v[i2][0], v[i2][1], v[i2][2]], [v[i3][0], v[i3][1], v[i3][2]]]; //polygon
}
};
MapGeodataGeometry.prototype.getElements = function(pathIndex) {
switch(this.type) {
- case 1: return this.vertexBuffer.length / 3; //point
+ case 1: //point
+ case 3: return this.surface.length / 3; //polygon
case 2: //line
pathIndex = pathIndex || 0;
@@ -272,6 +280,46 @@ MapGeodataGeometry.prototype.getPathsCount = function() {
};
+MapGeodataGeometry.prototype.getSurfaceArea = function() {
+ if (this.type != 3) {
+ return 0;
+ }
+
+ if (!this.surfaceArea) {
+ var v = this.vertexBuffer, s = this.surface;
+ var p1, p2, p3, l1, l2, l3, dx, dy, dz, perimeter, area;
+
+ this.surfaceArea = 0;
+ for (var i = 0, li = s.length; i < li; i+= 3) {
+ p1 = v[s[i]];
+ p2 = v[s[i+1]];
+ p3 = v[s[i+2]];
+
+ dx = p2[0] - p1[0];
+ dy = p2[1] - p1[1];
+ dz = p2[2] - p1[2];
+ l1 = Math.sqrt(dx*dx + dy*dy + dz*dz);
+
+ dx = p2[0] - p3[0];
+ dy = p2[1] - p3[1];
+ dz = p2[2] - p3[2];
+ l2 = Math.sqrt(dx*dx + dy*dy + dz*dz);
+
+ dx = p3[0] - p1[0];
+ dy = p3[1] - p1[1];
+ dz = p3[2] - p1[2];
+ l3 = Math.sqrt(dx*dx + dy*dy + dz*dz);
+
+ //Heron's formula
+ perimeter = (l1 + l2 + l3)/2;
+ area = Math.sqrt(perimeter*((perimeter-l1)*(perimeter-l2)*(perimeter-l3)));
+
+ this.surfaceArea += area;
+ }
+ }
+
+ return this.surfaceArea;
+};
export default MapGeodataGeometry;
diff --git a/src/core/map/geodata-import/geojson.js b/src/core/map/geodata-import/geojson.js
index ed1d8ad4..fb0e6b30 100644
--- a/src/core/map/geodata-import/geojson.js
+++ b/src/core/map/geodata-import/geojson.js
@@ -21,7 +21,7 @@ MapGeodataImportGeoJSON.prototype.processGeometry = function(geometry, feature)
break;
case 'MultiPoint':
- this.builder.addPointArray(cords, this.heightMode, feature['properties'], feature['properties'] ? feature['properties']['id'] : null, this.srs);
+ this.builder.addPointArray(coords, this.heightMode, feature['properties'], feature['properties'] ? feature['properties']['id'] : null, this.srs);
break;
case 'LineString':
diff --git a/src/core/map/interface.js b/src/core/map/interface.js
index 616dea9b..f2edd985 100755
--- a/src/core/map/interface.js
+++ b/src/core/map/interface.js
@@ -245,11 +245,38 @@ MapInterface.prototype.movePositionCoordsTo = function(position, azimuth, distan
};
+MapInterface.prototype.getGeodesicLinePoints = function(coords, coords2, height, density) {
+ return this.map.convert.getGeodesicLinePoints(coords, coords2, height, density);
+};
+
+
MapInterface.prototype.getSurfaceHeight = function(coords, precision) {
return this.map.measure.getSurfaceHeight(coords, this.map.measure.getOptimalHeightLodBySampleSize(coords, precision));
};
+MapInterface.prototype.getSurfaceAreaGeometry = function(coords, radius, mode, limit, callback, loadTextures) {
+ var res = this.map.measure.getSurfaceAreaGeometry(coords, radius, mode, limit, true, loadTextures);
+ //console.log('getSurfaceAreaGeometry');
+
+ if (!res[0]) {
+ return this.map.core.once('map-update', this.getSurfaceAreaGeometry.bind(this, coords, radius, mode, limit, callback, loadTextures), 1);
+ } else {
+ var buffer = res[1], ret = [], map = this.map;
+
+ if (map.tree) {
+ map.storedTilesRes = [];
+ map.tree.storeGeometry(buffer, buffer.length);
+ ret = map.storedTilesRes;
+ map.storedTilesRes = [];
+ }
+
+ callback(ret);
+ return (function(){});
+ }
+};
+
+
MapInterface.prototype.getDistance = function(coords, coords2, includingHeights, usePublic) {
return this.map.measure.getDistance(coords, coords2, includingHeights, usePublic);
};
@@ -260,8 +287,8 @@ MapInterface.prototype.getAzimuthCorrection = function(coords, coords2) {
};
-MapInterface.prototype.getNED = function(coords) {
- return this.map.measure.getNewNED(coords, true);
+MapInterface.prototype.getNED = function(coords, onlyMatrix) {
+ return this.map.measure.getNewNED(coords, (onlyMatrix === false) ? false : true);
};
@@ -395,6 +422,11 @@ MapInterface.prototype.renderToImage = function() {
};
+MapInterface.prototype.getCurrentGeometry = function() {
+ return this.map.getCurrentGeometry();
+};
+
+
MapInterface.prototype.getStats = function(switches) {
if (switches) {
return {
diff --git a/src/core/map/map.js b/src/core/map/map.js
index 5ca1615c..5eebb256 100755
--- a/src/core/map/map.js
+++ b/src/core/map/map.js
@@ -1122,6 +1122,16 @@ Map.prototype.hitTestGeoLayers = function(screenX, screenY, mode) {
}
};
+Map.prototype.getCurrentGeometry = function() {
+ if (this.draw.tree.surfaceSequence.length > 0) {
+ this.draw.tree.draw(true);
+ var res = this.storedTilesRes;
+ this.storedTilesRes = [];
+ return res;
+ }
+
+ return res;
+};
Map.prototype.applyCredits = function(tile) {
var value, value2;
diff --git a/src/core/map/measure.js b/src/core/map/measure.js
index 12a72fbd..64c9b6b9 100755
--- a/src/core/map/measure.js
+++ b/src/core/map/measure.js
@@ -25,6 +25,37 @@ var MapMeasure = function(map) {
this.maxDivisionNodeDepth = res[1];
};
+MapMeasure.prototype.getSurfaceAreaGeometry = function(coords, radius, mode, limit, loadMeshes, loadTextures) {
+ var tree = this.map.tree;
+
+ if (tree.surfaceSequence.length == 0) {
+ reurn [true, []];
+ }
+
+ var center = this.convert.convertCoords(coords, 'navigation', 'physical');
+ var coneVec = [0,0,0];
+
+ vec3.normalize(center, coneVec);
+
+ var distance = vec3.length(center);
+ var coneAngle = Math.atan(Math.tan(radius / distance));
+
+ tree.params = {
+ coneVec : coneVec,
+ coneAngle : coneAngle,
+ mode : mode,
+ limit : limit,
+ loaded : true,
+ areaTiles : [],
+ loadMeshes: (loadMeshes === true),
+ loadTextures: (loadTextures === true)
+ };
+
+ //priority = 0, noReadInly = false
+ tree.traceAreaTiles(tree.surfaceTree, 0, false);
+
+ return [tree.params.loaded, tree.params.areaTiles];
+};
MapMeasure.prototype.getSurfaceHeight = function(coords, lod, storeStats, node, nodeCoords, coordsArray, useNodeOnly) {
var tree = this.map.tree;
diff --git a/src/core/map/surface-tile.js b/src/core/map/surface-tile.js
index 089501af..755f9969 100755
--- a/src/core/map/surface-tile.js
+++ b/src/core/map/surface-tile.js
@@ -309,7 +309,7 @@ MapSurfaceTile.prototype.isMetanodeReady = function(tree, priority, preventLoad)
var ret = this.checkMetanode(tree, priority);
if (!ret && !(this.metanode != null && this.lastMetanode)) { //metanode is not ready yet
- return;
+ return false;
}
}
@@ -632,6 +632,17 @@ MapSurfaceTile.prototype.bboxVisible = function(id, bbox, cameraPos, node) {
}
};
+MapSurfaceTile.prototype.insideCone = function(coneVec, angle, node) {
+
+ if (this.map.isGeocent) { // && node.diskPos && node.diskNormal) {
+ var a = Math.acos(vec3.dot(coneVec, node.diskNormal));
+
+ return (a < angle + node.diskAngle2A);
+ }
+
+ return false;
+};
+
MapSurfaceTile.prototype.getPixelSize = function(bbox, screenPixelSize, cameraPos, worldPos, returnDistance) {
var min = bbox.min;
diff --git a/src/core/map/surface-tree.js b/src/core/map/surface-tree.js
index ca483543..ab113b4c 100755
--- a/src/core/map/surface-tree.js
+++ b/src/core/map/surface-tree.js
@@ -125,7 +125,7 @@ MapSurfaceTree.prototype.findNavTile = function(id) {
};
-MapSurfaceTree.prototype.draw = function() {
+MapSurfaceTree.prototype.draw = function(storeTilesOnly) {
this.cameraPos = [0,0,0];
this.worldPos = [0,0,0];
@@ -150,8 +150,8 @@ MapSurfaceTree.prototype.draw = function() {
this.drawSurface([0,0,0]);
if (periodicity.type == 'X') {
- this.drawSurface([periodicity.period,0,0]);
- this.drawSurface([-periodicity.period,0,0]);
+ this.drawSurface([periodicity.period,0,0], storeTilesOnly);
+ this.drawSurface([-periodicity.period,0,0], storeTilesOnly);
}
} else {
@@ -164,9 +164,9 @@ MapSurfaceTree.prototype.draw = function() {
}
switch(mode) {
- case 'topdown': this.drawSurface([0,0,0]); break;
- case 'fit': this.drawSurfaceFit([0,0,0]); break;
- case 'fitonly': this.drawSurfaceFitOnly([0,0,0]); break;
+ case 'topdown': this.drawSurface([0,0,0], storeTilesOnly); break;
+ case 'fit': this.drawSurfaceFit([0,0,0], storeTilesOnly); break;
+ case 'fitonly': this.drawSurfaceFitOnly([0,0,0], storeTilesOnly); break;
}
}
};
@@ -219,7 +219,7 @@ MapSurfaceTree.prototype.logTileInfo = function(tile, node, cameraPos) {
//loadmode = topdown
-MapSurfaceTree.prototype.drawSurface = function() {
+MapSurfaceTree.prototype.drawSurface = function(shift, storeTilesOnly) {
this.counter++;
var tile = this.surfaceTree;
@@ -403,6 +403,11 @@ MapSurfaceTree.prototype.drawSurface = function() {
processBufferIndex = newProcessBufferIndex;
} while(processBufferIndex > 0);
+
+ if (storeTilesOnly) {
+ this.storeDrawBufferGeometry(drawBufferIndex);
+ return;
+ }
if (best2 > draw.bestMeshTexelSize) {
draw.bestMeshTexelSize = best2;
@@ -449,7 +454,7 @@ MapSurfaceTree.prototype.drawSurface = function() {
//loadmode = fitonly
-MapSurfaceTree.prototype.drawSurfaceFitOnly = function() {
+MapSurfaceTree.prototype.drawSurfaceFitOnly = function(shift, storeTilesOnly) {
this.counter++;
// this.surfaceTracer.trace(this.surfaceTree);//this.rootId);
@@ -622,6 +627,11 @@ MapSurfaceTree.prototype.drawSurfaceFitOnly = function() {
} while(processBufferIndex > 0);
+ if (storeTilesOnly) {
+ this.storeDrawBufferGeometry(drawBufferIndex);
+ return;
+ }
+
var stats = map.stats;
stats.usedNodes = usedNodes;
@@ -658,7 +668,7 @@ MapSurfaceTree.prototype.drawSurfaceFitOnly = function() {
//loadmode = fit
-MapSurfaceTree.prototype.drawSurfaceFit = function() {
+MapSurfaceTree.prototype.drawSurfaceFit = function(shift, storeTilesOnly) {
this.counter++;
var tile = this.surfaceTree;
@@ -971,6 +981,11 @@ MapSurfaceTree.prototype.drawSurfaceFit = function() {
} while(processBufferIndex > 0);
+ if (storeTilesOnly) {
+ this.storeDrawBufferGeometry(drawBufferIndex);
+ return;
+ }
+
var stats = map.stats;
stats.usedNodes = usedNodes;
@@ -1011,6 +1026,55 @@ MapSurfaceTree.prototype.drawSurfaceFit = function() {
};
+MapSurfaceTree.prototype.storeDrawBufferGeometry = function(drawBufferIndex) {
+ var map = this.map;
+ var drawBuffer = map.draw.drawBuffer;
+
+ this.storeGeometry(drawBuffer, drawBufferIndex);
+};
+
+
+MapSurfaceTree.prototype.storeGeometry = function(array, length) {
+ var map = this.map;
+ var drawBuffer = array;
+ map.storedTilesRes = new Array(length);
+
+ for (var i = length - 1; i >= 0; i--) {
+ var tile = drawBuffer[i];
+
+ if (tile.metanode && tile.surface && tile.metanode.hasGeometry() &&
+ tile.surfaceMesh && tile.surfaceMesh.isReady(true, 0, true)) {
+
+ var mesh = tile.surfaceMesh;
+ var submeshes = [];
+
+ for (var j = 0, lj = mesh.submeshes.length; j < lj; j++) {
+ var submesh = mesh.submeshes[j],
+ vertices = submesh.vertices.slice(),
+ min = submesh.bbox.min,
+ max = submesh.bbox.max,
+ delta = [max[0] - min[0], max[1] - min[1], max[2] - min[2]];
+
+ for (var k = 0, lk = vertices.length; k < lk; k+=3) {
+ vertices[k] = vertices[k]*delta[0] + min[0];
+ vertices[k+1] = vertices[k+1]*delta[1] + min[1];
+ vertices[k+2] = vertices[k+2]*delta[2] + min[2];
+ }
+
+ submeshes.push({
+ "bbox": [min.slice(), max.slice()],
+ "vertices" : vertices });
+ }
+
+ map.storedTilesRes[i] = {
+ "id": tile.id.slice(),
+ "type": "mesh",
+ "submeshes": submeshes
+ };
+ }
+ }
+};
+
MapSurfaceTree.prototype.traceHeight = function(tile, params, nodeOnly) {
if (!tile) {
return;
@@ -1296,4 +1360,66 @@ MapSurfaceTree.prototype.getRenderedNodeById = function(id, drawCounter) {
};
+MapSurfaceTree.prototype.chekTileMesh = function(tile) {
+ if (this.params.loadMeshes || this.params.loadTextures) {
+
+ var tmp = this.config.mapNoTextures;
+ this.config.mapNoTextures = !this.params.loadTextures;
+
+ //are resources ready? priority=0, preventRender=true, preventLoad=false, doNotCheckGpu=true
+ if (!this.map.draw.drawTiles.drawSurfaceTile(tile, tile.metanode, this.map.renderer.cameraPosition, tile.texelSize, 0, true, false, true)) {
+ this.params.loaded = false;
+ }
+
+ this.config.mapNoTextures = tmp;
+ }
+};
+
+
+MapSurfaceTree.prototype.traceAreaTiles = function(tile, priority, nodeReadyOnly) {
+ if (tile == null) {
+ return;
+ }
+
+ if (!tile.isMetanodeReady(this, 0) || nodeReadyOnly) {
+ this.params.loaded = false;
+ //console.log('(L)' + JSON.stringify(tile.id));
+ tile.isMetanodeReady(this, 0);
+ return;
+ }
+
+ tile.metanode.metatile.used();
+
+ if (tile.lastSurface && tile.lastSurface == tile.surface) {
+ tile.lastSurface = null;
+ tile.restoreLastState();
+ //return;
+ }
+
+ if (!tile.insideCone(this.params.coneVec, this.params.coneAngle, tile.metanode)) {
+ return;
+ }
+
+ var fit = (this.params.mode == 'lod') ? (tile.id[0] >= this.params.limit) : (tile.metanode.pixelSize <= this.params.limit);
+
+ if (fit) {
+ //console.log('(A)' + JSON.stringify(tile.id));
+ this.chekTileMesh(tile);
+ this.params.areaTiles.push(tile);
+ return;
+ }
+
+ if (!tile.metanode.hasChildren()) {
+ //console.log('(A)' + JSON.stringify(tile.id));
+ this.chekTileMesh(tile);
+ this.params.areaTiles.push(tile);
+ } else {
+ for (var i = 0; i < 4; i++) {
+ this.traceAreaTiles(tile.children[i], priority, nodeReadyOnly);
+ }
+ }
+};
+
+
+
export default MapSurfaceTree;
diff --git a/src/core/renderer/draw.js b/src/core/renderer/draw.js
index e222c019..a13ffa3e 100755
--- a/src/core/renderer/draw.js
+++ b/src/core/renderer/draw.js
@@ -273,6 +273,7 @@ RendererDraw.prototype.drawLineString = function(points, screenSpace, size, colo
gl.depthMask(false);
}
+ gl.disable(gl.STENCIL_TEST);
gl.disable(gl.CULL_FACE);
}
diff --git a/src/core/renderer/interface.js b/src/core/renderer/interface.js
index 0af46484..1af5f3c0 100755
--- a/src/core/renderer/interface.js
+++ b/src/core/renderer/interface.js
@@ -2,12 +2,14 @@
import GpuTexture_ from './gpu/texture';
import GpuMesh_ from './gpu/mesh';
import GpuProgram_ from './gpu/program';
+import {Octree as Octree_, OctreeRaycaster as OctreeRaycaster_} from './octree.js';
//get rid of compiler mess
var GpuTexture = GpuTexture_;
var GpuMesh = GpuMesh_;
var GpuProgram = GpuProgram_;
-
+var Octree = Octree_;
+var OctreeRaycaster = OctreeRaycaster_;
var RendererInterface = function(renderer) {
this.renderer = renderer;
@@ -407,6 +409,19 @@ RendererInterface.prototype.drawDebugText = function(options) {
};
+RendererInterface.prototype.buildOctreeFromGeometry = function(geometry) {
+ var octree = new Octree();
+ octree.buildFromGeometry(geometry);
+ return octree;
+};
+
+
+RendererInterface.prototype.raycastOctreeGeometry = function(octree, rayPos, rayDir) {
+ var raycaster = new OctreeRaycaster(), intersects = [];
+ raycaster.intersectOctree(rayPos, rayDir, octree, intersects);
+ return raycaster.intersectOctants(rayPos, rayDir, intersects);
+};
+
RendererInterface.prototype.saveScreenshot = function(output, filename, filetype) {
return this.renderer.saveScreenshot(output, filename, filetype);
};
diff --git a/src/core/renderer/octree.js b/src/core/renderer/octree.js
new file mode 100644
index 00000000..7670282d
--- /dev/null
+++ b/src/core/renderer/octree.js
@@ -0,0 +1,567 @@
+
+import {vec3 as vec3_} from '../utils/matrix';
+
+//get rid of compiler mess
+var vec3 = vec3_;
+
+
+var Octree = function() {
+ this.root = null;
+ this.maxItemsPerNode = 20;
+ this.maxDepth = 20;
+
+ this.depthCount = [];
+ for (var i = 0; i < 1000; i++) {
+ this.depthCount[i] = 0;
+ }
+
+ /**
+ * A binary pattern that describes the standard octant layout:
+ *
+ * ```text
+ * 3____7
+ * 2/___6/|
+ * | 1__|_5
+ * 0/___4/
+ * ```
+ *
+ * This common layout is crucial for positional assumptions.
+ *
+ */
+
+ this.pattern = [
+ new Uint8Array([0, 0, 0]),
+ new Uint8Array([0, 0, 1]),
+ new Uint8Array([0, 1, 0]),
+ new Uint8Array([0, 1, 1]),
+
+ new Uint8Array([1, 0, 0]),
+ new Uint8Array([1, 0, 1]),
+ new Uint8Array([1, 1, 0]),
+ new Uint8Array([1, 1, 1])
+ ];
+};
+
+
+Octree.prototype.clear = function() {
+};
+
+
+Octree.prototype.buildFromGeometry = function(data) {
+ if (!data) {
+ return;
+ }
+
+ var i, li, j, lj, k, lk, v, item, submeshes, submesh, bbox, index,
+ minX, minY, minZ, maxX, maxY, maxZ, geometry, vertices;
+
+ minX = minY = minZ = Number.POSITIVE_INFINITY;
+ maxX = maxY = maxZ = Number.NEGATIVE_INFINITY;
+
+ //get gemetery bbox
+ for (i = 0, li = data.length; i < li; i++) {
+ geometry = data[i];
+ if (geometry["type"] == "mesh") {
+ submeshes = geometry["submeshes"];
+
+ for (j = 0, lj = submeshes.length; j < lj; j++) {
+ submesh = submeshes[j];
+ bbox = submesh["bbox"];
+
+ if (bbox[0][0] < minX) minX = bbox[0][0];
+ if (bbox[0][1] < minY) minY = bbox[0][1];
+ if (bbox[0][2] < minZ) minZ = bbox[0][2];
+
+ if (bbox[1][0] > maxX) maxX = bbox[1][0];
+ if (bbox[1][1] > maxY) maxY = bbox[1][1];
+ if (bbox[1][2] > maxZ) maxZ = bbox[1][2];
+ }
+ }
+ }
+
+ this.root = new OctreeNode([minX, minY, minZ], [maxX, maxY, maxZ]);
+
+ //get gemetery bbox
+ for (i = 0, li = data.length; i < li; i++) {
+ geometry = data[i];
+ if (geometry["type"] == "mesh") {
+ submeshes = geometry["submeshes"];
+
+ for (j = 0, lj = submeshes.length; j < lj; j++) {
+ submesh = submeshes[j];
+
+ vertices = submesh["vertices"];
+
+ for (k = 0, lk = vertices.length; k < lk; k += 9) {
+
+ minX = minY = minZ = Number.POSITIVE_INFINITY;
+ maxX = maxY = maxZ = Number.NEGATIVE_INFINITY;
+
+ for (v = 0; v < 3; v ++) {
+ index = k + v * 3;
+
+ if (vertices[index] < minX) minX = vertices[index];
+ if (vertices[index+1] < minY) minY = vertices[index+1];
+ if (vertices[index+2] < minZ) minZ = vertices[index+2];
+
+ if (vertices[index] > maxX) maxX = vertices[index];
+ if (vertices[index+1] > maxY) maxY = vertices[index+1];
+ if (vertices[index+2] > maxZ) maxZ = vertices[index+2];
+ }
+
+ //this.root.add([minX, minY, minZ], [maxX, maxY, maxZ], [vertices, k])
+ this.root.add([minX, minY, minZ, maxX, maxY, maxZ, vertices, k], this)
+ }
+
+ }
+ }
+ }
+
+
+};
+
+
+var OctreeNode = function(min, max) {
+ this.min = min;
+ this.max = max;
+ this.children = null;
+ this.items = null;
+};
+
+OctreeNode.prototype.add = function(item, octree, depth) {
+ if (this.children) {
+ if (!depth) {
+ depth = 0;
+ }
+
+ for (var i = 0; i < 8; i++) {
+ var child = this.children[i],
+ min = child.min,
+ max = child.max;
+
+ if (item[0] < max[0] && item[3] > min[0] &&
+ item[1] < max[1] && item[4] > min[1] &&
+ item[2] < max[2] && item[5] > min[2]) {
+
+ //collision detected, add item
+ child.add(item, octree, depth + 1);
+ }
+ }
+
+ return;
+ }
+
+ if (!this.items) {
+ this.items = [];
+ }
+
+ this.items.push(item);
+
+ if (depth < octree.maxDepth && this.items.length >= octree.maxItemsPerNode) {
+ this.split(octree, depth + 1);
+ }
+};
+
+OctreeNode.prototype.split = function(octree, depth) {
+ var min = this.min,
+ max = this.max,
+ mid = [(max[0] + min[0]) * 0.5, (max[1] + min[1]) * 0.5, (max[2] + min[2]) * 0.5],
+ i, li, j;
+
+ this.children = [
+ null, null,
+ null, null,
+ null, null,
+ null, null
+ ];
+
+ this.depthCount[depth]++;
+
+ for (i = 0; i < 8; i++) {
+ var combination = octree.pattern[i];
+
+ this.children[i] = new OctreeNode(
+ [
+ (combination[0] === 0) ? min[0] : mid[0],
+ (combination[1] === 0) ? min[1] : mid[1],
+ (combination[2] === 0) ? min[2] : mid[2]
+ ],
+
+ [
+ (combination[0] === 0) ? mid[0] : max[0],
+ (combination[1] === 0) ? mid[1] : max[1],
+ (combination[2] === 0) ? mid[2] : max[2]
+ ]
+ );
+ }
+
+ var items = this.items;
+
+ //distribute items
+ if (items) {
+ for (i = 0, li = items.length; i < li; i++) {
+ var item = items[i];
+
+ for (j = 0; j < 8; j++) {
+ var child = this.children[j];
+ min = child.min;
+ max = child.max;
+
+ if (item[0] < max[0] && item[3] > min[0] &&
+ item[1] < max[1] && item[4] > min[1] &&
+ item[2] < max[2] && item[5] > min[2]) {
+
+ //collision detected, add item
+ child.add(item, octree);
+ }
+ }
+ }
+ }
+
+ this.items = null;
+};
+
+
+var OctreeRaycaster = function() {
+
+ // A lookup-table containing octant ids. Used to determine the exit plane from an octant.
+ this.octantTable = [
+ new Uint8Array([4, 2, 1]),
+ new Uint8Array([5, 3, 8]),
+ new Uint8Array([6, 8, 3]),
+ new Uint8Array([7, 8, 8]),
+ new Uint8Array([8, 6, 5]),
+ new Uint8Array([8, 7, 8]),
+ new Uint8Array([8, 8, 7]),
+ new Uint8Array([8, 8, 8])
+ ];
+
+ // A byte that stores raycasting flags.
+ this.flags = 0;
+};
+
+
+/**
+ * Finds the entry plane of the first octant that a ray travels through.
+ *
+ * Determining the first octant requires knowing which of the t0s is the
+ * largest. The tms of the other axes must also be compared against that
+ * largest t0.
+ *
+ * tx0, ty0,tz0 - Ray projection parameter.
+ * txm, tym, tzm - Ray projection parameter mean.
+ * returns - index of the first octant that the ray travels through.
+ */
+
+OctreeRaycaster.prototype.findEntryOctant = function(tx0, ty0, tz0, txm, tym, tzm) {
+ var entry = 0;
+
+ // Find the entry plane.
+ if(tx0 > ty0 && tx0 > tz0) {
+
+ // YZ-plane.
+ if (tym < tx0) {
+ entry |= 2;
+ }
+
+ if (tzm < tx0) {
+ entry |= 1;
+ }
+
+ } else if (ty0 > tz0) {
+
+ // XZ-plane.
+ if (txm < ty0) {
+ entry |= 4;
+ }
+
+ if (tzm < ty0) {
+ entry |= 1;
+ }
+
+ } else {
+
+ // XY-plane.
+ if (txm < tz0) {
+ entry |= 4;
+ }
+
+ if (tym < tz0) {
+ entry |= 2;
+ }
+ }
+
+ return entry;
+}
+
+/**
+ * Finds the next octant that intersects with the ray based on the exit plane of
+ * the current one.
+ *
+ * urrentOctant - The index of the current octant.
+ * tx1, ty1, tz1 - Ray projection parameter.
+ * returns - index of the next octant that the ray travels through.
+ */
+
+OctreeRaycaster.prototype.findNextOctant = function(currentOctant, tx1, ty1, tz1) {
+ var min;
+ var exit = 0;
+
+ // Find the exit plane.
+ if (tx1 < ty1) {
+ min = tx1;
+ exit = 0; // YZ-plane.
+ } else {
+ min = ty1;
+ exit = 1; // XZ-plane.
+ }
+
+ if (tz1 < min) {
+ exit = 2; // XY-plane.
+ }
+
+ return this.octantTable[currentOctant][exit];
+}
+
+/**
+ * Finds all octants that intersect with the given ray.
+ *
+ * octant - The current octant.
+ * tx0 - Ray projection parameter. Initial tx0 = (minX - rayOriginX) / rayDirectionX.
+ * ty0 - Ray projection parameter. Initial ty0 = (minY - rayOriginY) / rayDirectionY.
+ * tz0 - Ray projection parameter. Initial tz0 = (minZ - rayOriginZ) / rayDirectionZ.
+ * tx1 - Ray projection parameter. Initial tx1 = (maxX - rayOriginX) / rayDirectionX.
+ * ty1 - Ray projection parameter. Initial ty1 = (maxY - rayOriginY) / rayDirectionY.
+ * tz1 - Ray projection parameter. Initial tz1 = (maxZ - rayOriginZ) / rayDirectionZ.
+ * intersects - An array to be filled with the intersecting octants.
+ * returns
+ */
+
+OctreeRaycaster.prototype.raycastOctant = function(octant, tx0, ty0, tz0, tx1, ty1, tz1, intersects) {
+ var children = octant.children;
+ var currentOctant;
+ var txm, tym, tzm;
+
+ if (tx1 >= 0.0 && ty1 >= 0.0 && tz1 >= 0.0) {
+
+ if (!children) {
+
+ // Leaf.
+ if (octant.items) {
+ intersects.push(octant);
+ }
+
+ } else {
+
+ // Compute means.
+ txm = 0.5 * (tx0 + tx1);
+ tym = 0.5 * (ty0 + ty1);
+ tzm = 0.5 * (tz0 + tz1);
+
+ currentOctant = this.findEntryOctant(tx0, ty0, tz0, txm, tym, tzm);
+
+ do {
+
+ /* The possibilities for the next node are passed in the same respective
+ * order as the t-values. Hence, if the first value is found to be the
+ * greatest, the fourth one will be returned. If the second value is the
+ * greatest, the fifth one will be returned, etc.
+ */
+
+ switch(currentOctant) {
+
+ case 0:
+ this.raycastOctant(children[this.flags], tx0, ty0, tz0, txm, tym, tzm, intersects);
+ currentOctant = this.findNextOctant(currentOctant, txm, tym, tzm);
+ break;
+
+ case 1:
+ this.raycastOctant(children[this.flags ^ 1], tx0, ty0, tzm, txm, tym, tz1, intersects);
+ currentOctant = this.findNextOctant(currentOctant, txm, tym, tz1);
+ break;
+
+ case 2:
+ this.raycastOctant(children[this.flags ^ 2], tx0, tym, tz0, txm, ty1, tzm, intersects);
+ currentOctant = this.findNextOctant(currentOctant, txm, ty1, tzm);
+ break;
+
+ case 3:
+ this.raycastOctant(children[this.flags ^ 3], tx0, tym, tzm, txm, ty1, tz1, intersects);
+ currentOctant = this.findNextOctant(currentOctant, txm, ty1, tz1);
+ break;
+
+ case 4:
+ this.raycastOctant(children[this.flags ^ 4], txm, ty0, tz0, tx1, tym, tzm, intersects);
+ currentOctant = this.findNextOctant(currentOctant, tx1, tym, tzm);
+ break;
+
+ case 5:
+ this.raycastOctant(children[this.flags ^ 5], txm, ty0, tzm, tx1, tym, tz1, intersects);
+ currentOctant = this.findNextOctant(currentOctant, tx1, tym, tz1);
+ break;
+
+ case 6:
+ this.raycastOctant(children[this.flags ^ 6], txm, tym, tz0, tx1, ty1, tzm, intersects);
+ currentOctant = this.findNextOctant(currentOctant, tx1, ty1, tzm);
+ break;
+
+ case 7:
+ this.raycastOctant(children[this.flags ^ 7], txm, tym, tzm, tx1, ty1, tz1, intersects);
+ // Far top right octant. No other octants can be reached from here.
+ currentOctant = 8;
+ break;
+
+ }
+
+ } while(currentOctant < 8);
+
+ }
+
+ }
+
+}
+
+OctreeRaycaster.prototype.hitFace = function(origin, dir, index, vertices) {
+ var EPSILON = 0.0000001,
+ v1 = [vertices[index], vertices[index+1], vertices[index+2]],
+ v2 = [vertices[index+3], vertices[index+4], vertices[index+5]],
+ v3 = [vertices[index+6], vertices[index+7], vertices[index+8]];
+
+ var h = [0,0,0], q = [0,0,0], s, a, f, u, v,
+ edge1 = [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]],
+ edge2 = [v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]];
+
+ vec3.cross(dir, edge2, h);
+ a = vec3.dot(edge1, h);
+
+ if (a > -EPSILON && a < EPSILON) {
+ return [false];
+ }
+
+ f = 1/a;
+ s = [origin[0] - v1[0], origin[1] - v1[1], origin[2] - v1[2]];
+ u = f * (vec3.dot(s, h));
+
+ if (u < 0.0 || u > 1.0) {
+ return [false];
+ }
+
+ q = vec3.cross(s, edge1);
+ v = f * vec3.dot(dir, q);
+ if (v < 0.0 || u + v > 1.0) {
+ return [false];
+ }
+
+ // At this stage we can compute t to find out where the intersection point is on the line.
+ var t = f * vec3.dot(edge2, q);
+ //if (t > EPSILON) { // ray intersection
+ return [true, t]; //[origin[0] + dir[0] * t, origin[1] + dir[1] * t, origin[2] + dir[2] * t ]];
+ //} else { // This means that there is a line intersection but not a ray intersection.
+ // return [false];
+ //}
+};
+
+
+/**
+ * Finds the octants that intersect with the given ray. The intersecting
+ * octants are sorted by distance, closest first.
+ *
+ * octree - An octree.
+ * intersects - A list to be filled with intersecting octants.
+ */
+
+// https://github.com/vanruesc/sparse-octree/blob/master/src/core/OctreeRaycaster.js
+
+OctreeRaycaster.prototype.intersectOctree = function(rayPos, rayDir, octree, intersects) {
+ // Translate the octree extents to the scene origin.
+ var min = [0,0,0];
+ var max = [octree.root.max[0] - octree.root.min[0],
+ octree.root.max[1] - octree.root.min[1],
+ octree.root.max[2] - octree.root.min[2]]
+
+ var dimensions = [max[0], max[1], max[2]];
+ var halfDimensions = [dimensions[0]*0.5, dimensions[1]*0.5, dimensions[2]*0.5];
+
+ var origin = [rayPos[0],rayPos[1],rayPos[2]];
+ var direction = [rayDir[0], rayDir[1], rayDir[2]];
+
+ var invDirX, invDirY, invDirZ;
+ var tx0, tx1, ty0, ty1, tz0, tz1;
+
+ var center = [(octree.root.max[0] + octree.root.min[0]) * 0.5,
+ (octree.root.max[1] + octree.root.min[1]) * 0.5,
+ (octree.root.max[2] + octree.root.min[2]) * 0.5]
+
+ // Translate the ray to the center of the octree.
+ //origin.sub(octree.getCenter(v[2])).add(halfDimensions);
+ origin[0] = (origin[0] - center[0]) + halfDimensions[0];
+ origin[1] = (origin[1] - center[1]) + halfDimensions[1];
+ origin[2] = (origin[2] - center[2]) + halfDimensions[2];
+
+ // Reset all flags.
+ this.flags = 0;
+
+ // Handle rays with negative directions.
+ if (direction[0] < 0.0) {
+ origin[0] = dimensions[0] - origin[0];
+ direction[0] = -direction[0];
+ this.flags |= 4;
+ }
+
+ if (direction[1] < 0.0) {
+ origin[1] = dimensions[1] - origin[1];
+ direction[1] = -direction[1];
+ this.flags |= 2;
+ }
+
+ if (direction[2] < 0.0) {
+ origin[2] = dimensions[2] - origin[2];
+ direction[2] = -direction[2];
+ this.flags |= 1;
+ }
+
+ // Improve IEEE double stability.
+ invDirX = 1.0 / direction[0];
+ invDirY = 1.0 / direction[1];
+ invDirZ = 1.0 / direction[2];
+
+ // Project the ray to the root's boundaries.
+ tx0 = (min[0] - origin[0]) * invDirX;
+ tx1 = (max[0] - origin[0]) * invDirX;
+ ty0 = (min[1] - origin[1]) * invDirY;
+ ty1 = (max[1] - origin[1]) * invDirY;
+ tz0 = (min[2] - origin[2]) * invDirZ;
+ tz1 = (max[2] - origin[2]) * invDirZ;
+
+ // Check if the ray hits the octree.
+ if (Math.max(Math.max(tx0, ty0), tz0) < Math.min(Math.min(tx1, ty1), tz1)) {
+ // Find the intersecting octants.
+ this.raycastOctant(octree.root, tx0, ty0, tz0, tx1, ty1, tz1, intersects);
+ }
+};
+
+OctreeRaycaster.prototype.intersectOctants = function(rayPos, rayDir, octants) {
+ var hits = [];
+ var t = Number.POSITIVE_INFINITY;
+
+ for (var i = 0, li = octants.length; i < li; i++) {
+ var items = octants[i].items;
+
+ for (var j = 0, lj = items.length; j < lj; j++) {
+ var item = items[j];
+ var res = this.hitFace(rayPos, rayDir, item[7], item[6]);
+
+ if (res[0] && res[1] < t) {
+ t = res[1];
+ }
+ }
+ }
+
+ if (t !== Number.POSITIVE_INFINITY) {
+ hits = [t];
+ }
+
+ return hits;
+};
+
+export {Octree, OctreeRaycaster};