source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.js@ 30121

Last change on this file since 30121 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 113.4 KB
Line 
1// -----------------------------------------------------------------------
2// <copyright file="Kinect-1.8.0.js" company="Microsoft">
3// Copyright (c) Microsoft Corporation. All rights reserved.
4// </copyright>
5// -----------------------------------------------------------------------
6
7
8//////////////////////////////////////////////////////////////
9// Create the global Kinect object
10var Kinect = (function () {
11 "use strict";
12
13 //////////////////////////////////////////////////////////////
14 // SocketManager object constructor
15 // SocketManager(uri, onmessage(socket,message) [, onconnection(isConnected)] )
16 //
17 // uri: URI of websocket endpoint to connect to
18 // onmessage: callback function to call whenever a message is received
19 // onconnection: function to call back whenever socket connection status changes
20 // from disconnected to connected or vice versa
21 function SocketManager(uri, onmessage, onconnection) {
22
23 //////////////////////////////////////////////////////////////
24 // Private SocketExec properties
25 var onStatusChanged = null;
26 var statusMessage = "";
27 var socket = null;
28 var socketManager = this;
29 var wsUri = uri.replace(/^http(s)?:/i, "ws$1:");
30
31 //////////////////////////////////////////////////////////////
32 // Private SocketExec methods
33 function updateStatusChanged() {
34 if (onStatusChanged != null) {
35 onStatusChanged(statusMessage);
36 }
37 }
38
39 function updateStatus(message) {
40 statusMessage = message;
41
42 updateStatusChanged();
43 }
44
45 function tryConnection() {
46 if (!socketManager.isStarted) {
47 return;
48 }
49
50 if (socket != null) {
51 updateStatus("Already connected." + (new Date()).toTimeString());
52 return;
53 }
54 updateStatus("Connecting to server...");
55
56 // Initialize a new web socket.
57 socket = new WebSocket(wsUri);
58
59 // Receive binary data as ArrayBuffer rather than Blob
60 socket.binaryType = "arraybuffer";
61
62 // Connection established.
63 socket.onopen = function () {
64 if (typeof onconnection == "function") {
65 onconnection(true);
66 }
67
68 updateStatus("Connected to server.");
69 };
70
71 // Connection closed.
72 socket.onclose = function () {
73 if (typeof onconnection == "function") {
74 onconnection(false);
75 }
76
77 updateStatus("Connection closed.");
78 if (socketManager.isStarted) {
79 // Keep trying to reconnect as long as we're started
80 setTimeout(tryConnection, socketManager.retryTimeout, socketManager);
81 }
82 socket = null;
83 };
84
85 // Receive data FROM the server!
86 socket.onmessage = function(message) {
87 onmessage(socket, message);
88 };
89 }
90
91 //////////////////////////////////////////////////////////////
92 // Public SocketManager properties
93
94 // connection retry timeout, in milliseconds
95 this.retryTimeout = 5000;
96
97 // true if socket has been started
98 this.isStarted = false;
99
100 //////////////////////////////////////////////////////////////
101 // Public SocketManager functions
102 this.setOnStatusChanged = function (statusChangedCallback) {
103 onStatusChanged = statusChangedCallback;
104 updateStatusChanged();
105 };
106
107 this.start = function() {
108 this.isStarted = true;
109
110 tryConnection(this);
111 };
112
113 this.stop = function() {
114 this.isStarted = false;
115
116 if (socket != null) {
117 socket.close();
118 }
119 };
120
121 //////////////////////////////////////////////////////////////
122 // SocketManager initialization code
123 if (window.WebSocket == null) {
124 updateStatus("Your browser does not support web sockets!");
125 return;
126 }
127 }
128
129 //////////////////////////////////////////////////////////////
130 // KinectConnector object constructor
131 function KinectConnector() {
132
133 //////////////////////////////////////////////////////////////
134 // Private KinectConnector properties
135
136 // Configure default connection values
137 var DEFAULT_HOST_URI = "http://localhost";
138 var DEFAULT_HOST_PORT = 8181;
139 var DEFAULT_BASE_PATH = "Kinect";
140 var DEFAULT_SENSOR_NAME = "default";
141
142 // If connectionUri is null, it indicates that we're not connected yet
143 var connectionUri = null;
144
145 // If explicitDisconnect is true, it indicates that client has called
146 // "KinectConnector.disconnect" explicitly.
147 var explicitDisconnect = false;
148
149 // Mapping between sensor names and KinectSensor objects
150 var sensorMap = {};
151
152 // true if server requests to REST endpoint should be asynchronous
153 var asyncRequests = true;
154
155 var connector = this;
156
157 //////////////////////////////////////////////////////////////
158 // KinectSensor object constructor
159 // KinectSensor(baseEndpointUri, onconnection(sensor, isConnected))
160 //
161 // baseEndpointUri: base URI for web endpoints corresponding to sensor
162 // onconnection: Function to call back whenever status of connection to the
163 // server that owns this sensor changes from disconnected to
164 // connected or vice versa.
165 function KinectSensor(baseEndpointUri, onconnection) {
166
167 //////////////////////////////////////////////////////////////
168 // Private KinectSensor properties
169
170 // URI used to connect to endpoint used to configure sensor state
171 var stateEndpoint = baseEndpointUri + "/state";
172
173 // true if sensor connection to server is currently enabled, false otherwise
174 var isConnectionEnabled = false;
175
176 // Array of registered stream frame ready handlers
177 var streamFrameHandlers = [];
178
179 // Header portion of a two-part stream message that is still missing a binary payload
180 var pendingStreamFrame = null;
181
182 // Reference to this sensor object for use in internal functions
183 var sensor = this;
184
185 var streamSocketManager = new SocketManager(
186 baseEndpointUri + "/stream",
187 function(socket, message) {
188
189 function broadcastFrame(frame) {
190 for (var iHandler = 0; iHandler < streamFrameHandlers.length; ++iHandler) {
191 streamFrameHandlers[iHandler](frame);
192 }
193 }
194
195 if (message.data instanceof ArrayBuffer) {
196 if ((pendingStreamFrame == null) || (pendingStreamFrame.bufferLength != message.data.byteLength)) {
197 // Ignore any binary messages that were not preceded by a header message
198 console.log("Binary socket message received without corresponding header");
199 pendingStreamFrame = null;
200 return;
201 }
202
203 pendingStreamFrame.buffer = message.data;
204 broadcastFrame(pendingStreamFrame);
205 pendingStreamFrame = null;
206 } else if (typeof(message.data) == "string") {
207 pendingStreamFrame = null;
208
209 try {
210 // Get the data in JSON format.
211 var frameData = JSON.parse(message.data);
212
213 // If message has a 'bufferLength' property, it means that this is a message header
214 // and we should wait for the binary payload before broadcasting this message.
215 if ("bufferLength" in frameData) {
216 pendingStreamFrame = frameData;
217 } else {
218 broadcastFrame(frameData);
219 }
220 }
221 catch(error) {
222 console.log('Tried processing stream message, but failed with error: ' + error);
223 }
224 } else {
225 // Ignore any messages of unexpected types.
226 console.log("Unexpected type for received socket message");
227 pendingStreamFrame = null;
228 return;
229 }
230 },
231 function(isConnected) {
232 // Use the existence of the stream channel connection to mean that a
233 // server exists and is ready to accept requests
234 sensor.isConnected = isConnected;
235 onconnection(sensor, isConnected);
236 });
237
238 // Array of registered event handlers
239 var eventHandlers = [];
240
241 var eventSocketManager = new SocketManager(baseEndpointUri + "/events", function(socket, message) {
242 // Get the data in JSON format.
243 var eventData = JSON.parse(message.data);
244
245 for (var iHandler = 0; iHandler < eventHandlers.length; ++iHandler) {
246 eventHandlers[iHandler](eventData);
247 }
248 });
249
250 // Registered hit test handler
251 var hitTestHandler = null;
252
253 var hitTestSocketManager = new SocketManager(baseEndpointUri + "/interaction/client", function (socket, message) {
254 // Get the data in JSON format.
255 var eventData = JSON.parse(message.data);
256 var id = eventData.id;
257 var name = eventData.name;
258 var args = eventData.args;
259
260 switch (name) {
261 case "getInteractionInfoAtLocation":
262 var handlerResult = null;
263 var defaultResult = { "isPressTarget": false, "isGripTarget": false };
264
265 if (hitTestHandler != null) {
266 try {
267 handlerResult = hitTestHandler.apply(sensor, args);
268 } catch(e) {
269 handlerResult = null;
270 }
271 }
272
273 socket.send(JSON.stringify({ "id": id, "result": ((handlerResult != null) ? handlerResult : defaultResult) }));
274 break;
275
276 case "ping":
277 socket.send(JSON.stringify({ "id": id, "result": true }));
278 break;
279 }
280 });
281
282 //////////////////////////////////////////////////////////////
283 // Private KinectSensor functions
284
285 // Perform ajax request
286 // ajax( method, uri, success(responseData) [, error(statusText)] )
287 //
288 // method: http method
289 // uri: target uri of ajax call
290 // requestData: data to send to uri as part of request (may be null)
291 // success: Callback function executed if request succeeds.
292 // error: Callback function executed if request fails (may be null)
293 function ajax(method, uri, requestData, success, error) {
294 if (!isConnectionEnabled) {
295 if (error != null) {
296 error("disconnected from server");
297 }
298 return;
299 }
300
301 var xhr = new XMLHttpRequest();
302 xhr.open(method, uri, asyncRequests);
303 xhr.onload = function (e) {
304 if (xhr.readyState === XMLHttpRequest.DONE) {
305 if (xhr.status == 200) {
306 success(JSON.parse(xhr.responseText));
307 } else if (error != null) {
308 error(xhr.statusText);
309 }
310 }
311 };
312 if (error != null) {
313 xhr.onerror = function (e) {
314 error(xhr.statusText);
315 };
316 }
317 xhr.send(requestData);
318 }
319
320 // Respond to changes in the connection enabled status
321 // onConnectionEnabledChanged( )
322 function onConnectionEnabledChanged() {
323 if (isConnectionEnabled && !streamSocketManager.isStarted) {
324 streamSocketManager.start();
325 } else if (!isConnectionEnabled && streamSocketManager.isStarted) {
326 streamSocketManager.stop();
327 }
328 }
329
330 // Respond to changes in the number of registered event handlers
331 // onEventHandlersChanged( )
332 function onEventHandlersChanged() {
333 if ((eventHandlers.length > 0) && !eventSocketManager.isStarted) {
334 eventSocketManager.start();
335 } else if ((eventHandlers.length == 0) && eventSocketManager.isStarted) {
336 eventSocketManager.stop();
337 }
338 }
339
340 // Respond to changes in the registered hit test handler
341 // onHitTestHandlerChanged( )
342 function onHitTestHandlerChanged() {
343 if ((hitTestHandler != null) && !hitTestSocketManager.isStarted) {
344 hitTestSocketManager.start();
345 } else if ((hitTestHandler == null) && hitTestSocketManager.isStarted) {
346 hitTestSocketManager.stop();
347 }
348 }
349
350 //////////////////////////////////////////////////////////////
351 // Public KinectSensor properties
352 this.isConnected = false;
353
354 //////////////////////////////////////////////////////////////
355 // Public KinectSensor functions
356
357 // Request current sensor configuration
358 // .getConfig( success(configData) [, error(statusText)] )
359 //
360 // success: Callback function executed if request succeeds.
361 // error: Callback function executed if request fails.
362 this.getConfig = function (success, error) {
363 if ((arguments.length < 1) || (typeof success != 'function')) {
364 throw new Error("first parameter must be specified and must be a function");
365 }
366
367 if ((arguments.length >= 2) && (typeof error != 'function')) {
368 throw new Error("if second parameter is specified, it must be a function");
369 }
370
371 ajax("GET", stateEndpoint, null,
372 function(response) { success(response); },
373 (error != null) ? function (statusText) { error(statusText); } : null
374 );
375 };
376
377 // Update current sensor configuration
378 // .postConfig( configData [, error(statusText [, data] )] )
379 //
380 // configData: New configuration property/value pairs to update
381 // for each sensor stream.
382 // error: Callback function executed if request fails.
383 // data parameter of error callback will be null if request
384 // could not be completed at all, but will be a JSON object
385 // giving failure details in case of semantic failure to
386 // satisfy request.
387 this.postConfig = function (configData, error) {
388 if ((arguments.length < 1) || (typeof configData != 'object')) {
389 throw new Error("first parameter must be specified and must be an object");
390 }
391
392 if ((arguments.length >= 2) && (typeof error != 'function')) {
393 throw new Error("if second parameter is specified, it must be a function");
394 }
395
396 ajax("POST", stateEndpoint, JSON.stringify(configData),
397 function(response) {
398 if (!response.success && (error != null)) {
399 error("semantic failure", response);
400 }
401 },
402 (error != null) ? function (statusText) { error(statusText); } : null
403 );
404 };
405
406 // Enable connections with server
407 // .connect()
408 this.connect = function () {
409 isConnectionEnabled = true;
410
411 onConnectionEnabledChanged();
412 onEventHandlersChanged();
413 onHitTestHandlerChanged();
414 };
415
416 // Disable connections with server
417 // .disconnect()
418 this.disconnect = function () {
419 isConnectionEnabled = false;
420
421 onConnectionEnabledChanged();
422 onEventHandlersChanged();
423 onHitTestHandlerChanged();
424 };
425
426 // Add a new stream frame handler.
427 // .addStreamFrameHandler( handler(streamFrame) )] )
428 //
429 // handler: Callback function to be executed when a stream frame is ready to
430 // be processed.
431 this.addStreamFrameHandler = function (handler) {
432 if (typeof(handler) != "function") {
433 throw new Error("first parameter must be specified and must be a function");
434 }
435
436 streamFrameHandlers.push(handler);
437 };
438
439 // Removes one (or all) stream frame handler(s).
440 // .removeStreamFrameHandler( [handler(streamFrame)] )] )
441 //
442 // handler: Stream frame handler callback function to be removed.
443 // If omitted, all stream frame handlers are removed.
444 this.removeStreamFrameHandler = function (handler) {
445 switch (typeof(handler)) {
446 case "undefined":
447 streamFrameHandlers = [];
448 break;
449 case "function":
450 var index = streamFrameHandlers.indexOf(handler);
451 if (index >= 0) {
452 streamFrameHandlers.slice(index, index + 1);
453 }
454 break;
455
456 default:
457 throw new Error("first parameter must either be a function or left unspecified");
458 }
459 };
460
461 // Add a new event handler.
462 // .addEventHandler( handler(event) )] )
463 //
464 // handler: Callback function to be executed when an event is ready to be processed.
465 this.addEventHandler = function (handler) {
466 if (typeof (handler) != "function") {
467 throw new Error("first parameter must be specified and must be a function");
468 }
469
470 eventHandlers.push(handler);
471
472 onEventHandlersChanged();
473 };
474
475 // Removes one (or all) event handler(s).
476 // .removeEventHandler( [handler(streamFrame)] )] )
477 //
478 // handler: Event handler callback function to be removed.
479 // If omitted, all event handlers are removed.
480 this.removeEventHandler = function (handler) {
481 switch (typeof (handler)) {
482 case "undefined":
483 eventHandlers = [];
484 break;
485 case "function":
486 var index = eventHandlers.indexOf(handler);
487 if (index >= 0) {
488 eventHandlers.slice(index, index + 1);
489 }
490 break;
491
492 default:
493 throw new Error("first parameter must either be a function or left unspecified");
494 }
495
496 onEventHandlersChanged();
497 };
498
499 // Set hit test handler.
500 // .setHitTestHandler( [handler(skeletonTrackingId, handType, x, y)] )] )
501 //
502 // handler: Callback function to be executed when interaction stream needs
503 // interaction information in order to adjust a hand pointer position
504 // relative to UI.
505 // If omitted, hit test handler is removed.
506 // - skeletonTrackingId: The skeleton tracking ID for which interaction
507 // information is being retrieved.
508 // - handType: "left" or "right" to represent hand type for which
509 // interaction information is being retrieved.
510 // - x: X-coordinate of UI location for which interaction information
511 // is being retrieved. 0.0 corresponds to left edge of interaction
512 // region and 1.0 corresponds to right edge of interaction region.
513 // - y: Y-coordinate of UI location for which interaction information
514 // is being retrieved. 0.0 corresponds to top edge of interaction
515 // region and 1.0 corresponds to bottom edge of interaction region.
516 this.setHitTestHandler = function (handler) {
517 switch (typeof (handler)) {
518 case "undefined":
519 case "function":
520 hitTestHandler = handler;
521 break;
522
523 default:
524 throw new Error("first parameter must be either a function or left unspecified");
525 }
526
527 onHitTestHandlerChanged();
528 };
529 }
530
531 //////////////////////////////////////////////////////////////
532 // Public KinectConnector properties
533
534 // Server connection defaults
535 this.DEFAULT_HOST_URI = DEFAULT_HOST_URI;
536 this.DEFAULT_HOST_PORT = DEFAULT_HOST_PORT;
537 this.DEFAULT_BASE_PATH = DEFAULT_BASE_PATH;
538 this.DEFAULT_SENSOR_NAME = DEFAULT_SENSOR_NAME;
539
540 // Supported stream names
541 this.INTERACTION_STREAM_NAME = "interaction";
542 this.USERVIEWER_STREAM_NAME = "userviewer";
543 this.BACKGROUNDREMOVAL_STREAM_NAME = "backgroundRemoval";
544 this.SKELETON_STREAM_NAME = "skeleton";
545 this.SENSORSTATUS_STREAM_NAME = "sensorStatus";
546
547 // Supported event categories and associated event types
548 this.USERSTATE_EVENT_CATEGORY = "userState";
549 this.PRIMARYUSERCHANGED_EVENT_TYPE = "primaryUserChanged";
550 this.USERSTATESCHANGED_EVENT_TYPE = "userStatesChanged";
551 this.SENSORSTATUS_EVENT_CATEGORY = "sensorStatus";
552 this.SENSORSTATUSCHANGED_EVENT_TYPE = "statusChanged";
553
554 //////////////////////////////////////////////////////////////
555 // Public KinectConnector methods
556
557 // Enable connections with server
558 // .connect( [ hostUri [, hostPort [, basePath ] ] ] )
559 //
560 // hostUri: URI for host that is serving Kinect data.
561 // Defaults to "http://localhost".
562 // hostPort: HTTP port from which Kinect data is being served.
563 // Defaults to 8181.
564 // basePath: base URI path for all endpoints that serve Kinect data.
565 // Defaults to "kinect".
566 this.connect = function (hostUri, hostPort, basePath) {
567 hostUri = (hostUri != null) ? hostUri : DEFAULT_HOST_URI;
568 hostPort = (hostPort != null) ? hostPort : DEFAULT_HOST_PORT;
569 basePath = (basePath != null) ? basePath : DEFAULT_BASE_PATH;
570
571 // Indicate we're now connected
572 connectionUri = hostUri + ":" + hostPort + "/" + basePath;
573 explicitDisconnect = false;
574 };
575
576 // Disable connections with server
577 // .disconnect()
578 this.disconnect = function () {
579 // stop all sensors currently tracked
580
581 for (var sensorKey in sensorMap) {
582 var sensor = sensorMap[sensorKey];
583 sensor.disconnect();
584 }
585
586 sensorMap = {};
587
588 // Indicate we're now disconnected
589 connectionUri = null;
590 explicitDisconnect = true;
591 };
592
593 // Gets an object representing a connection with a specific Kinect sensor.
594 // .sensor( [sensorName [, onconnection(sensor, isConnected)]] )
595 //
596 // sensorName: name used to refer to a specific Kinect sensor exposed by the server.
597 // Defaults to "default".
598 // onconnection: Function to call back whenever status of connection to the
599 // server that owns the sensor with specified name changes from
600 // disconnected to connected or vice versa.
601 //
602 // Remarks
603 // - If client has never called KinectConnector.connect or .disconnect, .connect() is
604 // called implicitly with default server URI parameters.
605 // - If client has explicitly called KinectConnector.disconnect, calling this method
606 // returns null.
607 // - If a non-null sensor is returned, it will already be in connected state (i.e.:
608 // there is no need to call KinectSensor.connect immediately after calling this
609 // method).
610 this.sensor = function (sensorName, onconnection) {
611 switch (typeof(sensorName)) {
612 case "undefined":
613 case "string":
614 break;
615
616 default:
617 throw new Error("first parameter must be a string or left unspecified");
618 }
619 switch (typeof (onconnection)) {
620 case "undefined":
621 // provide a guarantee to KinectSensor constructor that onconnection
622 // will exist and be a real function
623 onconnection = function() { };
624 break;
625 case "function":
626 break;
627
628 default:
629 throw new Error("second parameter must be a function or left unspecified");
630 }
631
632 if (explicitDisconnect) {
633 return null;
634 }
635
636 if (connectionUri == null) {
637 // If we're not connected yet, connect with default parameters
638 // when someone asks for a sensor.
639 this.connect();
640 }
641
642 sensorName = (sensorName != null) ? sensorName : DEFAULT_SENSOR_NAME;
643
644 // If sensor for specified name doesn't exist yet, create it
645 if (!sensorMap.hasOwnProperty(sensorName)) {
646 sensorMap[sensorName] = new KinectSensor(connectionUri + "/" + sensorName, onconnection);
647
648 // Start the sensors connected by default when they are requested
649 sensorMap[sensorName].connect();
650 }
651
652 return sensorMap[sensorName];
653 };
654
655 // Get whether server requests to REST endpoints should be asynchronous (true by default)
656 // .async()
657 //
658 // -------------------------------------------------------------------------------------
659 // Specify whether server requests to REST endpoints should be asynchronous
660 // .async( asyncRequests )
661 //
662 // isAsync: true if server requests to REST endpoints should be asynchronous
663 // false if server requests to REST endpoints should be synchronous
664 this.async = function(isAsync) {
665 if (typeof isAsync == 'undefined') {
666 return asyncRequests;
667 }
668
669 var newAsync = isAsync == true;
670 asyncRequests = newAsync;
671 };
672 }
673
674 // The global Kinect object is a singleton instance of our internal KinectConnector type
675 return new KinectConnector();
676})();
677
678//////////////////////////////////////////////////////////////
679// Create the global Kinect UI factory object
680var KinectUI = (function () {
681 "use strict";
682
683 //////////////////////////////////////////////////////////////
684 // KinectUIAdapter object constructor
685 function KinectUIAdapter() {
686
687 //////////////////////////////////////////////////////////////
688 // HandPointer object constructor
689 function HandPointer(trackingId, playerIndex, handType) {
690 //////////////////////////////////////////////////////////////
691 // Public HandPointer properties
692 this.trackingId = trackingId;
693 this.playerIndex = playerIndex;
694 this.handType = handType;
695 this.timestampOfLastUpdate = 0;
696 this.handEventType = "None";
697 this.isTracked = false;
698 this.isActive = false;
699 this.isInteractive = false;
700 this.isPressed = false;
701 this.isPrimaryHandOfUser = false;
702 this.isPrimaryUser = false;
703 this.isInGripInteraction = false;
704 this.captured = null;
705 this.x = 0.0;
706 this.y = 0.0;
707 this.pressExtent = 0.0;
708 this.rawX = 0.0;
709 this.rawY = 0.0;
710 this.rawZ = 0.0;
711 this.originalHandPointer = null;
712 this.updated = false;
713
714 //////////////////////////////////////////////////////////////
715 // Public HandPointer methods
716
717 // Get all DOM elements that this hand pointer has entered.
718 // .getEnteredElements()
719 this.getEnteredElements = function () {
720 var key = getHandPointerKey(this.trackingId, this.handType);
721 var data = internalHandPointerData[key];
722
723 if (data == null) {
724 return [];
725 }
726
727 return data.enteredElements;
728 };
729
730 // Determine whether this hand pointer is over the specified DOM element.
731 // .getIsOver( element )
732 //
733 // element: DOM element against which to check hand pointer position.
734 this.getIsOver = function (element) {
735 // Iterate over cached elements
736 var enteredElements = this.getEnteredElements();
737
738 for (var iElement = 0; iElement < enteredElements.length; ++iElement) {
739 if (enteredElements[iElement] === element) {
740 return true;
741 }
742 }
743
744 return false;
745 };
746
747 // Create a capture association between this hand pointer and the specified DOM
748 // element. This means that event handlers associated with element will receive
749 // events triggered by this hand pointer even if hand pointer is not directly
750 // over element.
751 // .capture( element )
752 //
753 // element: DOM element capturing hand pointer.
754 // If null, this hand pointer stops being captured and resumes normal
755 // event routing behavior.
756 this.capture = function (element) {
757 return captureHandPointer(this, element);
758 };
759
760 // Determine whether this hand pointer corresponds to the primary hand of the
761 // primary user.
762 // .getIsPrimaryHandOfPrimaryUser()
763 this.getIsPrimaryHandOfPrimaryUser = function () {
764 return this.isPrimaryUser && this.isPrimaryHandOfUser;
765 };
766
767 // Update this hand pointer's properties from the specified hand pointer received
768 // from the interaction stream.
769 // .update( streamHandPointer )
770 this.update = function (streamHandPointer) {
771 // save the stream hand pointer in case it has additional properties
772 // that clients might be interested in
773 this.originalHandPointer = streamHandPointer;
774
775 this.updated = true;
776
777 this.timestampOfLastUpdate = streamHandPointer.timestampOfLastUpdate;
778 this.handEventType = streamHandPointer.handEventType;
779
780 var pressedChanged = this.isPressed != streamHandPointer.isPressed;
781 this.isPressed = streamHandPointer.isPressed;
782
783 this.isTracked = streamHandPointer.isTracked;
784 this.isActive = streamHandPointer.isActive;
785 this.isInteractive = streamHandPointer.isInteractive;
786
787 var primaryHandOfPrimaryUserChanged = this.getIsPrimaryHandOfPrimaryUser() != (streamHandPointer.isPrimaryHandOfUser && streamHandPointer.isPrimaryUser);
788 this.isPrimaryHandOfUser = streamHandPointer.isPrimaryHandOfUser;
789 this.isPrimaryUser = streamHandPointer.isPrimaryUser;
790
791 var newPosition = interactionZoneToWindow({ "x": streamHandPointer.x, "y": streamHandPointer.y });
792 var positionChanged = !areUserInterfaceValuesClose(newPosition.x, this.x) ||
793 !areUserInterfaceValuesClose(newPosition.y, this.y) ||
794 !areUserInterfaceValuesClose(streamHandPointer.pressExtent, this.pressExtent);
795 this.x = newPosition.x;
796 this.y = newPosition.y;
797 this.pressExtent = streamHandPointer.pressExtent;
798
799 this.rawX = streamHandPointer.rawX;
800 this.rawY = streamHandPointer.rawY;
801 this.rawZ = streamHandPointer.rawZ;
802
803 return {
804 "pressedChanged": pressedChanged,
805 "primaryHandOfPrimaryUserChanged": primaryHandOfPrimaryUserChanged,
806 "positionChanged": positionChanged
807 };
808 };
809 }
810
811 //////////////////////////////////////////////////////////////
812 // ImageWorkerManager object constructor
813 // new ImageWorkerManager()
814 //
815 // Remarks
816 // Manages background thread work related to processing image
817 // stream frames to get them ready to be displayed in a canvas
818 // element.
819 function ImageWorkerManager() {
820
821 //////////////////////////////////////////////////////////////
822 // ImageMetadata object constructor
823 function ImageMetadata(imageCanvas) {
824 this.isProcessing = false;
825 this.canvas = imageCanvas;
826 this.width = 0;
827 this.height = 0;
828 }
829
830 //////////////////////////////////////////////////////////////
831 // Private ImageWorkerManager properties
832 var workerThread = null;
833 var imageMetadataMap = {};
834
835 //////////////////////////////////////////////////////////////
836 // Private ImageWorkerManager methods
837 function ensureInitialized() {
838 // Lazy-initialize web worker thread
839 if (workerThread == null) {
840 workerThread = new Worker(scriptRootURIPath + "KinectWorker-1.8.0.js");
841 workerThread.addEventListener("message", function (event) {
842 var imageName = event.data.imageName;
843 if (!imageMetadataMap.hasOwnProperty(imageName)) {
844 return;
845 }
846 var metadata = imageMetadataMap[imageName];
847
848 switch (event.data.message) {
849 case "imageReady":
850 // Put ready image data in associated canvas
851 var canvasContext = metadata.canvas.getContext("2d");
852 canvasContext.putImageData(event.data.imageData, 0, 0);
853 metadata.isProcessing = false;
854 break;
855
856 case "notProcessed":
857 metadata.isProcessing = false;
858 break;
859 }
860 });
861 }
862 }
863
864 //////////////////////////////////////////////////////////////
865 // Public ImageWorkerManager methods
866
867 // Send named image data to be processed by worker thread
868 // .processImageData(imageName, imageBuffer, width, height)
869 //
870 // imageName: Name of image to process
871 // imageBuffer: ArrayBuffer containing image data
872 // width: width of image corresponding to imageBuffer data
873 // height: height of image corresponding to imageBuffer data
874 this.processImageData = function (imageName, imageBuffer, width, height) {
875 ensureInitialized();
876
877 if (!imageMetadataMap.hasOwnProperty(imageName)) {
878 // We're not tracking this image, so no work to do
879 return;
880 }
881 var metadata = imageMetadataMap[imageName];
882
883 if (metadata.isProcessing || (width <= 0) || (height <= 0)) {
884 // Don't send more data to worker thread when we are in the middle
885 // of processing data already.
886 // Also, Only do work if image data to process is of the expected size
887 return;
888 }
889
890 metadata.isProcessing = true;
891
892 if ((width != metadata.width) || (height != metadata.height)) {
893 // Whenever the image width or height changes, update image tracking metadata
894 // and canvas ImageData associated with worker thread
895
896 var canvasContext = metadata.canvas.getContext("2d");
897 var imageData = canvasContext.createImageData(width, height);
898 metadata.width = width;
899 metadata.height = height;
900 metadata.canvas.width = width;
901 metadata.canvas.height = height;
902
903 workerThread.postMessage({ "message": "setImageData", "imageName": imageName, "imageData": imageData });
904 }
905
906 workerThread.postMessage({ "message": "processImageData", "imageName": imageName, "imageBuffer": imageBuffer });
907 };
908
909 // Associate specified image name with canvas ImageData object for future usage by
910 // worker thread
911 // .setImageData(imageName, canvas)
912 //
913 // imageName: Name of image stream to associate with ImageData object
914 // canvas: Canvas to bind to user viewer stream
915 this.setImageData = function (imageName, canvas) {
916 ensureInitialized();
917
918 if (canvas != null) {
919 var metadata = new ImageMetadata(canvas);
920 imageMetadataMap[imageName] = metadata;
921 } else if (imageMetadataMap.hasOwnProperty(imageName)) {
922 // If specified canvas is null but we're already tracking image data,
923 // remove metadata associated with this image.
924 delete imageMetadataMap[imageName];
925 }
926 };
927 }
928
929 //////////////////////////////////////////////////////////////
930 // Private KinectUIAdapter constants
931 var LAZYRELEASE_RADIUS = 0.3;
932 var LAZYRELEASE_YCUTOFF = LAZYRELEASE_RADIUS / 3;
933
934 //////////////////////////////////////////////////////////////
935 // Private KinectUIAdapter properties
936 var uiAdapter = this;
937 var isInInteractionFrame = false;
938 var isClearRequestPending = false;
939 var handPointerEventsEnabled = true;
940 // Property names of internalHandPointerData are keys that encode user tracking id and hand type.
941 // Property values are JSON objects with "handPointer" and "enteredElements" properties.
942 var internalHandPointerData = {};
943 var excludedElements = [];
944 var eventHandlers = {};
945 var bindableStreamNames = {};
946 bindableStreamNames[Kinect.USERVIEWER_STREAM_NAME] = true;
947 bindableStreamNames[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = true;
948 var imageWorkerManager = new ImageWorkerManager();
949
950 //////////////////////////////////////////////////////////////
951 // Private KinectUIAdapter methods
952 function interactionZoneToElement(point, element) {
953 return { "x": point.x * element.offsetWidth, "y": point.y * element.offsetHeight };
954 }
955
956 function elementToInteractionZone(point, element) {
957 return { "x": point.x / element.offsetWidth, "y": point.y / element.offsetHeight };
958 }
959
960 function interactionZoneToWindow(point) {
961 return { "x": point.x * window.innerWidth, "y": point.y * window.innerHeight };
962 }
963
964 function windowToInteractionZone(point) {
965 return { "x": point.x / window.innerWidth, "y": point.y / window.innerHeight };
966 }
967
968 function areUserInterfaceValuesClose(a, b) {
969 return Math.abs(a - b) < 0.5;
970 }
971
972 function getPressTargetPoint(element) {
973 // Hardcoded to always return the center point
974 return { "x": 0.5, "y": 0.5 };
975 }
976
977 function getHandPointerKey(trackingId, handType) {
978 return handType + trackingId;
979 }
980
981 function createInteractionEvent(eventName, detail) {
982 var event = document.createEvent("CustomEvent");
983 var canBubble = true;
984 var cancelable = false;
985
986 switch (eventName) {
987 case uiController.HANDPOINTER_ENTER:
988 case uiController.HANDPOINTER_LEAVE:
989 canBubble = false;
990 break;
991 }
992
993 event.initCustomEvent(eventName, canBubble, cancelable, detail);
994
995 return event;
996 }
997
998 function raiseHandPointerEvent(element, eventName, handPointer) {
999 if (!handPointerEventsEnabled) {
1000 return;
1001 }
1002
1003 var event = createInteractionEvent(eventName, { "handPointer": handPointer });
1004 element.dispatchEvent(event);
1005 }
1006
1007 function raiseGlobalUIEvent(eventName, eventData) {
1008 if (!handPointerEventsEnabled || !eventHandlers.hasOwnProperty(eventName)) {
1009 // Events are disabled, or there are no event handlers registered for event.
1010 return;
1011 }
1012
1013 var handlerArray = eventHandlers[eventName];
1014 for (var i = 0; i < handlerArray.length; ++i) {
1015 handlerArray[i](eventData);
1016 }
1017 }
1018
1019 function switchCapture(handPointer, oldElement, newElement) {
1020 handPointer.captured = newElement;
1021
1022 if (oldElement != null) {
1023 raiseHandPointerEvent(oldElement, uiController.HANDPOINTER_LOSTCAPTURE, handPointer);
1024 }
1025
1026 if (newElement != null) {
1027 raiseHandPointerEvent(newElement, uiController.HANDPOINTER_GOTCAPTURE, handPointer);
1028 }
1029 }
1030
1031 function captureHandPointer(handPointer, element) {
1032 var id = getHandPointerKey(handPointer.trackingId, handPointer.handType);
1033
1034 if (!internalHandPointerData.hasOwnProperty(id)) {
1035 // We're not already tracking a hand pointer with the same id.
1036 return false;
1037 }
1038
1039 var handPointerData = internalHandPointerData[id];
1040 var checkHandPointer = handPointerData.handPointer;
1041 if (checkHandPointer !== handPointer) {
1042 // The hand pointer we're tracking is not the same one that was handed in.
1043 // Caller may have a stale hand pointer instance.
1044 return false;
1045 }
1046
1047 if (element != null && handPointer.captured != null) {
1048 // Request wasn't to clear capture and some other element already has this
1049 // HandPointer captured.
1050 return false;
1051 }
1052
1053 switchCapture(handPointer, handPointer.captured, element);
1054 return true;
1055 }
1056
1057 // Get the DOM element branch (parent chain), rooted in body element,
1058 // that includes the specified leaf element.
1059 // If leafElement is null, the returned branch will be empty
1060 function getDOMBranch(leafElement) {
1061 var scanStop = document.body.parentNode;
1062 var branch = [];
1063
1064 for (var scan = leafElement; (scan != null) && (scan != scanStop) && (scan != document) ; scan = scan.parentNode) {
1065 branch.push(scan);
1066 }
1067
1068 return branch;
1069 }
1070
1071 function doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements) {
1072 var isPrimaryHandOfPrimaryUser = handPointer.getIsPrimaryHandOfPrimaryUser();
1073 var wasPrimaryHandOfPrimaryUser = primaryHandOfPrimaryUserChanged ? !isPrimaryHandOfPrimaryUser : isPrimaryHandOfPrimaryUser;
1074
1075 // find common elements between old and new set
1076 // We take advantage of the fact that later elements in the array contain earlier
1077 // elements, so they will remain in the same state for a longer time.
1078 var firstOldMatch = oldEnteredElements.length;
1079 var firstNewMatch = newEnteredElements.length;
1080 while ((firstOldMatch > 0) &&
1081 (firstNewMatch > 0) &&
1082 (oldEnteredElements[firstOldMatch - 1] === newEnteredElements[firstNewMatch - 1])) {
1083 --firstOldMatch;
1084 --firstNewMatch;
1085 }
1086
1087 // Tell old entered elements that are not entered anymore that we have left them
1088 for (var iLeft = 0; iLeft < oldEnteredElements.length; ++iLeft) {
1089 var leftElement = oldEnteredElements[iLeft];
1090
1091 if (iLeft < firstOldMatch) {
1092 // This element is not one of the common elements, so we have left it
1093
1094 // If we were or still are the HandPointer for the primary user's
1095 // primary hand then clear out the "IsPrimaryHandPointerOver" property.
1096 if (wasPrimaryHandOfPrimaryUser) {
1097 uiController.setIsPrimaryHandPointerOver(leftElement, false);
1098 }
1099
1100 // Tell this element that this hand pointer has left
1101 raiseHandPointerEvent(leftElement, uiController.HANDPOINTER_LEAVE, handPointer);
1102 } else {
1103 // This is one of the common elements
1104 if (wasPrimaryHandOfPrimaryUser && !isPrimaryHandOfPrimaryUser) {
1105 // Hand pointer didn't leave the element but it is no longer the primary
1106 uiController.setIsPrimaryHandPointerOver(leftElement, false);
1107 }
1108 }
1109 }
1110
1111 // Tell new entered elements that were not previously entered we are now over them
1112 for (var iEntered = 0; iEntered < newEnteredElements.length; ++iEntered) {
1113 var enteredElement = newEnteredElements[iEntered];
1114
1115 if (iEntered < firstNewMatch) {
1116 // This element is not one of the common elements, so we have entered it
1117
1118 // If we are the HandPointer for the primary user's primary hand then
1119 // set the "IsPrimaryHandPointerOver" property.
1120 if (isPrimaryHandOfPrimaryUser) {
1121 uiController.setIsPrimaryHandPointerOver(enteredElement, true);
1122 }
1123
1124 // Tell this element that this hand pointer has left
1125 raiseHandPointerEvent(enteredElement, uiController.HANDPOINTER_ENTER, handPointer);
1126 } else {
1127 // This is one of the common elements
1128 if (!wasPrimaryHandOfPrimaryUser && isPrimaryHandOfPrimaryUser) {
1129 // Hand pointer was already in this element but it became the primary
1130 uiController.setIsPrimaryHandPointerOver(enteredElement, true);
1131 }
1132 }
1133 }
1134 }
1135
1136 function beginInteractionFrame() {
1137 if (isInInteractionFrame) {
1138 console.log("Call to beginInteractionFrame was made without corresponding call to endInteractionFrame");
1139 return;
1140 }
1141
1142 isInInteractionFrame = true;
1143 isClearRequestPending = false;
1144
1145 for (var handPointerKey in internalHandPointerData) {
1146 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1147 handPointer.updated = false;
1148 }
1149 }
1150
1151 function handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed) {
1152 var doPress = false;
1153 var doRelease = false;
1154 var doMove = false;
1155 var doLostCapture = false;
1156 var doGrip = false;
1157 var doGripRelease = false;
1158 var handPointer = handPointerData.handPointer;
1159
1160 if (removed) {
1161 // Deny the existence of this hand pointer
1162 doRelease = handPointer.isPressed;
1163 doLostCapture = handPointer.captured != null;
1164 } else {
1165 if (pressedChanged) {
1166 doPress = handPointer.isPressed;
1167 doRelease = !handPointer.isPressed;
1168 }
1169
1170 if (positionChanged) {
1171 doMove = true;
1172 }
1173
1174 doGrip = handPointer.handEventType.toLowerCase() == "grip";
1175 doGripRelease = handPointer.handEventType.toLowerCase() == "griprelease";
1176 }
1177
1178 if (doLostCapture) {
1179 switchCapture(handPointer, handPointer.captured, null);
1180 }
1181
1182 var targetElement = handPointer.captured;
1183 if (targetElement == null) {
1184 targetElement = hitTest(handPointer.x, handPointer.y);
1185 }
1186
1187 // Update internal enter/leave state
1188 var oldEnteredElements = handPointerData.enteredElements;
1189 var newEnteredElements = getDOMBranch(removed ? null : targetElement);
1190 handPointerData.enteredElements = newEnteredElements;
1191
1192 // See if this hand pointer is participating in a grip-initiated
1193 // interaction.
1194 var newIsInGripInteraction = false;
1195 if (targetElement != null) {
1196 var result = raiseHandPointerEvent(targetElement, uiController.QUERY_INTERACTION_STATUS, handPointer);
1197
1198 if ((result != null) && result.isInGripInteraction) {
1199 newIsInGripInteraction = true;
1200 }
1201 }
1202
1203 handPointer.isInGripInteraction = newIsInGripInteraction;
1204
1205 //// After this point there should be no more changes to the internal
1206 //// state of the handPointers. We don't want event handlers calling us
1207 //// when our internal state is inconsistent.
1208
1209 doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements);
1210
1211 if (targetElement == null) {
1212 return;
1213 }
1214
1215 if (doGrip) {
1216 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIP, handPointer);
1217 }
1218
1219 if (doGripRelease) {
1220 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIPRELEASE, handPointer);
1221 }
1222
1223 if (doPress) {
1224 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESS, handPointer);
1225 }
1226
1227 if (doMove) {
1228 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_MOVE, handPointer);
1229 }
1230
1231 if (doRelease) {
1232 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESSRELEASE, handPointer);
1233 }
1234 }
1235
1236 function handleHandPointerData(interactionStreamHandPointers) {
1237 if (!isInInteractionFrame) {
1238 console.log("Call to handleHandPointerData was made without call to beginInteractionFrame");
1239 return;
1240 }
1241
1242 if (isClearRequestPending) {
1243 // We don't care about new hand pointer data if client requested to clear
1244 // all hand pointers while in the middle of interaction frame processing.
1245 return;
1246 }
1247
1248 for (var iData = 0; iData < interactionStreamHandPointers.length; ++iData) {
1249 var streamHandPointer = interactionStreamHandPointers[iData];
1250 var id = getHandPointerKey(streamHandPointer.trackingId, streamHandPointer.handType);
1251
1252 var handPointerData;
1253 var handPointer;
1254 if (internalHandPointerData.hasOwnProperty(id)) {
1255 handPointerData = internalHandPointerData[id];
1256 handPointer = handPointerData.handPointer;
1257 } else {
1258 handPointer = new HandPointer(streamHandPointer.trackingId, streamHandPointer.playerIndex, streamHandPointer.handType);
1259
1260 handPointerData = { "handPointer": handPointer, "enteredElements": [] };
1261 internalHandPointerData[id] = handPointerData;
1262 }
1263
1264 var result = handPointer.update(streamHandPointer);
1265 handleHandPointerChanges(handPointerData, result.pressedChanged, result.positionChanged, result.primaryHandOfPrimaryUserChanged, false);
1266 }
1267 }
1268
1269 function endInteractionFrame() {
1270 if (!isInInteractionFrame) {
1271 console.log("Call to endInteractionFrame was made without call to beginInteractionFrame");
1272 return;
1273 }
1274
1275 removeStaleHandPointers();
1276
1277 isClearRequestPending = false;
1278 isInInteractionFrame = false;
1279 }
1280
1281 function removeStaleHandPointers() {
1282 var nonUpdatedHandpointers = [];
1283
1284 for (var handPointerKey in internalHandPointerData) {
1285 // If we need to stop tracking this hand pointer
1286 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1287 if (isClearRequestPending || !handPointer.updated) {
1288 nonUpdatedHandpointers.push(handPointerKey);
1289 }
1290 }
1291
1292 for (var i = 0; i < nonUpdatedHandpointers.length; ++i) {
1293 var handPointerKey = nonUpdatedHandpointers[i];
1294 var handPointerData = internalHandPointerData[handPointerKey];
1295 var handPointer = handPointerData.handPointer;
1296 var pressedChanged = handPointer.isPressed;
1297 var positionChanged = false;
1298 var primaryHandOfPrimaryUserChanged = handPointer.getIsPrimaryHandOfPrimaryUser();
1299 var removed = true;
1300
1301 handPointer.isTracked = false;
1302 handPointer.isActive = false;
1303 handPointer.isInteractive = false;
1304 handPointer.isPressed = false;
1305 handPointer.isPrimaryUser = false;
1306 handPointer.isPrimaryHandOfUser = false;
1307
1308 handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed);
1309
1310 delete internalHandPointerData[handPointerKey];
1311 }
1312 }
1313
1314 function updatePublicHandPointers() {
1315 var iHandPointer = 0;
1316
1317 for (var handPointerKey in internalHandPointerData) {
1318 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1319
1320 if (handPointer.isTracked) {
1321 if (uiAdapter.handPointers.length > iHandPointer) {
1322 uiAdapter.handPointers[iHandPointer] = handPointer;
1323 } else {
1324 uiAdapter.handPointers.push(handPointer);
1325 }
1326
1327 ++iHandPointer;
1328 }
1329 }
1330
1331 // Truncate the length of the array to the number of valid elements
1332 uiAdapter.handPointers.length = iHandPointer;
1333
1334 raiseGlobalUIEvent(uiController.HANDPOINTERS_UPDATED, uiAdapter.handPointers);
1335 }
1336
1337 function hitTest(x, y) {
1338 // Ensure excluded elements are hidden before performing hit test
1339 var displayProp = "display";
1340 var displayValues = [];
1341 for (var i = 0; i < excludedElements.length; ++i) {
1342 var element = excludedElements[i];
1343 displayValues.push(element.style[displayProp]);
1344 element.style[displayProp] = "none";
1345 }
1346
1347 var hitElement = document.elementFromPoint(x, y);
1348
1349 // Restore visibility to excluded elements
1350 for (var i = 0; i < excludedElements.length; ++i) {
1351 excludedElements[i].style[displayProp] = displayValues[i];
1352 }
1353
1354 return hitElement;
1355 }
1356
1357 function getElementId(element) {
1358 // First check if we've already set the kinect element ID.
1359 var id = jQuery.data(element, kinectIdPrefix);
1360 if (id != null) {
1361 return id;
1362 }
1363
1364 // If not, fall back to regular element id, if present
1365 id = element.id;
1366
1367 if (id.trim() == "") {
1368 // If there is no id on element, assign one ourselves
1369 id = kinectIdPrefix + (++lastAssignedControlId);
1370 }
1371
1372 // Remember assigned id
1373 jQuery.data(element, kinectIdPrefix, id);
1374 return id;
1375 }
1376
1377 function setCapture(element) {
1378 // Not all browsers support mouse capture functionality
1379 if (typeof element.setCapture == "function") {
1380 element.setCapture(true);
1381 return true;
1382 }
1383
1384 return false;
1385 }
1386
1387 function releaseCapture() {
1388 // Not all browsers support mouse capture functionality
1389 if (typeof document.releaseCapture == "function") {
1390 document.releaseCapture();
1391 return true;
1392 }
1393
1394 return false;
1395 }
1396
1397 function promoteToButton(element) {
1398 var content = element.innerHTML;
1399
1400 if (element.children.length <= 0) {
1401 // If element has no children, wrap content in an element that will perform
1402 // default text alignment
1403 content = "<span class='kinect-button-text'>" + content + "</span>";
1404 }
1405
1406 // Wrap all content in a "kinect-button-surface" div element to allow button
1407 // shrinking and expanding as user hovers over or presses button
1408 element.innerHTML = "<div class='kinect-button-surface'>" + content + "</div>";
1409
1410 uiController.setIsPressTarget(element, true);
1411 var capturedHandPointer = null;
1412 var pressedElement = null;
1413 var isMouseOver = false;
1414 var isMouseCaptured = false;
1415 var removeCaptureEvents = false;
1416
1417 var releaseButtonCapture = function(event) {
1418 releaseCapture();
1419 isMouseCaptured = false;
1420
1421 if (removeCaptureEvents) {
1422 $(event.target).off("mousemove");
1423 }
1424 };
1425
1426 $(element).on({
1427 // respond to hand pointer events
1428 handPointerPress: function (event) {
1429 var handPointer = event.originalEvent.detail.handPointer;
1430 if (capturedHandPointer == null && handPointer.getIsPrimaryHandOfPrimaryUser()) {
1431 handPointer.capture(event.currentTarget);
1432 event.stopPropagation();
1433 }
1434 },
1435 handPointerGotCapture: function (event) {
1436 var handPointer = event.originalEvent.detail.handPointer;
1437 if (capturedHandPointer == null) {
1438 capturedHandPointer = handPointer;
1439 pressedElement = handPointer.captured;
1440 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1441 event.stopPropagation();
1442 }
1443 },
1444 handPointerPressRelease: function (event) {
1445 var handPointer = event.originalEvent.detail.handPointer;
1446 if (capturedHandPointer == handPointer) {
1447 var captured = handPointer.captured;
1448 handPointer.capture(null);
1449
1450 if (captured == event.currentTarget) {
1451 // Trigger click on press release if release point is close enough
1452 // to around the top half of button, or if it's anywhere below
1453 // button. I.e.: accept a "lazy-press" as a real press.
1454
1455 var box = captured.getBoundingClientRect();
1456 var insideButton = false;
1457 if ((box.left <= handPointer.x) && (handPointer.x <= box.right) &&
1458 (box.top <= handPointer.y) && (handPointer.y <= box.bottom)) {
1459 insideButton = true;
1460 }
1461
1462 // Map hand pointer to a coordinate space around center of button
1463 // and convert to be relative to window size.
1464 var relativePoint = windowToInteractionZone({
1465 "x": handPointer.x - ((box.left + box.right) / 2),
1466 "y": handPointer.y - ((box.top + box.bottom) / 2)
1467 });
1468 var isWithinLazyReleaseArea = true;
1469 if (relativePoint.y < LAZYRELEASE_YCUTOFF) {
1470 isWithinLazyReleaseArea =
1471 Math.sqrt((relativePoint.x * relativePoint.x) + (relativePoint.y * relativePoint.y)) < LAZYRELEASE_RADIUS;
1472 }
1473
1474 if (insideButton || isWithinLazyReleaseArea) {
1475 $(element).click();
1476 }
1477
1478 event.stopPropagation();
1479 }
1480 }
1481 },
1482 handPointerLostCapture: function (event) {
1483 var handPointer = event.originalEvent.detail.handPointer;
1484 if (capturedHandPointer == handPointer) {
1485 capturedHandPointer = null;
1486 pressedElement = null;
1487 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1488 event.stopPropagation();
1489 }
1490 },
1491 handPointerEnter: function (event) {
1492 var target = event.currentTarget;
1493 if (KinectUI.getIsPrimaryHandPointerOver(target)) {
1494 $(element).addClass(uiController.BUTTON_HOVER_CLASS);
1495 }
1496 },
1497 handPointerLeave: function (event) {
1498 var target = event.currentTarget;
1499 if (!KinectUI.getIsPrimaryHandPointerOver(target)) {
1500 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1501 }
1502 },
1503 // respond to mouse events in a similar way to hand pointer events
1504 // Note: We use button-hover class rather than :hover pseudo-class because
1505 // :hover does not play well with our button-pressed class.
1506 mouseenter: function(event) {
1507 isMouseOver = true;
1508 $(element).addClass(uiController.BUTTON_HOVER_CLASS);
1509 if (pressedElement != null) {
1510 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1511 }
1512 },
1513 mouseleave: function (event) {
1514 isMouseOver = false;
1515 if (!isMouseCaptured || (pressedElement != null)) {
1516 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1517 pressedElement = null;
1518 }
1519 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1520 },
1521 mousedown: function (event) {
1522 if (event.button == 0) {
1523 pressedElement = event.target;
1524 if (setCapture(event.target)) {
1525 isMouseCaptured = true;
1526
1527 // Different browsers have slightly different behaviors related to which element
1528 // will be considered the event target, and how mouseenter and mouseleave events
1529 // are sent in case of capture, so cover all bases.
1530 if (event.target != event.currentTarget) {
1531 removeCaptureEvents = true;
1532 $(event.target).mousemove(function (moveEvent) {
1533 var box = element.getBoundingClientRect();
1534 var insideBox = (box.left <= moveEvent.clientX) && (moveEvent.clientX <= box.right) &&
1535 (box.top <= moveEvent.clientY) && (moveEvent.clientY <= box.bottom);
1536 if (!insideBox && isMouseCaptured) {
1537 releaseButtonCapture(moveEvent);
1538 }
1539 });
1540 }
1541 }
1542 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1543
1544 event.stopPropagation();
1545 }
1546 },
1547 mouseup: function (event) {
1548 if (event.button == 0) {
1549 var fakeClick = false;
1550 if ((pressedElement != null) && !isMouseCaptured && (pressedElement != event.target)) {
1551 // If this browser doesn't support capture and the event target
1552 // during mouse up does not match exactly the event target during
1553 // mouse down then browser will not automatically trigger a click
1554 // event, so we will trigger one ourselves because we know that the
1555 // same logical Kinect button received a mouse down and mouse up.
1556 //
1557 // This condition can happen because of shrink and expand
1558 // animations for Kinect button hover and press.
1559 fakeClick = true;
1560 }
1561
1562 pressedElement = null;
1563 if (isMouseCaptured) {
1564 releaseButtonCapture(event);
1565 }
1566 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1567 if (!isMouseOver) {
1568 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1569 }
1570
1571 if (fakeClick) {
1572 $(element).click();
1573 }
1574
1575 event.stopPropagation();
1576 }
1577 }
1578 });
1579 }
1580
1581 //////////////////////////////////////////////////////////////
1582 // Public KinectUIAdapter properties
1583 this.handPointers = [];
1584
1585 //////////////////////////////////////////////////////////////
1586 // Public KinectUIAdapter methods
1587
1588 // Function called back when a sensor stream frame is ready to be processed.
1589 // .streamFrameHandler(streamFrame)
1590 //
1591 // streamFrame: stream frame ready to be processed
1592 //
1593 // Remarks
1594 // Processes interaction frames and ignores all other kinds of stream frames.
1595 this.streamFrameHandler = function (streamFrame) {
1596 if (typeof streamFrame != "object") {
1597 throw new Error("Frame must be an object");
1598 }
1599
1600 if (streamFrame == null) {
1601 // Ignore null frames
1602 return;
1603 }
1604
1605 var streamName = streamFrame.stream;
1606 switch (streamName) {
1607 case "interaction":
1608 beginInteractionFrame();
1609
1610 if (streamFrame.handPointers != null) {
1611 handleHandPointerData(streamFrame.handPointers);
1612 }
1613
1614 endInteractionFrame();
1615
1616 updatePublicHandPointers();
1617 break;
1618
1619 default:
1620 if (bindableStreamNames[streamName]) {
1621 // If this is one of the bindable stream names
1622 imageWorkerManager.processImageData(streamName, streamFrame.buffer, streamFrame.width, streamFrame.height);
1623 }
1624 break;
1625 }
1626 };
1627
1628 // Resets the hand pointer array to its initial state (zero hand pointers tracked).
1629 // .clearHandPointers()
1630 this.clearHandPointers = function () {
1631 if (!isInInteractionFrame) {
1632 // If we're not already processing an interaction frame, we fake
1633 // an empty frame so that all hand pointers get cleared out.
1634 beginInteractionFrame();
1635 endInteractionFrame();
1636 updatePublicHandPointers();
1637 } else {
1638 // If we're in the middle of processing an interaction frame, we
1639 // can't modify all of our data structures immediately, but need to
1640 // remember to do so.
1641 isClearRequestPending = true;
1642 }
1643 };
1644
1645 // Function called back when a sensor's interaction stream needs interaction
1646 // information in order to adjust a hand pointer position relative to UI.
1647 // .hitTestHandler(trackingId, handType, xPos, yPos)
1648 //
1649 // trackingId: The skeleton tracking ID for which interaction information is
1650 // being retrieved.
1651 // handType: "left" or "right" to represent hand type for which interaction
1652 // information is being retrieved.
1653 // xPos: X-coordinate of UI location for which interaction information is being
1654 // retrieved. 0.0 corresponds to left edge of interaction region and 1.0
1655 // corresponds to right edge of interaction region.
1656 // yPos: Y-coordinate of UI location for which interaction information is being
1657 // retrieved. 0.0 corresponds to top edge of interaction region and 1.0
1658 // corresponds to bottom edge of interaction region.
1659 this.hitTestHandler = function (trackingId, handType, xPos, yPos) {
1660 if (typeof trackingId != "number") {
1661 throw new Error("First parameter must be a number");
1662 }
1663 if (typeof handType != "string") {
1664 throw new Error("Second parameter must be a string");
1665 }
1666
1667 var interactionInfo = { "isPressTarget": false, "isGripTarget": false };
1668
1669 // First scale coordinates to be relative to full browser window
1670 var windowPos = interactionZoneToWindow({ "x": xPos, "y": yPos });
1671 var handPointerId = getHandPointerKey(trackingId, handType);
1672 var capturedElement = internalHandPointerData.hasOwnProperty(handPointerId) ? internalHandPointerData[handPointerId].handPointer.captured : null;
1673 var targetElement = (capturedElement != null) ? capturedElement : hitTest(windowPos.x, windowPos.y);
1674
1675 if (targetElement != null) {
1676 // Walk up the tree and try to find a grip target and/or a press target
1677 for (var searchElement = targetElement;
1678 searchElement != null && searchElement != document &&
1679 (!interactionInfo.isGripTarget || !interactionInfo.isPressTarget) ;
1680 searchElement = searchElement.parentNode) {
1681
1682 if (!interactionInfo.isPressTarget) {
1683 if (uiController.getIsPressTarget(searchElement)) {
1684 interactionInfo.isPressTarget = true;
1685 interactionInfo.pressTargetControlId = getElementId(searchElement);
1686
1687 // Get the press target point in client coordinates relative to search element
1688 var pressTargetPoint = interactionZoneToElement(getPressTargetPoint(searchElement), searchElement);
1689 var boundingRect = searchElement.getBoundingClientRect();
1690
1691 // Now adjust press target point coordinate to be relative to viewport
1692 pressTargetPoint.x += boundingRect.left;
1693 pressTargetPoint.y += boundingRect.top;
1694
1695 // Now scale press attraction point back to interaction zone coordinates
1696 var adjustedPoint = windowToInteractionZone(pressTargetPoint);
1697 interactionInfo.pressAttractionPointX = adjustedPoint.x;
1698 interactionInfo.pressAttractionPointY = adjustedPoint.y;
1699 }
1700 }
1701
1702 if (!interactionInfo.isGripTarget) {
1703 if (uiController.getIsGripTarget(searchElement)) {
1704 interactionInfo.isGripTarget = true;
1705 }
1706 }
1707 }
1708 }
1709
1710 return interactionInfo;
1711 };
1712
1713 // Adds an element that should be excluded from hit testing (both for press adjustment
1714 // and for UI message routing).
1715 // .addHitTestExclusion( element )
1716 //
1717 // element: Element to exlude from hit testing.
1718 this.addHitTestExclusion = function (element) {
1719 if (element == null) {
1720 console.log('Tried to add a hit test exclusion for a null element.');
1721 return;
1722 }
1723
1724 excludedElements.push(element);
1725 };
1726
1727 // Stops excluding an element from hit testing (both for press adjustment and for UI
1728 // message routing).
1729 // .removeHitTestExclusion( element )
1730 //
1731 // element: Element to stop exluding from hit testing.
1732 this.removeHitTestExclusion = function (element) {
1733 if (element == null) {
1734 console.log('Tried to remove a hit test exclusion for a null element.');
1735 return;
1736 }
1737
1738 var index = excludedElements.indexOf(element);
1739 if (index >= 0) {
1740 excludedElements.splice(index, 1);
1741 }
1742 };
1743
1744 // Bind the specified canvas element with the specified image stream
1745 // .bindStreamToCanvas( streamName, canvas )
1746 //
1747 // streamName: name of stream to bind to canvas element. Must be one of the supported
1748 // image stream names (e.g.: KinectUI.USERVIEWER_STREAM_NAME and
1749 // KinectUI.BACKGROUNDREMOVAL_STREAM_NAME)
1750 // canvas: Canvas to bind to user viewer stream
1751 //
1752 // Remarks
1753 // After binding a stream to a canvas, image data for that stream will
1754 // be rendered into the canvas whenever a new stream frame arrives.
1755 this.bindStreamToCanvas = function (streamName, canvas, width, height) {
1756 if (!bindableStreamNames[streamName]) {
1757 throw new Error("first parameter must be specified and must be one of the supported stream names");
1758 }
1759
1760 if (!(canvas instanceof HTMLCanvasElement)) {
1761 throw new Error("second parameter must be specified and must be a canvas element");
1762 }
1763
1764 this.unbindStreamFromCanvas(streamName);
1765
1766 imageWorkerManager.setImageData(streamName, canvas);
1767 };
1768
1769 // Unbind the specified image stream from previously bound canvas element, if any.
1770 // .unbindStreamFromCanvas(streamName)
1771 //
1772 // streamName: name of stream to unbind from its corresponding canvas element
1773 this.unbindStreamFromCanvas = function(streamName) {
1774 imageWorkerManager.setImageData(streamName, null);
1775 };
1776
1777 // Attach a new handler for a specified global UI event.
1778 // .on( eventName, handler(eventData) )] )
1779 //
1780 // eventName: Name of global event for which handler is being attached.
1781 // handler: Callback function to be executed when a global UI event of the specified
1782 // name occurs. E.g.: when set of hand pointers has been updated (eventName =
1783 // "handPointersUpdated").
1784 this.on = function (eventName, handler) {
1785 if (typeof (eventName) != "string") {
1786 throw new Error("first parameter must be specified and must be a string");
1787 }
1788 if (typeof (handler) != "function") {
1789 throw new Error("second parameter must be specified and must be a function");
1790 }
1791
1792 var handlerArray;
1793 if (eventHandlers.hasOwnProperty(eventName)) {
1794 handlerArray = eventHandlers[eventName];
1795 } else {
1796 handlerArray = [];
1797 eventHandlers[eventName] = handlerArray;
1798 }
1799
1800 handlerArray.push(handler);
1801 };
1802
1803 // Removes one (or all) global UI event handler(s) for specified global UI event.
1804 // .removeEventHandler( eventName, [handler(eventData)] )] )
1805 //
1806 // eventName: Name of global event for which handler is being removed.
1807 // handler: Global UI event handler callback function to be removed.
1808 // If omitted, all event handlers for specified event are removed.
1809 this.off = function (eventName, handler) {
1810 if (typeof (handler) != "string") {
1811 throw new Error("first parameter must be specified and must be a string");
1812 }
1813
1814 var removeAll = false;
1815 switch (typeof (handler)) {
1816 case "undefined":
1817 removeAll = true;
1818 break;
1819 case "function":
1820 break;
1821
1822 default:
1823 throw new Error("second parameter must either be a function or left unspecified");
1824 }
1825
1826 if (!eventHandlers.hasOwnProperty(eventName)) {
1827 // If there are no event handlers associated with event, just return
1828 return;
1829 }
1830
1831 if (removeAll) {
1832 eventHandlers[eventName] = [];
1833 } else {
1834 var index = eventHandlers[eventName].indexOf(handler);
1835 if (index >= 0) {
1836 eventHandlers[eventName].slice(index, index + 1);
1837 }
1838 }
1839 };
1840
1841 // Configures the specified DOM element array to act as a Kinect UI button
1842 // .promoteButtons([elements])
1843 //
1844 // elements: DOM element array or HTMLCollection where each element will be configured
1845 // to act as a Kinect UI button.
1846 // If left unspecified, all document elements with class equal to
1847 // KinectUI.BUTTON_CLASS will be promoted to buttons.
1848 this.promoteButtons = function (elements) {
1849 var type = typeof elements;
1850 var error = new Error("first parameter must be left unspecified or be an array equivalent");
1851 switch (type) {
1852 case "undefined":
1853 elements = document.getElementsByClassName(uiController.BUTTON_CLASS);
1854 break;
1855 case "object":
1856 if (elements == null) {
1857 // If null, treat as if an empty array was specified
1858 return;
1859 }
1860
1861 if (!("length" in elements)) {
1862 throw error;
1863 }
1864 break;
1865
1866 default:
1867 throw error;
1868 }
1869
1870 for (var i = 0; i < elements.length; ++i) {
1871 var element = elements[i];
1872 if ((typeof element != "object") || (element.nodeType != Node.ELEMENT_NODE)) {
1873 throw new Error("each array element must be a DOM element");
1874 }
1875
1876 promoteToButton(elements[i]);
1877 }
1878 };
1879
1880 // Creates the default cursor UI elements with associated behavior
1881 // .createDefaultCursor()
1882 //
1883 // Returns an object with the following properties/functions:
1884 // - element: property referencing the DOM element that represents the cursor
1885 // - hide(): function used to hide the cursor
1886 // - show(): function used to show the cursor
1887 this.createDefaultCursor = function() {
1888 // First check if cursor element has already been created
1889 var cursorElement = document.getElementById(uiController.CURSOR_ID);
1890 if (cursorElement != null) {
1891 return jQuery.data(cursorElement, uiController.CURSOR_ID);
1892 }
1893 //<path id="not-kinect-grip-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="m 181.93858,119.37788 c -5.53253,-12.70786 -17.98221,-8.32935 -19.87611,-6.05884 -1.93242,2.33185 -2.55388,10.20311 -3.11979,7.43102 -0.52841,-2.58838 -1.58534,-6.48216 -3.05075,-8.33167 -3.71325,-4.68654 -18.26158,-5.14609 -20.28816,1.42556 -0.60793,-2.5391 -2.08123,9.29661 -2.71043,6.39601 -0.80661,-3.71845 -1.46647,-5.09336 -3.10202,-8.04206 -3.52367,-6.35277 -25.21241,-4.55749 -26.94562,0.8136 -2.23168,1.60831 -0.45609,3.96744 -2.20942,8.65039 -0.11175,0.29847 0.28587,-4.91744 -2.132876,-9.19244 -2.409535,-4.25872 -19.828184,-1.33427 -20.975736,0.12408 -6.966521,6.47431 -9.645445,17.93226 -6.313239,32.77615 -1.470297,3.68614 -7.438345,11.46835 -12.632726,14.31443 18.050822,-8.87437 10.347115,-23.76108 10.347115,-23.76108 1.654793,-11.75616 -15.575036,0.50533 -15.942531,0.54616 -6.318,6.317 -13.368249,17.51059 -10.003249,21.57959 0,0 12.442962,34.33433 25.103962,49.50633 16.952148,16.18215 37.23324,18.66434 60.51992,14.78272 32.68992,-5.44904 49.05986,-5.46272 56.41008,-41.64172 5.70646,-64.06554 -1.17616,-23.47973 -3.07842,-61.31823 z"></path>\
1894 var showCursor = true;
1895 cursorElement = document.createElement("div");
1896 cursorElement.id = uiController.CURSOR_ID;
1897 cursorElement.innerHTML = '\
1898 <svg viewBox="0 0 250 250">\
1899 <defs>\
1900 <radialGradient id="kinect-pressing-gradient" r="150%">\
1901 <stop stop-color="#663085" offset="0.0%"></stop>\
1902 <stop stop-color="white" offset="100.0%" ></stop>\
1903 </radialGradient>\
1904 <radialGradient id="kinect-extended-gradient">\
1905 <stop stop-color="#01b3ff" offset="0.0%"></stop>\
1906 <stop stop-color="#04e5ff" offset="98.1%" ></stop>\
1907 <stop stop-color="#04e5ff" offset="99.2%" ></stop>\
1908 <stop stop-color="#04e5ff" offset="100.0%" ></stop>\
1909 </radialGradient>\
1910 <path id="kinect-open-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="M 184.325,52.329 C 177.166,51.961 174.195,56.492 173.858,59.838 C 173.858,59.838 170.148,117.100 170.148,118.229 C 170.148,121.405 166.840,124.394 163.662,124.394 C 160.488,124.394 157.912,121.819 157.912,118.644 C 157.912,117.843 160.558,26.069 160.558,26.069 C 160.746,21.648 156.897,14.964 148.905,14.845 C 140.912,14.727 137.941,20.814 137.759,25.106 C 137.759,25.106 136.400,107.066 136.400,108.288 C 136.400,111.463 133.825,114.038 130.650,114.038 C 127.474,114.038 124.900,111.463 124.900,108.288 C 124.900,107.743 120.237,15.217 119.553,13.920 C 118.498,10.296 113.940,7.103 108.746,7.227 C 102.840,7.368 98.306,11.584 98.410,15.973 C 98.412,16.051 98.427,16.125 98.431,16.202 C 98.162,17.142 102.662,111.920 102.662,112.943 C 102.662,116.118 100.087,118.693 96.912,118.693 C 93.735,118.693 89.829,116.118 89.829,112.943 C 89.829,112.611 78.418,41.710 77.470,40.035 C 77.142,38.973 75.392,30.547 65.819,31.637 C 56.246,32.727 56.261,43.062 56.588,44.996 L 71.393,145.191 C 72.726,150.746 73.426,157.547 67.168,159.684 C 66.531,159.901 46.958,133.145 46.958,133.145 C 25.587,108.413 11.363,121.842 11.363,121.842 C 5.045,128.159 9.566,133.019 12.931,137.088 C 12.931,137.088 55.426,192.383 68.087,207.555 C 80.746,222.727 102.327,240.773 125.829,240.773 C 158.920,240.773 182.465,216.875 185.017,180.696 C 187.571,144.516 193.480,62.303 193.480,62.303 C 193.712,60.008 191.484,52.696 184.325,52.329 Z"></path>\
1911 <clipPath id="kinect-cursor-press-clip">\
1912 <circle id="kinect-cursor-press-clip-circle" cx="125.0" cy="250.0" r="0.0"></circle>\
1913 </clipPath>\
1914 <path id="kinect-grip-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="m 181.93858,119.37788 c -5.53253,-12.70786 -17.98221,-8.32935 -19.87611,-6.05884 -1.93242,2.33185 -2.55388,10.20311 -3.11979,7.43102 -0.52841,-2.58838 -1.58534,-6.48216 -3.05075,-8.33167 -3.71325,-4.68654 -18.26158,-5.14609 -20.28816,1.42556 -0.60793,-2.5391 -2.08123,9.29661 -2.71043,6.39601 -0.80661,-3.71845 -1.46647,-5.09336 -3.10202,-8.04206 -3.52367,-6.35277 -25.21241,-4.55749 -26.94562,0.8136 -2.23168,1.60831 -0.45609,3.96744 -2.20942,8.65039 -0.11175,0.29847 0.28587,-4.91744 -2.132876,-9.19244 -2.409535,-4.25872 -19.828184,-1.33427 -20.975736,0.12408 -6.966521,6.47431 -9.645445,17.93226 -6.313239,32.77615 -1.470297,3.68614 -7.438345,11.46835 -12.632726,14.31443 18.050822,-8.87437 10.347115,-23.76108 10.347115,-23.76108 1.654793,-11.75616 -15.575036,0.50533 -15.942531,0.54616 -6.318,6.317 -13.368249,17.51059 -10.003249,21.57959 0,0 12.442962,34.33433 25.103962,49.50633 16.952148,16.18215 37.23324,18.66434 60.51992,14.78272 32.68992,-5.44904 49.05986,-5.46272 56.41008,-41.64172 5.70646,-64.06554 -1.17616,-23.47973 -3.07842,-61.31823 z"></path>\
1915 </defs>\
1916 <path id="kinect-cursor-glow" fill="none" stroke="#7c6dcf" stroke-width="16" stroke-miterlimit="2.75" d="M 183.605,52.966 C 176.446,52.598 173.475,57.129 173.138,60.476 C 173.138,60.476 169.428,117.739 169.428,118.866 C 169.428,122.043 166.120,125.032 162.943,125.032 C 159.768,125.032 157.193,122.457 157.193,119.282 C 157.193,118.481 159.838,26.707 159.838,26.707 C 160.026,22.285 156.177,15.602 148.185,15.483 C 140.193,15.365 137.221,21.452 137.039,25.744 C 137.039,25.744 135.680,107.703 135.680,108.926 C 135.680,112.101 133.105,114.676 129.930,114.676 C 126.754,114.676 124.179,112.101 124.179,108.926 C 124.179,108.382 119.517,15.854 118.833,14.558 C 117.779,10.934 113.220,7.741 108.026,7.865 C 102.120,8.005 97.586,12.222 97.690,16.611 C 97.693,16.689 97.707,16.763 97.711,16.840 C 97.443,17.780 101.943,112.558 101.943,113.580 C 101.943,116.757 99.367,119.330 96.193,119.330 C 93.016,119.330 89.109,116.757 89.109,113.580 C 89.109,113.249 77.698,42.348 76.750,40.673 C 76.422,39.611 74.672,31.185 65.099,32.275 C 55.526,33.365 55.542,43.700 55.868,45.634 L 70.673,145.828 C 72.006,151.384 72.706,158.185 66.448,160.322 C 65.811,160.539 46.238,133.783 46.238,133.783 C 24.867,109.051 10.643,122.479 10.643,122.479 C 4.325,128.796 8.846,133.658 12.211,137.726 C 12.211,137.726 54.706,193.021 67.367,208.193 C 80.026,223.365 101.607,241.410 125.109,241.410 C 158.200,241.410 181.745,217.513 184.297,181.334 C 186.851,145.154 192.760,62.940 192.760,62.940 C 192.992,60.645 190.764,53.335 183.605,52.966 Z "></path>\
1917 <use id="kinect-cursor-normal" xlink:href="#kinect-open-hand-base" fill="white" stroke="black"></use>\
1918 <g id="kinect-cursor-progress" clip-path="url(#kinect-cursor-press-clip)">\
1919 <use xlink:href="#kinect-open-hand-base" fill="url(#kinect-pressing-gradient)" stroke="none"></use>\
1920 <path fill="#ffffff" opacity="0.26667" d="M 124.427,213.384 C 112.266,213.384 102.021,221.573 98.898,232.737 C 100.149,233.406 101.418,234.039 102.704,234.633 C 103.681,230.576 105.768,226.953 108.627,224.091 C 112.678,220.043 118.251,217.547 124.427,217.545 C 130.605,217.547 136.180,220.043 140.229,224.091 C 143.494,227.359 145.749,231.617 146.500,236.382 C 147.841,235.885 149.154,235.336 150.435,234.742 C 148.039,222.567 137.308,213.384 124.427,213.384"></path>\
1921 <path fill="#ffffff" opacity="0.26667" d="M 124.427,198.974 C 107.230,198.976 92.510,209.587 86.458,224.618 C 87.591,225.494 88.748,226.351 89.928,227.182 C 91.784,222.146 94.714,217.629 98.437,213.902 C 105.097,207.247 114.272,203.137 124.427,203.136 C 134.584,203.137 143.759,207.247 150.417,213.902 C 154.657,218.144 157.862,223.407 159.632,229.293 C 160.827,228.411 161.981,227.481 163.102,226.501 C 157.556,210.480 142.338,198.976 124.427,198.974"></path>\
1922 <path fill="#ffffff" opacity="0.26667" d="M 124.427,184.565 C 102.982,184.566 84.386,196.770 75.204,214.613 C 76.212,215.635 77.255,216.656 78.331,217.667 C 80.843,212.467 84.209,207.756 88.252,203.713 C 97.516,194.451 110.293,188.728 124.427,188.726 C 138.563,188.728 151.341,194.451 160.607,203.713 C 164.788,207.896 168.248,212.796 170.785,218.212 C 171.735,216.955 172.636,215.651 173.492,214.300 C 164.254,196.626 145.751,184.566 124.427,184.565"></path>\
1923 <path fill="#ffffff" opacity="0.26667" d="M 124.427,170.155 C 99.400,170.155 77.453,183.343 65.150,203.145 C 66.181,204.417 67.117,205.563 67.944,206.563 C 70.765,201.798 74.169,197.419 78.062,193.523 C 89.934,181.653 106.315,174.317 124.427,174.317 C 142.542,174.317 158.922,181.653 170.795,193.523 C 173.825,196.555 176.559,199.879 178.951,203.450 C 179.564,201.877 180.126,200.265 180.635,198.609 C 167.941,181.352 147.493,170.155 124.427,170.155"></path>\
1924 <path fill="#ffffff" opacity="0.26667" d="M 124.427,155.746 C 96.053,155.747 70.960,169.793 55.719,191.310 C 56.641,192.479 57.540,193.618 58.415,194.721 C 61.215,190.636 64.383,186.827 67.874,183.335 C 82.353,168.858 102.336,159.909 124.427,159.908 C 146.520,159.909 166.502,168.858 180.983,183.335 C 181.800,184.152 182.599,184.986 183.380,185.838 C 183.594,184.171 183.765,182.476 183.887,180.752 C 183.895,180.627 183.904,180.506 183.914,180.381 C 168.687,165.159 147.658,155.747 124.427,155.746"></path>\
1925 <path fill="#ffffff" opacity="0.26667" d="M 124.427,141.337 C 105.045,141.338 86.970,146.934 71.728,156.599 C 70.962,158.106 69.668,159.318 67.603,160.021 C 67.549,160.021 67.405,159.895 67.185,159.658 C 59.361,165.252 52.385,171.964 46.499,179.558 C 47.386,180.694 48.270,181.825 49.147,182.943 C 51.768,179.485 54.621,176.210 57.686,173.146 C 74.774,156.061 98.359,145.500 124.427,145.499 C 147.411,145.500 168.463,153.709 184.835,167.359 C 184.953,165.715 185.072,164.022 185.195,162.298 C 168.453,149.168 147.353,141.338 124.427,141.337"></path>\
1926 <path fill="#ffffff" opacity="0.26667" d="M 58.345,148.266 C 50.558,153.893 43.518,160.486 37.399,167.873 C 38.266,168.990 39.140,170.114 40.016,171.242 C 42.368,168.354 44.865,165.589 47.497,162.957 C 51.632,158.824 56.096,155.021 60.846,151.594 C 60.036,150.521 59.194,149.401 58.345,148.266 M 124.427,126.928 C 105.097,126.928 86.896,131.786 70.987,140.345 L 71.635,144.735 C 87.268,136.041 105.265,131.091 124.427,131.090 C 147.312,131.091 168.535,138.151 186.056,150.215 C 186.168,148.633 186.282,147.040 186.397,145.429 C 168.603,133.731 147.312,126.928 124.427,126.928"></path>\
1927 <path fill="#ffffff" opacity="0.26667" d="M 49.764,136.688 C 41.913,142.379 34.739,148.942 28.380,156.237 C 29.224,157.328 30.094,158.452 30.979,159.594 C 33.003,157.241 35.116,154.962 37.309,152.768 C 41.936,148.143 46.925,143.885 52.234,140.040 C 51.303,138.780 50.463,137.639 49.764,136.688 M 168.998,120.536 C 168.581,121.903 167.600,123.146 166.369,124.003 C 173.664,126.645 180.643,129.956 187.224,133.863 C 187.333,132.322 187.443,130.781 187.555,129.239 C 181.648,125.862 175.448,122.946 168.998,120.536 M 124.427,112.518 C 116.992,112.518 109.701,113.156 102.614,114.379 C 102.373,117.292 99.933,119.577 96.962,119.577 C 94.928,119.577 92.586,118.507 91.196,116.898 C 83.399,119.001 75.894,121.826 68.763,125.296 L 69.403,129.622 C 85.962,121.340 104.648,116.681 124.427,116.680 C 135.839,116.680 146.888,118.231 157.374,121.134 C 157.224,120.626 157.144,120.087 157.144,119.530 C 157.144,119.451 157.171,118.473 157.216,116.779 C 149.919,114.840 142.373,113.535 134.623,112.921 C 133.582,114.180 132.008,114.982 130.247,114.982 C 128.305,114.982 126.591,114.009 125.569,112.524 C 125.188,112.520 124.810,112.518 124.427,112.518"></path>\
1928 <path fill="#ffffff" opacity="0.26667" d="M 39.819,126.114 C 32.433,131.616 25.597,137.825 19.419,144.632 C 20.211,145.659 21.073,146.779 22.001,147.981 C 23.659,146.135 25.366,144.333 27.121,142.579 C 32.061,137.639 37.375,133.073 43.014,128.926 C 41.928,127.893 40.860,126.956 39.819,126.114 M 170.043,105.607 C 169.946,107.133 169.856,108.576 169.773,109.915 C 176.197,112.156 182.408,114.863 188.360,117.991 C 188.469,116.474 188.576,114.969 188.682,113.473 C 182.702,110.426 176.478,107.793 170.043,105.607 M 88.200,102.780 C 80.728,104.749 73.501,107.312 66.564,110.417 L 67.197,114.699 C 74.130,111.524 81.370,108.909 88.865,106.909 C 88.665,105.662 88.441,104.276 88.200,102.780 M 136.090,98.581 C 136.065,100.105 136.043,101.504 136.022,102.752 C 143.367,103.365 150.542,104.553 157.514,106.273 C 157.552,104.928 157.593,103.506 157.636,102.019 C 150.641,100.340 143.443,99.180 136.090,98.581 M 124.014,98.109 C 116.527,98.131 109.170,98.733 101.998,99.873 C 102.066,101.364 102.132,102.760 102.192,104.057 C 109.366,102.891 116.723,102.281 124.225,102.270 C 124.162,101.018 124.092,99.624 124.014,98.109"></path>\
1929 <path fill="#ffffff" opacity="0.26667" d="M 25.247,119.229 C 20.067,123.493 15.168,128.082 10.577,132.969 C 11.247,134.191 12.138,135.342 13.033,136.436 C 14.308,135.063 15.607,133.716 16.932,132.390 C 21.157,128.166 25.626,124.191 30.320,120.486 C 28.510,119.813 26.815,119.418 25.247,119.229 M 170.989,90.758 C 170.899,92.191 170.806,93.620 170.716,95.037 C 177.170,97.096 183.429,99.574 189.475,102.441 C 189.581,100.935 189.688,99.449 189.793,97.995 C 183.724,95.194 177.450,92.774 170.989,90.758 M 85.878,88.493 C 78.490,90.370 71.310,92.774 64.383,95.660 L 65.013,99.910 C 71.944,96.964 79.138,94.518 86.550,92.617 C 86.332,91.274 86.106,89.893 85.878,88.493 M 136.324,84.145 C 136.302,85.568 136.279,86.961 136.257,88.314 C 143.644,88.883 150.880,89.979 157.934,91.565 C 157.973,90.176 158.014,88.763 158.056,87.329 C 150.976,85.776 143.727,84.703 136.324,84.145 M 123.275,83.703 C 115.823,83.758 108.499,84.332 101.330,85.396 C 101.396,86.819 101.459,88.213 101.523,89.575 C 108.691,88.492 116.029,87.909 123.491,87.864 C 123.421,86.512 123.349,85.128 123.275,83.703"></path>\
1930 <path fill="#ffffff" opacity="0.26667" d="M 171.940,75.994 C 171.852,77.362 171.759,78.786 171.667,80.251 C 178.143,82.164 184.452,84.460 190.573,87.115 C 190.682,85.596 190.790,84.128 190.889,82.722 C 184.746,80.121 178.425,77.871 171.940,75.994 M 83.526,74.225 C 76.227,76.022 69.113,78.287 62.217,80.990 L 62.842,85.218 C 69.746,82.467 76.883,80.161 84.209,78.344 C 83.983,76.969 83.754,75.594 83.526,74.225 M 136.561,69.714 C 136.538,71.106 136.516,72.501 136.493,73.883 C 143.926,74.414 151.223,75.436 158.354,76.912 C 158.395,75.505 158.434,74.097 158.475,72.688 C 151.318,71.238 144.014,70.238 136.561,69.714 M 122.520,69.300 C 115.118,69.381 107.814,69.936 100.665,70.932 C 100.729,72.330 100.793,73.726 100.857,75.108 C 108.012,74.093 115.322,73.534 122.740,73.460 C 122.668,72.083 122.594,70.697 122.520,69.300"></path>\
1931 <path fill="#ffffff" opacity="0.26667" d="M 172.901,61.296 C 172.890,61.366 172.882,61.436 172.876,61.506 C 172.876,61.506 172.779,63.011 172.615,65.532 C 179.120,67.326 185.475,69.474 191.663,71.954 C 191.785,70.258 191.889,68.793 191.976,67.601 C 185.773,65.167 179.407,63.058 172.901,61.296 M 81.131,59.975 C 73.933,61.701 66.901,63.847 60.058,66.388 L 60.680,70.598 C 67.541,68.013 74.599,65.833 81.829,64.088 C 81.592,62.680 81.358,61.305 81.131,59.975 M 136.799,55.287 C 136.777,56.658 136.755,58.045 136.732,59.453 C 144.212,59.956 151.571,60.913 158.774,62.299 C 158.814,60.876 158.856,59.470 158.895,58.086 C 151.670,56.724 144.294,55.782 136.799,55.287 M 121.748,54.899 C 114.383,55.003 107.139,55.537 100.007,56.478 C 100.069,57.858 100.133,59.252 100.196,60.651 C 107.331,59.695 114.599,59.156 121.973,59.058 C 121.899,57.666 121.823,56.276 121.748,54.899"></path>\
1932 <path fill="#ffffff" opacity="0.26667" d="M 78.613,45.759 C 71.559,47.418 64.653,49.450 57.909,51.836 L 58.528,56.033 C 65.309,53.602 72.264,51.536 79.368,49.857 C 79.086,48.290 78.833,46.911 78.613,45.759 M 137.038,40.863 C 137.016,42.184 136.993,43.580 136.970,45.028 C 144.504,45.506 151.917,46.410 159.193,47.718 C 159.237,46.259 159.276,44.854 159.314,43.512 C 152.015,42.224 144.588,41.334 137.038,40.863 M 120.948,40.500 C 113.651,40.624 106.448,41.142 99.363,42.031 C 99.421,43.387 99.482,44.782 99.546,46.203 C 106.646,45.300 113.867,44.778 121.183,44.659 C 121.104,43.243 121.024,41.856 120.948,40.500"></path>\
1933 <path fill="#ffffff" opacity="0.26667" d="M 67.994,33.588 C 65.759,34.198 63.541,34.842 61.336,35.523 C 59.365,36.980 58.275,39.064 57.693,41.068 C 63.251,39.203 68.915,37.564 74.669,36.163 C 73.215,34.735 71.092,33.607 67.994,33.588 M 137.328,26.443 C 137.298,26.715 137.275,26.981 137.264,27.241 C 137.264,27.241 137.244,28.463 137.209,30.606 C 144.794,31.062 152.266,31.921 159.613,33.163 C 159.668,31.221 159.712,29.780 159.734,28.963 C 152.384,27.741 144.913,26.895 137.328,26.443 M 120.090,26.104 C 112.882,26.247 105.763,26.747 98.746,27.587 C 98.799,28.872 98.857,30.267 98.919,31.758 C 105.961,30.905 113.110,30.400 120.347,30.262 C 120.259,28.784 120.173,27.393 120.090,26.104"></path>\
1934 <use xlink:href="#kinect-open-hand-base" fill="none" stroke="black"></use>\
1935 </g>\
1936 <use id="kinect-cursor-extended" xlink:href="#kinect-open-hand-base" fill="url(#kinect-extended-gradient)" stroke="black"></use>\
1937 </svg>';
1938 document.body.appendChild(cursorElement);
1939
1940 var cursorData = {
1941 "element": cursorElement,
1942 "show": function () {
1943 // Cursor will be shown next time we get a valid hand pointer
1944 showCursor = true;
1945 },
1946 "hide": function () {
1947 // Hide cursor immediately
1948 showCursor = false;
1949 $(cursorElement).hide();
1950 }
1951 };
1952 jQuery.data(cursorElement, uiController.CURSOR_ID, cursorData);
1953
1954 var wasHovering = false;
1955 var wasPressing = false;
1956 uiAdapter.addHitTestExclusion(cursorElement);
1957
1958 uiAdapter.on(uiController.HANDPOINTERS_UPDATED, function(handPointers) {
1959 var MAXIMUM_CURSOR_SCALE = 1.0;
1960 var MINIMUM_CURSOR_SCALE = 0.8;
1961 var MINIMUM_PROGRESS_CLIP_RADIUS = 8.0;
1962 var RANGE_PROGRESS_CLIP_RADIUS = 240.0;
1963
1964 // If cursor is not supposed to be showing, bail out immediately
1965 if (!showCursor) {
1966 return;
1967 }
1968
1969 var handPointer = null;
1970
1971 for (var iPointer = 0; iPointer < handPointers.length; ++iPointer) {
1972 var curPointer = handPointers[iPointer];
1973
1974 if (curPointer.getIsPrimaryHandOfPrimaryUser()) {
1975 handPointer = curPointer;
1976 break;
1977 }
1978 }
1979
1980 if (cursorElement == null) {
1981 return;
1982 }
1983
1984 function clamp(value, min, max) {
1985 return Math.max(min, Math.min(max, value));
1986 }
1987
1988 if (handPointer != null) {
1989 var isOpen = !handPointer.isInGripInteraction;
1990
1991 // Get information about what this hand pointer is over
1992 var isHovering = false;
1993 var isOverPressTarget = false;
1994 var enteredElements = handPointer.getEnteredElements();
1995 for (var iEnteredElement = 0; iEnteredElement < enteredElements.length; ++iEnteredElement) {
1996 var enteredElement = enteredElements[iEnteredElement];
1997 if (KinectUI.getIsPressTarget(enteredElement)) {
1998 isHovering = true;
1999 isOverPressTarget = true;
2000 break;
2001 }
2002
2003 if (KinectUI.getIsGripTarget(enteredElement)) {
2004 isHovering = true;
2005 }
2006 }
2007
2008 var isPressing = isOverPressTarget && handPointer.isPressed && !handPointer.isInGripInteraction;
2009
2010 if (isHovering != wasHovering) {
2011 if (isHovering) {
2012 $(cursorElement).addClass("cursor-hover");
2013 } else {
2014 $(cursorElement).removeClass("cursor-hover");
2015 }
2016 wasHovering = isHovering;
2017 }
2018
2019 if (isPressing != wasPressing) {
2020 if (isPressing) {
2021 $(cursorElement).addClass("cursor-pressed");
2022 } else {
2023 $(cursorElement).removeClass("cursor-pressed");
2024 }
2025 wasPressing = isPressing;
2026 }
2027
2028 var artworkWidth = $(cursorElement).width();
2029 var artworkHeight = $(cursorElement).height();
2030 var adjustedPressExtent = isOverPressTarget ? handPointer.pressExtent : 0.0;
2031
2032 document.getElementById("kinect-cursor-press-clip-circle").r.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, MINIMUM_PROGRESS_CLIP_RADIUS + (RANGE_PROGRESS_CLIP_RADIUS * adjustedPressExtent));
2033
2034 var scale = (1.0 - (adjustedPressExtent * ((MAXIMUM_CURSOR_SCALE - MINIMUM_CURSOR_SCALE) / 2.0)));
2035 var xScale = (handPointer.handType == "Left") ? -scale : scale;
2036 var yScale = scale;
2037 $(cursorElement).css("transform", "scale(" + xScale + "," + yScale + ")");
2038
2039 var xPos = clamp(handPointer.x, 0, window.innerWidth);
2040 var yPos = clamp(handPointer.y, 0, window.innerHeight);
2041 var isClamped = (xPos != handPointer.x) || (yPos != handPointer.y);
2042 var xDelta = (artworkWidth / 2) * scale;
2043 var yDelta = (artworkHeight / 2) * scale;
2044 var opacity = !isClamped ? 1.0 : 0.3;
2045
2046 $(cursorElement).show();
2047 $(cursorElement).offset({ left: xPos - xDelta, top: yPos - yDelta });
2048 $(cursorElement).css("opacity", opacity);
2049 } else {
2050 $(cursorElement).hide();
2051 }
2052 });
2053
2054 return cursorData;
2055 };
2056
2057 // Enable triggering of UI events as interaction frames are received.
2058 // .enableEvents()
2059 //
2060 // Remarks
2061 // This and .disableEvents() function serve as a global on/off switch
2062 // so that clients don't have to be registering and unregistering all
2063 // event handlers as they enter/exit a state where they don't want or
2064 // need to process Kinect UI events.
2065 // Events are enabled by default.
2066 this.enableEvents = function() {
2067 handPointerEventsEnabled = true;
2068 };
2069
2070 // Disable triggering of UI events as interaction frames are received.
2071 // .disableEvents()
2072 //
2073 // Remarks
2074 // This and .enableEvents() function serve as a global on/off switch
2075 // so that clients don't have to be registering and unregistering all
2076 // event handlers as they enter/exit a state where they don't want or
2077 // need to process Kinect UI events.
2078 // Events are enabled by default.
2079 this.disableEvents = function () {
2080 handPointerEventsEnabled = false;
2081 };
2082 }
2083
2084 //////////////////////////////////////////////////////////////
2085 // KinectUIController object constructor
2086 function KinectUIController() {
2087
2088 //////////////////////////////////////////////////////////////
2089 // Public KinectUIController methods
2090
2091 // Creates a new Kinect UI adapter object optionally associated with a Kinect sensor.
2092 // .createAdapter( [sensor] )
2093 //
2094 // sensor: sensor object associated with the new UI adapter
2095 this.createAdapter = function (sensor) {
2096 if (typeof sensor != 'object') {
2097 throw new Error("first parameter must be either a sensor object or left unspecified");
2098 }
2099
2100 var uiAdapter = new KinectUIAdapter();
2101
2102 if (sensor != null) {
2103 sensor.addStreamFrameHandler(uiAdapter.streamFrameHandler);
2104 sensor.setHitTestHandler(uiAdapter.hitTestHandler);
2105 }
2106
2107 return uiAdapter;
2108 };
2109
2110 // Determines whether the specified element has been designated as a press target
2111 // .getIsPressTarget( element )
2112 //
2113 // element: DOM element for which we're querying press target status.
2114 this.getIsPressTarget = function (element) {
2115 return jQuery.data(element, "isPressTarget") ? true : false;
2116 };
2117
2118 // Specifies whether the specified element should be designated as a press target
2119 // .setIsPressTarget( element, isPressTarget )
2120 //
2121 // element: DOM element for which we're specifying press target status.
2122 // isPressTarget: true if element should be designated as a press target.
2123 // false otherwise.
2124 this.setIsPressTarget = function (element, isPressTarget) {
2125 jQuery.data(element, "isPressTarget", isPressTarget);
2126 };
2127
2128 // Determines whether the specified element has been designated as a grip target
2129 // .getIsPressTarget( element )
2130 //
2131 // element: DOM element for which we're querying grip target status.
2132 this.getIsGripTarget = function (element) {
2133 return jQuery.data(element, "isGripTarget") ? true : false;
2134 };
2135
2136 // Specifies whether the specified element should be designated as a grip target
2137 // .setIsGripTarget( element, isGripTarget )
2138 //
2139 // element: DOM element for which we're specifying grip target status.
2140 // isGripTarget: true if element should be designated as a grip target.
2141 // false otherwise.
2142 this.setIsGripTarget = function (element, isGripTarget) {
2143 jQuery.data(element, "isGripTarget", isGripTarget);
2144 };
2145
2146 // Determines whether the primary hand pointer is over the specified element
2147 // .getIsPrimaryHandPointerOver( element )
2148 //
2149 // element: DOM element for which we're querying if primary hand pointer is over it.
2150 this.getIsPrimaryHandPointerOver = function (element) {
2151 return jQuery.data(element, "isPrimaryHandPointerOver") ? true : false;
2152 };
2153
2154 // Specifies whether the primary hand pointer is over the specified element
2155 // .setIsPrimaryHandPointerOver( element )
2156 //
2157 // element: DOM element for which we're specifying if primary hand pointer is over it.
2158 // over: true if primary hand pointer is over the specified element. false otherwise.
2159 this.setIsPrimaryHandPointerOver = function (element, over) {
2160 jQuery.data(element, "isPrimaryHandPointerOver", over);
2161 };
2162
2163 //////////////////////////////////////////////////////////////
2164 // Public KinectUIController properties
2165
2166 // Element event names
2167 this.HANDPOINTER_MOVE = "handPointerMove";
2168 this.HANDPOINTER_ENTER = "handPointerEnter";
2169 this.HANDPOINTER_LEAVE = "handPointerLeave";
2170 this.HANDPOINTER_PRESS = "handPointerPress";
2171 this.HANDPOINTER_PRESSRELEASE = "handPointerPressRelease";
2172 this.HANDPOINTER_GRIP = "handPointerGrip";
2173 this.HANDPOINTER_GRIPRELEASE = "handPointerGripRelease";
2174 this.HANDPOINTER_GOTCAPTURE = "handPointerGotCapture";
2175 this.HANDPOINTER_LOSTCAPTURE = "handPointerLostCapture";
2176 this.QUERY_INTERACTION_STATUS = "queryInteractionStatus";
2177
2178 // Global UI event names
2179 this.HANDPOINTERS_UPDATED = "handPointersUpdated";
2180
2181 // Global UI id/class names
2182 this.BUTTON_CLASS = "kinect-button";
2183 this.BUTTON_TEXT_CLASS = "kinect-button-text";
2184 this.BUTTON_HOVER_CLASS = "button-hover";
2185 this.BUTTON_PRESSED_CLASS = "button-pressed";
2186
2187 this.CURSOR_ID = "kinect-cursor";
2188 };
2189
2190 //////////////////////////////////////////////////////////////
2191 // Private global properties common to all KinectUI objects
2192
2193 // Get the path common to all script files
2194 var scriptRootURIPath = (function () {
2195 var rootPath = "";
2196
2197 try {
2198 var scriptFileName = document.scripts[document.scripts.length - 1].src;
2199 var pathSeparatorIndex = scriptFileName.lastIndexOf("/");
2200
2201 if (pathSeparatorIndex >= 0) {
2202 // If pathSeparator is found, return path up to and including separator
2203 rootPath = scriptFileName.substring(0, pathSeparatorIndex + 1);
2204 }
2205 } catch(error) {
2206 }
2207
2208 return rootPath;
2209 })();
2210
2211 // The global Kinect UI controller object is a singleton instance of our internal KinectUIController type
2212 var uiController = new KinectUIController();
2213
2214 // Prefix for element IDs assigned by Kinect UI layer for purposes of hit test detection
2215 var kinectIdPrefix = "kinectId";
2216
2217 // Id in sequence to assign to controls that don't already have an assigned DOM element id
2218 var lastAssignedControlId = 0;
2219
2220 // return singleton
2221 return uiController;
2222})();
2223
2224// Guarantee that Kinect and KinectUI objects are available from the global scope even if
2225// this file was loaded on-demand, in a restricted functional scope, a long time after web
2226// document was initially loaded.
2227window.Kinect = Kinect;
2228window.KinectUI = KinectUI;
Note: See TracBrowser for help on using the repository browser.