changeset 64:5e11f5880e6d

simplified the embedding of Nexus viewer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 15 Jun 2024 16:35:21 +0200
parents 1fd6d0f8fdc9
children 6a891818bad5
files CMakeLists.txt Resources/CMake/NexusViewer.cmake Resources/CMake/nexus-viewer-4.2.patch Resources/Nexus.txt Resources/Nexus/js/TrackballControls.js Resources/Nexus/js/meco.js Resources/Nexus/js/nexus.js Resources/Nexus/js/nexus_three.js Resources/Nexus/threejs.html Sources/Plugin.cpp
diffstat 10 files changed, 65 insertions(+), 3105 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sat Jun 15 16:17:41 2024 +0200
+++ b/CMakeLists.txt	Sat Jun 15 16:35:21 2024 +0200
@@ -198,26 +198,10 @@
 list(APPEND STATIC_ASSETS_CONTENT  "${CMAKE_SOURCE_DIR}/JavaScriptLibraries/dist-three")
 
 if (ENABLE_NEXUS)
-  set(NEXUS_ASSETS_DIR ${AUTOGENERATED_DIR}/nexus)
-  file(MAKE_DIRECTORY ${NEXUS_ASSETS_DIR})
-
-  DownloadCompressedFile(
-    "df21a4a192c0952a1189125609cc76f9"
-    "https://orthanc.uclouvain.be/downloads/third-party-downloads/STL/three-84.js.gz"
-    "${NEXUS_ASSETS_DIR}/three-84.js")
+  include(${CMAKE_SOURCE_DIR}/Resources/CMake/NexusViewer.cmake)
 
   list(APPEND STATIC_ASSETS_PREFIXES "nexus")
-  list(APPEND STATIC_ASSETS_CONTENT
-    ${NEXUS_ASSETS_DIR}   # This adds "three-84.js" that is needed by the Nexus viewer
-    )
-
-  list(APPEND EMBEDDED_RESOURCES
-    NEXUS_HTML           ${CMAKE_SOURCE_DIR}/Resources/Nexus/threejs.html
-    NEXUS_JS             ${CMAKE_SOURCE_DIR}/Resources/Nexus/js/nexus.js
-    NEXUS_MECO_JS        ${CMAKE_SOURCE_DIR}/Resources/Nexus/js/meco.js
-    NEXUS_THREE_JS       ${CMAKE_SOURCE_DIR}/Resources/Nexus/js/nexus_three.js
-    NEXUS_TRACKBALL_JS   ${CMAKE_SOURCE_DIR}/Resources/Nexus/js/TrackballControls.js
-    )
+  list(APPEND STATIC_ASSETS_CONTENT  ${NEXUS_VIEWER_DIR})
 
   add_definitions(-DORTHANC_ENABLE_NEXUS=1)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/NexusViewer.cmake	Sat Jun 15 16:35:21 2024 +0200
@@ -0,0 +1,50 @@
+# SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+
+# STL plugin for Orthanc
+# Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+DownloadPackage(
+  "6069b141edb7ce1d543b53ddaa4b41d1"
+  "https://orthanc.uclouvain.be/downloads/third-party-downloads/STL/nexus-4.2.zip"
+  "${CMAKE_BINARY_DIR}/nexus-4.2")
+
+set(NEXUS_VIEWER_DIR  ${CMAKE_CURRENT_BINARY_DIR}/nexus)
+file(MAKE_DIRECTORY ${NEXUS_VIEWER_DIR})
+
+DownloadCompressedFile(
+  "df21a4a192c0952a1189125609cc76f9"
+  "https://orthanc.uclouvain.be/downloads/third-party-downloads/STL/three-84.js.gz"
+  "${NEXUS_VIEWER_DIR}/three-84.js")
+
+file(COPY
+  ${CMAKE_BINARY_DIR}/nexus-4.2/html/js
+  ${CMAKE_BINARY_DIR}/nexus-4.2/html/threejs.html
+  DESTINATION
+  ${NEXUS_VIEWER_DIR}
+  )
+
+execute_process(
+  COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+  ${CMAKE_CURRENT_LIST_DIR}/nexus-viewer-4.2.patch
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+  RESULT_VARIABLE Failure
+  )
+
+list(APPEND STATIC_ASSETS_PREFIXES "nexus")
+list(APPEND STATIC_ASSETS_CONTENT  ${NEXUS_VIEWER_DIR})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/nexus-viewer-4.2.patch	Sat Jun 15 16:35:21 2024 +0200
@@ -0,0 +1,12 @@
+diff -urEb nexus.orig/threejs.html nexus/threejs.html
+--- nexus.orig/threejs.html	2024-06-15 16:27:10.763574203 +0200
++++ nexus/threejs.html	2024-06-15 16:28:03.807136118 +0200
+@@ -5,7 +5,7 @@
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+ <style>body { margin: 0px; overflow: hidden; }</style>
+-<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
++<script src="three-84.js"></script>
+ <script src="js/TrackballControls.js"></script>
+ <script src="js/nexus.js"></script>
+ <script src="js/nexus_three.js"></script>
--- a/Resources/Nexus.txt	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-Homepage: https://vcg.isti.cnr.it/nexus/
-
-GitHub: https://github.com/cnr-isti-vclab/nexus
-
-
-The files in folder "./Nexus" come from folder "html" in release 4.2
-(Nexus 2018), where the line:
-
-<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
-
-is replaced by the line:
-
-<script src="three-84.js"></script>
-
-
-WARNING: Releases 4.2.1, 4.2.2, and 4.3 do not seem to work anymore.
--- a/Resources/Nexus/js/TrackballControls.js	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,604 +0,0 @@
-/**
- * @author Eberhard Graether / http://egraether.com/
- * @author Mark Lundin 	/ http://mark-lundin.com
- * @author Simone Manini / http://daron1337.github.io
- * @author Luca Antiga 	/ http://lantiga.github.io
- */
-
-THREE.TrackballControls = function ( object, domElement ) {
-
-	var _this = this;
-	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
-
-	this.object = object;
-	this.domElement = ( domElement !== undefined ) ? domElement : document;
-
-	// API
-
-	this.enabled = true;
-
-	this.screen = { left: 0, top: 0, width: 0, height: 0 };
-
-	this.rotateSpeed = 1.0;
-	this.zoomSpeed = 1.2;
-	this.panSpeed = 0.3;
-
-	this.noRotate = false;
-	this.noZoom = false;
-	this.noPan = false;
-
-	this.staticMoving = false;
-	this.dynamicDampingFactor = 0.2;
-
-	this.minDistance = 0;
-	this.maxDistance = Infinity;
-
-	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
-
-	// internals
-
-	this.target = new THREE.Vector3();
-
-	var EPS = 0.000001;
-
-	var lastPosition = new THREE.Vector3();
-
-	var _state = STATE.NONE,
-	_prevState = STATE.NONE,
-
-	_eye = new THREE.Vector3(),
-
-	_movePrev = new THREE.Vector2(),
-	_moveCurr = new THREE.Vector2(),
-
-	_lastAxis = new THREE.Vector3(),
-	_lastAngle = 0,
-
-	_zoomStart = new THREE.Vector2(),
-	_zoomEnd = new THREE.Vector2(),
-
-	_touchZoomDistanceStart = 0,
-	_touchZoomDistanceEnd = 0,
-
-	_panStart = new THREE.Vector2(),
-	_panEnd = new THREE.Vector2();
-
-	// for reset
-
-	this.target0 = this.target.clone();
-	this.position0 = this.object.position.clone();
-	this.up0 = this.object.up.clone();
-
-	// events
-
-	var changeEvent = { type: 'change' };
-	var startEvent = { type: 'start' };
-	var endEvent = { type: 'end' };
-
-
-	// methods
-
-	this.handleResize = function () {
-
-		if ( this.domElement === document ) {
-
-			this.screen.left = 0;
-			this.screen.top = 0;
-			this.screen.width = window.innerWidth;
-			this.screen.height = window.innerHeight;
-
-		} else {
-
-			var box = this.domElement.getBoundingClientRect();
-			// adjustments come from similar code in the jquery offset() function
-			var d = this.domElement.ownerDocument.documentElement;
-			this.screen.left = box.left + window.pageXOffset - d.clientLeft;
-			this.screen.top = box.top + window.pageYOffset - d.clientTop;
-			this.screen.width = box.width;
-			this.screen.height = box.height;
-
-		}
-
-	};
-
-	this.handleEvent = function ( event ) {
-
-		if ( typeof this[ event.type ] == 'function' ) {
-
-			this[ event.type ]( event );
-
-		}
-
-	};
-
-	var getMouseOnScreen = ( function () {
-
-		var vector = new THREE.Vector2();
-
-		return function getMouseOnScreen( pageX, pageY ) {
-
-			vector.set(
-				( pageX - _this.screen.left ) / _this.screen.width,
-				( pageY - _this.screen.top ) / _this.screen.height
-			);
-
-			return vector;
-
-		};
-
-	}() );
-
-	var getMouseOnCircle = ( function () {
-
-		var vector = new THREE.Vector2();
-
-		return function getMouseOnCircle( pageX, pageY ) {
-
-			vector.set(
-				( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
-				( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
-			);
-
-			return vector;
-
-		};
-
-	}() );
-
-	this.rotateCamera = ( function() {
-
-		var axis = new THREE.Vector3(),
-			quaternion = new THREE.Quaternion(),
-			eyeDirection = new THREE.Vector3(),
-			objectUpDirection = new THREE.Vector3(),
-			objectSidewaysDirection = new THREE.Vector3(),
-			moveDirection = new THREE.Vector3(),
-			angle;
-
-		return function rotateCamera() {
-
-			moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
-			angle = moveDirection.length();
-
-			if ( angle ) {
-
-				_eye.copy( _this.object.position ).sub( _this.target );
-
-				eyeDirection.copy( _eye ).normalize();
-				objectUpDirection.copy( _this.object.up ).normalize();
-				objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
-
-				objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
-				objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
-
-				moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
-
-				axis.crossVectors( moveDirection, _eye ).normalize();
-
-				angle *= _this.rotateSpeed;
-				quaternion.setFromAxisAngle( axis, angle );
-
-				_eye.applyQuaternion( quaternion );
-				_this.object.up.applyQuaternion( quaternion );
-
-				_lastAxis.copy( axis );
-				_lastAngle = angle;
-
-			} else if ( ! _this.staticMoving && _lastAngle ) {
-
-				_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
-				_eye.copy( _this.object.position ).sub( _this.target );
-				quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
-				_eye.applyQuaternion( quaternion );
-				_this.object.up.applyQuaternion( quaternion );
-
-			}
-
-			_movePrev.copy( _moveCurr );
-
-		};
-
-	}() );
-
-
-	this.zoomCamera = function () {
-
-		var factor;
-
-		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
-
-			factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
-			_touchZoomDistanceStart = _touchZoomDistanceEnd;
-			_eye.multiplyScalar( factor );
-
-		} else {
-
-			factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
-
-			if ( factor !== 1.0 && factor > 0.0 ) {
-
-				_eye.multiplyScalar( factor );
-
-				if ( _this.staticMoving ) {
-
-					_zoomStart.copy( _zoomEnd );
-
-				} else {
-
-					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
-
-				}
-
-			}
-
-		}
-
-	};
-
-	this.panCamera = ( function() {
-
-		var mouseChange = new THREE.Vector2(),
-			objectUp = new THREE.Vector3(),
-			pan = new THREE.Vector3();
-
-		return function panCamera() {
-
-			mouseChange.copy( _panEnd ).sub( _panStart );
-
-			if ( mouseChange.lengthSq() ) {
-
-				mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
-
-				pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
-				pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
-
-				_this.object.position.add( pan );
-				_this.target.add( pan );
-
-				if ( _this.staticMoving ) {
-
-					_panStart.copy( _panEnd );
-
-				} else {
-
-					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
-
-				}
-
-			}
-
-		};
-
-	}() );
-
-	this.checkDistances = function () {
-
-		if ( ! _this.noZoom || ! _this.noPan ) {
-
-			if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
-
-				_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
-				_zoomStart.copy( _zoomEnd );
-
-			}
-
-			if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
-
-				_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
-				_zoomStart.copy( _zoomEnd );
-
-			}
-
-		}
-
-	};
-
-	this.update = function () {
-
-		_eye.subVectors( _this.object.position, _this.target );
-
-		if ( ! _this.noRotate ) {
-
-			_this.rotateCamera();
-
-		}
-
-		if ( ! _this.noZoom ) {
-
-			_this.zoomCamera();
-
-		}
-
-		if ( ! _this.noPan ) {
-
-			_this.panCamera();
-
-		}
-
-		_this.object.position.addVectors( _this.target, _eye );
-
-		_this.checkDistances();
-
-		_this.object.lookAt( _this.target );
-
-		if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
-
-			_this.dispatchEvent( changeEvent );
-
-			lastPosition.copy( _this.object.position );
-
-		}
-
-	};
-
-	this.reset = function () {
-
-		_state = STATE.NONE;
-		_prevState = STATE.NONE;
-
-		_this.target.copy( _this.target0 );
-		_this.object.position.copy( _this.position0 );
-		_this.object.up.copy( _this.up0 );
-
-		_eye.subVectors( _this.object.position, _this.target );
-
-		_this.object.lookAt( _this.target );
-
-		_this.dispatchEvent( changeEvent );
-
-		lastPosition.copy( _this.object.position );
-
-	};
-
-	// listeners
-
-	function keydown( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		_prevState = _state;
-
-		if ( _state !== STATE.NONE ) {
-
-			return;
-
-		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
-
-			_state = STATE.ROTATE;
-
-		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
-
-			_state = STATE.ZOOM;
-
-		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
-
-			_state = STATE.PAN;
-
-		}
-
-	}
-
-	function keyup( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		_state = _prevState;
-
-	}
-
-	function mousedown( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		if ( _state === STATE.NONE ) {
-
-			_state = event.button;
-
-		}
-
-		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
-
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-			_movePrev.copy( _moveCurr );
-
-		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
-
-			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_zoomEnd.copy( _zoomStart );
-
-		} else if ( _state === STATE.PAN && ! _this.noPan ) {
-
-			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_panEnd.copy( _panStart );
-
-		}
-
-		document.addEventListener( 'mousemove', mousemove, false );
-		document.addEventListener( 'mouseup', mouseup, false );
-
-		_this.dispatchEvent( startEvent );
-
-	}
-
-	function mousemove( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
-
-			_movePrev.copy( _moveCurr );
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-
-		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
-
-			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-
-		} else if ( _state === STATE.PAN && ! _this.noPan ) {
-
-			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-
-		}
-
-	}
-
-	function mouseup( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		_state = STATE.NONE;
-
-		document.removeEventListener( 'mousemove', mousemove );
-		document.removeEventListener( 'mouseup', mouseup );
-		_this.dispatchEvent( endEvent );
-
-	}
-
-	function mousewheel( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		var delta = 0;
-
-		if ( event.wheelDelta ) {
-
-			// WebKit / Opera / Explorer 9
-
-			delta = event.wheelDelta / 40;
-
-		} else if ( event.detail ) {
-
-			// Firefox
-
-			delta = - event.detail / 3;
-
-		}
-
-		_zoomStart.y += delta * 0.01;
-		_this.dispatchEvent( startEvent );
-		_this.dispatchEvent( endEvent );
-
-	}
-
-	function touchstart( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		switch ( event.touches.length ) {
-
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
-
-			default: // 2 or more
-				_state = STATE.TOUCH_ZOOM_PAN;
-				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
-
-				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
-				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
-				_panStart.copy( getMouseOnScreen( x, y ) );
-				_panEnd.copy( _panStart );
-				break;
-
-		}
-
-		_this.dispatchEvent( startEvent );
-
-	}
-
-	function touchmove( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		switch ( event.touches.length ) {
-
-			case 1:
-				_movePrev.copy( _moveCurr );
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				break;
-
-			default: // 2 or more
-				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
-
-				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
-				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
-				_panEnd.copy( getMouseOnScreen( x, y ) );
-				break;
-
-		}
-
-	}
-
-	function touchend( event ) {
-
-		if ( _this.enabled === false ) return;
-
-		switch ( event.touches.length ) {
-
-			case 0:
-				_state = STATE.NONE;
-				break;
-
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
-
-		}
-
-		_this.dispatchEvent( endEvent );
-
-	}
-
-	function contextmenu( event ) {
-
-		event.preventDefault();
-
-	}
-
-	this.dispose = function() {
-
-		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
-		this.domElement.removeEventListener( 'mousedown', mousedown, false );
-		this.domElement.removeEventListener( 'mousewheel', mousewheel, false );
-		this.domElement.removeEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox
-
-		this.domElement.removeEventListener( 'touchstart', touchstart, false );
-		this.domElement.removeEventListener( 'touchend', touchend, false );
-		this.domElement.removeEventListener( 'touchmove', touchmove, false );
-
-		document.removeEventListener( 'mousemove', mousemove, false );
-		document.removeEventListener( 'mouseup', mouseup, false );
-
-		window.removeEventListener( 'keydown', keydown, false );
-		window.removeEventListener( 'keyup', keyup, false );
-
-	};
-
-	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
-	this.domElement.addEventListener( 'mousedown', mousedown, false );
-	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
-	this.domElement.addEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox
-
-	this.domElement.addEventListener( 'touchstart', touchstart, false );
-	this.domElement.addEventListener( 'touchend', touchend, false );
-	this.domElement.addEventListener( 'touchmove', touchmove, false );
-
-	window.addEventListener( 'keydown', keydown, false );
-	window.addEventListener( 'keyup', keyup, false );
-
-	this.handleResize();
-
-	// force an update at start
-	this.update();
-
-};
-
-THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
--- a/Resources/Nexus/js/meco.js	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,954 +0,0 @@
-/*
-Nexus
-Copyright (c) 2012-2018, Visual Computing Lab, ISTI - CNR
-All rights reserved.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-onmessage = function(job) {
-	if(typeof(job.data) == "string") return;
-	var node = job.data.node;
-	var signature = job.data.signature;
-	var patches = job.data.patches;
-//	var now =new Date().getTime();
-
-	var size;
-	if(!node.buffer) return;
-	else size = node.buffer.byteLength;
-	var buffer;
-	for(var i =0 ; i < 1; i++) {
-		var coder = new MeshCoder(signature, node, patches);
-		buffer = coder.decode(node.buffer);
-	}
-	node.buffer = buffer;
-	node.owner = job.owner;
-//	var elapsed = new Date().getTime() - now;
-//	var t = node.nface;
-//	console.log("Z Time: " + elapsed + " Size: " + size + " KT/s " + (t/(elapsed)) + " Mbps " + (8*1000*node.buffer.byteLength/elapsed)/(1<<20));
-	postMessage(node);
-}
-
-//	actually bitstreams expects a little endian uin64 type. convert it to 2 uint32
-
-BitStream = function(array) { 
-	this.a = array;
-	for(var i = 0; i < array.length; i += 2) {
-		var s = array[i];
-		array[i] = array[i+1];
-		array[i+1] = s;
-	}
-	this.position = 0;
-	this.bitsPending = 0;
-};
- 
-BitStream.prototype = { 
-	read: function(bits) {
-		var bitBuffer = 0;
-		while(bits > 0) {
-			var partial;
-			var bitsConsumed;
-			if (this.bitsPending > 0) {
-				var byte = (this.a[this.position - 1] & (0xffffffff >>> (32 - this.bitsPending)))>>>0;
-				bitsConsumed = Math.min(this.bitsPending, bits);
-				this.bitsPending -= bitsConsumed;
-				partial = byte >>> this.bitsPending;
-			} else {
-				bitsConsumed = Math.min(32, bits);
-				this.bitsPending = 32 - bitsConsumed;
-				partial = this.a[this.position++] >>> this.bitsPending;
-			}
-			bits -= bitsConsumed;
-			bitBuffer = ((bitBuffer << bitsConsumed) | partial)>>>0;
-		}
-		return bitBuffer; 
-	},
-	replace: function(bits, value) {
-		//zero last part
-		value = (value & (0xffffffff >>> 32 - bits)) >>> 0;
-		value = (value | read(bits)) >>> 0;
-		return value;
-	}
-};
-
-Stream = function(buffer) {
-	this.data = buffer;
-	this.buffer = new Uint8Array(buffer);
-	this.pos = 0;
-}
-
-Stream.prototype = {
-	readChar: function() {
-		var c = this.buffer[this.pos++];
-		if(c > 127) c -= 256;
-		return c;
-	},
-	readUChar: function() {
-		return this.buffer[this.pos++];
-	},	
-	readInt: function() {
-		var c = this.buffer[this.pos + 3]
-		c <<= 8;
-		c |= this.buffer[this.pos + 2];
-		c <<= 8;
-		c |= this.buffer[this.pos + 1];
-		c <<= 8;
-		c |= this.buffer[this.pos + 0];
-		this.pos += 4;
-		return c;
-	},
-	readArray: function(n) {
-		var a = this.buffer.subarray(this.pos, this.pos+n);
-		this.pos += n;
-		return a;
-	},
-	readBitStream:function() {
-		var n = this.readInt();
-		var pad = this.pos & 0x3;
-		if(pad != 0)
-			this.pos += 4 - pad;
-		var b = new BitStream(new Uint32Array(this.data, this.pos, n*2));
-		this.pos += n*8;
-		return b;
-	}
-};
-
-function Tunstall(wordsize, lookup_size) {
-	this.wordsize = wordsize? wordsize : 8;
-	this.lookup_size = lookup_size? lookup_size : 8;
-}
-
-Tunstall.prototype = {
-	decompress: function(stream) {
-		var nsymbols = stream.readUChar();
-		this.probabilities = stream.readArray(nsymbols*2);
-		this.createDecodingTables();
-		var size = stream.readInt();
-		var data = new Uint8Array(size);
-		var compressed_size = stream.readInt();
-		var compressed_data = stream.readArray(compressed_size);
-		if(size)
-			this._decompress(compressed_data, compressed_size, data, size);
-		return data;
-	}, 
-
-	createDecodingTables: function() {
-		//read symbol,prob,symbol,prob as uchar.
-		//Here probabilities will range from 0 to 0xffff for better precision
-
-		var n_symbols = this.probabilities.length/2;
-		if(n_symbols <= 1) return;
-
-		var queues = []; //array of arrays
-		var buffer = []; 
-
-		//initialize adding all symbols to queues
-		for(var i = 0; i < n_symbols; i++) {
-			var symbol = this.probabilities[i*2];
-			var s = [(this.probabilities[i*2+1])<<8, buffer.length, 1]; //probability, position in the buffer, length
-			queues[i] = [s];
-			buffer.push(this.probabilities[i*2]); //symbol
-		}
-		var dictionary_size = 1<<this.wordsize;
-		var n_words = n_symbols;
-		var table_length = n_symbols;
-
-		//at each step we grow all queues using the most probable sequence
-		while(n_words < dictionary_size - n_symbols +1) {
-			//Should use a stack or something to be faster, but we have few symbols
-			//find highest probability word
-			var best = 0;
-			var max_prob = 0;
-			for(var i = 0; i < n_symbols; i++) {
-				var p = queues[i][0][0]; //front of queue probability.
-				if(p > max_prob) {
-					best = i;
-					max_prob = p;
-				}
-			}
-			var symbol = queues[best][0];
-			var pos = buffer.length;
-			
-			for(var i = 0; i < n_symbols; i++) {
-				var sym = this.probabilities[i*2];
-				var prob = this.probabilities[i*2+1]<<8;
-				var s = [((prob*symbol[0])>>>16), pos, symbol[2]+1]; //combine probabilities, keep track of buffer, keep length of queue
-
-				for(var k  = 0; k < symbol[2]; k++)
-					buffer[pos+k] = buffer[symbol[1] + k]; //copy sequence of symbols
-
-				pos += symbol[2];
-				buffer[pos++] = sym; //append symbol
-				queues[i].push(s);
-			}
-			table_length += (n_symbols-1)*(symbol[2] + 1) +1; 
-			n_words += n_symbols -1;
-			queues[best].shift(); //remove first thing
-		}
-
-		this.index = new Uint32Array(n_words);
-		this.lengths = new Uint32Array(n_words);
-		this.table = new Uint8Array(table_length);
-		var word = 0;
-		var pos = 0;
-		for(i = 0; i < queues.length; i++) {
-			var queue = queues[i];
-			for(var k = 0; k < queue.length; k++) {
-				var s = queue[k];
-				this.index[word] = pos;
-				this.lengths[word] = s[2]; //length
-				word++;
-
-				for(var j = 0; j < s[2]; j++)
-					this.table[pos + j] = buffer[s[1] + j]; //buffer of offset
-				pos += s[2]; //length
-			}
-		}
-	},
-	_decompress: function(input, input_size, output, output_size) {
-		var input_pos = 0;
-		var output_pos = 0;
-		if(this.probabilities.length == 2) {
-			var symbol = this.probabilities[0];
-			for(var i = 0; i < output_size; i++)
-				output[i] = symbol;
-			return;
-		}
-
-		while(input_pos < input_size-1) {
-			var symbol = input[input_pos++];
-			var start = this.index[symbol];
-			var end = start + this.lengths[symbol];
-			for(var i = start; i < end; i++) 
-				output[output_pos++] = this.table[i];
-		}
-
-		//last symbol might override so we check.
-		var symbol = input[input_pos];
-		var start = this.index[symbol];
-		var end = start + output_size - output_pos;
-		var length = output_size - output_pos;
-		for(var i = start; i < end; i++)
-			output[output_pos++] = this.table[i];
-
-		return output;
-	}
-}
-
-ZPoint = function(h, l) {
-	this.lo = l;
-	this.hi = h;
-}
-
-ZPoint.prototype = {
-	copy: function(z) {
-		this.lo = z.lo;
-		this.hi = z.hi;
-	},
-	setBit: function(d) {
-		if(d < 32)
-			this.lo = (this.lo | (1<<d))>>>0;
-		else
-			this.hi = (this.hi | (1<<(d-32)))>>>0;
-	},
-	toPoint: function(min, step, buffer, pos) {
-		var x = this.morton3(this.lo, this.hi>>>1);
-		var y = this.morton3(this.lo>>>1, this.hi>>>2);
-		var z = this.morton3((this.lo>>>2 | (this.hi & 0x1)<<30 )>>>0, this.hi>>>3); //first hi bit needs to go into low.
-
-		buffer[pos+0] = (x + min[0])*step;
-		buffer[pos+1] = (y + min[1])*step;
-		buffer[pos+2] = (z + min[2])*step;
-	},
-	morton3: function(lo, hi) {
-		lo = ( lo                & 0x49249249)>>>0;
-        lo = ((lo | (lo >>> 2 )) & 0xc30c30c3)>>>0;
-        lo = ((lo | (lo >>> 4 )) & 0x0f00f00f)>>>0;
-        lo = ((lo | (lo >>> 8 )) & 0xff0000ff)>>>0;
-        lo = ((lo | (lo >>> 16)) & 0x0000ffff)>>>0;
-
-		hi = ( hi                & 0x49249249)>>>0;
-        hi = ((hi | (hi >> 2 ))  & 0xc30c30c3)>>>0;
-        hi = ((hi | (hi >> 4 ))  & 0x0f00f00f)>>>0;
-        hi = ((hi | (hi >> 8 ))  & 0xff0000ff)>>>0;
-        hi = ((hi | (hi >> 16))  & 0x0000ffff)>>>0;
-
-		return ((hi<<11) | lo)>>>0;
-	}
-};
-
-//node is an object with nvert, nface
-//patches is an array of offsets in the index, triangle are grouped by those offsets
-//signature tells wether mesh has indices, normals, colors, etc. {'colors': true, 'normals':true, 'indices': true }
-
-function MeshCoder(signature, node, patches) {
-	this.sig = signature;
-	this.node = node;
-	this.patches = patches;
-
-	this.last = new Int32Array(this.node.nvert);
-	this.last_count = 0;
-}
-
-MeshCoder.prototype = {
-	//assumes input is an ArrayBuffer
-decode: function(input) {
-	var t = this;
-
-	t.buffer = new ArrayBuffer(t.node.nvert*(12 + t.sig.texcoords*8 + t.sig.normals*6 + t.sig.colors*4) + t.node.nface*t.sig.indices*6);
-
-	var size = t.node.nvert*12; //float
-	t.coords = new Float32Array(t.buffer, 0, t.node.nvert*3);
-
-	if(t.sig.texcoords) {
-		t.texcoords = new Float32Array(t.buffer, size, t.node.nvert*2);
-		size += t.node.nvert*8; //float
-	}
-	if(t.sig.normals) {
-		t.normals = new Int16Array(t.buffer, size, t.node.nvert*3);
-		size += t.node.nvert*6; //short
-	}
-	if(t.sig.colors) {
-		t.colors = new Uint8ClampedArray(t.buffer, size, t.node.nvert*4);
-		size += t.node.nvert*4; //chars
-	}
-	if(t.sig.indices) {
-		t.faces = new Uint16Array(t.buffer, size, t.node.nface*3);
-		size += t.node.nface*6; //short
-	}
-
-	t.stream = new Stream(input);
-	
-	t.stack = new Float32Array(12); //min0, min1, min2, step, tmin0, tmin1, tstep
-
-	t.stack[3] = t.stream.readInt();
-	t.stack[4] = t.stream.readInt();
-	t.stack[5] = t.stream.readInt();
-
-	t.coord_q = t.stream.readChar();
-	t.coord_bits = t.stream.readChar()*3;
-
-	t.stack[6] = Math.pow(2.0, t.coord_q);
-
-	if(t.sig.texcoords) {
-		t.stack[9] = t.stream.readInt();
-		t.stack[10] = t.stream.readInt();
-
-		t.texcoord_q = t.stream.readChar();
-		t.texcoord_bits = t.stream.readChar()*2;
-		t.stack[11] = Math.pow(2.0, t.texcoord_q);
-	}
-
-	if(t.sig.indices) {
-		t.decodeFaces();
-
-//	var faces = window.performance.now() - start;
-//	start += faces;
-	} else {
-		t.decodeCoordinates();
-		
-//	var coords = window.performance.now() - start;
-//	start += coords;
-	}
-
-	if(t.sig.normals)
-		t.decodeNormals();
-//	var normals = window.performance.now() - start;
-//	start += normals;
-	if(t.sig.colors)
-		t.decodeColors();
-//	var colors = window.performance.now() - start;
-//	start += colors;
-//	console.log("Decode " + (faces + coords + normals + colors) + "ms. C: " + coords + " F: " + faces + " N: " + normals + " C: " + colors);
-
-	return t.buffer;
-},
-
-decodeCoordinates: function() {
-	var t = this;
-	t.min = [t.stack[3], t.stack[4], t.stack[5]];
-
-	var step = Math.pow(2.0, t.coord_q);
-
-	var hi_bits = Math.max(t.coord_bits - 32, 0);
-	var lo_bits = Math.min(t.coord_bits, 32);
-
-    var bitstream = t.stream.readBitStream();
-
-	var tunstall = new Tunstall;
-	var diffs = tunstall.decompress(t.stream);	
-
-	var hi = bitstream.read(hi_bits);
-	var lo = bitstream.read(lo_bits);
-	var p = new ZPoint(hi, lo);
-	var count = 0;
-	p.toPoint(t.min, step, t.coords, count);
-	count += 3;
-    for(var i = 1; i < t.node.nvert; i++) {
-		var d = diffs[i-1];
-		p.setBit(d, 1);
-		if(d > 32) {
-			p.hi = (p.hi & ~((1<<(d-32))-1))>>>0;
-			var e = bitstream.read(d - 32);
-			p.hi = (p.hi | e)>>>0;
-			p.lo = bitstream.read(32);
-		} else {
-
-			if(d == 32) {
-				p.lo = bitstream.read(d);
-			} else {
-				var e = bitstream.read(d);
-				p.lo = (p.lo & ~((1<<d) -1))>>>0;
-				p.lo = (p.lo | e)>>>0;
-			}
-		}
-		p.toPoint(t.min, step, t.coords, count);
-		count += 3;
-	}
-},
-
-decodeFaces: function() {
-	if(!this.node.nface) return;
-	
-	this.vertex_count = 0;
-	var start = 0;
-	for(var p = 0; p < this.patches.length; p++) {
-		var end = this.patches[p];
-		this.decodeConnectivity(end - start, start*3);
-		start = end;
-	}
-	//dequantize positions
-	var tot = this.node.nvert*3;
-	var coords = this.coords;
-	var stack = this.stack;
-	for(var i = 0; i < tot; ) {
-		coords[i] = (coords[i] + stack[3])*stack[6]; i++;
-		coords[i] = (coords[i] + stack[4])*stack[6]; i++;
-		coords[i] = (coords[i] + stack[5])*stack[6]; i++;
-	}
-	if(this.sig.texcoords) {
-		var t_tot = this.node.nvert*2;
-		var t_coords = this.texcoords;
-		for(var i = 0; i < tot; ) {
-			t_coords[i] = (t_coords[i] + stack[9])*stack[11]; i++;
-			t_coords[i] = (t_coords[i] + stack[10])*stack[11]; i++;
-		}		
-	}
-},
-
-decodeNormals: function() {
-	var norm_q = this.stream.readChar();
-
-	var dtunstall = new Tunstall;
-	var diffs = dtunstall.decompress(this.stream);
-
-	var stunstall = new Tunstall;
-	var signs = stunstall.decompress(this.stream);
-	var bitstream = this.stream.readBitStream();
-
-	var side = (1<<(16 - norm_q))>>>0;
-	var diffcount = 0;
-	var signcount = 0;
-
-	if(!this.sig.indices) {
-		for(var k = 0; k < 2; k++) {
-			var on = 0;
-			for(var i = 0; i < this.node.nvert; i++) {
-				var d = this.decodeDiff(diffs[diffcount++], bitstream);
-				on = on + d;
-				this.normals[3*i + k] = on*side;
-			}
-		}
-		for(var i = 0; i < this.node.nvert; i++) {
-			var offset = i*3;
-			var x = this.normals[offset + 0];
-			var y = this.normals[offset + 1];
-			var z = 32767.0*32767.0 - x*x - y*y;
-
-        	if(z < 0) z = 0;
-        	z = Math.sqrt(z);
-			if(z > 32767) z = 32767;
-			if(signs[i] == 0)
-				z = -z;
-			this.normals[offset + 2] = z;
-		}
-		return;
-	}
-
-	var boundary = this.markBoundary();
-	this.computeNormals();
-
-	if(this.sig.texcoords) //hack, fixing normals makes it worse actually
-		return;
-
-	var stat = 0;
-	//get difference between original and predicted
-	for(var i = 0; i < this.node.nvert; i++) {
-		if(!boundary[i]) continue;
-		var offset = i*3;
-		var x = (this.normals[offset + 0]/side);
-		var y = (this.normals[offset + 1]/side);
-		var dx = this.decodeDiff(diffs[diffcount++], bitstream);
-		var dy = this.decodeDiff(diffs[diffcount++], bitstream);
-		x = (x + dx)*side;
-		y = (y + dy)*side;
-
-        var z = 32767.0*32767.0 - x*x - y*y;
-
-        if(z < 0) z = 0;
-        z = Math.sqrt(z);
-        //sign
-        if(z > 32767.0) z = 32767.0;
-        var signbit = signs[signcount++];
-//        if(this.normals[offset+2] < 0 != signbit)
-        if((this.normals[offset+2] < 0 && signbit == 0) || (this.normals[offset+2] > 0 && signbit == 1))
-        	z = -z;
-		this.normals[offset + 0] = x;
-		this.normals[offset + 1] = y;
-        this.normals[offset + 2] = z;
-	}
-},
-
-decodeColors: function() {
-	var color_q = [];
-	for(var k = 0; k < 4; k++)
-		color_q[k] = this.stream.readChar();
-
-	var diffs = [];
-	for(var k = 0; k < 4; k++) {
-		var tunstall = new Tunstall;;
-		diffs[k] = tunstall.decompress(this.stream);
-	}
-	var bitstream = this.stream.readBitStream();
-
-	var count = 0;
-	if(this.sig.indices) {
-		for(var i = 0; i < this.node.nvert; i++) {
-			var last  = this.last[i]*4;
-			var offset = i*4;
-
-			for(var k = 0; k < 4; k++) {
-				var c = this.decodeDiff(diffs[k][count], bitstream);
-
-				if(last >= 0)
-					c += this.colors[last + k];
-				this.colors[offset] = c;
-				offset++;
-			}
-			count++;
-		}
-	} else {
-		for(var k = 0; k < 4; k++)
-			this.colors[k] = this.decodeDiff(diffs[k][count], bitstream); 
-		count++;
-
-		var offset = 4;
-		for(var i = 1; i < this.node.nvert; i++) {
-			for(var k = 0; k < 4; k++) {
-				var d = this.decodeDiff(diffs[k][count], bitstream); 
-				this.colors[offset] = this.colors[offset-4] + d;
-				offset ++;
-			}
-			count++;
-		}
-	}
-
-	var steps = [];
-	for(var k = 0; k < 4; k++)
-		steps[k] = (1<<(8 - color_q[k]));
-
-	//convert to rgb
-	for(var i = 0; i < this.node.nvert; i++) {
-		var offset = i*4;
-
-		var e0 = this.colors[offset + 0] * steps[0];
-		var e1 = this.colors[offset + 1] * steps[1];
-		var e2 = this.colors[offset + 2] * steps[2];
-
-		this.colors[offset + 0] = (e2 + e0)&0xff;
-		this.colors[offset + 1] = e0;
-		this.colors[offset + 2] = (e1 + e0)&0xff;
-	}
-},
-
-//how to determine if a vertex is a boundary without topology:
-//for each edge a vertex is in, add or subtract the id of the other vertex depending on order
-//for internal vertices sum is zero.
-//unless we have strange configurations and a lot of sfiga, zero wont happen. //TODO think about this
-markBoundary: function() {
-//	var boundary = new Uint8Array(this.node.nvert);
-	var count = new Uint32Array(this.node.nvert);
-
-	var offset = 0;
-	for(var i = 0; i < this.node.nface; i++) {
-		count[this.faces[offset + 0]] += this.faces[offset + 1] - this.faces[offset + 2];
-		count[this.faces[offset + 1]] += this.faces[offset + 2] - this.faces[offset + 0];
-		count[this.faces[offset + 2]] += this.faces[offset + 0] - this.faces[offset + 1];
-		offset += 3;
-	}
-	return count;
-//	for(var i = 0; i < this.node.nvert; i++)
-//		if(count[i] != 0)
-//			boundary[i] = true;
-//	return boundary;
-},
-
-norm: function(buffer, a, b, c) { //a b c offsets in the buffer
-	var ba0 = buffer[b+0] - buffer[a+0];
-	var ba1 = buffer[b+1] - buffer[a+1];
-	var ba2 = buffer[b+2] - buffer[a+2];
-
-	var ca0 = buffer[c+0] - buffer[a+0];
-	var ca1 = buffer[c+1] - buffer[a+1];
-	var ca2 = buffer[c+2] - buffer[a+2];
-
-	var p = [];
-	p[0] = ba1*ca2 - ba2*ca1;
-	p[1] = ba2*ca0 - ba0*ca2;
-	p[2] = ba0*ca1 - ba1*ca0;
-	return p;
-},
-
-normalize: function(buffer, offset) {
-	var x = buffer[offset + 0];
-	var y = buffer[offset + 1];
-	var z = buffer[offset + 2];
-	var n = Math.sqrt(x*x + y*y + z*z);
-	if(n > 0) {
-		buffer[offset + 0] = x/n;
-		buffer[offset + 1] = y/n;
-		buffer[offset + 2] = z/n;
-	}
-},
-
-computeNormals:function() {
-	var tmp_normals = new Float32Array(this.node.nvert*3);
-
-	var offset = 0;
-	for(var i = 0; i < this.node.nface; i++) {
-		var a = 3*this.faces[offset + 0];
-		var b = 3*this.faces[offset + 1];
-		var c = 3*this.faces[offset + 2];
-
-		var buffer = this.coords;
-		var ba0 = buffer[b+0] - buffer[a+0];
-		var ba1 = buffer[b+1] - buffer[a+1];
-		var ba2 = buffer[b+2] - buffer[a+2];
-
-		var ca0 = buffer[c+0] - buffer[a+0];
-		var ca1 = buffer[c+1] - buffer[a+1];
-		var ca2 = buffer[c+2] - buffer[a+2];
-
-		var n0 = ba1*ca2 - ba2*ca1;
-		var n1 = ba2*ca0 - ba0*ca2;
-		var n2 = ba0*ca1 - ba1*ca0;
-
-		tmp_normals[a + 0] += n0;
-		tmp_normals[a + 1] += n1;
-		tmp_normals[a + 2] += n2;
-		tmp_normals[b + 0] += n0;
-		tmp_normals[b + 1] += n1;
-		tmp_normals[b + 2] += n2;
-		tmp_normals[c + 0] += n0;
-		tmp_normals[c + 1] += n1;
-		tmp_normals[c + 2] += n2;
-		offset += 3;
-	}
-
-	//normalize
-	var offset = 0;
-	for(var i = 0; i < this.node.nvert; i++) {
-		var x = tmp_normals[offset + 0];
-		var y = tmp_normals[offset + 1];
-		var z = tmp_normals[offset + 2];
-		var n = Math.sqrt(x*x + y*y + z*z);
-		if(n > 0) {
-			tmp_normals[offset + 0] = x/n;
-			tmp_normals[offset + 1] = y/n;
-			tmp_normals[offset + 2] = z/n;
-		}
-		this.normals[offset + 0] = tmp_normals[offset + 0]*32767;
-		this.normals[offset + 1] = tmp_normals[offset + 1]*32767;
-		this.normals[offset + 2] = tmp_normals[offset + 2]*32767;
-		offset += 3;
-	}
-},
-
-decodeDiff: function(diff, bitstream) {
-	var val;
-	if(diff == 0) {
-		val = 1;
-	} else {
-		val = 1<<(diff);
-		val |= bitstream.read(diff);
-	};
-	val--; //vall is always >= 1
-	if(val & 0x1)
-		val = -((val+1)>>1);
-	else
-		val = val>>1;
-	return val;
-},
-
-/* an edge is:   uint16_t face, uint16_t side, uint32_t prev, next, bool deleted
-I do not want to create millions of small objects, I will use aUint32Array.
-Problem is how long, sqrt(nface) we will over blow using nface.
-*/
-
-decodeConnectivity: function(length, start) {
-
-	var t = this;
-	var ctunstall = new Tunstall;
-	var clers = ctunstall.decompress(this.stream);
-	var cler_count = 0;
-
-	var dtunstall = new Tunstall;
-	var diffs = dtunstall.decompress(this.stream);
-	var diff_count = 0;
-
-	var tdiffs;
-	var tdiff_count = 0;
-	if(t.sig.texcoords) {
-		var ttunstall = new Tunstall;
-		tdiffs = ttunstall.decompress(this.stream);	
-	}
-
-	var bitstream = this.stream.readBitStream(bitstream);
-
-	var current_face = 0;          //keep track of connected component start
-	//t.vertex_count = 0;
-	var front = new Uint32Array(this.node.nface*18);
-	var front_count = 0; //count each integer so it's front_back*5
-	function addFront(_v0, _v1, _v2, _prev, _next) {
-		front[front_count++] = _v0;
-		front[front_count++] = _v1;
-		front[front_count++] = _v2;
-		front[front_count++] = _prev;
-		front[front_count++] = _next;
-		front[front_count++] = 0; //deleted
-	}
-	function _next(t) {
-		t++;
-		if(t == 3) t = 0;
-		return t;
-	}
-	function _prev(t) {
-		t--;
-		if(t == -1) t = 2;
-		return t;
-	}
-
-	var delayed = [];
-	var faceorder = [];
-
-	var faces_count = start; //count indices in this.faces array
-	var totfaces = length;
-//	var estimated = [0, 0, 0]; //no! use stack.
-	var stack = this.stack;
-	var coords = this.coords;
-	var texcoords = this.texcoords;
-	var hasTexCoords = t.sig.texcoords;
-
-	while(totfaces > 0) {
-		if(!faceorder.length && !delayed.length) {
-			if(current_face == this.node.nface) break; //no more faces to encode exiting
-
-			stack[0] = stack[1] = stack[2] = 0;
-			stack[7] = stack[8] = 0; //texcoords
-			var last_index = -1;
-			var index = [];
-			for(var k = 0; k < 3; k++) {
-				this.last[this.last_count++] = last_index;
-				var diff = diffs[diff_count++];
-				var tdiff = diff && hasTexCoords? tdiffs[tdiff_count++] : 0;
-				var v = this.decodeVertex(bitstream, diff, tdiff);
-				index[k] = v; 
-				this.faces[faces_count++] = v;
-				stack[0] = coords[v*3];
-				stack[1] = coords[v*3+1];
-				stack[2] = coords[v*3+2]; 
-				if(t.sig.texcoords) {
-					stack[7] = texcoords[v*2];
-					stack[8] = texcoords[v*2+1];
-				}
-				last_index = v;
-			}
-			var current_edge = front_count;
-			for(var k = 0; k < 3; k++) {
-				faceorder.push(front_count);
-				front[front_count++] = index[_next(k)];
-				front[front_count++] = index[_prev(k)]; 
-				front[front_count++] = index[k];
-				front[front_count++] = current_edge + _prev(k)*6;
-				front[front_count++] = current_edge + _next(k)*6;
-				front_count++;
-//				addFront(index[_next(k)], index[_prev(k)], index[k], current_edge + _prev(k)*6, current_edge + _next(k)*6);
-			}
-			current_face++;
-			totfaces--;
-			continue;
-		}
-		var f;
-		if(faceorder.length) 
-			f = faceorder.shift();
-		else 
-			f = delayed.pop();
-		
-		var edge_start = f;
-
-		if(front[edge_start + 5]) continue; //deleted
-		front[edge_start + 5] = 1; //set edge as deleted anyway
-
-		var c = clers[cler_count++];
-		if(c == 4) continue; //BOUNDARY
-
-		var v0   = front[edge_start + 0];
-		var v1   = front[edge_start + 1];
-		var v2   = front[edge_start + 2];
-		var prev = front[edge_start + 3];
-		var next = front[edge_start + 4];
-
-		var first_edge = front_count; //points to new edge to be inserted
-		var opposite = -1;
-		if(c == 0) { //VERTEX
-			//predict position based on v0, v1 and v2
-			for(var k = 0; k < 3; k++) 
-				stack[k] = coords[v0*3 + k] + coords[v1*3 + k] - coords[v2*3 + k];
-
-			if(hasTexCoords)
-				for(var k = 0; k < 2; k++)
-					stack[7+k] = texcoords[v0*2 + k]  + texcoords[v1*2 + k] - texcoords[v2*2 + k];
-			
-			var diff = diffs[diff_count++];
-			var tdiff = diff && hasTexCoords? tdiffs[tdiff_count++] : 0;
-			opposite = this.decodeVertex(bitstream, diff, tdiff);
-			if(diff != 0)
-				this.last[this.last_count++] = v1;
-
-			front[prev + 4] = first_edge;
-			front[next + 3] = first_edge + 6;
-			faceorder.unshift(front_count);
-
-			front[front_count++] = v0;
-			front[front_count++] = opposite;
-			front[front_count++] = v1;
-			front[front_count++] = prev;
-			front[front_count++] = first_edge+6;
-			front_count++; 
-//			addFront(v0, opposite, v1, prev, first_edge + 6);
-
-			faceorder.push(front_count);
-
-			front[front_count++] = opposite;
-			front[front_count++] = v1;
-			front[front_count++] = v0;
-			front[front_count++] = first_edge; 
-			front[front_count++] = next;
-			front_count++; 
-//			addFront(opposite, v1, v0, first_edge, next);
-
-		} else if(c == 3) { //END
-			front[prev + 5] = 1;
-			front[next + 5] = 1;
-			front[front[prev + 3] + 4] = front[next + 4];
-			front[front[next + 4] + 3] = front[prev + 3];
-			opposite = front[prev + 0];
-
-		} else if(c == 1) { //LEFT
-			front[prev + 5] = 1; //deleted
-			front[front[prev + 3] + 4] = first_edge;
-			front[next + 3] = first_edge;
-			opposite = front[prev + 0];
-
-			faceorder.unshift(front_count);
-
-			front[front_count++] = opposite;
-			front[front_count++] = v1;
-			front[front_count++] = v0;
-			front[front_count++] = front[prev +3];
-			front[front_count++] = next;
-			front_count++; 
-//			addFront(opposite, v1, v0, front[prev + 3], next);
-
-		} else if(c == 2) { //RIGHT
-			front[next + 5] = 1;
-			front[front[next + 4] + 3] = first_edge;
-			front[prev + 4] = first_edge;
-			opposite = front[next + 1];
-
-
-			faceorder.unshift(front_count);
-
-			front[front_count++] = v0;
-			front[front_count++] = opposite;
-			front[front_count++] = v1;
-			front[front_count++] = prev;
-			front[front_count++] = front[next+4];
-			front_count++; 
-//			addFront(v0, opposite, v1, prev, front[next + 4]);
-
-		} else if(c == 5) { //DELAY
-			front[edge_start + 5] = 0;
-			delayed.push(edge_start);
-			continue;
-		}
-		this.faces[faces_count++] = v1;
-		this.faces[faces_count++] = v0;
-		this.faces[faces_count++] = opposite;
-		totfaces--;
-	}
-},
-   
-decodeVertex: function(bitstream, diff, tdiff) {
-	if(diff == 0) 
-		return bitstream.read(16);
-
-	var v = this.vertex_count++;
-
-	var max = 1<<(diff-1);
-
-	for(var k = 0; k < 3; k++) {
-		var d = bitstream.read(diff) - max;
-		this.coords[v*3+k] = this.stack[k] + d; //stack 0-3 is used as extimated
-	}
-	if(this.sig.texcoords) {
-		var tmax = 1<<(tdiff-1);
-		for(var k = 0; k < 2; k++) {
-			var d = bitstream.read(tdiff) - tmax;
-			this.texcoords[v*2+k] = this.stack[7+k] + d; //stack 7-9 is used as extimated
-		}
-	}
-	return v;
-},
-
-decodeDiff: function(diff, bitstream) {
-	var val;
-	if(diff == 0) {
-		return 0;
-	} 
-	val = 1<<diff;
-	val += bitstream.read(diff);
-
-
-	if(val & 0x1) 
-		val >>>= 1;
-	else 
-		val = -(val>>>1);
-
-	return val;
-}
-
-};
-
-var tot = 0;
--- a/Resources/Nexus/js/nexus.js	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1257 +0,0 @@
-/*
-Nexus
-Copyright (c) 2012-2018, Visual Computing Lab, ISTI - CNR
-All rights reserved.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-Nexus = function() {
-
-/* WORKER INITIALIZED ONCE */
-
-var meco;
-var corto;
-
-var scripts = document.getElementsByTagName('script');
-var i, j, k;
-var path;
-for(i = 0; i < scripts.length; i++) {
-	var attrs = scripts[i].attributes;
-	for(j = 0; j < attrs.length; j++) {
-		var a = attrs[j];
-		if(a.name != 'src') continue;
-		if(!a.value) continue;
-		if(a.value.search('nexus.js') >= 0) {
-			path = a.value;
-			break;
-		}
-	}
-}
-var meco = null;
-function loadMeco() {
-	meco = new Worker(path.replace('nexus.js', 'meco.js'));
-
-	meco.onerror = function(e) { console.log(e); }
-	meco.requests = {};
-	meco.count = 0;
-	meco.postRequest = function(sig, node, patches) {
-		var signature = {
-			texcoords: sig.texcoords ? 1 : 0,
-			colors   : sig.colors    ? 1 : 0,
-			normals  : sig.normals   ? 1 : 0,
-			indices  : sig.indices   ? 1 : 0
-		};
-		meco.postMessage({ 
-			signature:signature,
-			node:{ nface: node.nface, nvert: node.nvert, buffer:node.buffer, request:this.count}, 
-			patches:patches
-		});
-		this.requests[this.count++] = node;
-	};
-	meco.onmessage = function(e) {
-		var node = this.requests[e.data.request];
-		node.buffer = e.data.buffer;
-		readyNode(node);
-	};
-}
-
-var corto = null;
-
-function loadCorto() {
-	corto = new Worker(path.replace('nexus.js', 'corto.js'));
-	corto.requests = {};
-	corto.count = 0;
-	corto.postRequest = function(node) {
-		corto.postMessage({ buffer: node.buffer, request:this.count, rgba_colors: true, short_normals: true });
-		this.requests[this.count++] = node;
-	}
-	corto.onmessage = function(e) {
-		var node = this.requests[e.data.request];
-		node.buffer = e.data.buffer;
-		node.model = e.data.model;
-		readyNode(node);
-	};
-}
-
-/* UTILITIES */
-
-function getUint64(view) {
-	var s = 0;
-	var lo = view.getUint32(view.offset, true);
-	var hi = view.getUint32(view.offset + 4, true);
-	view.offset += 8;
-	return ((hi * (1 << 32)) + lo);
-}
-
-function getUint32(view) {
-	var s = view.getUint32(view.offset, true);
-	view.offset += 4;
-	return s;
-}
-
-function getUint16(view) {
-	var s = view.getUint16(view.offset, true);
-	view.offset += 2;
-	return s;
-}
-
-function getFloat32(view) {
-	var s = view.getFloat32(view.offset, true);
-	view.offset += 4;
-	return s;
-}
-
-/* MATRIX STUFF */
-
-function vecMul(m, v, r) {
-	var w = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15];
-
-	r[0] = (m[0]*v[0]  + m[4]*v[1]  + m[8 ]*v[2] + m[12 ])/w;
-	r[1] = (m[1]*v[0]  + m[5]*v[1]  + m[9 ]*v[2] + m[13 ])/w;
-	r[2] = (m[2]*v[0]  + m[6]*v[1]  + m[10]*v[2] + m[14])/w;
-}
-
-
-function matMul(a, b, r) {
-	r[ 0] = a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3];
-	r[ 1] = a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3];
-	r[ 2] = a[2]*b[0] + a[6]*b[1] + a[10]*b[2] + a[14]*b[3];
-	r[ 3] = a[3]*b[0] + a[7]*b[1] + a[11]*b[2] + a[15]*b[3];
-
-	r[ 4] = a[0]*b[4] + a[4]*b[5] + a[8]*b[6] + a[12]*b[7];
-	r[ 5] = a[1]*b[4] + a[5]*b[5] + a[9]*b[6] + a[13]*b[7];
-	r[ 6] = a[2]*b[4] + a[6]*b[5] + a[10]*b[6] + a[14]*b[7];
-	r[ 7] = a[3]*b[4] + a[7]*b[5] + a[11]*b[6] + a[15]*b[7];
-
-	r[ 8] = a[0]*b[8] + a[4]*b[9] + a[8]*b[10] + a[12]*b[11];
-	r[ 9] = a[1]*b[8] + a[5]*b[9] + a[9]*b[10] + a[13]*b[11];
-	r[10] = a[2]*b[8] + a[6]*b[9] + a[10]*b[10] + a[14]*b[11];
-	r[11] = a[3]*b[8] + a[7]*b[9] + a[11]*b[10] + a[15]*b[11];
-
-	r[12] = a[0]*b[12] + a[4]*b[13] + a[8]*b[14] + a[12]*b[15];
-	r[13] = a[1]*b[12] + a[5]*b[13] + a[9]*b[14] + a[13]*b[15];
-	r[14] = a[2]*b[12] + a[6]*b[13] + a[10]*b[14] + a[14]*b[15];
-	r[15] = a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15];
-}
-
-function matInv(m, t) {
-	var s = 1.0/(
-		m[12]* m[9]*m[6]*m[3]-m[8]*m[13]*m[6]*m[3]-m[12]*m[5]*m[10]*m[3]+m[4]*m[13]*m[10]*m[3]+
-		m[8]*m[5]*m[14]*m[3]-m[4]*m[9]*m[14]*m[3]-m[12]*m[9]*m[2]*m[7]+m[8]*m[13]*m[2]*m[7]+
-		m[12]*m[1]*m[10]*m[7]-m[0]*m[13]*m[10]*m[7]-m[8]*m[1]*m[14]*m[7]+m[0]*m[9]*m[14]*m[7]+
-		m[12]*m[5]*m[2]*m[11]-m[4]*m[13]*m[2]*m[11]-m[12]*m[1]*m[6]*m[11]+m[0]*m[13]*m[6]*m[11]+
-		m[4]*m[1]*m[14]*m[11]-m[0]*m[5]*m[14]*m[11]-m[8]*m[5]*m[2]*m[15]+m[4]*m[9]*m[2]*m[15]+
-		m[8]*m[1]*m[6]*m[15]-m[0]*m[9]*m[6]*m[15]-m[4]*m[1]*m[10]*m[15]+m[0]*m[5]*m[10]*m[15]
-	);
-
-	t[ 0] = (m[9]*m[14]*m[7]-m[13]*m[10]*m[7]+m[13]*m[6]*m[11]-m[5]*m[14]*m[11]-m[9]*m[6]*m[15]+m[5]*m[10]*m[15])*s;
-	t[ 1] = (m[13]*m[10]*m[3]-m[9]*m[14]*m[3]-m[13]*m[2]*m[11]+m[1]*m[14]*m[11]+m[9]*m[2]*m[15]-m[1]*m[10]*m[15])*s;
-	t[ 2] = (m[5]*m[14]*m[3]-m[13]*m[6]*m[3]+m[13]*m[2]*m[7]-m[1]*m[14]*m[7]-m[5]*m[2]*m[15]+m[1]*m[6]*m[15])*s;
-	t[ 3] = (m[9]*m[6]*m[3]-m[5]*m[10]*m[3]-m[9]*m[2]*m[7]+m[1]*m[10]*m[7]+m[5]*m[2]*m[11]-m[1]*m[6]*m[11])*s;
-
-	t[ 4] = (m[12]*m[10]*m[7]-m[8]*m[14]*m[7]-m[12]*m[6]*m[11]+m[4]*m[14]*m[11]+m[8]*m[6]*m[15]-m[4]*m[10]*m[15])*s;
-	t[ 5] = (m[8]*m[14]*m[3]-m[12]*m[10]*m[3]+m[12]*m[2]*m[11]-m[0]*m[14]*m[11]-m[8]*m[2]*m[15]+m[0]*m[10]*m[15])*s;
-	t[ 6] = (m[12]*m[6]*m[3]-m[4]*m[14]*m[3]-m[12]*m[2]*m[7]+m[0]*m[14]*m[7]+m[4]*m[2]*m[15]-m[0]*m[6]*m[15])*s;
-	t[ 7] = (m[4]*m[10]*m[3]-m[8]*m[6]*m[3]+m[8]*m[2]*m[7]-m[0]*m[10]*m[7]-m[4]*m[2]*m[11]+m[0]*m[6]*m[11])*s;
-
-	t[ 8] = (m[8]*m[13]*m[7]-m[12]*m[9]*m[7]+m[12]*m[5]*m[11]-m[4]*m[13]*m[11]-m[8]*m[5]*m[15]+m[4]*m[9]*m[15])*s;
-	t[ 9] = (m[12]*m[9]*m[3]-m[8]*m[13]*m[3]-m[12]*m[1]*m[11]+m[0]*m[13]*m[11]+m[8]*m[1]*m[15]-m[0]*m[9]*m[15])*s;
-	t[10] = (m[4]*m[13]*m[3]-m[12]*m[5]*m[3]+m[12]*m[1]*m[7]-m[0]*m[13]*m[7]-m[4]*m[1]*m[15]+m[0]*m[5]*m[15])*s;
-	t[11] = (m[8]*m[5]*m[3]-m[4]*m[9]*m[3]-m[8]*m[1]*m[7]+m[0]*m[9]*m[7]+m[4]*m[1]*m[11]-m[0]*m[5]*m[11])*s;
-
-	t[12] = (m[12]*m[9]*m[6]-m[8]*m[13]*m[6]-m[12]*m[5]*m[10]+m[4]*m[13]*m[10]+m[8]*m[5]*m[14]-m[4]*m[9]*m[14])*s;
-	t[13] = (m[8]*m[13]*m[2]-m[12]*m[9]*m[2]+m[12]*m[1]*m[10]-m[0]*m[13]*m[10]-m[8]*m[1]*m[14]+m[0]*m[9]*m[14])*s;
-	t[14] = (m[12]*m[5]*m[2]-m[4]*m[13]*m[2]-m[12]*m[1]*m[6]+m[0]*m[13]*m[6]+m[4]*m[1]*m[14]-m[0]*m[5]*m[14])*s;
-	t[15] = (m[4]*m[9]*m[2]-m[8]*m[5]*m[2]+m[8]*m[1]*m[6]-m[0]*m[9]*m[6]-m[4]*m[1]*m[10]+m[0]*m[5]*m[10])*s;
-}
-
-/* PRIORITY QUEUE */
-
-PriorityQueue = function(max_length) {
-	this.error = new Float32Array(max_length);
-	this.data = new Int32Array(max_length);
-	this.size = 0;
-}
-
-PriorityQueue.prototype = {
-	push: function(data, error) {
-		this.data[this.size] = data;
-		this.error[this.size] = error;
-		this.bubbleUp(this.size);
-		this.size++;
-	},
-
-	pop: function() {
-		var result = this.data[0];
-		this.size--;
-		if(this.size > 0) {
-			this.data[0] = this.data[this.size];
-			this.error[0] = this.error[this.size];
-			this.sinkDown(0);
-		}
-		return result;
-	},
-
-	bubbleUp: function(n) {
-		var data = this.data[n];
-		var error = this.error[n];
-		while (n > 0) {
-			var pN = ((n+1)>>1) -1; 
-			var pError = this.error[pN];
-			if(pError > error)
-				break;
-			//swap
-			this.data[n] = this.data[pN];
-			this.error[n] = pError;
-			this.data[pN] = data;
-			this.error[pN] = error;
-			n = pN;
-		}
-	},
-
-	sinkDown: function(n) {
-		var data = this.data[n];
-		var error = this.error[n];
-
-		while(true) {
-			var child2N = (n + 1) * 2;
-			var child1N = child2N - 1;
-			var swap = -1;
-			if (child1N < this.size) {
-				var child1Error = this.error[child1N];
-				if(child1Error > error)
-					swap = child1N;
-			}
-			if (child2N < this.size) {
-				var child2Error = this.error[child2N];
-				if (child2Error > (swap == -1 ? error : child1Error))
-					swap = child2N;
-			}
-
-			if (swap == -1) break;
-
-			this.data[n] = this.data[swap];
-			this.error[n] = this.error[swap];
-			this.data[swap] = data;
-			this.error[swap] = error;
-			n = swap;
-		}
-	}
-};
-
-
-/* HEADER AND PARSING */
-
-var padding = 256;
-var Debug = { 
-	nodes   : false,  //color each node
-	culling : false,  //visibility culling disabled
-	draw    : false,  //final rendering call disabled
-	extract : false,  //no extraction
-	request : false,  //no network requests
-	worker  : false   //no web workers
-};
-
-
-var glP = WebGLRenderingContext.prototype;
-var attrGlMap = [glP.NONE, glP.BYTE, glP.UNSIGNED_BYTE, glP.SHORT, glP.UNSIGNED_SHORT, glP.INT, glP.UNSIGNED_INT, glP.FLOAT, glP.DOUBLE];
-var attrSizeMap = [0, 1, 1, 2, 2, 4, 4, 4, 8];
-
-
-var targetError   = 2.0;
-var targetFps     = 15;
-var maxPending    = 3;
-var maxBlocked    = 3;
-var maxReqAttempt = 2;
-var maxCacheSize  = 512*(1<<20); //TODO DEBUG
-var drawBudget    = 5*(1<<20);
-
-
-/* MESH DEFINITION */
-
-Mesh = function() {
-	var t = this;
-	t.onLoad = null;
-	t.reqAttempt = 0;
-}
-
-Mesh.prototype = {
-	open: function(url) {
-		var mesh = this;
-		mesh.url = url;
-		mesh.httpRequest(
-			0,
-			88,
-			function() {
-//				console.log("Loading header for " + mesh.url);
-				var view = new DataView(this.response);
-				view.offset = 0;
-				mesh.reqAttempt++;
-				var header = mesh.importHeader(view);
-				if(!header) { 
-					console.log("Empty header!");
-					if(mesh.reqAttempt < maxReqAttempt) mesh.open(mesh.url + '?' + Math.random()); // BLINK ENGINE CACHE BUG PATCH
-					return null;
-				}
-				mesh.reqAttempt = 0;
-				for(i in header)
-					mesh[i] = header[i];
-				mesh.vertex = mesh.signature.vertex;
-				mesh.face = mesh.signature.face;
-				mesh.renderMode = mesh.face.index?["FILL", "POINT"]:["POINT"];
-				mesh.compressed = (mesh.signature.flags & (2 | 4)); //meco or corto
-				mesh.meco = (mesh.signature.flags & 2);
-				mesh.corto = (mesh.signature.flags & 4);
-				mesh.requestIndex();
-			},
-			function() { console.log("Open request error!");},
-			function() { console.log("Open request abort!");}
-		);
-	},
-
-	httpRequest: function(start, end, load, error, abort, type) {
-		if(!type) type = 'arraybuffer';
-		var r = new XMLHttpRequest();
-		r.open('GET', this.url, true);
-		r.responseType = type;
-		r.setRequestHeader("Range", "bytes=" + start + "-" + (end -1));
-		r.onload = function(){
-			switch (this.status){
-				case 0:
-//					console.log("0 response: server unreachable.");//returned in chrome for local files
-				case 206:
-//					console.log("206 response: partial content loaded.");
-					load.bind(this)();
-					break;
-				case 200:
-//					console.log("200 response: server does not support byte range requests.");
-			}
-		};
-		r.onerror = error;
-		r.onabort = abort;
-		r.send();
-		return r;
-	},
-
-	requestIndex: function() {
-		var mesh = this;
-		var end = 88 + mesh.nodesCount*44 + mesh.patchesCount*12 + mesh.texturesCount*68;
-		mesh.httpRequest(
-			88,
-			end,
-			function() { mesh.handleIndex(this.response); },
-			function() { console.log("Index request error!");},
-			function() { console.log("Index request abort!");}
-		);
-	},
-
-	handleIndex: function(buffer) {
-		var t = this;
-		var view = new DataView(buffer);
-		view.offset = 0;
-
-		var n = t.nodesCount;
-
-		t.noffsets  = new Uint32Array(n);
-		t.nvertices = new Uint32Array(n);
-		t.nfaces    = new Uint32Array(n);
-		t.nerrors   = new Float32Array(n);
-		t.nspheres  = new Float32Array(n*5);
-		t.nsize     = new Float32Array(n);
-		t.nfirstpatch = new Uint32Array(n);
-
-		for(i = 0; i < n; i++) {
-			t.noffsets[i] = padding*getUint32(view); //offset
-			t.nvertices[i] = getUint16(view);        //verticesCount
-			t.nfaces[i] = getUint16(view);           //facesCount
-			t.nerrors[i] = getFloat32(view);
-			view.offset += 8;                        //skip cone
-			for(k = 0; k < 5; k++)
-				t.nspheres[i*5+k] = getFloat32(view);       //sphere + tight
-			t.nfirstpatch[i] = getUint32(view);          //first patch
-		}
-		t.sink = n -1;
-
-		t.patches = new Uint32Array(view.buffer, view.offset, t.patchesCount*3); //noded, lastTriangle, texture
-		t.nroots = t.nodesCount;
-		for(j = 0; j < t.nroots; j++) {
-			for(i = t.nfirstpatch[j]; i < t.nfirstpatch[j+1]; i++) {
-				if(t.patches[i*3] < t.nroots)
-					t.nroots = t.patches[i*3];
-			}
-		}
-
-		view.offset += t.patchesCount*12;
-
-		t.textures = new Uint32Array(t.texturesCount);
-		t.texref = new Uint32Array(t.texturesCount);
-		for(i = 0; i < t.texturesCount; i++) {
-			t.textures[i] = padding*getUint32(view);
-			view.offset += 16*4; //skip proj matrix
-		}
-
-		t.vsize = 12 + (t.vertex.normal?6:0) + (t.vertex.color?4:0) + (t.vertex.texCoord?8:0);
-		t.fsize = 6;
-		//problem: I have no idea how much space a texture is needed in GPU. 10x factor assumed.
-
-		var tmptexsize = new Uint32Array(n-1);
-		var tmptexcount = new Uint32Array(n-1);
-		for(var i = 0; i < n-1; i++) {
-			for(var p = t.nfirstpatch[i]; p != t.nfirstpatch[i+1]; p++) {
-				var tex = t.patches[p*3+2];
-				tmptexsize[i] += t.textures[tex+1] - t.textures[tex];
-				tmptexcount[i]++;
-			}
-			t.nsize[i] = t.vsize*t.nvertices[i] + t.fsize*t.nfaces[i];
-		}
-		for(var i = 0; i < n-1; i++) {
-			t.nsize[i] += 10*tmptexsize[i]/tmptexcount[i];
-		}
-
-		t.status = new Uint8Array(n); //0 for none, 1 for ready, 2+ for waiting data
-		t.frames = new Uint32Array(n);
-		t.errors = new Float32Array(n); //biggest error of instances
-		t.ibo    = new Array(n);
-		t.vbo    = new Array(n);
-		t.texids = new Array(n);
-
-		t.isReady = true;
-		if(t.onLoad) t.onLoad();
-	},
-
-	importAttribute: function(view) {
-		var a = {};
-		a.type = view.getUint8(view.offset++, true);
-		a.size = view.getUint8(view.offset++, true);
-		a.glType = attrGlMap[a.type];
-		a.normalized = a.type < 7;
-		a.stride = attrSizeMap[a.type]*a.size;
-		if(a.size == 0) return null;
-		return a;
-	},
-
-	importElement: function(view) {
-		var e = [];
-		for(i = 0; i < 8; i++)
-			e[i] = this.importAttribute(view);
-		return e;
-	},
-
-	importVertex: function(view) {	//enum POSITION, NORMAL, COLOR, TEXCOORD, DATA0
-		var e = this.importElement(view);
-		var color = e[2]; 
-		if(color) {
-			color.type = 2; //unsigned byte
-			color.glType = attrGlMap[2];
-		}
-		return { position: e[0], normal: e[1], color: e[2], texCoord: e[3], data: e[4] };
-	},
-
-	//enum INDEX, NORMAL, COLOR, TEXCOORD, DATA0
-	importFace: function(view) {
-		var e = this.importElement(view);
-		var color = e[2];
-		if(color) {
-			color.type = 2; //unsigned byte
-			color.glType = attrGlMap[2];
-		}
-		return { index: e[0], normal: e[1], color: e[2], texCoord: e[3], data: e[4] };
-	},
-
-	importSignature: function(view) {
-		var s = {};
-		s.vertex = this.importVertex(view);
-		s.face = this.importFace(view);
-		s.flags = getUint32(view);
-		return s;
-	},
-
-	importHeader: function(view) {
-		var magic = getUint32(view);
-		if(magic != 0x4E787320) return null;
-		var h = {};
-		h.version = getUint32(view);
-		h.verticesCount = getUint64(view);
-		h.facesCount = getUint64(view);
-		h.signature = this.importSignature(view);
-		h.nodesCount = getUint32(view);
-		h.patchesCount = getUint32(view);
-		h.texturesCount = getUint32(view);
-		h.sphere = { 
-			center: [getFloat32(view), getFloat32(view), getFloat32(view)],
-			radius: getFloat32(view)
-		};
-		return h;
-	}
-}; 
-
-Instance = function(gl) {
-	this.gl = gl;
-	this.onLoad = function() {};
-	this.onUpdate = function() {};
-	this.drawBudget = drawBudget;
-	this.attributes = { 'position':0, 'normal':1, 'color':2, 'uv':3 };
-}
-
-Instance.prototype = {
-	open: function(url) {
-		var t = this;
-		t.context = getContext(t.gl);
-
-		t.modelMatrix      = new Float32Array(16);
-		t.viewMatrix       = new Float32Array(16);
-		t.projectionMatrix = new Float32Array(16);
-		t.modelView        = new Float32Array(16);
-		t.modelViewInv     = new Float32Array(16);
-		t.modelViewProj    = new Float32Array(16);
-		t.modelViewProjInv = new Float32Array(16);
-		t.planes           = new Float32Array(24);
-		t.viewport         = new Float32Array(4);
-		t.viewpoint        = new Float32Array(4);
-
-		t.context.meshes.forEach(function(m) {
-			if(m.url == url){
-				t.mesh = m;
-				t.renderMode = t.mesh.renderMode;
-				t.mode = t.renderMode[0];
-				t.onLoad();
-			}
-		});
-
-		if(!t.mesh) {
-			t.mesh = new Mesh();
-			t.mesh.onLoad = function() { t.renderMode = t.mesh.renderMode; t.mode = t.renderMode[0]; t.onLoad(); }
-			t.mesh.open(url);
-			t.context.meshes.push(t.mesh);
-		}
-	},
-
-	close: function() {
-		//remove instance from mesh.
-	},
-
-	get isReady() { return this.mesh.isReady; },
-	setPrimitiveMode : function (mode) { this.mode = mode; },
-	get datasetRadius() { if(!this.isReady) return 1.0;       return this.mesh.sphere.radius; },
-	get datasetCenter() { if(!this.isReady) return [0, 0, 0]; return this.mesh.sphere.center; },
-
-	updateView: function(viewport, projection, modelView) {
-		var t = this;
-
-		for(var i = 0; i < 16; i++) {
-			t.projectionMatrix[i] = projection[i];
-			t.modelView[i] = modelView[i];
-		}
-		for(var i = 0; i < 4; i++)
-			t.viewport[i] = viewport[i];
-
-		matMul(t.projectionMatrix, t.modelView, t.modelViewProj);
-		matInv(t.modelViewProj, t.modelViewProjInv);
-
-		matInv(t.modelView, t.modelViewInv);
-		t.viewpoint[0] = t.modelViewInv[12]; 
-		t.viewpoint[1] = t.modelViewInv[13];
-		t.viewpoint[2] = t.modelViewInv[14]; 
-		t.viewpoint[3] = 1.0;
-
-
-		var m = t.modelViewProj;
-		var mi = t.modelViewProjInv;
-		var p = t.planes;
-
-		//left, right, bottom, top as Ax + By + Cz + D = 0;
-		p[0]  =  m[0] + m[3]; p[1]  =  m[4] + m[7]; p[2]  =  m[8] + m[11]; p[3]  =  m[12] + m[15];
-		p[4]  = -m[0] + m[3]; p[5]  = -m[4] + m[7]; p[6]  = -m[8] + m[11]; p[7]  = -m[12] + m[15];
-		p[8]  =  m[1] + m[3]; p[9]  =  m[5] + m[7]; p[10] =  m[9] + m[11]; p[11] =  m[13] + m[15];
-		p[12] = -m[1] + m[3]; p[13] = -m[5] + m[7]; p[14] = -m[9] + m[11]; p[15] = -m[13] + m[15];
-		p[16] = -m[2] + m[3]; p[17] = -m[6] + m[7]; p[18] = -m[10] + m[11]; p[19] = -m[14] + m[15];
-		p[20] = -m[2] + m[3]; p[21] = -m[6] + m[7]; p[22] = -m[10] + m[11]; p[23] = -m[14] + m[15];
-
-		for(var i = 0; i < 16; i+= 4) {
-			var l = Math.sqrt(p[i]*p[i] + p[i+1]*p[i+1] + p[i+2]*p[i+2]);
-			p[i] /= l; p[i+1] /= l; p[i+2] /= l; p[i+3] /= l;
-		}
-		//side is M'(1,0,0,1) - M'(-1,0,0,1) and they lie on the planes
-		var r3 = mi[3] + mi[15];
-		var r0 = (mi[0]  + mi[12 ])/r3;
-		var r1 = (mi[1]  + mi[13 ])/r3;
-		var r2 = (mi[2]  + mi[14 ])/r3;
- 
-		var l3 = -mi[3] + mi[15];
-		var l0 = (-mi[0]  + mi[12 ])/l3 - r0;
-		var l1 = (-mi[1]  + mi[13 ])/l3 - r1;
-		var l2 = (-mi[2]  + mi[14 ])/l3 - r2;
-		
-		var side = Math.sqrt(l0*l0 + l1*l1 + l2*l2);
-
-		//center of the scene is M'*(0, 0, 0, 1)
-		var c0 = mi[12]/mi[15] - t.viewpoint[0];
-		var c1 = mi[13]/mi[15] - t.viewpoint[1];
-		var c2 = mi[14]/mi[15] - t.viewpoint[2];
-		var dist = Math.sqrt(c0*c0 + c1*c1 + c2*c2);
-
-		t.resolution = (2*side/dist)/ t.viewport[2];
-	},
-
-	traversal : function () {
-		var t = this;
-		if(Debug.extract == true)
-			return;
-
-		if(!t.isReady) return;
-
-		var n = t.mesh.nodesCount;
-		t.visited  = new Uint8Array(n);
-		t.blocked  = new Uint8Array(n);
-		t.selected = new Uint8Array(n);
-
-		t.visitQueue = new PriorityQueue(n);
-		for(var i = 0; i < t.mesh.nroots; i++)
-			t.insertNode(i);
-
-		var candidatesCount = 0;
-		t.targetError = t.context.currentError;
-		t.currentError = 1e20;
-		t.drawSize = 0;
-
-		var nblocked = 0;
-		var requested = 0;
-		while(t.visitQueue.size && nblocked < maxBlocked) {
-			var error = t.visitQueue.error[0];
-			var node = t.visitQueue.pop();
-			if ((requested < maxPending) && (t.mesh.status[node] == 0)) {
-				t.context.candidates.push({id: node, instance:t, mesh:t.mesh, frame:t.context.frame, error:error});
-				requested++;
-			}
-
-			var blocked = t.blocked[node] || !t.expandNode(node, error);
-			if (blocked) 
-				nblocked++;
-			else {
-				t.selected[node] = 1;
-				t.currentError = error;
-			}
-			t.insertChildren(node, blocked);
-		}
-	},
-
-	insertNode: function (node) {
-		var t = this;
-		t.visited[node] = 1;
-
-		var error = t.nodeError(node);
-		if(node > 0 && error < t.targetError) return;  //2% speed TODO check if needed
-
-		var errors = t.mesh.errors;
-		var frames = t.mesh.frames;
-		if(frames[node] != t.context.frame || errors[node] < error) {
-			errors[node] = error;
-			frames[node] = t.context.frame;
-		}
-		t.visitQueue.push(node, error);
-	},
-
-	insertChildren : function (node, block) {
-		var t = this;
-		for(var i = t.mesh.nfirstpatch[node]; i < t.mesh.nfirstpatch[node+1]; ++i) {
-			var child = t.mesh.patches[i*3];
-			if (child == t.mesh.sink) return;
-			if (block) t.blocked[child] = 1;
-			if (!t.visited[child])
-				t.insertNode(child);
-		}
-	},
-
-	expandNode : function (node, error) {
-		var t = this;
-		if(node > 0 && error < t.targetError) {
-//			console.log("Reached error", error, t.targetError);
-			return false;
-		}
-
-		if(t.drawSize > t.drawBudget) {
-//			console.log("Reached drawsize", t.drawSize, t.drawBudget);
-			return false;
-		}
-
-		if(t.mesh.status[node] != 1) { //not ready
-//			console.log("Node " + node + " still not loaded (cache?)");
-			return false;
-		}
-
-		var sp = t.mesh.nspheres;
-		var off = node*5;
-		if(t.isVisible(sp[off], sp[off+1], sp[off+2], sp[off+3])) //expanded radius
-			t.drawSize += t.mesh.nvertices[node]*0.8; 
-			//we are adding half of the new faces. (but we are using the vertices so *2)
-
-		return true; 
-	},
-
-	nodeError : function (n, tight) {
-		var t = this;
-		var spheres = t.mesh.nspheres;
-		var b = t.viewpoint;
-		var off = n*5;
-		var cx = spheres[off+0];
-		var cy = spheres[off+1];
-		var cz = spheres[off+2];
-		var r  = spheres[off+3];
-		if(tight)
-			r = spheres[off+4];
-		var d0 = b[0] - cx;
-		var d1 = b[1] - cy;
-		var d2 = b[2] - cz;
-		var dist = Math.sqrt(d0*d0 + d1*d1 + d2*d2) - r;
-		if (dist < 0.1)
-			dist = 0.1;
-
-		//resolution is how long is a pixel at distance 1.
-		var error = t.mesh.nerrors[n]/(t.resolution*dist); //in pixels
-
-		if (!t.isVisible(cx, cy. cz, spheres[off+4]))
-			error /= 1000.0;
-		return error;
-	},
-
-	isVisible : function (x, y, z, r) {
-		var p = this.planes;
-		for (i = 0; i < 24; i +=4) {
-			if(p[i]*x + p[i+1]*y + p[i+2]*z +p[i+3] + r < 0) //ax+by+cz+w = 0;
-				return false;
-		}
-		return true;
-	},
-
-	renderNodes: function() {
-		var t = this;
-		var m = t.mesh;
-		var gl = t.gl;
-		var attr = t.attributes;
-
-		var vertexEnabled = gl.getVertexAttrib(attr.position, gl.VERTEX_ATTRIB_ARRAY_ENABLED);
-		var normalEnabled = gl.getVertexAttrib(attr.normal, gl.VERTEX_ATTRIB_ARRAY_ENABLED);
-		var colorEnabled = attr.color >= 0? gl.getVertexAttrib(attr.color, gl.VERTEX_ATTRIB_ARRAY_ENABLED): false;
-		var uvEnabled = attr.uv >= 0? gl.getVertexAttrib(attr.uv, gl.VERTEX_ATTRIB_ARRAY_ENABLED): false;
-
-		gl.enableVertexAttribArray(attr.position);
-		if(m.vertex.texCoord && attr.uv >= 0) gl.enableVertexAttribArray(attr.uv);
-		if(m.vertex.normal && attr.normal >= 0) gl.enableVertexAttribArray(attr.normal);
-		if(m.vertex.color && attr.color >= 0) gl.enableVertexAttribArray(attr.color);
-		gl.vertexAttrib4fv(2, [0.8, 0.8, 0.8, 1.0]);
-
-		if(Debug.nodes) {
-			gl.disableVertexAttribArray(2);
-			gl.disableVertexAttribArray(3);
-		}
-
-		var rendered = 0;
-		var last_texture = -1;
-		for(var n = 0; n < m.nodesCount; n++) {
-			if(!t.selected[n]) continue;
-
-			if(t.mode != "POINT") {
-				var skip = true;
-				for(var p = m.nfirstpatch[n]; p < m.nfirstpatch[n+1]; p++) {
-					var child = m.patches[p*3];
-					if(!t.selected[child]) {
-						skip = false;
-						break;
-					}
-				}
-				if(skip) continue;
-			}
-
-
-			var sp = t.mesh.nspheres;
-			var off = n*5;
-			if(!t.isVisible(sp[off], sp[off+1], sp[off+2], sp[off+4])) //tight radius
-				continue;
-
-			gl.bindBuffer(gl.ARRAY_BUFFER, m.vbo[n]);
-			if(t.mode != "POINT")
-				gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.ibo[n]);
-
-			var nv = m.nvertices[n];
-
-			gl.vertexAttribPointer(attr.position, 3, gl.FLOAT, false, 12, 0);
-			var offset = nv*12;
-			if(m.vertex.texCoord && attr.uv >= 0)
-				gl.vertexAttribPointer(attr.uv, 2, gl.FLOAT, false, 8, offset), offset += nv*8;
-			if(m.vertex.normal && attr.normal >= 0)
-				gl.vertexAttribPointer(attr.normal, 3, gl.SHORT, true, 6, offset), offset += nv*6;
-			if(m.vertex.color && attr.color >= 0)
-				gl.vertexAttribPointer(attr.color, 4, gl.UNSIGNED_BYTE, true, 4, offset);
-
-			if (Debug.nodes) {
-				var error = t.nodeError(n, true);
-				var palette = { 
-					1: [1, 1, 1, 1],
-					2: [0.5, 1, 1, 1],
-					4: [0, 1, 1, 1],
-					8: [0, 1, 0.5, 1],
-					12: [0, 1, 0, 1],
-					16: [0, 1, 0, 1],
-					20: [1, 1, 0, 1],
-					30: [1, 0.5, 0, 1],
-					1e20: [1, 0, 0, 1] };
-
-				for(i in palette)
-					if(i > error) {
-						gl.vertexAttrib4fv(attr.color, palette[i]);
-						break;
-					}
-//				gl.vertexAttrib4fv(2, [(n*200 %255)/255.0, (n*140 %255)/255.0,(n*90 %255)/255.0, 1]);
-			}
-
-			if (Debug.draw) continue;
-
-			if(t.mode == "POINT") {
-				var pointsize = Math.ceil(0.30*t.currentError);
-				if(pointsize > 2) pointsize = 2;
-				gl.vertexAttrib1fv(4, [pointsize]);
-
-				var error = t.nodeError(n);
-				var fraction = (error/t.currentError - 1);
-				if(fraction > 1) fraction = 1;
-
-				var count = fraction * nv;
-				if(count != 0) {
-					if(m.vertex.texCoord) {
-						var texid = m.patches[m.nfirstpatch[n]*3+2];
-						if(texid != -1 && texid != last_texture) { //bind texture
-							var tex = m.texids[texid];
-							gl.activeTexture(gl.TEXTURE0);
-							gl.bindTexture(gl.TEXTURE_2D, tex);
-						}
-					}
-					gl.drawArrays(gl.POINTS, 0, count);
-					rendered += count;
-				}
-				continue;
-			}
-
-			//concatenate renderings to remove useless calls. except we have textures.
-			var offset = 0;
-			var end = 0;
-			var last = m.nfirstpatch[n+1]-1;
-			for (var p = m.nfirstpatch[n]; p < m.nfirstpatch[n+1]; ++p) {
-				var child = m.patches[p*3];
-
-				if(!t.selected[child]) { 
-					end = m.patches[p*3+1];
-					if(p < last) //if textures we do not join. TODO: should actually check for same texture of last one. 
-						continue;
-				} 
-				if(end > offset) {
-					if(m.vertex.texCoord) {
-						var texid = m.patches[p*3+2];
-						if(texid != -1 && texid != last_texture) { //bind texture
-							var tex = m.texids[texid];
-							gl.activeTexture(gl.TEXTURE0);
-							gl.bindTexture(gl.TEXTURE_2D, tex);
-							last_texture = texid;
-						}
-					}
-					gl.drawElements(gl.TRIANGLES, (end - offset) * 3, gl.UNSIGNED_SHORT, offset * 6);
-					rendered += end - offset;
-				}
-				offset = m.patches[p*3+1];
-			}
-		} 
-
-		t.context.rendered += rendered;
-		if(!vertexEnabled) gl.disableVertexAttribArray(attr.position);
-		if(!normalEnabled && attr.normal >= 0) gl.disableVertexAttribArray(attr.normal);
-		if(!colorEnabled && attr.color >= 0) gl.disableVertexAttribArray(attr.color);
-		if(!uvEnabled && attr.uv >= 0) gl.disableVertexAttribArray(attr.uv);
-
-		gl.bindBuffer(gl.ARRAY_BUFFER, null);
-		if(t.mode != "POINT")
-			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
-	},
-
-	render: function() {
-		this.traversal();
-		this.renderNodes();
-	}
-};
-
-
-//keep track of meshes and which GL they belong to. (no sharing between contexts)
-var contexts = [];
-
-function getContext(gl) {
-	var c = null;
-	if(!gl.isTexture) throw "Something wrong";
-	contexts.forEach(function(g) { 
-		if(g.gl == gl) c = g;
-	});
-	if(c) return c;
-	c = { gl:gl, meshes:[], frame:0, cacheSize:0, candidates:[], pending:0, 
-		targetFps: targetFps, targetError: targetError, currentError: targetError };
-	contexts.push(c);
-	return c;
-}
-
-
-
-function beginFrame(gl, fps) { //each context has a separate frame count.
-	var c = getContext(gl);
-
-	c.frame++;
-	c.candidates = [];
-	if(fps && c.targetFps) {
-		var r = c.targetFps/fps;
-		if(r > 1.1)
-			c.currentError *= 1.05;
-		if(r < 0.9)
-			c.currentError *= 0.95;
-	
-		if(c.currentError < c.targetError)
-			c.currentError = c.targetError;
-		if(c.currentError > 10) c.currentError = 10;
-	}
-//	console.log("current", c.currentError, "fps", fps, "targetFps", c.targetFps, "rendered", c.rendered);
-	c.rendered = 0;
-}
-
-function endFrame(gl) {
-	updateCache(gl);
-}
-
-function removeNode(context, node) {
-	var n = node.id;
-	var m = node.mesh;
-	if(m.status[n] == 0) return;
-
-//	console.log("Removing " + m.url + " node: " + n);
-	m.status[n] = 0;
-
-	if (m.georeq.readyState != 4) m.georeq.abort();
-	if (m.texreq.readyState != 4) m.texreq.abort();
-
-	context.cacheSize -= m.nsize[n];
-	context.gl.deleteBuffer(m.vbo[n]);
-	context.gl.deleteBuffer(m.ibo[n]);
-	m.vbo[n] = m.ibo[n] = null;
-
-	if(!m.vertex.texCoord) return;
-	var tex = m.patches[m.nfirstpatch[n]*3+2]; //TODO assuming one texture per node
-	m.texref[tex]--;
-
-	if(m.texref[tex] == 0 && m.texids[tex]) {
-		context.gl.deleteTexture(m.texids[tex]);
-		m.texids[tex] = null;
-	}
-}
-
-function requestNode(context, node) {
-	var n = node.id;
-	var m = node.mesh;
-
-	m.status[n] = 2; //pending
-
-	context.pending++;
-	context.cacheSize += m.nsize[n];
-
-	node.reqAttempt = 0;
-	node.context = context;
-	node.nvert = m.nvertices[n];
-	node.nface = m.nfaces[n];
-
-//	console.log("Requesting " + m.url + " node: " + n);
-	requestNodeGeometry(context, node);
-	requestNodeTexture(context, node);
-}
-
-function requestNodeGeometry(context, node) {
-	var n = node.id;
-	var m = node.mesh;
-
-	m.status[n]++; //pending
-	m.georeq = m.httpRequest(
-		m.noffsets[n],
-		m.noffsets[n+1],
-		function() { loadNodeGeometry(this, context, node); },
-		function() {
-//			console.log("Geometry request error!"); 
-			recoverNode(context, node, 0);
-		},
-		function() {
-//			console.log("Geometry request abort!"); 
-			removeNode(context, node);
-		},
-		'arraybuffer'
-	);
-}
-
-function requestNodeTexture(context, node) {
-	var n = node.id;
-	var m = node.mesh;
-
-	if(!m.vertex.texCoord) return;
-
-	var tex = m.patches[m.nfirstpatch[n]*3+2];
-	m.texref[tex]++;
-	if(m.texids[tex])
-		return;
-
-	m.status[n]++; //pending
-	m.texreq = m.httpRequest(
-		m.textures[tex],
-		m.textures[tex+1],
-		function() { loadNodeTexture(this, context, node, tex); },
-		function() { 
-//			console.log("Texture request error!");
-			recoverNode(context, node, 1);
-		},
-		function() { 
-//			console.log("Texture request abort!");
-			removeNode(context, node);
-		},
-		'blob'
-	);
-}
-
-function recoverNode(context, node, id) {
-	var n = node.id;
-	var m = node.mesh;
-	if(m.status[n] == 0) return;
-
-	m.status[n]--;
-
-	if(node.reqAttempt > maxReqAttempt) {
-//		console.log("Max request limit for " + m.url + " node: " + n);
-		removeNode(context, node);
-		return;
-	}
-
-	node.reqAttempt++;
-
-	switch (id){ 
-		case 0:
-			requestNodeGeometry(context, node); 
-			console.log("Recovering geometry for " + m.url + " node: " + n);
-			break;
-		case 1:
-			requestNodeTexture(context, node); 
-			console.log("Recovering texture for " + m.url + " node: " + n);
-			break;
-	}
-}
-
-function loadNodeGeometry(request, context, node) {
-	var n = node.id;
-	var m = node.mesh;
-	if(m.status[n] == 0) return;
-
-	node.buffer = request.response;
-
-	if(!m.compressed)
-		readyNode(node);
-	else if(m.meco) {
-		var sig = { texcoords: m.vertex.texCoord, normals:m.vertex.normal, colors:m.vertex.color, indices: m.face.index }
-		var patches = [];
-		for(var k = m.nfirstpatch[n]; k < m.nfirstpatch[n+1]; k++)
-			patches.push(m.patches[k*3+1]);
-		if(!meco) loadMeco();
-		meco.postRequest(sig, node, patches);
-	} else {
-		if(!corto) loadCorto();
-		corto.postRequest(node);
-	}
-}
-
-function loadNodeTexture(request, context, node, texid) {
-	var n = node.id;
-	var m = node.mesh;
-	if(m.status[n] == 0) return;
-
-	var blob = request.response;
-
-	var urlCreator = window.URL || window.webkitURL;
-	var img = document.createElement('img');
-	img.onerror = function(e) { console.log("Texture loading error!"); };
-	img.src = urlCreator.createObjectURL(blob);
-
-	var gl = context.gl;
-	img.onload = function() { 
-		urlCreator.revokeObjectURL(img.src); 
-
-		var flip = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL);
-		gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
-		var tex = m.texids[texid] = gl.createTexture();
-		gl.bindTexture(gl.TEXTURE_2D, tex);
-		var s = gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
-		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
-		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
-		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
-		gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flip);
-
-		m.status[n]--;
-
-		if(m.status[n] == 2) {
-			m.status[n]--; //ready
-			node.reqAttempt = 0;
-			node.context.pending--;
-			node.instance.onUpdate();
-			updateCache(gl);
-		}
-	}
-}
-
-function scramble(n, coords, normals, colors) {
-	while (n > 0) {
-		var i = Math.floor(Math.random() * n);
-		n--;
-		for(var k =0; k < 3; k++) {
-			var v = coords[n*3+k];
-			coords[n*3+k] = coords[i*3+k];
-			coords[i*3+k] = v;
-
-			if(normals) {
-				var v = normals[n*3+k];
-				normals[n*3+k] = normals[i*3+k];
-				normals[i*3+k] = v;
-			}
-			if(colors) {
-				var v = colors[n*4+k];
-				colors[n*4+k] = colors[i*4+k];
-				colors[i*4+k] = v;
-			}
-		}
-	}
-}
-
-function readyNode(node) {
-	var m = node.mesh;
-	var n = node.id;
-	var nv = m.nvertices[n];
-	var nf = m.nfaces[n];
-	var model = node.model;
-
-	var vertices;
-	var indices;
-
-	if(!m.corto) {
-		vertices = new Uint8Array(node.buffer, 0, nv*m.vsize);
-		//dangerous alignment 16 bits if we had 1b attribute
-		indices  = new Uint8Array(node.buffer, nv*m.vsize,  nf*m.fsize);
-		if(nf == 0) {
-			var off = nv*12;
-			var v = new Float32Array(node.buffer, 0, nv*3);
-			if(m.vertex.normal) {
-				var no = new Int16Array(node.buffer, off, nv*3); off += nv*6;
-			}
-			if(m.vertex.color) {
-				var co = new Uint8Array(node.buffer, off, nv*4);
-			}
-			scramble(nv, v, no, co);
-		}
-	} else {
-		indices = node.model.index;
-		vertices = new ArrayBuffer(nv*m.vsize);
-		var v = new Float32Array(vertices, 0, nv*3);
-		v.set(model.position, 0, nv*3);
-		var off = nv*12;
-		if(model.uv) {
-			var uv = new Float32Array(vertices, off, nv*2);
-			uv.set(model.uv); off += nv*8;
-		}
-		if(model.normal) {
-			var no = new Int16Array(vertices, off, nv*3);
-			no.set(model.normal); off += nv*6; 
-		}
-		if(model.color) {
-			var co = new Uint8Array(vertices, off, nv*4);
-			co.set(model.color); off += nv*4;  
-		}
-		if(nf == 0)
-			scramble(nv, v, no, co);
-	}
-
-	var gl = node.context.gl;
-	var vbo = m.vbo[n] = gl.createBuffer();
-	gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
-	gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
-	var ibo = m.ibo[n] = gl.createBuffer();
-	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
-	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
-
-	m.status[n]--;
-
-	if(m.status[n] == 2) {
-		m.status[n]--; //ready
-		node.reqAttempt = 0;
-		node.context.pending--;
-		node.instance.onUpdate();
-		updateCache(gl);
-	}
-}
-
-function updateCache(gl) {
-	var context = getContext(gl);
-
-	var best = null;
-	context.candidates.forEach(function(e) {
-		if(e.mesh.status[e.id] == 0 && (!best || e.error > best.error)) best = e;
-	});
-	context.candidates = [];
-	if(!best) return;
-
-	while(context.cacheSize > maxCacheSize) {
-		var worst = null;
-		//find worst in cache
-		context.meshes.forEach(function(m) {
-			var n = m.nodesCount;
-			for(i = 0; i < n; i++)
-				if(!worst || (m.status[i] == 1 && m.errors[i] < worst.error))
-					worst = {error: m.errors[i], frame: m.frames[i], mesh:m, id:i};
-		});
-
-		if(!worst || (worst.error >= best.error && worst.frame == best.frame))
-			return;
-		removeNode(context, worst);
-	}
-
-	requestNode(context, best);
-
-	if(context.pending < maxPending)
-		updateCache(gl);
-}
-
-//nodes are loaded asincronously, just update mesh content (VBO) cache size is kept globally.
-//but this could be messy.
-
-function setTargetError(gl, error) {
-	var context = getContext(gl);
-	context.targetError = error;
-}
-function setTargetFps(gl, fps) {
-	var context = getContext(gl);
-	context.targetFps = fps;
-}
-function setMaxCacheSize(gl, size) {
-	var context = getContext(gl);
-	context.maxCacheSize = size;
-}
-
-return { Mesh: Mesh, Renderer: Instance, Renderable: Instance, Instance:Instance,
-	Debug: Debug, contexts: contexts, beginFrame:beginFrame, endFrame:endFrame, 
-	setTargetError:setTargetError, setTargetFps:setTargetFps, setMaxCacheSize:setMaxCacheSize };
-
-}();
--- a/Resources/Nexus/js/nexus_three.js	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-function NexusObject(url, renderer, render, material) {
-	var gl = renderer.context;
-	var geometry = new THREE.BufferGeometry();
-	var positions = new Float32Array(3);
-
-
-	geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
-	
-
-	if(!material)
-		this.autoMaterial = true;
-
-	THREE.Mesh.call( this, geometry, material);
-	this.frustumCulled = false;
-
-	var mesh = this;
-	var instance = this.instance = new Nexus.Instance(gl);
-	instance.open(url);
-	instance.onLoad = function() {
-		var s = 1/instance.mesh.sphere.radius;
-		var pos = instance.mesh.sphere.center;
-		mesh.position.set(-pos[0]*s, -pos[1]*s, -pos[2]*s);
-		mesh.scale.set(s, s, s);
-		if(mesh.autoMaterial)
-			mesh.material = new THREE.MeshLambertMaterial( { color: 0xffffff } );
-
-		if(this.mesh.vertex.normal) {
-			var normals = new Float32Array(3);
-			geometry.addAttribute( 'normal', new THREE.BufferAttribute(normals, 3));
-		}
-		if(this.mesh.vertex.color) {
-			var colors = new Float32Array(4);
-			geometry.addAttribute( 'color', new THREE.BufferAttribute(colors, 4));
-			if(mesh.autoMaterial)
-				mesh.material = new THREE.MeshLambertMaterial({ vertexColors: THREE.VertexColors });
-		}
-
-		if(this.mesh.vertex.texCoord) {
-			var uv = new Float32Array(2);
-			geometry.addAttribute( 'uv', new THREE.BufferAttribute(uv, 2));
-			if(mesh.autoMaterial) {
-				var texture = new THREE.DataTexture( new Uint8Array([1, 1, 1]), 1, 1, THREE.RGBFormat );
-				texture.needsUpdate = true;
-				mesh.material = new THREE.MeshLambertMaterial( { color: 0xffffff, map: texture } );
-			}
-		}
-
-		if(this.mesh.face.index) {
-			var indices = new Uint32Array(3);
-			geometry.setIndex(new THREE.BufferAttribute( indices, 3) );
-		}
-		render();
-	};
-	instance.onUpdate = function() { render(); }
-
-	this.onAfterRender = function(renderer, scene, camera, geometry, material, group) { 
-		if(!instance.isReady) return;
-		var s = renderer.getSize();
-		instance.updateView([0, 0, s.width, s.height], 
-			camera.projectionMatrix.elements, 
-			mesh.modelViewMatrix.elements);
-		var program = renderer.context.getParameter(gl.CURRENT_PROGRAM);
-		instance.attributes['position'] = renderer.context.getAttribLocation(program, "position");
-		instance.attributes['normal'] = renderer.context.getAttribLocation(program, "normal");
-		instance.attributes['color'] = renderer.context.getAttribLocation(program, "color");
-		instance.attributes['uv'] = renderer.context.getAttribLocation(program, "uv");
-
-		instance.render();
-	}
-}
-
-NexusObject.prototype = Object.create(THREE.Mesh.prototype);
-
-NexusObject.prototype.raycast = function(raycaster, intersects) {
-	var instance = this.instance;
-	var nexus = instance.mesh;
-	if(!nexus.sphere) return;
-	var sp = nexus.sphere;
-	var c = sp.center;
-	var center = new THREE.Vector3(c[0], c[1], c[2]);
-	var sphere = new THREE.Sphere(center, sp.radius);
-	sphere.applyMatrix4( this.matrixWorld );
-
-	if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;
-	//just check the last level spheres.
-	if(!nexus.sink) return;
-
-	var distance = -1.0;
-	for(var i = 0; i < nexus.sink; i++) {
-		var patch = nexus.nfirstpatch[i];
-		if(nexus.patches[patch*3] != nexus.sink)
-			continue;
-		var x = nexus.nspheres[i*5];
-		var y = nexus.nspheres[i*5+1];
-		var z = nexus.nspheres[i*5+2];
-		var r = nexus.nspheres[i*5+4]; //tight radius
-		var sphere = new THREE.Sphere(new THREE.Vector3(x, y, z), r);
-		sphere.applyMatrix4( this.matrixWorld );
-		if ( raycaster.ray.intersectsSphere( sphere ) != false ) {
-			var d = sphere.center.lengthSq();
-			if(distance == -1.0 || d < distance)
-				distance = d;
-		}
-	}
-	if(distance == -1.0) return;
-
-	intersects.push({ distance: distance, object: this} );
-}
--- a/Resources/Nexus/threejs.html	Sat Jun 15 16:17:41 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-<title>Nexus threejs</title>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-<style>body { margin: 0px; overflow: hidden; }</style>
-<script src="three-84.js"></script>
-<script src="js/TrackballControls.js"></script>
-<script src="js/nexus.js"></script>
-<script src="js/nexus_three.js"></script>
-</head>
-
-<body>
-	<div id="container"></div>
-</body>
-
-<script>
-
-var camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.1, 100 );
-camera.position.z = 4;
-
-var controls = new THREE.TrackballControls( camera );
-controls.rotateSpeed = 10.0;
-controls.zoomSpeed = 1.5;
-controls.panSpeed = 0.8;
-controls.noZoom = false;
-controls.noPan = false;
-controls.staticMoving = true;
-controls.dynamicDampingFactor = 0.3;
-controls.keys = [ 65, 83, 68 ];
-controls.addEventListener( 'change', render );
-
-var scene = new THREE.Scene();
-scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );
-scene.add( new THREE.AmbientLight( 0x444444 ) );
-
-var light1 = new THREE.DirectionalLight( 0xffffff, 1.0 );
-light1.position.set( 1, 1, -1 );
-scene.add( light1 );
-
-var light2 = new THREE.DirectionalLight( 0xffffff, 1.0 );
-light2.position.set( -1, -1, 1 );
-scene.add( light2 );
-
-var renderer = new THREE.WebGLRenderer( { antialias: false } );
-renderer.setClearColor( scene.fog.color );
-renderer.setPixelRatio( window.devicePixelRatio );
-renderer.setSize( window.innerWidth, window.innerHeight);
-
-var container = document.getElementById( 'container');
-container.appendChild( renderer.domElement );
-
-/* An appropriate material can be used as a fourth arg for the NexusObject constructor
-
-var texture = new THREE.DataTexture( new Uint8Array([1, 1, 1]), 1, 1, THREE.RGBFormat );
-texture.needsUpdate = true;
-var material = new THREE.MeshLambertMaterial( { color: 0xffffff, map: texture } );
-*/
-
-function getURLParameter(name) {
-	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
-}
-
-var model = getURLParameter('model') || "models/gargo.nxz";
-
-var nexus_obj = new NexusObject(model, renderer, render);
-scene.add(nexus_obj);
-
-window.addEventListener( 'resize', onWindowResize, false );
-render();
-
-
-function onWindowResize() {
-
-	camera.aspect = window.innerWidth / window.innerHeight;
-	camera.updateProjectionMatrix();
-
-	renderer.setSize( window.innerWidth, window.innerHeight );
-
-	controls.handleResize();
-	controls.update();
-	render();
-}
-
-function animate() {
-	requestAnimationFrame( animate );
-	controls.update();
-}
-
-function render() {
-	Nexus.beginFrame(renderer.context);
-	renderer.render( scene, camera );
-	Nexus.endFrame(renderer.context);
-}
-
-animate();
-
-</script>
-
-
-</html>
-
--- a/Sources/Plugin.cpp	Sat Jun 15 16:17:41 2024 +0200
+++ b/Sources/Plugin.cpp	Sat Jun 15 16:35:21 2024 +0200
@@ -771,51 +771,7 @@
   }
 
   const std::string file = request->groups[0];
-
-  if (file == "three-84.js")
-  {
-    cache_.Answer(output, "nexus/three-84.js");
-  }
-  else
-  {
-    Orthanc::EmbeddedResources::FileResourceId resourceId;
-    Orthanc::MimeType mimeType;
-
-    if (file == "threejs.html")
-    {
-      resourceId = Orthanc::EmbeddedResources::NEXUS_HTML;
-      mimeType = Orthanc::MimeType_Html;
-    }
-    else if (file == "js/meco.js")
-    {
-      resourceId = Orthanc::EmbeddedResources::NEXUS_MECO_JS;
-      mimeType = Orthanc::MimeType_JavaScript;
-    }
-    else if (file == "js/nexus.js")
-    {
-      resourceId = Orthanc::EmbeddedResources::NEXUS_JS;
-      mimeType = Orthanc::MimeType_JavaScript;
-    }
-    else if (file == "js/nexus_three.js")
-    {
-      resourceId = Orthanc::EmbeddedResources::NEXUS_THREE_JS;
-      mimeType = Orthanc::MimeType_JavaScript;
-    }
-    else if (file == "js/TrackballControls.js")
-    {
-      resourceId = Orthanc::EmbeddedResources::NEXUS_TRACKBALL_JS;
-      mimeType = Orthanc::MimeType_JavaScript;
-    }
-    else
-    {
-      OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 404);
-      return;
-    }
-
-    std::string s;
-    Orthanc::EmbeddedResources::GetFileResource(s, resourceId);
-    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(mimeType));
-  }
+  cache_.Answer(output, "nexus/" + file);
 }