API Docs for: 1.0.2
Show:

File: GIScene\Process\LineOfSight_fastClient.js

/**
 * The Line of Sight Process calculates the visibility bewtween two points in the scene
 * 
 * @namespace GIScene
 * @class Process.LineOfSight_fastClient
 * @constructor
 * @extends GIScene.Process 
 */
GIScene.Process.LineOfSight_fastClient = function() {
	
	var config = {
		identifier		: "GIScene:lineOfSight",
		title			: "Line of Sight",
		abstract		: "Given two loactions and possible obstacle objects this process will compute the visibility between the two loactions and provides a graphical 3D line.",
		metadata		: null,
		processVersion	: "1.0",
		description		: {inputs:[
								{
									identifier: 'GIScene:lineOfSight:observerPoint',
									title:    'Observer Point', 
									abstract: 'Point of Observer, where the line of sight starts.', 
									dataType:   'THREE.Vector3', //??? short graphic coords
									minOccurs: 1,
									maxOccurs: 1,
								},
								{
									identifier: 'GIScene:lineOfSight:observerOffset',
									title:	'Observer Offset',
									abstract:	'Additional height offset to observer point.',
									dataType:	'Number',
									minOccurs: 0,
									maxOccurs: 1,
									defaultValue:0
								},
								{	identifier: 'GIScene:lineOfSight:targetPoint',
									title:    'Tartget Point', 
									abstract: 'Point of Target, where the line of sight ends.', 
									dataType:   'THREE.Vector3', //???
									minOccurs: 1,
									maxOccurs: 1
								},
								{
									identifier: 'GIScene:lineOfSight:targetOffset',
									title:	'Target Offset',
									abstract:	'Additional height offset to target point.',
									dataType:	'Number',
									minOccurs: 0,
									maxOccurs: 1,
									defaultValue:0
								},
								{
									identifier: 'GIScene:lineOfSight:obstacleLayers',
									title:    'Obstacle Layers',
									abstract: 'Layers whose objects are possible obstacles to be reflected in the calculation.',
									dataType  : 'Array(GIScene.Layer)',
									minOccurs: 1,
									maxOccurs: 'unbounded' //like in xml
								}
							],
							outputs:[
								{
									identifier: 'GIScene:lineOfSight:lineOfSight',
									title:	'Line Of Sight',
									abstract:	'The calculated Line of Sight between observer and target.',
									dataType:	'THREE.Object3D' //???
									
								},
								{
									identifier: 'GIScene:lineOfSight:isVisible',
									title:	'Target is visible',
									abstract:	'The result of the visibility calculation.',
									dataType:	'boolean'
								}
							]
						}
	};
	
	//make this a Process
	GIScene.Process.apply(this, [config]);

	
	//setDefaults	
	this.config.description.inputs.forEach( function(e, i, a) {
		if (e.defaultValue != undefined) {
			//console.log(e, e.defaultValue, e.identifier);
			this.setInput(e.identifier, e.defaultValue);
		}
	}.bind(this)); 

	
	this.raycaster	= new THREE.Raycaster();

	//INPUTS
	// this.inputs.observerPoint  = null; //THREE.Vector3
	// this.inputs.targetPoint	= null; //THREE.Vector3
	// this.inputs.observerOffset = 0;
	// this.inputs.targetOffset	= 0;
	// this.inputs.obstacles		= null;
// 	
	// //OUTPUTS
	// this.ouputs.visibility = null;
	// this.ouputs.lineOfSight = null;
	
	// var inputParams = {
		// 'GIScene:lineOfSight:observerPoint'	: new THREE.Vector3(0,100,0),
		// 'GIScene:lineOfSight:observerOffset': 5,
		// 'GIScene:lineOfSight:targetPoint'	: new THREE.Vector3(0,-100,0),
		// 'GIScene:lineOfSight:targetOffset'	: 0,
		// 'GIScene:lineOfSight:obstacles'		: null//this.scene.root		
	// };
// 	
	// this.setInputs(inputParams);
	
	var lineMatVisible = new THREE.LineBasicMaterial({color: new THREE.Color(0x00ff00)});
	var lineMatNotVisible = new THREE.LineBasicMaterial({color: new THREE.Color(0xff0000)});
	
	
	
	/**
	 * run the process with the inputs that have been set before
	 *  @method execute
	 *  @return {object} data an object with all input and output values of the process
	 */
	
	this.execute = function() {
		var observerV3 		= this.data.inputs['GIScene:lineOfSight:observerPoint'];
		var observerOffset 	= this.data.inputs['GIScene:lineOfSight:observerOffset'];	
		var targetV3		= this.data.inputs['GIScene:lineOfSight:targetPoint'];
		var targetOffset	= this.data.inputs['GIScene:lineOfSight:targetOffset'];
		var obstacleLayers	= this.data.inputs['GIScene:lineOfSight:obstacleLayers'];	
		
		//@TODO evaluate inputs (occurences etc.)
		var scene = obstacleLayers[0].scene; // get scene from layers, better set sceen as a process param?
		
		var start 		= observerV3.clone().add(new THREE.Vector3(0,observerOffset,0)); //short graphic coords
		var end			= targetV3.clone().add(new THREE.Vector3(0,targetOffset,0));
		var direction 	= end.clone().sub(start).normalize();
		// var loading = new GIScene.Grid.TileStore(); // need it per layer
		var intersections; 
		var nearestIntersection = null;
		var numCheckedLayers = 0;
		var targetIsVisible;
		
		this.raycaster.set(start, direction); //(origin, direction) direction must be normalized
		
		this.raycaster.far = start.distanceTo(end);//targetV3.clone().sub(observerV3).length();
		
		console.log("far", this.raycaster.far);
		
		/**
		 * 
		 * @method getNearestIntersectionObject
		 * @private
		 * @param {THREE.Vector3} referencePoint
		 * @param {Object} intersectionObjectA object returned from Raycaster.intersectObjects() method
		 * @param {Object} intersectionObjectB object returned from Raycaster.intersectObjects() method
		 * @return {Object} nearestIntersectionObject
		 */
		var getNearestIntersectionObject = function(referencePoint, intersectionObjectA, intersectionObjectB) {
			//only valid if both are created by the same ray
			return ( intersectionObjectA.distance < intersectionObjectB.distance )?
					 intersectionObjectA
					:
					 intersectionObjectB
					;
			
			// return ( referencePoint.distanceTo(intersectionObjectA.point) < referencePoint.distanceTo(intersectionObjectB.point) )?
					  // intersectionObjectA
					 // :
					  // intersectionObjectB
					 // ;
			
		};
		
		//returns true if newIntersection is nearest
		var updateNearestIntersection = function(nearestIntersection_, newIntersection) {
			if ( !!(newIntersection) ){ //intersections found
			  	if(!nearestIntersection){ 
			  		nearestIntersection = newIntersection;
			  		return true;
			  	}
			  	else{
			  		nearestIntersection = getNearestIntersectionObject(start,nearestIntersection_,newIntersection);
			  		return (nearestIntersection === newIntersection); 
			  		};
			  	}
			  	else { //no intersections found
			  		return false;
			    }
		};
		
		// //find indexOf first element with true if no undefined is found before, otherwise if 
		// undefined before return -1
		// no undefined is found and all are false return false
		var getIndexOfFirstIntersectionTile = function(ctrl){
		  var first = false;
		  for (var i=0,j=ctrl.length;i<j;i++){
		
		    if(ctrl[i] === undefined)	{first = -1;	break;}
		    if(ctrl[i] === true)		{first = true;	break;}
		  
		  }
		  return first;
		};
		
		//all from here must be wrapped in a returnResults function
		var returnResults = function(layer, loading, computeTileIndicesHandler) {
			
			if(layer && loading){
				//abort all still loading in a finished layer
				var aborts = 0;
				for ( tile in loading.store ){
					aborts++;
					loading.store[tile].object.abort();
					loading.remove(new GIScene.Grid.Index().fromString(tile));
				}
				console.log(layer.name + ": optimized uncached tiles by abort running requests: " + aborts);
			}
			
			//restore original state
		  	if(computeTileIndicesHandler) layer.computeTileIndicesHandler = computeTileIndicesHandler;
			
			//wait until last layer has finished 
			if( !( numCheckedLayers == obstacleLayers.length ) ){
				console.log("Number of checked Layers",numCheckedLayers);	
				return;
				}
			console.log("Number of checked Layers",numCheckedLayers, "Ready.");
			
			
			//return to normal work
			
			gridLayers.forEach(function(layer,i,a){ layer.startUpdate(); });
			
			//evaluate intersections
			console.log("THE END");
			
			// var targetIsVisible = true;
			var visibilityLines;
			var group = new THREE.Object3D();
			
			if( nearestIntersection ) { 
				targetIsVisible = false; 
				
				//visLine
				var geomVis = new THREE.Geometry();
				geomVis.vertices = [start,nearestIntersection.point];
				var visLine = new THREE.Line(geomVis, lineMatVisible);
				//notVisLine
				var geomNotVis = new THREE.Geometry();
				geomNotVis.vertices = [nearestIntersection.point,end];
				var notvisLine = new THREE.Line(geomNotVis, lineMatNotVisible);
				
				group.add(visLine);
				group.add(notvisLine);
				}
			else {
				targetIsVisible = true;
				var geom = new THREE.Geometry();
				geom.vertices = [start,end];
				var visLine = new THREE.Line(geom, lineMatVisible);
				
				group.add(visLine);
			}
			
			this.data.outputs['GIScene:lineOfSight:lineOfSight'] = group;
			this.data.outputs['GIScene:lineOfSight:isVisible']	 = targetIsVisible;
			
			this.dispatchEvent({type:'execute', content : this.data});
			
			return this.data;
			
		}.bind(this);
		
		var checkUncachedTile = function(index, layer, loading, controlArray, uncachedTiles, uncachedTilesIndex, computeTileIndicesHandler, nearestIntersectionTileIndex) {
			
			var gridIndex = uncachedTiles[index];
			
			//TODO immediately skip if tile is completely beyond nearestIntersection
			// if tileCenter is further away from nearestIntersection than the boundingRadius (circle)
			if(nearestIntersection){
				var tileCenter = layer.grid.getCentroidFromIndex(gridIndex); //Vec2 short graphic coords
				var boundingRadius = Math.sqrt( 2 * Math.pow(gridIndex.tileSize, 2) );
				
				if( GIScene.Utils.vector3ToVector2(nearestIntersection.point.clone().sub(start)).length < tileCenter.clone().sub(GIScene.Utils.vector3ToVector2(start)) ){
					console.log(layer.name + ": optimize uncached tiles. Already nearer intersection found. Skip analysisTiles index" + uncachedTilesIndex[index]);
					return false; 
				}
			}
			
			
			var requestUrl = layer.config.service.getGetSceneUrl(gridIndex, layer.grid);
			
			var onSuccess = function(result) { //result is a THREE.Scene Object
				
				console.log(layer.name + ': LineOfSight:load uncached tiles:onSuccess',index);
				
				//remove from loading
				loading.remove(gridIndex);
				
				//rotate model if z is up
				if(layer.verticalAxis.toUpperCase() == "Z"){
					result.applyMatrix( new THREE.Matrix4().makeRotationX( -Math.PI/2 ) );
				}
				
				//setOverrideMaterial
				layer.setOverrideMaterial(result, layer.config.overrideMaterial);
				
				layer.root.add(result);
		  		result.updateMatrixWorld();
		  		
		  		//TODO may be wait for after render, so that everything is updated correctly?
		  		intersections = this.raycaster.intersectObject(layer.root, true);
		  		
		  		//TODO if no obstruction point needed then if intersections.length > 0 indicates visibility false and the whole process can be stopped here
		  		if(intersections.length > 0){
			  		var isNearestTile = updateNearestIntersection(nearestIntersection, intersections[0]);
			  		
			  		if(isNearestTile){nearestIntersectionTileIndex = uncachedTilesIndex[index]; }
			  		console.log("nearestIntersectionTileIndex", nearestIntersectionTileIndex);
			  		
			  		controlArray[uncachedTilesIndex[index]] = true;
		  		}
		  		else{
		  			controlArray[uncachedTilesIndex[index]] = false;
		  		}
		  		layer.root.remove(result);
		  		
		  		//add tile to cache to make use of having loaded it for after analysis visualization
		  		layer.cache.add(gridIndex,result);
				
				//TODO if there is no tile before left to be checked and an intersection was found here: continue the loop to the next layer
			//TODO check if all tiles have been tested then execute returnResults()
			var firstIntersectionTile = getIndexOfFirstIntersectionTile(controlArray);
			if(firstIntersectionTile !== -1){
				numCheckedLayers++;
				console.log(layer.name +" : firstIntersection found! Layer checked by UNCACHED!");
				returnResults(layer, loading, computeTileIndicesHandler);
			}
			
			
			}.bind(this);
			var onError = function() {};//TODO Sceneloader will never throw it
			
			var loader = new GIScene.ModelLoader(); //need a loader for every parallel request
			loader.load(requestUrl, layer.format, onSuccess, null, onError);
			loading.add(gridIndex,loader);
		}.bind(this);
		
		//get layers to be included in analysis
		//->obstacleLayers
		
		//find out which are Grid and which not
		var gridLayers = [];
		var staticLayers = [];
		obstacleLayers.forEach(function(e,i,a){
			if (e instanceof GIScene.Layer.Grid){gridLayers.push(e);}
			else {staticLayers.push(e);}
		});
		
		console.log("obstacle layers", obstacleLayers.length);
		console.log("static layers", staticLayers.length);
		console.log("grid layers", gridLayers.length);
		
		//first check the static layers then the more complicated gridLayers
		for(var i=0,j=staticLayers.length; i<j; i++){
		  
		  intersections = this.raycaster.intersectObject(staticLayers[i].root, true);
		  
		  //TODO if no obstruction point needed then if intersections.length > 0 indicates visibility false and the whole process can be stopped here
		  
		  //check which intersection is the first (nearest to start)
		  updateNearestIntersection(nearestIntersection, intersections[0]);
		  
		  numCheckedLayers++;
		  console.log(staticLayers[i].name +" : firstIntersection found! Layer checked STATIC!");
		  returnResults();
		  
		}; //static layer check end
		
		
		for(var i=0,j=gridLayers.length; i<j; i++){
		  
		  //get AnalysisTiles per layer
		  var layer = gridLayers[i];
		  var grid = layer.grid;
		  var smallestTileSize = grid.tileSizes[grid.tileSizes.length-1]; //tilesizes are sorted from big to small
		  var observerV2 = new GIScene.Coordinate2().fromVector2( GIScene.Utils.vector3ToVector2(observerV3.clone().add(grid._sceneOffset)) );
		  var targetV2 =   new GIScene.Coordinate2().fromVector2( GIScene.Utils.vector3ToVector2(  targetV3.clone().add(grid._sceneOffset)) );
		  
		  var optimizedTarget1 = (nearestIntersection)? new GIScene.Coordinate2().fromVector2( GIScene.Utils.vector3ToVector2(  nearestIntersection.point.clone().add(grid._sceneOffset)) ) : targetV2;
		  var woOptimizedTarget = grid.getTilesFromLineIntersection(observerV2,targetV2,smallestTileSize);
		  var analysisTiles = grid.getTilesFromLineIntersection(observerV2,optimizedTarget1,smallestTileSize);
		  var loading = new GIScene.Grid.TileStore();
		  
		  console.log("Start Analyzing " + layer.name);
		  console.log(layer.name + ": "+ optimizedTarget1.toArray());
		  console.log(layer.name + ": optimized number of tiles: " + analysisTiles.length +"/"+ woOptimizedTarget.length + " " + ((analysisTiles.length *100) / woOptimizedTarget.length) + "%");
		  
		  var controlArray = new Array(analysisTiles.length);
		  //check each analysisTile for obstructions asnchronuously
		  //tiles have to be loaded and added to the scene, to be sure that all matrices are applied correctly when tested with Raycaster
		  
		  //store layer function to be restores after the analysis
		  var computeTileIndicesHandler = layer.computeTileIndicesHandler;
		  
		  // remove all tiles
		  //better in future: remove tiles in two steps to be sure that the analysis tile get into the cache at last
		  layer.stopUpdate();
		  layer.computeTileIndicesHandler = function() {return [];};
		  layer.update(); //this removes all tiles from the scene
		  
		  //first check available tiles from cache
		  var cachedTiles	=[]; //Objects
		  var cachedTilesIndex = [];
		  var uncachedTiles	=[]; //TileIndex
		  var uncachedTilesIndex =[];
		  
		  var nearestIntersectionTileIndex;//analysisTiles Index //= (nearestIntersection)? layer.grid.getIndexFromPoint2d(GIScene.Utils.vector3ToVector2(nearestIntersection.point)) : undefined;
		  
		  var nearestIntersectionGridIndex = (nearestIntersection)? layer.grid.getIndexFromPoint2d(GIScene.Utils.vector3ToVector2(nearestIntersection.point),smallestTileSize) : undefined;
		  
		  
		  
		  
		  analysisTiles.forEach(function(e,i,a){
								  		if(nearestIntersectionGridIndex && e.equals(nearestIntersectionGridIndex)){nearestIntersectionTileIndex = i+1}
								  		var tileFromCache = layer.cache.getTile(e);
								  		if(tileFromCache){
								  			cachedTiles.push(tileFromCache);
								  			cachedTilesIndex.push(i);
								  		} 
								  		else {
								  			uncachedTiles.push(e);
								  			uncachedTilesIndex.push(i);
								  		}
					}.bind(this)
		  );
		  console.log("nearestIntersectionTileIndex", nearestIntersectionTileIndex);
		  console.log(layer.name + ": cached / uncached tiles", cachedTiles.length, uncachedTiles.length);
		  
		  console.log(layer.name + ": Analyzing "+ cachedTiles.length +" CACHED tiles");
		  
		  var layerChecked=false;
		  for(var iiii=0,jjjj=cachedTiles.length; iiii<jjjj; iiii++){
		  	
		  	var tileFromCache = cachedTiles[iiii];
		  	
		  	layer.root.add(tileFromCache);
		  		
	  		//TODO may be wait for after render, so that everything is updated correctly?
	  		intersections = this.raycaster.intersectObject(layer.root, true);
	  		
	  		//TODO if no obstruction point needed then if intersections.length > 0 indicates visibility false and the whole process can be stopped here
	  		if(intersections.length > 0){
	  			var isNearestTile = updateNearestIntersection(nearestIntersection, intersections[0]);
	  		
	  			if(isNearestTile){nearestIntersectionTileIndex = cachedTilesIndex[iiii]; }
	  			console.log("nearestIntersectionTileIndex", nearestIntersectionTileIndex);
	  			controlArray[cachedTilesIndex[iiii]] = true;
	  		}
	  		else{
	  			controlArray[cachedTilesIndex[iiii]] = false;
	  		}
	  		
	  		layer.root.remove(tileFromCache);
	  		
	  		var firstIntersectionTile = getIndexOfFirstIntersectionTile(controlArray); // true, false or -1
	  		console.log(layer.name + ' :firstIntersectionTile', firstIntersectionTile, controlArray.length);
	  		if(firstIntersectionTile !== -1){
	  			numCheckedLayers++;
	  			console.log(layer.name +" : firstIntersection found! Layer checked by CACHED!");
				returnResults(layer, loading, computeTileIndicesHandler);
				
				layerChecked = true;
				break; //no more cached tile testing
			}
		  };
		  if(layerChecked){continue;} //when checked by cached no testing of uncached --> test next layer
		  // cachedTiles.forEach(
		  	// function(tileFromCache,i,a){
		  		// layer.root.add(tileFromCache);
// 		  		
		  		// //TODO may be wait for after render, so that everything is updated correctly?
		  		// intersections = this.raycaster.intersectObject(layer.root, true);
// 		  		
		  		// //TODO if no obstruction point needed then if intersections.length > 0 indicates visibility false and the whole process can be stopped here
		  		// if(intersections.length > 0){
		  			// var isNearestTile = updateNearestIntersection(nearestIntersection, intersections[0]);
// 		  		
		  			// if(isNearestTile){nearestIntersectionTileIndex = cachedTilesIndex[i]; }
// 		  			
		  			// controlArray[cachedTilesIndex[i]] = true;
		  		// }
		  		// else{
		  			// controlArray[cachedTilesIndex[i]] = false;
		  		// }
// 		  		
// 		  		
		  		// layer.root.remove(tileFromCache);
// 		  		
		  		// var firstIntersectionTile = getIndexOfFirstIntersectionTile(controlArray); // true, false or -1
		  		// if(firstIntersectionTile !== -1){
		  			// numCheckedLayers++;
		  			// console.log(layer.name +" : firstIntersection found! Layer checked by CACHED!");
					// returnResults(layer, loading);
// 					
				// }
		  	// }.bind(this)
		  // );
		  
		  
		  
		  //reduce uncachedTiles according to former found intersections
		  //2nd reduction if uncached are left but found intersection in cached
		  if(nearestIntersection){
		  	var deleteFromIndex = null;
		  	for(var iii=uncachedTilesIndex.length-1, jjj=0; iii>=jjj; iii--){
				if(uncachedTilesIndex[iii] >= nearestIntersectionTileIndex){
					deleteFromIndex = iii;
				}
			  };
			  
			 if(deleteFromIndex != null){
			 	console.log('controlArray:before', controlArray);
			 	var uncached_orig_length = uncachedTiles.length;
			 	uncachedTiles.splice(deleteFromIndex,Number.MAX_VALUE); //???
			 	
			 	for(var v = deleteFromIndex, vi = uncached_orig_length/*uncachedTiles.length*/; v < vi; v++){
			 		controlArray[uncachedTilesIndex[v]] = false;
			 	}
			 	
			 	uncachedTilesIndex.splice(deleteFromIndex,Number.MAX_VALUE); //???
			 	
			 	//TODO update uncachedTileIndex and CONTROLARRAY 
			 	console.log(layer.name + ": optimized number of uncached tiles: " + uncached_orig_length +"-->"+ uncachedTiles.length);
			 	console.log('controlArray:after', controlArray);
			 }
		  }
		  
		  //load unavailable tiles and check async
		  //check remaining uncached tiles
		  console.log(layer.name + ": Analyzing "+ uncachedTiles.length +" UNCACHED tiles");
		  for(var ii=0,jj=uncachedTiles.length; ii<jj; ii++){
					
			if (checkUncachedTile(ii, layer, loading, controlArray, uncachedTiles, uncachedTilesIndex,computeTileIndicesHandler, nearestIntersectionTileIndex) === false) { continue; }
			
			// var gridIndex = uncachedTiles[ii]; 
// 			
			// var requestUrl = layer.config.service.getGetSceneUrl(gridIndex, layer.grid);
// 			
			// var onSuccess = function(result) { //result is a THREE.Scene Object
// 				
				// console.log('LineOfSight:load uncached tiles:onSuccess',ii);
// 				
				// layer.root.add(result);
// 		  		
		  		// //TODO may be wait for after render, so that everything is updated correctly?
		  		// intersections = this.raycaster.intersectObject(layer.root, true);
// 		  		
		  		// //TODO if no obstruction point needed then if intersections.length > 0 indicates visibility false and the whole process can be stopped here
		  		// if(intersections.length > 0){
			  		// var isNearestTile = updateNearestIntersection(nearestIntersection, intersections[0]);
// 			  		
			  		// if(isNearestTile){nearestIntersectionTileIndex = uncachedTilesIndex[ii]; }
// 			  		
			  		// controlArray[uncachedTilesIndex[ii]] = true;
		  		// }
		  		// else{
		  			// controlArray[uncachedTilesIndex[ii]] = false;
		  		// }
		  		// layer.root.remove(result);
// 		  		
		  		// //add tile to cache to make use of having loaded it for after analysis visualization
		  		// layer.cache.add(gridIndex,result);
// 				
				// //TODO if there is no tile before left to be checked and an intersection was found here: continue the loop to the next layer
			// //TODO check if all tiles have been tested then execute returnResults()
			// var firstIntersectionTile = getIndexOfFirstIntersectionTile(controlArray);
			// if(firstIntersectionTile !== -1){
				// return returnResults();
			// }
// 			
// 			
			// }.bind(this);
			// var onError = function() {};//TODO Sceneloader will never throw it
// 			
			// var loader = new GIScene.ModelLoader(); //need a loader for every parallel request
			// loader.load(requestUrl, layer.format, onSuccess, null, onError);
			
		  };
		  
		  // //restore original state
		  // layer.computeTileIndicesHandler = computeTileIndicesHandler;
		  // computeTileIndicesHandler = null;
		  // layer.startUpdate(); start upating all gridLayers later when analysis is finished 
		  
		}; // gridLayer check end
		
		

		
		
		
	};
		
	this.execute_ = function() {
		
		var observerV3 		= this.data.inputs['GIScene:lineOfSight:observerPoint'];
		var observerOffset 	= this.data.inputs['GIScene:lineOfSight:observerOffset'];	
		var targetV3		= this.data.inputs['GIScene:lineOfSight:targetPoint'];
		var targetOffset	= this.data.inputs['GIScene:lineOfSight:targetOffset'];
		var obstacles		= this.data.inputs['GIScene:lineOfSight:obstacles'];	
		
		var start 		= observerV3.clone().add(new THREE.Vector3(0,observerOffset,0));
		var end			= targetV3.clone().add(new THREE.Vector3(0,targetOffset,0));
		var direction 	= end.clone().sub(start).normalize();
		
		this.raycaster.set(start, direction); //(origin, direction) direction must be normalized
		
		this.raycaster.far = start.distanceTo(end);//targetV3.clone().sub(observerV3).length();
		
		console.log("far", this.raycaster.far);
		
		var intersections = this.raycaster.intersectObjects(obstacles, true);
		
		console.log("intersections",intersections);
		
		var targetIsVisible = true;
		var visibilityLines;
		var group = new THREE.Object3D();
		
		if( intersections.length > 0 ) { 
			targetIsVisible = false; 
			
			//visLine
			var geomVis = new THREE.Geometry();
			geomVis.vertices = [start,intersections[0].point];
			var visLine = new THREE.Line(geomVis, lineMatVisible);
			//notVisLine
			var geomNotVis = new THREE.Geometry();
			geomNotVis.vertices = [intersections[0].point,end];
			var notvisLine = new THREE.Line(geomNotVis, lineMatNotVisible);
			
			group.add(visLine);
			group.add(notvisLine);
			}
		else {
			var geom = new THREE.Geometry();
			geom.vertices = [start,end];
			var visLine = new THREE.Line(geom, lineMatVisible);
			
			group.add(visLine);
		}
		
		this.data.outputs['GIScene:lineOfSight:lineOfSight'] = group;
		this.data.outputs['GIScene:lineOfSight:isVisible']	 = targetIsVisible;
		
		console.log(this._listeners);
		this.dispatchEvent({type:'execute', content : this.data});
		
		return this.data;
		
	};
};

//Inherit from GIScene.Layer
GIScene.Process.LineOfSight_fastClient.prototype = Object.create( GIScene.Process.prototype );