/// <summary>
		/// Constructs a .MOT file client instance.
		/// </summary>
		///
		public FileClient()
		{
			this.sceneListeners = new List<SceneListener>();
			scene               = new Scene();
			sceneMutex          = new Mutex();
			dataStream          = null;
			streamingTimer      = null;
			notifyListeners     = false;
		}
		/// <summary>
		/// Constructs a NatNet client instance.
		/// This does not yet attempt to actually connect to the server.
		/// </summary>
		/// <param name="clientAppName">Name of the client to report to the server</param>
		/// <param name="clientAppVersion">Version number of the client to report to the server</param>
		/// 
		public NatNetClient(string clientAppName, byte[] clientAppVersion)
		{
			this.clientAppName       = clientAppName;
			this.clientAppVersion    = clientAppVersion;
			this.clientNatNetVersion = new byte[] {2, 9, 0, 0};

			this.sceneListeners  = new List<SceneListener>();

			serverInfo.serverName = "";
			multicastAddress      = null;
			scene                 = new Scene();
			connected             = false;
		}
		public void SceneChanged(Scene scene)
		{
			// scene description has changed > search for channel again
			oldValue = 0;
			pressed  = false;
			released = false;
			lastFrame = 0;

			channel = null;
			Device device = scene.FindDevice(deviceName);
			if (device != null)
			{
				channel = device.FindChannel(channelName);
			}
			if (channel != null)
			{
				Debug.Log("Handler registered for input device '" + deviceName + "', channel '" + channelName + "'");
			}
			else
			{
				Debug.LogWarning("Could not register handler for input device '" + deviceName + "', channel '" + channelName + "'");
			}
		}
		public          Channel[] channels; // channel data


		/// <summary>
		/// Creates a new device with a name and an ID.
		/// </summary>
		/// <param name="scene">scene this device belongs to</param>
		/// <param name="name">name of the device</param>
		/// <param name="id">ID of the device</param>
		/// 
		public Device(Scene scene, string name, int id)
		{
			this.scene = scene;
			this.name  = name;
			this.id    = id;
		}
		public Bone[]   bones;        // Bone data


		/// <summary>
		/// Creates a new actor.
		/// </summary>
		/// <param name="scene">the scene the actor belongs to</param>
		/// <param name="name">the name of the actor</param>
		/// <param name="id">the ID of the actor</param>
		/// 
		public Actor(Scene scene, string name, int id)
		{
			this.scene = scene;
			this.id    = id;
			this.name  = name;

			markers = new Marker[0];
			bones   = new Bone[0]; 
		}
		/// <summary>
		/// Constructs a dummy MoCap client
		/// </summary>
		///
		public DummyClient()
		{
			this.sceneListeners  = new List<SceneListener>();
			scene                = new Scene();
			connected            = false;
		}
		public void SceneChanged(Scene scene)
		{
			// re-match bone names with the next update
			dataBuffers = null;

			actor = scene.FindActor(actorName);
			if (actor != null)
			{
				Debug.Log("MoCapModel '" + this.name + "' controlled by MoCap actor '" + actorName + "'.");
			}
			else
			{
				Debug.LogWarning("MoCapModel '" + this.name + "' cannot find MoCap actor '" + actorName + "'.");
			}
		}
		public void SceneUpdated(Scene scene)
		{
			// create marker position array if necessary
			// but only when tracking is OK, otherwise the bone lengths are undefined
			if ((dataBuffers == null) && (actor != null))
			{
				MatchBones(actor.bones);
			}
		}
		/// <summary>
		/// Constructs a MoCap client that tracks HTC Vive HMDs and controllers.
		/// </summary>
		///
		public HtcViveClient()
		{
			sceneListeners  = new List<SceneListener>();
			scene           = new Scene();
			connected       = false;
		}
		public void SceneChanged(Scene scene)
		{
			// actor has changed > rebuild skeleton on next update
			if (skeletonNode != null)
			{
				// if necessary, destroy old container
				GameObject.Destroy(skeletonNode);
				skeletonNode = null;
			}

			actor = scene.FindActor(actorName);
			if (actor != null)
			{
				Debug.Log("Skeleton Renderer '" + this.name + "' controlled by MoCap actor '" + actorName + "'.");
			}
			else
			{
				Debug.LogWarning("Skeleton Renderer '" + this.name + "' cannot find MoCap actor '" + actorName + "'.");
			}
		}
		public void SceneUpdated(Scene scene)
		{
			// create marker position array if necessary
			// but only when tracking is OK, otherwise the bone lengths are undefined
			if ((skeletonNode == null) && (actor != null) && actor.bones[0].tracked)
			{
				CreateBones(actor.bones);
			}
		}
		public void SceneUpdated(Scene scene)
		{
			// create marker position array if necessary
			if ((markerNode == null) && (actor !=null))
			{
				CreateMarkers(actor.markers);
			}
		}
		public void SceneChanged(Scene scene)
		{
			// actor has changed > rebuild hierarchy on next update
			if (rootNode != null)
			{
				// if necessary, destroy old container
				this.transform.parent = rootNode.transform.parent;
				GameObject.Destroy(rootNode);
				rootNode = null;
			}

			Actor actor = scene.FindActor(actorName);
			if (actor != null)
			{
				if (boneName.Length > 0)
				{
					controllingBone = actor.FindBone(boneName);
				}
				if (controllingBone == null)
				{
					// not found, or not defined > use root bone
					Debug.Log("MoCap Object '" + this.name + "' controlled by MoCap actor '" + actorName + "'.");
					controllingBone = actor.bones[0];
				}
				else
				{
					Debug.Log("MoCap Object '" + this.name + "' controlled by MoCap actor/bone '" +
						actorName + "/" + controllingBone.name + "'.");
				}
			}
			else
			{
				Debug.LogWarning("Mocap Object '" + this.name + "' cannot find MoCap actor '" + actorName + "'.");
				if (trackingLostBehaviour == TrackingLostBehaviour.Disable)
				{
					// MoCap Actor not found at all > time to disable object?
					this.gameObject.SetActive(false);
				}
			}
		}
		public void SceneUpdated(Scene scene)
		{
			// create node hierarchy if not already built.
			// but only when tracking is OK, otherwise the bone lengths are undefined
			if ((rootNode == null) && (controllingBone != null))
			{
				CreateHierarchy();
			}
			if (!this.gameObject.activeInHierarchy)
			{
				// game object is inactive, so Update() doesn't get called any more
				// which would mean once deactivated, the object stays hidden forever
				// Workaround > call from here
				Update();
			}
		}
		public void SceneUpdated(Scene scene)
		{
			if (Time.frameCount != lastFrame)
			{
				// SceneUpdates might happen several times per Unity Update.
				// Reset flags only when a new Unity frame has come
			released = false;
			pressed  = false;
			}

			// store values and flags for queries
			if (channel != null)
			{
				value = channel.value;
				if (value != oldValue)
				{
					// changed value: set booleans accordingly
					if ((oldValue >= PressThreshold) && (value <  PressThreshold)) { released = true; }
					if ((oldValue <  PressThreshold) && (value >= PressThreshold)) { pressed  = true; }

					oldValue = value;
				}
			}
			lastFrame = Time.frameCount;
		}