API Docs for: 2.0.0

src/node-omicron.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014-2015

/**
 * Omicron connection module for SAGE2
 * Provides external input device support
 * https://github.com/uic-evl/omicron
 *
 * @module server
 * @submodule omicron
 * @requires node-coordinateCalculator, node-1euro
 */

// require variables to be declared
"use strict";

var dgram     = require('dgram');
var net       = require('net');
var util      = require('util');
var sageutils           = require('./node-utils');            // provides the current version number

var CoordinateCalculator = require('./node-coordinateCalculator');
var OneEuroFilter        = require('./node-1euro');

/* eslint consistent-this: ["error", "omicronManager"] */
var omicronManager; // Handle to OmicronManager inside of udp blocks (instead of this)
var drawingManager; // Connect to the node-drawing
/**
 * Omicron setup and opens a listener socket for an Omicron input server to connect to
 *
 * @class OmicronManager
 * @constructor
 * @param sysConfig {Object} SAGE2 system configuration file. Primararly used to grab display dimensions and Omicron settings
 */
function OmicronManager(sysConfig) {
	omicronManager = this;

	this.coordCalculator = null;

	this.oinputserverSocket = null;
	this.omicronDataPort = 9123;

	this.eventDebug   = false;
	this.gestureDebug = false;

	this.pointerOffscreen  = false;
	this.showPointerToggle = true;

	this.lastPosX = 0;
	this.lastPosY = 0;

	this.totalWidth  = 0;
	this.totalHeight = 0;

	// Touch
	this.enableTouch = true;
	this.touchOffset = [0, 0];
	this.wandScaleDelta = 250;
	this.acceleratedDragScale = 0;

	this.touchZoomScale = 520;

	// Mocap
	this.enableMocap = false;

	// Wand
	this.enableWand = false;
	this.wandLabel = "wandTracker";
	this.wandColor = "rgba(250, 5, 5, 1.0)";

	this.wandXFilter = null;
	this.wandYFilter = null;

	this.lastWandFlags     = 0;

	// 1 euro filtering
	var freq = 120;
	var mincutoff = 1.25;
	var beta = 2;
	var dcutoff = 10;

	this.wandXFilter = new OneEuroFilter(freq, mincutoff, beta, dcutoff);
	this.wandYFilter = new OneEuroFilter(freq, mincutoff, beta, dcutoff);

	if (sysConfig.experimental !== undefined) {
		this.config = sysConfig.experimental.omicron;
	}

	this.coordCalculator = new CoordinateCalculator(this.config);

	var serverHost = sysConfig.host;

	// Used to determine the initial position of a zoom gesture
	// If the distance from the initial position exceeds threshold,
	// zoom becomes a drag
	this.initZoomPos = {};
	this.zoomToMoveGestureMinimumDistance = 100;

	// Used to track changes in the pointer state (like a zoom becoming a move)
	this.pointerGestureState = {};

	// Default Gestures
	this.enableDoubleClickMaximize = false;
	this.enableThreeFingerRightClick = true;
	this.enableTwoFingerWindowDrag = false;
	this.enableTwoFingerZoom = true;
	this.enableFiveFingerCloseApp = false;

	// Default config
	if (this.config === undefined) {
		this.config = {};
		this.config.enable = false;
		this.config.dataPort = 30005;
		this.config.eventDebug = false;

		this.config.zoomGestureScale = 2000;
		this.config.acceleratedDragScale = 3;
		this.config.gestureDebug = false;

		this.config.msgPort = 28000;
	}

	if (this.config.enable === false) {
		return;
	}

	// Config: Touch
	this.enableTouch =  this.config.enableTouch;
	console.log(sageutils.header('Omicron') + 'Touch Enabled: ', this.enableTouch);

	// Config: Mocap
	this.enableMocap =  this.config.enableMocap;
	console.log(sageutils.header('Omicron') + 'Mocap Enabled: ', this.enableMocap);

	// Config: Wand
	this.enableWand =  this.config.enableWand;
	console.log(sageutils.header('Omicron') + 'Wand Enabled: ', this.enableWand);

	if (this.config.touchOffset) {
		this.touchOffset =  this.config.touchOffset;
		console.log(sageutils.header('Omicron') + 'Touch points offset by: ', this.touchOffset);
	}

	if (this.config.zoomGestureScale) {
		this.touchZoomScale = this.config.zoomGestureScale;
	}

	if (this.config.acceleratedDragScale) {
		this.acceleratedDragScale = this.config.acceleratedDragScale;
	}

	if (this.config.enableDoubleClickMaximize !== undefined) {
		this.enableDoubleClickMaximize = this.config.enableDoubleClickMaximize;
	}
	if (this.config.enableThreeFingerRightClick !== undefined) {
		this.enableThreeFingerRightClick = this.config.enableThreeFingerRightClick;
	}
	if (this.config.enableTwoFingerWindowDrag !== undefined) {
		this.enableTwoFingerWindowDrag = this.config.enableTwoFingerWindowDrag;
	}
	if (this.config.enableTwoFingerZoom !== undefined) {
		this.enableTwoFingerZoom = this.config.enableTwoFingerZoom;
	}
	if (this.config.enableFiveFingerCloseApp !== undefined) {
		this.enableFiveFingerCloseApp = this.config.enableFiveFingerCloseApp;
	}

	// Config: Omicron
	if (this.config.host === undefined) {
		sageutils.log('Omicron', 'Using web server hostname:', sysConfig.host);
	} else {
		serverHost = this.config.host;
		sageutils.log('Omicron', 'Using server hostname:', serverHost);
	}

	if (this.config.dataPort === undefined) {
		sageutils.log('Omicron', 'dataPort undefined. Using default:', this.omicronDataPort);
	} else {
		this.omicronDataPort =  this.config.dataPort;
		sageutils.log('Omicron', 'Listening for input server on port:', this.omicronDataPort);
	}

	if (this.config.touchOffset) {
		this.touchOffset =  this.config.touchOffset;
		sageutils.log('Omicron', 'Touch points offset by:', this.touchOffset);
	}

	if (this.config.eventDebug) {
		this.eventDebug =  this.config.eventDebug;
		sageutils.log('Omicron', 'Event Debug Info:', this.eventDebug);
	}

	if (this.config.gestureDebug) {
		this.gestureDebug =  this.config.gestureDebug;
		sageutils.log('Omicron', 'Gesture Debug Info:', this.gestureDebug);
	}

	if (sysConfig.resolution) {
		var columns = 1;
		var rows    = 1;

		if (sysConfig.layout) {
			columns = sysConfig.layout.columns;
			rows    = sysConfig.layout.rows;
		}

		this.totalWidth  = sysConfig.resolution.width * columns;
		this.totalHeight = sysConfig.resolution.height * rows;

		sageutils.log('Omicron', 'Touch Display Resolution:', this.totalWidth, this.totalHeight);
	} else {
		this.totalWidth  = 8160;
		this.totalHeight = 2304;
	}

	// For accepting input server connection
	var server = net.createServer(function(socket) {
		sageutils.log('Omicron', 'Input server',
			socket.remoteAddress, 'connected on port', socket.remotePort);

		socket.on('error', function(e) {
			sageutils.log('Omicron', 'Input server disconnected');
			socket.destroy(); // Clean up disconnected socket
		});

	});

	server.listen(this.omicronDataPort, serverHost);

	if (this.config.inputServerIP !== undefined) {
		omicronManager.oinputserverConnected = false;
		var msgPort = 28000;
		if (this.config.msgPort) {
			msgPort = this.config.msgPort;
		}

		omicronManager.connect(msgPort);

		// attempt to connect every 15 seconds, if connection failed
		setInterval(function() {
			if (omicronManager.oinputserverConnected === false) {
				omicronManager.connect(msgPort);
			}
		}, 15000);

		omicronManager.runTracker();
	}
}

/**
 * Initalizes connection with Omicron input servr
 *
 * @method connect
 */
OmicronManager.prototype.connect = function(msgPort) {
	sageutils.log('Omicron', 'Connecting to Omicron oinputserver at "' +
		omicronManager.config.inputServerIP + '" on msgPort: ' + msgPort + '.');

	omicronManager.oinputserverSocket = net.connect(msgPort, omicronManager.config.inputServerIP,  function() {
		// 'connect' listener
		sageutils.log('Omicron', 'Connection Successful. Requesting data on port',
			omicronManager.omicronDataPort);
		omicronManager.oinputserverConnected = true;

		var sendbuf = util.format("omicron_data_on,%d\n", omicronManager.omicronDataPort);
		omicronManager.oinputserverSocket.write(sendbuf);
	});
	omicronManager.oinputserverSocket.on('error', function(e) {
		sageutils.log('Omicron', 'oinputserver connection error - code:', e.code);
		omicronManager.oinputserverConnected = false;
	});
	omicronManager.oinputserverSocket.on('end', function(e) {
		sageutils.log('Omicron', 'oinputserver disconnected');
		omicronManager.oinputserverConnected = false;
	});
	omicronManager.oinputserverSocket.on('data', function(e) {
		// sageutils.log('Omicron', 'oinputserver receiving data:', e);
		// TCP stream
		// omicronManager.processIncomingEvent(e);
	});
};


/**
 * Sends disconnect signal to input server
 *
 * @method disconnect
 */
OmicronManager.prototype.disconnect = function() {
	if (this.oinputserverSocket) {
		var sendbuf = util.format("data_off");
		sageutils.log('Omicron', 'Sending disconnect signal');
		this.oinputserverSocket.write(sendbuf);
	}
};


/**
 * Links the drawing manager to the omicron server
 *
 * @method linkDrawingManager
 */
OmicronManager.prototype.linkDrawingManager = function(dManager) {
	drawingManager = dManager;
};


/**
 * Receives server pointer functions
 *
 * @method setCallbacks
 */
OmicronManager.prototype.setCallbacks = function(
	sagePointerList,
	createSagePointerCB,
	showPointerCB,
	pointerPressCB,
	pointerMoveCB,
	pointerPositionCB,
	hidePointerCB,
	pointerReleaseCB,
	pointerScrollStartCB,
	pointerScrollCB,
	pointerScrollEndCB,
	pointerDblClickCB,
	pointerCloseGestureCB,
	keyDownCB,
	keyUpCB,
	keyPressCB,
	createRadialMenuCB,
	omi_pointerChangeModeCB,
	kinectInputCB,
	remoteInteractionCB) {
	this.sagePointers        = sagePointerList;
	this.createSagePointer   = createSagePointerCB;
	this.showPointer         = showPointerCB;
	this.pointerPress        = pointerPressCB;
	this.pointerMove         = pointerMoveCB;
	this.pointerPosition     = pointerPositionCB;
	this.hidePointer         = hidePointerCB;
	this.pointerRelease      = pointerReleaseCB;
	this.pointerScrollStart  = pointerScrollStartCB;
	this.pointerScroll       = pointerScrollCB;
	this.pointerScrollEnd       = pointerScrollEndCB;
	this.pointerDblClick     = pointerDblClickCB;
	this.pointerCloseGesture = pointerCloseGestureCB;
	this.keyDown             = keyDownCB;
	this.keyUp               = keyUpCB;
	this.keyPress            = keyPressCB;
	this.createRadialMenu    = createRadialMenuCB;
	this.kinectInput 				 = kinectInputCB;
	this.pointerChangeMode = omi_pointerChangeModeCB;
	this.remoteInteraction = remoteInteractionCB;

	this.createSagePointer(this.config.inputServerIP);

	// sageutils.log('Omicron', "Server callbacks set");
};

/**
 * Manages incoming input server data
 *
 * @method runTracker
 */
OmicronManager.prototype.runTracker = function() {
	if (this.config.enable === false) {
		return;
	}

	var udp = dgram.createSocket("udp4");

	udp.on("message", function(msg, rinfo) {
		omicronManager.processIncomingEvent(msg, rinfo);
	});

	udp.on("listening", function() {
		var address = udp.address();
		sageutils.log('Omicron', 'UDP listening on port', address.port);
	});

	udp.bind(this.omicronDataPort);
};

OmicronManager.prototype.sageToOmicronEvent = function(uniqueID, pointerX, pointerY, data, type, color) {
	var e = {};
	e.timestamp = Date.now();
	e.sourceId = uniqueID;
	e.serviceType = 0; // 0 = pointer
	e.type = type;
	e.flags = 0;
	e.posx = pointerX;
	e.posy = pointerY;
	e.posz = 0;
	e.orw = 0;
	e.orx = 0;
	e.ory = 0;
	e.orz = 0;
	e.extraDataType = 0;
	e.extraDataItems = 0;
	e.extraDataMask = 0;
	e.extraDataSize = 0;
	e.extraDataString = color;
	return e;
};

OmicronManager.prototype.processIncomingEvent = function(msg, rinfo) {
	var dstart = Date.now();
	var emit   = 0;
	this.nonCriticalEventDelay = -1;
	this.lastNonCritEventTime = dstart;

	/*
	if(rinfo == undefined) {
		sageutils.log('Omicron', "incoming TCP");
	} else {
		sageutils.log('Omicron', "incoming UDP");
	}
	*/
	var offset = 0;
	var e = {};
	if (offset < msg.length) {
		e.timestamp = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.sourceId = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.serviceId = msg.readInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.serviceType = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.type = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.flags = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.posx = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.posy = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.posz = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.orw  = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.orx  = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.ory  = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.orz  = msg.readFloatLE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.extraDataType  = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.extraDataItems = msg.readUInt32LE(offset); offset += 4;
	}
	if (offset < msg.length) {
		e.extraDataMask  = msg.readUInt32LE(offset); offset += 4;
	}
	// Extra data types:
	//    0 ExtraDataNull,
	//    1 ExtraDataFloatArray,
	//    2 ExtraDataIntArray,
	//    3 ExtraDataVector3Array,
	//    4 ExtraDataString,
	//    5 ExtraDataKinectSpeech
	if (e.extraDataType == 0) {
		e.extraDataSize = 0;
	} else if (e.extraDataType == 1 || e.extraDataType == 2) {
		e.extraDataSize = e.extraDataItems * 4;
	} else if (e.extraDataType == 3) {
		e.extraDataSize = e.extraDataItems * 4 * 3;
	} else if (e.extraDataType == 4) {
		e.extraDataSize = e.extraDataItems;
	} else if (e.extraDataType == 5) {
		e.extraDataSize = e.extraDataItems;
	}

	// var r_roll  = Math.asin(2.0*e.orx*e.ory + 2.0*e.orz*e.orw);
	// var r_yaw   = Math.atan2(2.0*e.ory*e.orw-2.0*e.orx*e.orz , 1.0 - 2.0*e.ory*e.ory - 2.0*e.orz*e.orz);
	// var r_pitch = Math.atan2(2.0*e.orx*e.orw-2.0*e.ory*e.orz , 1.0 - 2.0*e.orx*e.orx - 2.0*e.orz*e.orz);
	var posX = e.posx * omicronManager.totalWidth;
	var posY = e.posy * omicronManager.totalHeight;
	posX += omicronManager.touchOffset[0];
	posY += omicronManager.touchOffset[1];

	var sourceID = e.sourceId;

	// serviceType:
	// 0 = Pointer
	// 1 = Mocap
	// 2 = Keyboard
	// 3 = Controller
	// 4 = UI
	// 5 = Generic
	// 6 = Brain
	// 7 = Wand
	// 8 = Speech
	// 9 = Ipad Framework
	var serviceType = e.serviceType;
	// console.log("Event service type: " + serviceType);

	// console.log(e.sourceId, e.posx, e.posy, e.posz);
	// serviceID:
	// (Note: this depends on the order the services are specified on the server)
	// 0 = Touch
	// 1 = Classic SAGEPointer
	// var serviceID = e.serviceId;

	// Appending sourceID to pointer address ID
	var address = sourceID;
	if (rinfo !== undefined) {
		address = rinfo.address + ":" + sourceID;
	} else {
		address = omicronManager.config.inputServerIP + ":" + sourceID;
	}

	// ServiceTypePointer
	//
	if (serviceType === 0 && omicronManager.enableTouch) {
		omicronManager.processPointerEvent(e, sourceID, posX, posY, msg, offset, address, emit, dstart);
	} else if (serviceType === 1 && omicronManager.enableMocap) {

		// Kinect v2.0 data has 29 extra data fields
		if (this.kinectInput != undefined && e.extraDataItems == 29) {
			if (omicronManager.eventDebug) {
				sageutils.log('Omicron', "Kinect body " + sourceID +
					" head Pos: (" + e.posx + ", " + e.posy + "," + e.posz + ")");
			}

			var extraData = [];

			while (offset < msg.length) {
				extraData.push(msg.readFloatLE(offset));
				offset += 4;
			}

			var bodyParts = [
				"OMICRON_SKEL_HIP_CENTER",
				"OMICRON_SKEL_HEAD",
				"Junk",
				"Junk",
				"Junk",
				"Junk",
				"OMICRON_SKEL_LEFT_SHOULDER",
				"OMICRON_SKEL_LEFT_ELBOW",
				"OMICRON_SKEL_LEFT_WRIST",
				"OMICRON_SKEL_LEFT_HAND",
				"OMICRON_SKEL_LEFT_FINGERTIP",
				"OMICRON_SKEL_LEFT_HIP",
				"OMICRON_SKEL_LEFT_KNEE",
				"OMICRON_SKEL_LEFT_ANKLE",
				"OMICRON_SKEL_LEFT_FOOT",
				"Junk",
				"OMICRON_SKEL_RIGHT_SHOULDER",
				"OMICRON_SKEL_RIGHT_ELBOW",
				"OMICRON_SKEL_RIGHT_WRIST",
				"OMICRON_SKEL_RIGHT_HAND",
				"OMICRON_SKEL_RIGHT_FINGERTIP",
				"OMICRON_SKEL_RIGHT_HIP",
				"OMICRON_SKEL_RIGHT_KNEE",
				"OMICRON_SKEL_RIGHT_ANKLE",
				"OMICRON_SKEL_RIGHT_FOOT",
				"OMICRON_SKEL_SPINE",
				"OMICRON_SKEL_SHOULDER_CENTER",
				"OMICRON_SKEL_LEFT_THUMB",
				"OMICRON_SKEL_RIGHT_THUMB"
			];

			var bodyPartIndex = 0;
			var posIndex = 0;
			var skeletonData = {};
			while (bodyPartIndex < bodyParts.length) {
				const bodyPart = bodyParts[bodyPartIndex++];
				skeletonData[bodyPart] = {
					x: extraData[posIndex++],
					y: extraData[posIndex++],
					z: extraData[posIndex++]
				};
			}

			skeletonData.skeletonID = sourceID;
			skeletonData.type = "kinectInput";

			this.kinectInput(sourceID, skeletonData);
		} else {
			// Treat as single marker mocap
			if (omicronManager.eventDebug) {
				sageutils.log('Omicron', "MocapID " + sourceID +
					" (" + e.posx + ", " + e.posy + "," + e.posz + ")");
			}
		}
	} else if (serviceType === 7 && omicronManager.enableWand) {
		// ServiceTypeWand
		//
		// Wand Button Flags
		// var button1 = 1;
		var button2 = 2; // Circle
		var button3 = 4; // Cross
		// var specialButton1 = 8;
		// var specialButton2 = 16;
		// var specialButton3 = 32;
		// var button4 = 64;
		var button5 = 128; // L1
		// var button6 = 256; // L3
		var button7 = 512; // L2
		var buttonUp = 1024;
		var buttonDown = 2048;
		var buttonLeft = 4096;
		var buttonRight = 8192;
		// var button8 = 32768;
		// var button9 = 65536;

		// Wand SAGE2 command mapping
		var clickDragButton = button3;
		var menuButton      = button2;
		var showHideButton  = button7;
		var scaleUpButton   = buttonUp;
		var scaleDownButton = buttonDown;
		var maximizeButton  = button5;
		var previousButton  = buttonLeft;
		var nextButton      = buttonRight;
		var playButton      = button2;

		// console.log("Wand Position: ("+e.posx+", "+e.posy+","+e.posz+")" );
		// console.log("Wand Rotation: ("+e.orx+", "+e.ory+","+e.orz+","+e.orw+")" );
		var screenPos = omicronManager.coordCalculator.wandToScreenCoordinates(
			e.posx, e.posy, e.posz, e.orx, e.ory, e.orz, e.orw
		);
		// console.log("Screen pos: ("+screenPos.x+", "+screenPos.y+")" );

		address = omicronManager.config.inputServerIP;

		// if( omicronManager.showPointerToggle === false )
		// return;
		var timeSinceLastNonCritEvent = Date.now() - omicronManager.lastNonCritEventTime;

		if (omicronManager.showPointerToggle && screenPos.x !== -1 && screenPos.y !== -1) {
			var timestamp = e.timestamp / 1000;
			posX = screenPos.x;
			posY = screenPos.y;

			// 1euro filter
			posX = omicronManager.wandXFilter.filter(screenPos.x, timestamp);
			posY = omicronManager.wandYFilter.filter(screenPos.y, timestamp);

			posX *= omicronManager.totalWidth;
			posY *= omicronManager.totalHeight;

			omicronManager.lastPosX = posX;
			omicronManager.lastPosY = posY;

			if (omicronManager.pointerOffscreen && omicronManager.showPointerToggle) {
				omicronManager.showPointer(omicronManager.config.inputServerIP, {
					label: omicronManager.wandLabel + " " + sourceID, color: omicronManager.wandColor
				});
				omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });
				omicronManager.pointerOffscreen = false;
			}
		} else {
			posX = omicronManager.lastPosX;
			posY = omicronManager.lastPosY;
			if (!omicronManager.pointerOffscreen && omicronManager.showPointerToggle) {
				omicronManager.hidePointer(omicronManager.config.inputServerIP);
				omicronManager.pointerOffscreen = true;
			}
		}

		if (timeSinceLastNonCritEvent >= omicronManager.nonCriticalEventDelay) {
			omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });
			omicronManager.lastNonCritEventTime = Date.now();
		}

		if (e.flags !== 0) {
			// console.log("Wand flags: " + e.flags + " " + (omicronManager.lastWandFlags & playButton) );
			if ((e.flags & clickDragButton) === clickDragButton && omicronManager.showPointerToggle) {
				if (omicronManager.lastWandFlags === 0) {
					// Wand Click
					omicronManager.pointerPress(address, posX, posY, { button: "left" });
				} else {
					// Wand Drag
					if (timeSinceLastNonCritEvent >= omicronManager.nonCriticalEventDelay) {
						omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });
						omicronManager.pointerMove(address, posX, posY, { deltaX: 0, deltaY: 0, button: "left" });

						// console.log((Date.now() - dstart) + "] Wand drag");
						omicronManager.lastNonCritEventTime = Date.now();
					}
				}
			} else if (omicronManager.lastWandFlags === 0 && (e.flags & menuButton) === menuButton &&
						omicronManager.showPointerToggle) {
				omicronManager.pointerPress(address, posX, posY, { button: "right" });
			} else if (omicronManager.lastWandFlags === 0 && (e.flags & showHideButton) === showHideButton) {
				if (!omicronManager.showPointerToggle) {
					omicronManager.showPointerToggle = true;
					omicronManager.showPointer(omicronManager.config.inputServerIP, {
						label:  omicronManager.wandLabel + " " + sourceID, color: omicronManager.wandColor
					});
					omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });
				} else {
					omicronManager.showPointerToggle = false;
					// hidePointer( omicronManager.config.inputServerIP );
				}
			} else if (omicronManager.lastWandFlags === 0 &&
					(e.flags & scaleUpButton) === scaleUpButton &&
					omicronManager.showPointerToggle) {
				omicronManager.pointerScrollStart(address, posX, posY);

				// Casting the parameters to correct type
				omicronManager.pointerScroll(address, { wheelDelta: parseInt(-omicronManager.wandScaleDelta, 10) });
			} else if (omicronManager.lastWandFlags === 0 &&
						(e.flags & scaleDownButton) === scaleDownButton &&
						omicronManager.showPointerToggle) {
				omicronManager.pointerScrollStart(address, posX, posY);

				// Casting the parameters to correct type
				omicronManager.pointerScroll(address, { wheelDelta: parseInt(omicronManager.wandScaleDelta, 10) });
			} else if (omicronManager.lastWandFlags === 0 &&
					(e.flags & maximizeButton) === maximizeButton &&
					omicronManager.showPointerToggle) {
				omicronManager.pointerDblClick(address, posX, posY);
			} else if ((omicronManager.lastWandFlags & previousButton) === 0 &&
					(e.flags & previousButton) === previousButton) {
				omicronManager.keyDown(address, posX, posY, { code: 37 });
			} else if ((omicronManager.lastWandFlags & nextButton) === 0 &&
					(e.flags & nextButton) === nextButton) {
				omicronManager.keyDown(address, posX, posY, { code: 39 });
			} else if ((omicronManager.lastWandFlags & playButton) === 0  &&
					(e.flags & playButton) === playButton) {
				omicronManager.keyPress(address, posX, posY, { code: 32 });
			}

			omicronManager.lastWandFlags = e.flags;
		} else if (omicronManager.lastWandFlags !== 0) {
			// TODO: Add a smarter way of detecting press, drag, release from button flags
			if ((omicronManager.lastWandFlags & clickDragButton) === clickDragButton) {
				// console.log("wandPointer release");
				omicronManager.pointerRelease(address, posX, posY, { button: "left" });

				omicronManager.lastWandFlags = 0;
			} else if ((omicronManager.lastWandFlags & showHideButton) === showHideButton) {
				omicronManager.lastWandFlags = 0;
			} else if ((omicronManager.lastWandFlags & scaleUpButton) === scaleUpButton) {
				omicronManager.lastWandFlags = 0;
			} else if ((omicronManager.lastWandFlags & scaleDownButton) === scaleDownButton) {
				omicronManager.lastWandFlags = 0;
			} else if ((omicronManager.lastWandFlags & maximizeButton) === maximizeButton) {
				omicronManager.lastWandFlags = 0;
			} else if ((omicronManager.lastWandFlags & previousButton) === previousButton) {
				omicronManager.lastWandFlags = 0;
				omicronManager.keyUp(address, posX, posY, { code: 37 });
			} else if ((omicronManager.lastWandFlags & nextButton) === nextButton) {
				omicronManager.lastWandFlags = 0;
				omicronManager.keyUp(address, posX, posY, { code: 39 });
			} else if ((omicronManager.lastWandFlags & playButton) === playButton) {
				omicronManager.lastWandFlags = 0;
				omicronManager.keyUp(address, posX, posY, { code: 32 });
			}
		}
	} // ServiceTypeWand ends ///////////////////////////////////////////
};

/**
 * Manages pointer (serviceType = 0) type events
 *
 * @method processPointerEvent
 * @param e {Event} Omicron event
 * @param sourceID {Integer} Pointer ID
 * @param posX {Float} Pointer x position in screen coordinates
 * @param posY {Float} Pointer y position in screen coordinates
 * @param msg {Binary} Binary message. Used to get extraData values
 * @param offset {Integer} Current offset position of msg
 * @param emit {}
 * @param dstart {}
 */
OmicronManager.prototype.processPointerEvent = function(e, sourceID, posX, posY, msg, offset, address, emit, dstart) {
	var touchWidth  = 0;
	var touchHeight = 0;

	if (e.extraDataItems >= 2) {
		touchWidth  = msg.readFloatLE(offset); offset += 4;
		touchHeight = msg.readFloatLE(offset); offset += 4;
	}

	// the touch size is normalized
	touchWidth *=  omicronManager.totalWidth;
	touchHeight *= omicronManager.totalHeight;

	if (omicronManager.eventDebug) {
		var eventTypeSrt = "";
		if (e.type == 4) {
			eventTypeSrt = "Move";
		} else if (e.type == 5) {
			eventTypeSrt = "Down";
		} else if (e.type == 6) {
			eventTypeSrt = "Up";
		}
		sageutils.log('Omicron', "pointer ID", sourceID, " event! type:", eventTypeSrt);
		// sageutils.log('Omicron', "pointer event! type: " + e.type);
		// sageutils.log('Omicron', "ServiceTypePointer> source", e.sourceId);
		// sageutils.log('Omicron', "ServiceTypePointer> serviceID", e.serviceId);
		// sageutils.log('Omicron', "   pos: " + posX.toFixed(2) + ", " + posY.toFixed(2) + " size: " + touchWidth.toFixed(2) + ", " + touchHeight.toFixed(2));
		// sageutils.log('Omicron', "pointer address", address);
	}

	if (drawingManager.drawingMode && e.type !== 6) {
		// If the touch is coming from oinput send it to node-drawing and stop after that
		// If touch up, still send to SAGE to clear touch
		drawingManager.pointerEvent(e, sourceID, posX, posY, touchWidth, touchHeight);
		return;
	}

	// If the user touches on the palette with drawing disabled, enable it
	if ((!drawingManager.drawingMode) && drawingManager.touchInsidePalette(posX, posY)
		&& e.type === 5) {
		// drawingManager.reEnableDrawingMode();
	}

	// TouchGestureManager Flags:
	// 1 << 18 = User flag start (as of 8/3/14)
	// User << 1 = Unprocessed
	// User << 2 = Single touch
	// User << 3 = Big touch
	// User << 4 = 5-finger hold
	// User << 5 = 5-finger swipe
	// User << 6 = 3-finger hold
	var User = 1 << 18;

	var FLAG_SINGLE_TOUCH = User << 2;
	var FLAG_BIG_TOUCH = User << 3;
	var FLAG_FIVE_FINGER_HOLD = User << 4;
	var FLAG_FIVE_FINGER_SWIPE = User << 5;
	var FLAG_THREE_FINGER_HOLD = User << 6;
	var FLAG_SINGLE_CLICK = User << 7;
	var FLAG_DOUBLE_CLICK = User << 8;
	var FLAG_MULTI_TOUCH = User << 9;

	var initX = 0;
	var initY = 0;

	var distance = 0;
	var angle = 0;
	var accelDistance = 0;
	var accelX = 0;
	var accelY = 0;

	// As of 2015/11/13 all touch gesture events touch have an init value
	// (zoomDelta moved to extraData index 4 instead of 2)
	// ExtraDataFloats
	// [0] width
	// [1] height
	// [2] initX
	// [3] initY
	// [4] touch count in group
	// [c] id of touch n
	// [c+1] xPos of touch n
	// [c+2] yPos of touch n
	if (e.extraDataItems > 2) {
		initX = msg.readFloatLE(offset); offset += 4;
		initY = msg.readFloatLE(offset); offset += 4;

		initX *= omicronManager.totalWidth;
		initY *= omicronManager.totalHeight;
	} else {
		initX = posX;
		initY = posY;
	}

	// var timeSinceLastNonCritEvent = Date.now() - omicronManager.lastNonCritEventTime;

	var flagStrings = {};
	flagStrings[FLAG_SINGLE_TOUCH] = "FLAG_SINGLE_TOUCH";
	flagStrings[FLAG_BIG_TOUCH] = "FLAG_BIG_TOUCH";
	flagStrings[FLAG_FIVE_FINGER_HOLD] = "FLAG_FIVE_FINGER_HOLD";
	flagStrings[FLAG_FIVE_FINGER_SWIPE] = "FLAG_FIVE_FINGER_SWIPE";
	flagStrings[FLAG_THREE_FINGER_HOLD] = "FLAG_THREE_FINGER_HOLD";
	flagStrings[FLAG_SINGLE_CLICK] = "FLAG_SINGLE_CLICK";
	flagStrings[FLAG_DOUBLE_CLICK] = "FLAG_DOUBLE_CLICK";
	flagStrings[FLAG_MULTI_TOUCH] = "FLAG_MULTI_TOUCH";

	var typeStrings = {};
	typeStrings[0] = "Select";
	typeStrings[1] = "Toggle";
	typeStrings[2] = "ChangeValue";
	typeStrings[3] = "Update";
	typeStrings[4] = "Move";
	typeStrings[5] = "Down";
	typeStrings[6] = "Up";
	typeStrings[7] = "Trace/Connect";
	typeStrings[8] = "Untrace/Disconnect";
	typeStrings[9] = "Click";
	typeStrings[15] = "Zoom";
	typeStrings[18] = "Split";
	typeStrings[21] = "Rotate";

	if (e.type === 4) { // EventType: MOVE
		//if (omicronManager.sagePointers[address] === undefined) {
		//	return;
		//}

		if (omicronManager.gestureDebug) {
			//sageutils.log('Omicron', "Touch move at - (" + posX.toFixed(2) + "," + posY.toFixed(2) + ") initPos: ("
			//+ initX.toFixed(2) + "," + initY.toFixed(2) + ")");
		}

		// Update pointer position
		omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });
		omicronManager.pointerMove(address, posX, posY, { deltaX: 0, deltaY: 0, button: "left" });

		/*
		if (timeSinceLastNonCritEvent > omicronManager.nonCriticalEventDelay) {
			if (e.flags == 0 || e.flags == FLAG_SINGLE_TOUCH) { // Basic touch event, non-gesture
				if (omicronManager.gestureDebug) {
					console.log("Touch move at - (" + posX.toFixed(2) + "," + posY.toFixed(2) + ") initPos: ("
					+ initX.toFixed(2) + "," + initY.toFixed(2) + ")");
				}

				distance = Math.sqrt(Math.pow(Math.abs(posX - initX), 2) + Math.pow(Math.abs(posY - initY), 2));
				angle = Math.atan2(posY -  initY, posX - initX);

				accelDistance = distance * omicronManager.acceleratedDragScale;
				accelX = posX + accelDistance * Math.cos(angle);
				accelY = posY + accelDistance * Math.sin(angle);

				omicronManager.pointerPosition(address, { pointerX: accelX, pointerY: accelY });
				omicronManager.pointerMove(address, accelX, accelY, { deltaX: 0, deltaY: 0, button: "left" });
				omicronManager.lastNonCritEventTime = Date.now();
			}
		}
		*/
	} else if (e.type === 5) { // EventType: DOWN
		//if (omicronManager.sagePointers[address] !== undefined) {
		//	return;
		//}

		if (omicronManager.gestureDebug) {
			sageutils.log('Omicron',
				"Touch down at - (" + posX.toFixed(2) + "," + posY.toFixed(2) + ") initPos: ("
				+ initX.toFixed(2) + "," + initY.toFixed(2) + ") flags:" + e.flags);
		}

		// Create the pointer
		omicronManager.createSagePointer(address);

		// Set the pointer style
		var pointerStyle = "Touch";
		if (omicronManager.config.style !== undefined) {
			pointerStyle = omicronManager.config.style;
		}
		omicronManager.showPointer(address, {
			label:  "Touch: " + sourceID,
			color: "rgba(242, 182, 15, 1.0)",
			sourceType: pointerStyle
		});

		// Set pointer mode
		var mode = "Window";
		if (omicronManager.config.interactionMode !== undefined) {
			mode = omicronManager.config.interactionMode;
		}

		if (mode === "App") {
			omicronManager.pointerChangeMode(address);
		}

		// Set the initial pointer position
		omicronManager.pointerPosition(address, { pointerX: posX, pointerY: posY });

		// Send 'click' event
		omicronManager.pointerPress(address, posX, posY, { button: "left" });

	} else if (e.type === 6) { // EventType: UP
		//if (omicronManager.sagePointers[address] === undefined) {
		//	return;
		//}

		if (omicronManager.gestureDebug) {
			// console.log("Touch release");
			sageutils.log('Omicron', "Touch up at - (" + posX.toFixed(2) + "," + posY.toFixed(2) + ") initPos: ("
				+ initX.toFixed(2) + "," + initY.toFixed(2) + ") flags:" + e.flags);
		}

		// Hide pointer
		omicronManager.hidePointer(address);

		// Release event
		omicronManager.pointerRelease(address, posX, posY, { button: "left" });

	} else if (e.type === 15 && omicronManager.enableTwoFingerZoom) {
		// zoom

		// Omicron zoom event extra data:
		// 0 = touchWidth (parsed above)
		// 1 = touchHeight (parsed above)
		// 2 = initX (parsed above)
		// 3 = initY (parsed above)
		// 4 = zoom delta
		// 5 = event second type ( 1 = Down, 2 = Move, 3 = Up )

		// extraDataType 1 = float
		// console.log("Touch zoom " + e.extraDataType  + " " + e.extraDataItems );
		if (e.extraDataType === 1 && e.extraDataItems >= 4) {
			var zoomDelta = msg.readFloatLE(offset); offset += 4;
			var eventType = msg.readFloatLE(offset);  offset += 4;

			// Zoom start/down
			if (eventType === 1) {
				// console.log("Touch zoom start");
				if (omicronManager.pointerGestureState[sourceID] !== "move") {
					omicronManager.pointerScrollStart(address, posX, posY);
					omicronManager.initZoomPos[sourceID] = {initX: posX, initY: posY};
					omicronManager.pointerGestureState[sourceID] = "zoom";
				}
			} else {
				// Zoom move
				omicronManager.pointerScroll(address, { wheelDelta: -zoomDelta * omicronManager.touchZoomScale });

				if (omicronManager.initZoomPos[sourceID] !== undefined &&
					omicronManager.pointerGestureState[sourceID] === "zoom") {
					initX = omicronManager.initZoomPos[sourceID].initX;
					initY = omicronManager.initZoomPos[sourceID].initY;
				}

				distance = Math.sqrt(Math.pow(Math.abs(posX - initX), 2) + Math.pow(Math.abs(posY - initY), 2));

				if (omicronManager.gestureDebug) {
					console.log("Touch zoom at - (" + posX.toFixed(2) + "," + posY.toFixed(2) + ") initPos: ("
					+ initX.toFixed(2) + "," + initY.toFixed(2) + ")");
					console.log("Touch zoom distance: " + distance);
					console.log("Touch zoom state: " + omicronManager.pointerGestureState[sourceID]);
				}

				if (omicronManager.enableTwoFingerWindowDrag && distance > omicronManager.zoomToMoveGestureMinimumDistance) {
					if (omicronManager.pointerGestureState[sourceID] === "zoom") {
						omicronManager.pointerScrollEnd(address, posX, posY);
						omicronManager.pointerRelease(address, posX, posY, { button: "left" });
						omicronManager.createSagePointer(address);
						omicronManager.pointerPress(address, posX, posY, { button: "left" });
						omicronManager.pointerGestureState[sourceID] = "move";
					}

				}
				angle = Math.atan2(posY -  initY, posX - initX);

				accelDistance = distance * omicronManager.acceleratedDragScale;
				accelX = posX + accelDistance * Math.cos(angle);
				accelY = posY + accelDistance * Math.sin(angle);

				omicronManager.pointerPosition(address, { pointerX: accelX, pointerY: accelY });
				omicronManager.pointerMove(address, accelX, accelY, { deltaX: 0, deltaY: 0, button: "left" });
				omicronManager.lastNonCritEventTime = Date.now();
			}
		}
	} else {
		console.log("\t UNKNOWN event type ", e.type, typeStrings[e.type]);
	}

	if (emit > 2) {
		dstart = Date.now();
		emit = 0;
	}
};

module.exports = OmicronManager;