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

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

GUI front-end to server base plus web page content

File size: 111.1 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 } catch(error) {
221 console.log('Tried processing stream message, but failed with error: ' + error);
222 }
223 } else {
224 // Ignore any messages of unexpected types.
225 console.log("Unexpected type for received socket message");
226 pendingStreamFrame = null;
227 return;
228 }
229 },
230 function(isConnected) {
231 // Use the existence of the stream channel connection to mean that a
232 // server exists and is ready to accept requests
233 sensor.isConnected = isConnected;
234 onconnection(sensor, isConnected);
235 });
236
237 // Array of registered event handlers
238 var eventHandlers = [];
239
240 var eventSocketManager = new SocketManager(baseEndpointUri + "/events", function(socket, message) {
241 // Get the data in JSON format.
242 var eventData = JSON.parse(message.data);
243
244 for (var iHandler = 0; iHandler < eventHandlers.length; ++iHandler) {
245 eventHandlers[iHandler](eventData);
246 }
247 });
248
249 // Registered hit test handler
250 var hitTestHandler = null;
251
252 var hitTestSocketManager = new SocketManager(baseEndpointUri + "/interaction/client", function (socket, message) {
253 // Get the data in JSON format.
254 var eventData = JSON.parse(message.data);
255 var id = eventData.id;
256 var name = eventData.name;
257 var args = eventData.args;
258
259 switch (name) {
260 case "getInteractionInfoAtLocation":
261 var handlerResult = null;
262 var defaultResult = { "isPressTarget": false, "isGripTarget": false };
263
264 if (hitTestHandler != null) {
265 try {
266 handlerResult = hitTestHandler.apply(sensor, args);
267 } catch(e) {
268 handlerResult = null;
269 }
270 }
271
272 socket.send(JSON.stringify({ "id": id, "result": ((handlerResult != null) ? handlerResult : defaultResult) }));
273 break;
274
275 case "ping":
276 socket.send(JSON.stringify({ "id": id, "result": true }));
277 break;
278 }
279 });
280
281 //////////////////////////////////////////////////////////////
282 // Private KinectSensor functions
283
284 // Perform ajax request
285 // ajax( method, uri, success(responseData) [, error(statusText)] )
286 //
287 // method: http method
288 // uri: target uri of ajax call
289 // requestData: data to send to uri as part of request (may be null)
290 // success: Callback function executed if request succeeds.
291 // error: Callback function executed if request fails (may be null)
292 function ajax(method, uri, requestData, success, error) {
293 if (!isConnectionEnabled) {
294 if (error != null) {
295 error("disconnected from server");
296 }
297 return;
298 }
299
300 var xhr = new XMLHttpRequest();
301 xhr.open(method, uri, asyncRequests);
302 xhr.onload = function (e) {
303 if (xhr.readyState === XMLHttpRequest.DONE) {
304 if (xhr.status == 200) {
305 success(JSON.parse(xhr.responseText));
306 } else if (error != null) {
307 error(xhr.statusText);
308 }
309 }
310 };
311 if (error != null) {
312 xhr.onerror = function (e) {
313 error(xhr.statusText);
314 };
315 }
316 xhr.send(requestData);
317 }
318
319 // Respond to changes in the connection enabled status
320 // onConnectionEnabledChanged( )
321 function onConnectionEnabledChanged() {
322 if (isConnectionEnabled && !streamSocketManager.isStarted) {
323 streamSocketManager.start();
324 } else if (!isConnectionEnabled && streamSocketManager.isStarted) {
325 streamSocketManager.stop();
326 }
327 }
328
329 // Respond to changes in the number of registered event handlers
330 // onEventHandlersChanged( )
331 function onEventHandlersChanged() {
332 if ((eventHandlers.length > 0) && !eventSocketManager.isStarted) {
333 eventSocketManager.start();
334 } else if ((eventHandlers.length == 0) && eventSocketManager.isStarted) {
335 eventSocketManager.stop();
336 }
337 }
338
339 // Respond to changes in the registered hit test handler
340 // onHitTestHandlerChanged( )
341 function onHitTestHandlerChanged() {
342 if ((hitTestHandler != null) && !hitTestSocketManager.isStarted) {
343 hitTestSocketManager.start();
344 } else if ((hitTestHandler == null) && hitTestSocketManager.isStarted) {
345 hitTestSocketManager.stop();
346 }
347 }
348
349 //////////////////////////////////////////////////////////////
350 // Public KinectSensor properties
351 this.isConnected = false;
352
353 //////////////////////////////////////////////////////////////
354 // Public KinectSensor functions
355
356 // Request current sensor configuration
357 // .getConfig( success(configData) [, error(statusText)] )
358 //
359 // success: Callback function executed if request succeeds.
360 // error: Callback function executed if request fails.
361 this.getConfig = function (success, error) {
362 if ((arguments.length < 1) || (typeof success != 'function')) {
363 throw new Error("first parameter must be specified and must be a function");
364 }
365
366 if ((arguments.length >= 2) && (typeof error != 'function')) {
367 throw new Error("if second parameter is specified, it must be a function");
368 }
369
370 ajax("GET", stateEndpoint, null,
371 function(response) { success(response); },
372 (error != null) ? function (statusText) { error(statusText); } : null
373 );
374 };
375
376 // Update current sensor configuration
377 // .postConfig( configData [, error(statusText [, data] )] )
378 //
379 // configData: New configuration property/value pairs to update
380 // for each sensor stream.
381 // error: Callback function executed if request fails.
382 // data parameter of error callback will be null if request
383 // could not be completed at all, but will be a JSON object
384 // giving failure details in case of semantic failure to
385 // satisfy request.
386 this.postConfig = function (configData, error) {
387 if ((arguments.length < 1) || (typeof configData != 'object')) {
388 throw new Error("first parameter must be specified and must be an object");
389 }
390
391 if ((arguments.length >= 2) && (typeof error != 'function')) {
392 throw new Error("if second parameter is specified, it must be a function");
393 }
394
395 ajax("POST", stateEndpoint, JSON.stringify(configData),
396 function(response) {
397 if (!response.success && (error != null)) {
398 error("semantic failure", response);
399 }
400 },
401 (error != null) ? function (statusText) { error(statusText); } : null
402 );
403 };
404
405 // Enable connections with server
406 // .connect()
407 this.connect = function () {
408 isConnectionEnabled = true;
409
410 onConnectionEnabledChanged();
411 onEventHandlersChanged();
412 onHitTestHandlerChanged();
413 };
414
415 // Disable connections with server
416 // .disconnect()
417 this.disconnect = function () {
418 isConnectionEnabled = false;
419
420 onConnectionEnabledChanged();
421 onEventHandlersChanged();
422 onHitTestHandlerChanged();
423 };
424
425 // Add a new stream frame handler.
426 // .addStreamFrameHandler( handler(streamFrame) )] )
427 //
428 // handler: Callback function to be executed when a stream frame is ready to
429 // be processed.
430 this.addStreamFrameHandler = function (handler) {
431 if (typeof(handler) != "function") {
432 throw new Error("first parameter must be specified and must be a function");
433 }
434
435 streamFrameHandlers.push(handler);
436 };
437
438 // Removes one (or all) stream frame handler(s).
439 // .removeStreamFrameHandler( [handler(streamFrame)] )] )
440 //
441 // handler: Stream frame handler callback function to be removed.
442 // If omitted, all stream frame handlers are removed.
443 this.removeStreamFrameHandler = function (handler) {
444 switch (typeof(handler)) {
445 case "undefined":
446 streamFrameHandlers = [];
447 break;
448 case "function":
449 var index = streamFrameHandlers.indexOf(handler);
450 if (index >= 0) {
451 streamFrameHandlers.slice(index, index + 1);
452 }
453 break;
454
455 default:
456 throw new Error("first parameter must either be a function or left unspecified");
457 }
458 };
459
460 // Add a new event handler.
461 // .addEventHandler( handler(event) )] )
462 //
463 // handler: Callback function to be executed when an event is ready to be processed.
464 this.addEventHandler = function (handler) {
465 if (typeof (handler) != "function") {
466 throw new Error("first parameter must be specified and must be a function");
467 }
468
469 eventHandlers.push(handler);
470
471 onEventHandlersChanged();
472 };
473
474 // Removes one (or all) event handler(s).
475 // .removeEventHandler( [handler(streamFrame)] )] )
476 //
477 // handler: Event handler callback function to be removed.
478 // If omitted, all event handlers are removed.
479 this.removeEventHandler = function (handler) {
480 switch (typeof (handler)) {
481 case "undefined":
482 eventHandlers = [];
483 break;
484 case "function":
485 var index = eventHandlers.indexOf(handler);
486 if (index >= 0) {
487 eventHandlers.slice(index, index + 1);
488 }
489 break;
490
491 default:
492 throw new Error("first parameter must either be a function or left unspecified");
493 }
494
495 onEventHandlersChanged();
496 };
497
498 // Set hit test handler.
499 // .setHitTestHandler( [handler(skeletonTrackingId, handType, x, y)] )] )
500 //
501 // handler: Callback function to be executed when interaction stream needs
502 // interaction information in order to adjust a hand pointer position
503 // relative to UI.
504 // If omitted, hit test handler is removed.
505 // - skeletonTrackingId: The skeleton tracking ID for which interaction
506 // information is being retrieved.
507 // - handType: "left" or "right" to represent hand type for which
508 // interaction information is being retrieved.
509 // - x: X-coordinate of UI location for which interaction information
510 // is being retrieved. 0.0 corresponds to left edge of interaction
511 // region and 1.0 corresponds to right edge of interaction region.
512 // - y: Y-coordinate of UI location for which interaction information
513 // is being retrieved. 0.0 corresponds to top edge of interaction
514 // region and 1.0 corresponds to bottom edge of interaction region.
515 this.setHitTestHandler = function (handler) {
516 switch (typeof (handler)) {
517 case "undefined":
518 case "function":
519 hitTestHandler = handler;
520 break;
521
522 default:
523 throw new Error("first parameter must be either a function or left unspecified");
524 }
525
526 onHitTestHandlerChanged();
527 };
528 }
529
530 //////////////////////////////////////////////////////////////
531 // Public KinectConnector properties
532
533 // Server connection defaults
534 this.DEFAULT_HOST_URI = DEFAULT_HOST_URI;
535 this.DEFAULT_HOST_PORT = DEFAULT_HOST_PORT;
536 this.DEFAULT_BASE_PATH = DEFAULT_BASE_PATH;
537 this.DEFAULT_SENSOR_NAME = DEFAULT_SENSOR_NAME;
538
539 // Supported stream names
540 this.INTERACTION_STREAM_NAME = "interaction";
541 this.USERVIEWER_STREAM_NAME = "userviewer";
542 this.BACKGROUNDREMOVAL_STREAM_NAME = "backgroundRemoval";
543 this.SKELETON_STREAM_NAME = "skeleton";
544 this.SENSORSTATUS_STREAM_NAME = "sensorStatus";
545
546 // Supported event categories and associated event types
547 this.USERSTATE_EVENT_CATEGORY = "userState";
548 this.PRIMARYUSERCHANGED_EVENT_TYPE = "primaryUserChanged";
549 this.USERSTATESCHANGED_EVENT_TYPE = "userStatesChanged";
550 this.SENSORSTATUS_EVENT_CATEGORY = "sensorStatus";
551 this.SENSORSTATUSCHANGED_EVENT_TYPE = "statusChanged";
552
553 //////////////////////////////////////////////////////////////
554 // Public KinectConnector methods
555
556 // Enable connections with server
557 // .connect( [ hostUri [, hostPort [, basePath ] ] ] )
558 //
559 // hostUri: URI for host that is serving Kinect data.
560 // Defaults to "http://localhost".
561 // hostPort: HTTP port from which Kinect data is being served.
562 // Defaults to 8181.
563 // basePath: base URI path for all endpoints that serve Kinect data.
564 // Defaults to "kinect".
565 this.connect = function (hostUri, hostPort, basePath) {
566 hostUri = (hostUri != null) ? hostUri : DEFAULT_HOST_URI;
567 hostPort = (hostPort != null) ? hostPort : DEFAULT_HOST_PORT;
568 basePath = (basePath != null) ? basePath : DEFAULT_BASE_PATH;
569
570 // Indicate we're now connected
571 connectionUri = hostUri + ":" + hostPort + "/" + basePath;
572 explicitDisconnect = false;
573 };
574
575 // Disable connections with server
576 // .disconnect()
577 this.disconnect = function () {
578 // stop all sensors currently tracked
579
580 for (var sensorKey in sensorMap) {
581 var sensor = sensorMap[sensorKey];
582 sensor.disconnect();
583 }
584
585 sensorMap = {};
586
587 // Indicate we're now disconnected
588 connectionUri = null;
589 explicitDisconnect = true;
590 };
591
592 // Gets an object representing a connection with a specific Kinect sensor.
593 // .sensor( [sensorName [, onconnection(sensor, isConnected)]] )
594 //
595 // sensorName: name used to refer to a specific Kinect sensor exposed by the server.
596 // Defaults to "default".
597 // onconnection: Function to call back whenever status of connection to the
598 // server that owns the sensor with specified name changes from
599 // disconnected to connected or vice versa.
600 //
601 // Remarks
602 // - If client has never called KinectConnector.connect or .disconnect, .connect() is
603 // called implicitly with default server URI parameters.
604 // - If client has explicitly called KinectConnector.disconnect, calling this method
605 // returns null.
606 // - If a non-null sensor is returned, it will already be in connected state (i.e.:
607 // there is no need to call KinectSensor.connect immediately after calling this
608 // method).
609 this.sensor = function (sensorName, onconnection) {
610 switch (typeof(sensorName)) {
611 case "undefined":
612 case "string":
613 break;
614
615 default:
616 throw new Error("first parameter must be a string or left unspecified");
617 }
618 switch (typeof (onconnection)) {
619 case "undefined":
620 // provide a guarantee to KinectSensor constructor that onconnection
621 // will exist and be a real function
622 onconnection = function() { };
623 break;
624 case "function":
625 break;
626
627 default:
628 throw new Error("second parameter must be a function or left unspecified");
629 }
630
631 if (explicitDisconnect) {
632 return null;
633 }
634
635 if (connectionUri == null) {
636 // If we're not connected yet, connect with default parameters
637 // when someone asks for a sensor.
638 this.connect();
639 }
640
641 sensorName = (sensorName != null) ? sensorName : DEFAULT_SENSOR_NAME;
642
643 // If sensor for specified name doesn't exist yet, create it
644 if (!sensorMap.hasOwnProperty(sensorName)) {
645 sensorMap[sensorName] = new KinectSensor(connectionUri + "/" + sensorName, onconnection);
646
647 // Start the sensors connected by default when they are requested
648 sensorMap[sensorName].connect();
649 }
650
651 return sensorMap[sensorName];
652 };
653
654 // Get whether server requests to REST endpoints should be asynchronous (true by default)
655 // .async()
656 //
657 // -------------------------------------------------------------------------------------
658 // Specify whether server requests to REST endpoints should be asynchronous
659 // .async( asyncRequests )
660 //
661 // isAsync: true if server requests to REST endpoints should be asynchronous
662 // false if server requests to REST endpoints should be synchronous
663 this.async = function(isAsync) {
664 if (typeof isAsync == 'undefined') {
665 return asyncRequests;
666 }
667
668 var newAsync = isAsync == true;
669 asyncRequests = newAsync;
670 };
671 }
672
673 // The global Kinect object is a singleton instance of our internal KinectConnector type
674 return new KinectConnector();
675})();
676
677//////////////////////////////////////////////////////////////
678// Create the global Kinect UI factory object
679var KinectUI = (function () {
680 "use strict";
681
682 //////////////////////////////////////////////////////////////
683 // KinectUIAdapter object constructor
684 function KinectUIAdapter() {
685
686 //////////////////////////////////////////////////////////////
687 // HandPointer object constructor
688 function HandPointer(trackingId, playerIndex, handType) {
689 //////////////////////////////////////////////////////////////
690 // Public HandPointer properties
691 this.trackingId = trackingId;
692 this.playerIndex = playerIndex;
693 this.handType = handType;
694 this.timestampOfLastUpdate = 0;
695 this.handEventType = "None";
696 this.isTracked = false;
697 this.isActive = false;
698 this.isInteractive = false;
699 this.isPressed = false;
700 this.isPrimaryHandOfUser = false;
701 this.isPrimaryUser = false;
702 this.isInGripInteraction = false;
703 this.captured = null;
704 this.x = 0.0;
705 this.y = 0.0;
706 this.pressExtent = 0.0;
707 this.rawX = 0.0;
708 this.rawY = 0.0;
709 this.rawZ = 0.0;
710 this.originalHandPointer = null;
711 this.updated = false;
712
713 //////////////////////////////////////////////////////////////
714 // Public HandPointer methods
715
716 // Get all DOM elements that this hand pointer has entered.
717 // .getEnteredElements()
718 this.getEnteredElements = function () {
719 var key = getHandPointerKey(this.trackingId, this.handType);
720 var data = internalHandPointerData[key];
721
722 if (data == null) {
723 return [];
724 }
725
726 return data.enteredElements;
727 };
728
729 // Determine whether this hand pointer is over the specified DOM element.
730 // .getIsOver( element )
731 //
732 // element: DOM element against which to check hand pointer position.
733 this.getIsOver = function (element) {
734 // Iterate over cached elements
735 var enteredElements = this.getEnteredElements();
736
737 for (var iElement = 0; iElement < enteredElements.length; ++iElement) {
738 if (enteredElements[iElement] === element) {
739 return true;
740 }
741 }
742
743 return false;
744 };
745
746 // Create a capture association between this hand pointer and the specified DOM
747 // element. This means that event handlers associated with element will receive
748 // events triggered by this hand pointer even if hand pointer is not directly
749 // over element.
750 // .capture( element )
751 //
752 // element: DOM element capturing hand pointer.
753 // If null, this hand pointer stops being captured and resumes normal
754 // event routing behavior.
755 this.capture = function (element) {
756 return captureHandPointer(this, element);
757 };
758
759 // Determine whether this hand pointer corresponds to the primary hand of the
760 // primary user.
761 // .getIsPrimaryHandOfPrimaryUser()
762 this.getIsPrimaryHandOfPrimaryUser = function () {
763 return this.isPrimaryUser && this.isPrimaryHandOfUser;
764 };
765
766 // Update this hand pointer's properties from the specified hand pointer received
767 // from the interaction stream.
768 // .update( streamHandPointer )
769 this.update = function (streamHandPointer) {
770 // save the stream hand pointer in case it has additional properties
771 // that clients might be interested in
772 this.originalHandPointer = streamHandPointer;
773
774 this.updated = true;
775
776 this.timestampOfLastUpdate = streamHandPointer.timestampOfLastUpdate;
777 this.handEventType = streamHandPointer.handEventType;
778
779 var pressedChanged = this.isPressed != streamHandPointer.isPressed;
780 this.isPressed = streamHandPointer.isPressed;
781
782 this.isTracked = streamHandPointer.isTracked;
783 this.isActive = streamHandPointer.isActive;
784 this.isInteractive = streamHandPointer.isInteractive;
785
786 var primaryHandOfPrimaryUserChanged = this.getIsPrimaryHandOfPrimaryUser() != (streamHandPointer.isPrimaryHandOfUser && streamHandPointer.isPrimaryUser);
787 this.isPrimaryHandOfUser = streamHandPointer.isPrimaryHandOfUser;
788 this.isPrimaryUser = streamHandPointer.isPrimaryUser;
789
790 var newPosition = interactionZoneToWindow({ "x": streamHandPointer.x, "y": streamHandPointer.y });
791 var positionChanged = !areUserInterfaceValuesClose(newPosition.x, this.x) ||
792 !areUserInterfaceValuesClose(newPosition.y, this.y) ||
793 !areUserInterfaceValuesClose(streamHandPointer.pressExtent, this.pressExtent);
794 this.x = newPosition.x;
795 this.y = newPosition.y;
796 this.pressExtent = streamHandPointer.pressExtent;
797
798 this.rawX = streamHandPointer.rawX;
799 this.rawY = streamHandPointer.rawY;
800 this.rawZ = streamHandPointer.rawZ;
801
802 return {
803 "pressedChanged": pressedChanged,
804 "primaryHandOfPrimaryUserChanged": primaryHandOfPrimaryUserChanged,
805 "positionChanged": positionChanged
806 };
807 };
808 }
809
810 //////////////////////////////////////////////////////////////
811 // ImageWorkerManager object constructor
812 // new ImageWorkerManager()
813 //
814 // Remarks
815 // Manages background thread work related to processing image
816 // stream frames to get them ready to be displayed in a canvas
817 // element.
818 function ImageWorkerManager() {
819
820 //////////////////////////////////////////////////////////////
821 // ImageMetadata object constructor
822 function ImageMetadata(imageCanvas) {
823 this.isProcessing = false;
824 this.canvas = imageCanvas;
825 this.width = 0;
826 this.height = 0;
827 }
828
829 //////////////////////////////////////////////////////////////
830 // Private ImageWorkerManager properties
831 var workerThread = null;
832 var imageMetadataMap = {};
833
834 //////////////////////////////////////////////////////////////
835 // Private ImageWorkerManager methods
836 function ensureInitialized() {
837 // Lazy-initialize web worker thread
838 if (workerThread == null) {
839 workerThread = new Worker(scriptRootURIPath + "KinectWorker-1.8.0.js");
840 workerThread.addEventListener("message", function (event) {
841 var imageName = event.data.imageName;
842 if (!imageMetadataMap.hasOwnProperty(imageName)) {
843 return;
844 }
845 var metadata = imageMetadataMap[imageName];
846
847 switch (event.data.message) {
848 case "imageReady":
849 // Put ready image data in associated canvas
850 var canvasContext = metadata.canvas.getContext("2d");
851 canvasContext.putImageData(event.data.imageData, 0, 0);
852 metadata.isProcessing = false;
853 break;
854
855 case "notProcessed":
856 metadata.isProcessing = false;
857 break;
858 }
859 });
860 }
861 }
862
863 //////////////////////////////////////////////////////////////
864 // Public ImageWorkerManager methods
865
866 // Send named image data to be processed by worker thread
867 // .processImageData(imageName, imageBuffer, width, height)
868 //
869 // imageName: Name of image to process
870 // imageBuffer: ArrayBuffer containing image data
871 // width: width of image corresponding to imageBuffer data
872 // height: height of image corresponding to imageBuffer data
873 this.processImageData = function (imageName, imageBuffer, width, height) {
874 ensureInitialized();
875
876 if (!imageMetadataMap.hasOwnProperty(imageName)) {
877 // We're not tracking this image, so no work to do
878 return;
879 }
880 var metadata = imageMetadataMap[imageName];
881
882 if (metadata.isProcessing || (width <= 0) || (height <= 0)) {
883 // Don't send more data to worker thread when we are in the middle
884 // of processing data already.
885 // Also, Only do work if image data to process is of the expected size
886 return;
887 }
888
889 metadata.isProcessing = true;
890
891 if ((width != metadata.width) || (height != metadata.height)) {
892 // Whenever the image width or height changes, update image tracking metadata
893 // and canvas ImageData associated with worker thread
894
895 var canvasContext = metadata.canvas.getContext("2d");
896 var imageData = canvasContext.createImageData(width, height);
897 metadata.width = width;
898 metadata.height = height;
899 metadata.canvas.width = width;
900 metadata.canvas.height = height;
901
902 workerThread.postMessage({ "message": "setImageData", "imageName": imageName, "imageData": imageData });
903 }
904
905 workerThread.postMessage({ "message": "processImageData", "imageName": imageName, "imageBuffer": imageBuffer });
906 };
907
908 // Associate specified image name with canvas ImageData object for future usage by
909 // worker thread
910 // .setImageData(imageName, canvas)
911 //
912 // imageName: Name of image stream to associate with ImageData object
913 // canvas: Canvas to bind to user viewer stream
914 this.setImageData = function (imageName, canvas) {
915 ensureInitialized();
916
917 if (canvas != null) {
918 var metadata = new ImageMetadata(canvas);
919 imageMetadataMap[imageName] = metadata;
920 } else if (imageMetadataMap.hasOwnProperty(imageName)) {
921 // If specified canvas is null but we're already tracking image data,
922 // remove metadata associated with this image.
923 delete imageMetadataMap[imageName];
924 }
925 };
926 }
927
928 //////////////////////////////////////////////////////////////
929 // Private KinectUIAdapter constants
930 var LAZYRELEASE_RADIUS = 0.3;
931 var LAZYRELEASE_YCUTOFF = LAZYRELEASE_RADIUS / 3;
932
933 //////////////////////////////////////////////////////////////
934 // Private KinectUIAdapter properties
935 var uiAdapter = this;
936 var isInInteractionFrame = false;
937 var isClearRequestPending = false;
938 var handPointerEventsEnabled = true;
939 // Property names of internalHandPointerData are keys that encode user tracking id and hand type.
940 // Property values are JSON objects with "handPointer" and "enteredElements" properties.
941 var internalHandPointerData = {};
942 var excludedElements = [];
943 var eventHandlers = {};
944 var bindableStreamNames = {};
945 bindableStreamNames[Kinect.USERVIEWER_STREAM_NAME] = true;
946 bindableStreamNames[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = true;
947 var imageWorkerManager = new ImageWorkerManager();
948
949 //////////////////////////////////////////////////////////////
950 // Private KinectUIAdapter methods
951 function interactionZoneToElement(point, element) {
952 return { "x": point.x * element.offsetWidth, "y": point.y * element.offsetHeight };
953 }
954
955 function elementToInteractionZone(point, element) {
956 return { "x": point.x / element.offsetWidth, "y": point.y / element.offsetHeight };
957 }
958
959 function interactionZoneToWindow(point) {
960 return { "x": point.x * window.innerWidth, "y": point.y * window.innerHeight };
961 }
962
963 function windowToInteractionZone(point) {
964 return { "x": point.x / window.innerWidth, "y": point.y / window.innerHeight };
965 }
966
967 function areUserInterfaceValuesClose(a, b) {
968 return Math.abs(a - b) < 0.5;
969 }
970
971 function getPressTargetPoint(element) {
972 // Hardcoded to always return the center point
973 return { "x": 0.5, "y": 0.5 };
974 }
975
976 function getHandPointerKey(trackingId, handType) {
977 return handType + trackingId;
978 }
979
980 function createInteractionEvent(eventName, detail) {
981 var event = document.createEvent("CustomEvent");
982 var canBubble = true;
983 var cancelable = false;
984
985 switch (eventName) {
986 case uiController.HANDPOINTER_ENTER:
987 case uiController.HANDPOINTER_LEAVE:
988 canBubble = false;
989 break;
990 }
991
992 event.initCustomEvent(eventName, canBubble, cancelable, detail);
993
994 return event;
995 }
996
997 function raiseHandPointerEvent(element, eventName, handPointer) {
998 if (!handPointerEventsEnabled) {
999 return;
1000 }
1001
1002 var event = createInteractionEvent(eventName, { "handPointer": handPointer });
1003 element.dispatchEvent(event);
1004 }
1005
1006 function raiseGlobalUIEvent(eventName, eventData) {
1007 if (!handPointerEventsEnabled || !eventHandlers.hasOwnProperty(eventName)) {
1008 // Events are disabled, or there are no event handlers registered for event.
1009 return;
1010 }
1011
1012 var handlerArray = eventHandlers[eventName];
1013 for (var i = 0; i < handlerArray.length; ++i) {
1014 handlerArray[i](eventData);
1015 }
1016 }
1017
1018 function switchCapture(handPointer, oldElement, newElement) {
1019 handPointer.captured = newElement;
1020
1021 if (oldElement != null) {
1022 raiseHandPointerEvent(oldElement, uiController.HANDPOINTER_LOSTCAPTURE, handPointer);
1023 }
1024
1025 if (newElement != null) {
1026 raiseHandPointerEvent(newElement, uiController.HANDPOINTER_GOTCAPTURE, handPointer);
1027 }
1028 }
1029
1030 function captureHandPointer(handPointer, element) {
1031 var id = getHandPointerKey(handPointer.trackingId, handPointer.handType);
1032
1033 if (!internalHandPointerData.hasOwnProperty(id)) {
1034 // We're not already tracking a hand pointer with the same id.
1035 return false;
1036 }
1037
1038 var handPointerData = internalHandPointerData[id];
1039 var checkHandPointer = handPointerData.handPointer;
1040 if (checkHandPointer !== handPointer) {
1041 // The hand pointer we're tracking is not the same one that was handed in.
1042 // Caller may have a stale hand pointer instance.
1043 return false;
1044 }
1045
1046 if (element != null && handPointer.captured != null) {
1047 // Request wasn't to clear capture and some other element already has this
1048 // HandPointer captured.
1049 return false;
1050 }
1051
1052 switchCapture(handPointer, handPointer.captured, element);
1053 return true;
1054 }
1055
1056 // Get the DOM element branch (parent chain), rooted in body element,
1057 // that includes the specified leaf element.
1058 // If leafElement is null, the returned branch will be empty
1059 function getDOMBranch(leafElement) {
1060 var scanStop = document.body.parentNode;
1061 var branch = [];
1062
1063 for (var scan = leafElement; (scan != null) && (scan != scanStop) && (scan != document) ; scan = scan.parentNode) {
1064 branch.push(scan);
1065 }
1066
1067 return branch;
1068 }
1069
1070 function doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements) {
1071 var isPrimaryHandOfPrimaryUser = handPointer.getIsPrimaryHandOfPrimaryUser();
1072 var wasPrimaryHandOfPrimaryUser = primaryHandOfPrimaryUserChanged ? !isPrimaryHandOfPrimaryUser : isPrimaryHandOfPrimaryUser;
1073
1074 // find common elements between old and new set
1075 // We take advantage of the fact that later elements in the array contain earlier
1076 // elements, so they will remain in the same state for a longer time.
1077 var firstOldMatch = oldEnteredElements.length;
1078 var firstNewMatch = newEnteredElements.length;
1079 while ((firstOldMatch > 0) &&
1080 (firstNewMatch > 0) &&
1081 (oldEnteredElements[firstOldMatch - 1] === newEnteredElements[firstNewMatch - 1])) {
1082 --firstOldMatch;
1083 --firstNewMatch;
1084 }
1085
1086 // Tell old entered elements that are not entered anymore that we have left them
1087 for (var iLeft = 0; iLeft < oldEnteredElements.length; ++iLeft) {
1088 var leftElement = oldEnteredElements[iLeft];
1089
1090 if (iLeft < firstOldMatch) {
1091 // This element is not one of the common elements, so we have left it
1092
1093 // If we were or still are the HandPointer for the primary user's
1094 // primary hand then clear out the "IsPrimaryHandPointerOver" property.
1095 if (wasPrimaryHandOfPrimaryUser) {
1096 uiController.setIsPrimaryHandPointerOver(leftElement, false);
1097 }
1098
1099 // Tell this element that this hand pointer has left
1100 raiseHandPointerEvent(leftElement, uiController.HANDPOINTER_LEAVE, handPointer);
1101 } else {
1102 // This is one of the common elements
1103 if (wasPrimaryHandOfPrimaryUser && !isPrimaryHandOfPrimaryUser) {
1104 // Hand pointer didn't leave the element but it is no longer the primary
1105 uiController.setIsPrimaryHandPointerOver(leftElement, false);
1106 }
1107 }
1108 }
1109
1110 // Tell new entered elements that were not previously entered we are now over them
1111 for (var iEntered = 0; iEntered < newEnteredElements.length; ++iEntered) {
1112 var enteredElement = newEnteredElements[iEntered];
1113
1114 if (iEntered < firstNewMatch) {
1115 // This element is not one of the common elements, so we have entered it
1116
1117 // If we are the HandPointer for the primary user's primary hand then
1118 // set the "IsPrimaryHandPointerOver" property.
1119 if (isPrimaryHandOfPrimaryUser) {
1120 uiController.setIsPrimaryHandPointerOver(enteredElement, true);
1121 }
1122
1123 // Tell this element that this hand pointer has left
1124 raiseHandPointerEvent(enteredElement, uiController.HANDPOINTER_ENTER, handPointer);
1125 } else {
1126 // This is one of the common elements
1127 if (!wasPrimaryHandOfPrimaryUser && isPrimaryHandOfPrimaryUser) {
1128 // Hand pointer was already in this element but it became the primary
1129 uiController.setIsPrimaryHandPointerOver(enteredElement, true);
1130 }
1131 }
1132 }
1133 }
1134
1135 function beginInteractionFrame() {
1136 if (isInInteractionFrame) {
1137 console.log("Call to beginInteractionFrame was made without corresponding call to endInteractionFrame");
1138 return;
1139 }
1140
1141 isInInteractionFrame = true;
1142 isClearRequestPending = false;
1143
1144 for (var handPointerKey in internalHandPointerData) {
1145 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1146 handPointer.updated = false;
1147 }
1148 }
1149
1150 function handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed) {
1151 var doPress = false;
1152 var doRelease = false;
1153 var doMove = false;
1154 var doLostCapture = false;
1155 var doGrip = false;
1156 var doGripRelease = false;
1157 var handPointer = handPointerData.handPointer;
1158
1159 if (removed) {
1160 // Deny the existence of this hand pointer
1161 doRelease = handPointer.isPressed;
1162 doLostCapture = handPointer.captured != null;
1163 } else {
1164 if (pressedChanged) {
1165 doPress = handPointer.isPressed;
1166 doRelease = !handPointer.isPressed;
1167 }
1168
1169 if (positionChanged) {
1170 doMove = true;
1171 }
1172
1173 doGrip = handPointer.handEventType.toLowerCase() == "grip";
1174 doGripRelease = handPointer.handEventType.toLowerCase() == "griprelease";
1175 }
1176
1177 if (doLostCapture) {
1178 switchCapture(handPointer, handPointer.captured, null);
1179 }
1180
1181 var targetElement = handPointer.captured;
1182 if (targetElement == null) {
1183 targetElement = hitTest(handPointer.x, handPointer.y);
1184 }
1185
1186 // Update internal enter/leave state
1187 var oldEnteredElements = handPointerData.enteredElements;
1188 var newEnteredElements = getDOMBranch(removed ? null : targetElement);
1189 handPointerData.enteredElements = newEnteredElements;
1190
1191 // See if this hand pointer is participating in a grip-initiated
1192 // interaction.
1193 var newIsInGripInteraction = false;
1194 if (targetElement != null) {
1195 var result = raiseHandPointerEvent(targetElement, uiController.QUERY_INTERACTION_STATUS, handPointer);
1196
1197 if ((result != null) && result.isInGripInteraction) {
1198 newIsInGripInteraction = true;
1199 }
1200 }
1201
1202 handPointer.isInGripInteraction = newIsInGripInteraction;
1203
1204 //// After this point there should be no more changes to the internal
1205 //// state of the handPointers. We don't want event handlers calling us
1206 //// when our internal state is inconsistent.
1207
1208 doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements);
1209
1210 if (targetElement == null) {
1211 return;
1212 }
1213
1214 if (doGrip) {
1215 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIP, handPointer);
1216 }
1217
1218 if (doGripRelease) {
1219 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIPRELEASE, handPointer);
1220 }
1221
1222 if (doPress) {
1223 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESS, handPointer);
1224 }
1225
1226 if (doMove) {
1227 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_MOVE, handPointer);
1228 }
1229
1230 if (doRelease) {
1231 raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESSRELEASE, handPointer);
1232 }
1233 }
1234
1235 function handleHandPointerData(interactionStreamHandPointers) {
1236 if (!isInInteractionFrame) {
1237 console.log("Call to handleHandPointerData was made without call to beginInteractionFrame");
1238 return;
1239 }
1240
1241 if (isClearRequestPending) {
1242 // We don't care about new hand pointer data if client requested to clear
1243 // all hand pointers while in the middle of interaction frame processing.
1244 return;
1245 }
1246
1247 for (var iData = 0; iData < interactionStreamHandPointers.length; ++iData) {
1248 var streamHandPointer = interactionStreamHandPointers[iData];
1249 var id = getHandPointerKey(streamHandPointer.trackingId, streamHandPointer.handType);
1250
1251 var handPointerData;
1252 var handPointer;
1253 if (internalHandPointerData.hasOwnProperty(id)) {
1254 handPointerData = internalHandPointerData[id];
1255 handPointer = handPointerData.handPointer;
1256 } else {
1257 handPointer = new HandPointer(streamHandPointer.trackingId, streamHandPointer.playerIndex, streamHandPointer.handType);
1258
1259 handPointerData = { "handPointer": handPointer, "enteredElements": [] };
1260 internalHandPointerData[id] = handPointerData;
1261 }
1262
1263 var result = handPointer.update(streamHandPointer);
1264 handleHandPointerChanges(handPointerData, result.pressedChanged, result.positionChanged, result.primaryHandOfPrimaryUserChanged, false);
1265 }
1266 }
1267
1268 function endInteractionFrame() {
1269 if (!isInInteractionFrame) {
1270 console.log("Call to endInteractionFrame was made without call to beginInteractionFrame");
1271 return;
1272 }
1273
1274 removeStaleHandPointers();
1275
1276 isClearRequestPending = false;
1277 isInInteractionFrame = false;
1278 }
1279
1280 function removeStaleHandPointers() {
1281 var nonUpdatedHandpointers = [];
1282
1283 for (var handPointerKey in internalHandPointerData) {
1284 // If we need to stop tracking this hand pointer
1285 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1286 if (isClearRequestPending || !handPointer.updated) {
1287 nonUpdatedHandpointers.push(handPointerKey);
1288 }
1289 }
1290
1291 for (var i = 0; i < nonUpdatedHandpointers.length; ++i) {
1292 var handPointerKey = nonUpdatedHandpointers[i];
1293 var handPointerData = internalHandPointerData[handPointerKey];
1294 var handPointer = handPointerData.handPointer;
1295 var pressedChanged = handPointer.isPressed;
1296 var positionChanged = false;
1297 var primaryHandOfPrimaryUserChanged = handPointer.getIsPrimaryHandOfPrimaryUser();
1298 var removed = true;
1299
1300 handPointer.isTracked = false;
1301 handPointer.isActive = false;
1302 handPointer.isInteractive = false;
1303 handPointer.isPressed = false;
1304 handPointer.isPrimaryUser = false;
1305 handPointer.isPrimaryHandOfUser = false;
1306
1307 handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed);
1308
1309 delete internalHandPointerData[handPointerKey];
1310 }
1311 }
1312
1313 function updatePublicHandPointers() {
1314 var iHandPointer = 0;
1315
1316 for (var handPointerKey in internalHandPointerData) {
1317 var handPointer = internalHandPointerData[handPointerKey].handPointer;
1318
1319 if (handPointer.isTracked) {
1320 if (uiAdapter.handPointers.length > iHandPointer) {
1321 uiAdapter.handPointers[iHandPointer] = handPointer;
1322 } else {
1323 uiAdapter.handPointers.push(handPointer);
1324 }
1325
1326 ++iHandPointer;
1327 }
1328 }
1329
1330 // Truncate the length of the array to the number of valid elements
1331 uiAdapter.handPointers.length = iHandPointer;
1332
1333 raiseGlobalUIEvent(uiController.HANDPOINTERS_UPDATED, uiAdapter.handPointers);
1334 }
1335
1336 function hitTest(x, y) {
1337 // Ensure excluded elements are hidden before performing hit test
1338 var displayProp = "display";
1339 var displayValues = [];
1340 for (var i = 0; i < excludedElements.length; ++i) {
1341 var element = excludedElements[i];
1342 displayValues.push(element.style[displayProp]);
1343 element.style[displayProp] = "none";
1344 }
1345
1346 var hitElement = document.elementFromPoint(x, y);
1347
1348 // Restore visibility to excluded elements
1349 for (var i = 0; i < excludedElements.length; ++i) {
1350 excludedElements[i].style[displayProp] = displayValues[i];
1351 }
1352
1353 return hitElement;
1354 }
1355
1356 function getElementId(element) {
1357 // First check if we've already set the kinect element ID.
1358 var id = jQuery.data(element, kinectIdPrefix);
1359 if (id != null) {
1360 return id;
1361 }
1362
1363 // If not, fall back to regular element id, if present
1364 id = element.id;
1365
1366 if (id.trim() == "") {
1367 // If there is no id on element, assign one ourselves
1368 id = kinectIdPrefix + (++lastAssignedControlId);
1369 }
1370
1371 // Remember assigned id
1372 jQuery.data(element, kinectIdPrefix, id);
1373 return id;
1374 }
1375
1376 function setCapture(element) {
1377 // Not all browsers support mouse capture functionality
1378 if (typeof element.setCapture == "function") {
1379 element.setCapture(true);
1380 return true;
1381 }
1382
1383 return false;
1384 }
1385
1386 function releaseCapture() {
1387 // Not all browsers support mouse capture functionality
1388 if (typeof document.releaseCapture == "function") {
1389 document.releaseCapture();
1390 return true;
1391 }
1392
1393 return false;
1394 }
1395
1396 function promoteToButton(element) {
1397 var content = element.innerHTML;
1398
1399 if (element.children.length <= 0) {
1400 // If element has no children, wrap content in an element that will perform
1401 // default text alignment
1402 content = "<span class='kinect-button-text'>" + content + "</span>";
1403 }
1404
1405 // Wrap all content in a "kinect-button-surface" div element to allow button
1406 // shrinking and expanding as user hovers over or presses button
1407 element.innerHTML = "<div class='kinect-button-surface'>" + content + "</div>";
1408
1409 uiController.setIsPressTarget(element, true);
1410 var capturedHandPointer = null;
1411 var pressedElement = null;
1412 var isMouseOver = false;
1413 var isMouseCaptured = false;
1414 var removeCaptureEvents = false;
1415
1416 var releaseButtonCapture = function(event) {
1417 releaseCapture();
1418 isMouseCaptured = false;
1419
1420 if (removeCaptureEvents) {
1421 $(event.target).off("mousemove");
1422 }
1423 };
1424
1425 $(element).on({
1426 // respond to hand pointer events
1427 handPointerPress: function (event) {
1428 var handPointer = event.originalEvent.detail.handPointer;
1429 if (capturedHandPointer == null && handPointer.getIsPrimaryHandOfPrimaryUser()) {
1430 handPointer.capture(event.currentTarget);
1431 event.stopPropagation();
1432 }
1433 },
1434 handPointerGotCapture: function (event) {
1435 var handPointer = event.originalEvent.detail.handPointer;
1436 if (capturedHandPointer == null) {
1437 capturedHandPointer = handPointer;
1438 pressedElement = handPointer.captured;
1439 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1440 event.stopPropagation();
1441 }
1442 },
1443 handPointerPressRelease: function (event) {
1444 var handPointer = event.originalEvent.detail.handPointer;
1445 if (capturedHandPointer == handPointer) {
1446 var captured = handPointer.captured;
1447 handPointer.capture(null);
1448
1449 if (captured == event.currentTarget) {
1450 // Trigger click on press release if release point is close enough
1451 // to around the top half of button, or if it's anywhere below
1452 // button. I.e.: accept a "lazy-press" as a real press.
1453
1454 var box = captured.getBoundingClientRect();
1455 var insideButton = false;
1456 if ((box.left <= handPointer.x) && (handPointer.x <= box.right) &&
1457 (box.top <= handPointer.y) && (handPointer.y <= box.bottom)) {
1458 insideButton = true;
1459 }
1460
1461 // Map hand pointer to a coordinate space around center of button
1462 // and convert to be relative to window size.
1463 var relativePoint = windowToInteractionZone({
1464 "x": handPointer.x - ((box.left + box.right) / 2),
1465 "y": handPointer.y - ((box.top + box.bottom) / 2)
1466 });
1467 var isWithinLazyReleaseArea = true;
1468 if (relativePoint.y < LAZYRELEASE_YCUTOFF) {
1469 isWithinLazyReleaseArea =
1470 Math.sqrt((relativePoint.x * relativePoint.x) + (relativePoint.y * relativePoint.y)) < LAZYRELEASE_RADIUS;
1471 }
1472
1473 if (insideButton || isWithinLazyReleaseArea) {
1474 $(element).click();
1475 }
1476
1477 event.stopPropagation();
1478 }
1479 }
1480 },
1481 handPointerLostCapture: function (event) {
1482 var handPointer = event.originalEvent.detail.handPointer;
1483 if (capturedHandPointer == handPointer) {
1484 capturedHandPointer = null;
1485 pressedElement = null;
1486 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1487 event.stopPropagation();
1488 }
1489 },
1490 handPointerEnter: function (event) {
1491 var target = event.currentTarget;
1492 if (KinectUI.getIsPrimaryHandPointerOver(target)) {
1493 $(element).addClass(uiController.BUTTON_HOVER_CLASS);
1494 }
1495 },
1496 handPointerLeave: function (event) {
1497 var target = event.currentTarget;
1498 if (!KinectUI.getIsPrimaryHandPointerOver(target)) {
1499 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1500 }
1501 },
1502 // respond to mouse events in a similar way to hand pointer events
1503 // Note: We use button-hover class rather than :hover pseudo-class because
1504 // :hover does not play well with our button-pressed class.
1505 mouseenter: function(event) {
1506 isMouseOver = true;
1507 $(element).addClass(uiController.BUTTON_HOVER_CLASS);
1508 if (pressedElement != null) {
1509 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1510 }
1511 },
1512 mouseleave: function (event) {
1513 isMouseOver = false;
1514 if (!isMouseCaptured || (pressedElement != null)) {
1515 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1516 pressedElement = null;
1517 }
1518 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1519 },
1520 mousedown: function (event) {
1521 if (event.button == 0) {
1522 pressedElement = event.target;
1523 if (setCapture(event.target)) {
1524 isMouseCaptured = true;
1525
1526 // Different browsers have slightly different behaviors related to which element
1527 // will be considered the event target, and how mouseenter and mouseleave events
1528 // are sent in case of capture, so cover all bases.
1529 if (event.target != event.currentTarget) {
1530 removeCaptureEvents = true;
1531 $(event.target).mousemove(function (moveEvent) {
1532 var box = element.getBoundingClientRect();
1533 var insideBox = (box.left <= moveEvent.clientX) && (moveEvent.clientX <= box.right) &&
1534 (box.top <= moveEvent.clientY) && (moveEvent.clientY <= box.bottom);
1535 if (!insideBox && isMouseCaptured) {
1536 releaseButtonCapture(moveEvent);
1537 }
1538 });
1539 }
1540 }
1541 $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
1542
1543 event.stopPropagation();
1544 }
1545 },
1546 mouseup: function (event) {
1547 if (event.button == 0) {
1548 var fakeClick = false;
1549 if ((pressedElement != null) && !isMouseCaptured && (pressedElement != event.target)) {
1550 // If this browser doesn't support capture and the event target
1551 // during mouse up does not match exactly the event target during
1552 // mouse down then browser will not automatically trigger a click
1553 // event, so we will trigger one ourselves because we know that the
1554 // same logical Kinect button received a mouse down and mouse up.
1555 //
1556 // This condition can happen because of shrink and expand
1557 // animations for Kinect button hover and press.
1558 fakeClick = true;
1559 }
1560
1561 pressedElement = null;
1562 if (isMouseCaptured) {
1563 releaseButtonCapture(event);
1564 }
1565 $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
1566 if (!isMouseOver) {
1567 $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
1568 }
1569
1570 if (fakeClick) {
1571 $(element).click();
1572 }
1573
1574 event.stopPropagation();
1575 }
1576 }
1577 });
1578 }
1579
1580 //////////////////////////////////////////////////////////////
1581 // Public KinectUIAdapter properties
1582 this.handPointers = [];
1583
1584 //////////////////////////////////////////////////////////////
1585 // Public KinectUIAdapter methods
1586
1587 // Function called back when a sensor stream frame is ready to be processed.
1588 // .streamFrameHandler(streamFrame)
1589 //
1590 // streamFrame: stream frame ready to be processed
1591 //
1592 // Remarks
1593 // Processes interaction frames and ignores all other kinds of stream frames.
1594 this.streamFrameHandler = function (streamFrame) {
1595 if (typeof streamFrame != "object") {
1596 throw new Error("Frame must be an object");
1597 }
1598
1599 if (streamFrame == null) {
1600 // Ignore null frames
1601 return;
1602 }
1603
1604 var streamName = streamFrame.stream;
1605 switch (streamName) {
1606 case "interaction":
1607 beginInteractionFrame();
1608
1609 if (streamFrame.handPointers != null) {
1610 handleHandPointerData(streamFrame.handPointers);
1611 }
1612
1613 endInteractionFrame();
1614
1615 updatePublicHandPointers();
1616 break;
1617
1618 default:
1619 if (bindableStreamNames[streamName]) {
1620 // If this is one of the bindable stream names
1621 imageWorkerManager.processImageData(streamName, streamFrame.buffer, streamFrame.width, streamFrame.height);
1622 }
1623 break;
1624 }
1625 };
1626
1627 // Resets the hand pointer array to its initial state (zero hand pointers tracked).
1628 // .clearHandPointers()
1629 this.clearHandPointers = function () {
1630 if (!isInInteractionFrame) {
1631 // If we're not already processing an interaction frame, we fake
1632 // an empty frame so that all hand pointers get cleared out.
1633 beginInteractionFrame();
1634 endInteractionFrame();
1635 updatePublicHandPointers();
1636 } else {
1637 // If we're in the middle of processing an interaction frame, we
1638 // can't modify all of our data structures immediately, but need to
1639 // remember to do so.
1640 isClearRequestPending = true;
1641 }
1642 };
1643
1644 // Function called back when a sensor's interaction stream needs interaction
1645 // information in order to adjust a hand pointer position relative to UI.
1646 // .hitTestHandler(trackingId, handType, xPos, yPos)
1647 //
1648 // trackingId: The skeleton tracking ID for which interaction information is
1649 // being retrieved.
1650 // handType: "left" or "right" to represent hand type for which interaction
1651 // information is being retrieved.
1652 // xPos: X-coordinate of UI location for which interaction information is being
1653 // retrieved. 0.0 corresponds to left edge of interaction region and 1.0
1654 // corresponds to right edge of interaction region.
1655 // yPos: Y-coordinate of UI location for which interaction information is being
1656 // retrieved. 0.0 corresponds to top edge of interaction region and 1.0
1657 // corresponds to bottom edge of interaction region.
1658 this.hitTestHandler = function (trackingId, handType, xPos, yPos) {
1659 if (typeof trackingId != "number") {
1660 throw new Error("First parameter must be a number");
1661 }
1662 if (typeof handType != "string") {
1663 throw new Error("Second parameter must be a string");
1664 }
1665
1666 var interactionInfo = { "isPressTarget": false, "isGripTarget": false };
1667
1668 // First scale coordinates to be relative to full browser window
1669 var windowPos = interactionZoneToWindow({ "x": xPos, "y": yPos });
1670 var handPointerId = getHandPointerKey(trackingId, handType);
1671 var capturedElement = internalHandPointerData.hasOwnProperty(handPointerId) ? internalHandPointerData[handPointerId].handPointer.captured : null;
1672 var targetElement = (capturedElement != null) ? capturedElement : hitTest(windowPos.x, windowPos.y);
1673
1674 if (targetElement != null) {
1675 // Walk up the tree and try to find a grip target and/or a press target
1676 for (var searchElement = targetElement;
1677 searchElement != null && searchElement != document &&
1678 (!interactionInfo.isGripTarget || !interactionInfo.isPressTarget) ;
1679 searchElement = searchElement.parentNode) {
1680
1681 if (!interactionInfo.isPressTarget) {
1682 if (uiController.getIsPressTarget(searchElement)) {
1683 interactionInfo.isPressTarget = true;
1684 interactionInfo.pressTargetControlId = getElementId(searchElement);
1685
1686 // Get the press target point in client coordinates relative to search element
1687 var pressTargetPoint = interactionZoneToElement(getPressTargetPoint(searchElement), searchElement);
1688 var boundingRect = searchElement.getBoundingClientRect();
1689
1690 // Now adjust press target point coordinate to be relative to viewport
1691 pressTargetPoint.x += boundingRect.left;
1692 pressTargetPoint.y += boundingRect.top;
1693
1694 // Now scale press attraction point back to interaction zone coordinates
1695 var adjustedPoint = windowToInteractionZone(pressTargetPoint);
1696 interactionInfo.pressAttractionPointX = adjustedPoint.x;
1697 interactionInfo.pressAttractionPointY = adjustedPoint.y;
1698 }
1699 }
1700
1701 if (!interactionInfo.isGripTarget) {
1702 if (uiController.getIsGripTarget(searchElement)) {
1703 interactionInfo.isGripTarget = true;
1704 }
1705 }
1706 }
1707 }
1708
1709 return interactionInfo;
1710 };
1711
1712 // Adds an element that should be excluded from hit testing (both for press adjustment
1713 // and for UI message routing).
1714 // .addHitTestExclusion( element )
1715 //
1716 // element: Element to exlude from hit testing.
1717 this.addHitTestExclusion = function (element) {
1718 if (element == null) {
1719 console.log('Tried to add a hit test exclusion for a null element.');
1720 return;
1721 }
1722
1723 excludedElements.push(element);
1724 };
1725
1726 // Stops excluding an element from hit testing (both for press adjustment and for UI
1727 // message routing).
1728 // .removeHitTestExclusion( element )
1729 //
1730 // element: Element to stop exluding from hit testing.
1731 this.removeHitTestExclusion = function (element) {
1732 if (element == null) {
1733 console.log('Tried to remove a hit test exclusion for a null element.');
1734 return;
1735 }
1736
1737 var index = excludedElements.indexOf(element);
1738 if (index >= 0) {
1739 excludedElements.splice(index, 1);
1740 }
1741 };
1742
1743 // Bind the specified canvas element with the specified image stream
1744 // .bindStreamToCanvas( streamName, canvas )
1745 //
1746 // streamName: name of stream to bind to canvas element. Must be one of the supported
1747 // image stream names (e.g.: KinectUI.USERVIEWER_STREAM_NAME and
1748 // KinectUI.BACKGROUNDREMOVAL_STREAM_NAME)
1749 // canvas: Canvas to bind to user viewer stream
1750 //
1751 // Remarks
1752 // After binding a stream to a canvas, image data for that stream will
1753 // be rendered into the canvas whenever a new stream frame arrives.
1754 this.bindStreamToCanvas = function (streamName, canvas, width, height) {
1755 if (!bindableStreamNames[streamName]) {
1756 throw new Error("first parameter must be specified and must be one of the supported stream names");
1757 }
1758
1759 if (!(canvas instanceof HTMLCanvasElement)) {
1760 throw new Error("second parameter must be specified and must be a canvas element");
1761 }
1762
1763 this.unbindStreamFromCanvas(streamName);
1764
1765 imageWorkerManager.setImageData(streamName, canvas);
1766 };
1767
1768 // Unbind the specified image stream from previously bound canvas element, if any.
1769 // .unbindStreamFromCanvas(streamName)
1770 //
1771 // streamName: name of stream to unbind from its corresponding canvas element
1772 this.unbindStreamFromCanvas = function(streamName) {
1773 imageWorkerManager.setImageData(streamName, null);
1774 };
1775
1776 // Attach a new handler for a specified global UI event.
1777 // .on( eventName, handler(eventData) )] )
1778 //
1779 // eventName: Name of global event for which handler is being attached.
1780 // handler: Callback function to be executed when a global UI event of the specified
1781 // name occurs. E.g.: when set of hand pointers has been updated (eventName =
1782 // "handPointersUpdated").
1783 this.on = function (eventName, handler) {
1784 if (typeof (eventName) != "string") {
1785 throw new Error("first parameter must be specified and must be a string");
1786 }
1787 if (typeof (handler) != "function") {
1788 throw new Error("second parameter must be specified and must be a function");
1789 }
1790
1791 var handlerArray;
1792 if (eventHandlers.hasOwnProperty(eventName)) {
1793 handlerArray = eventHandlers[eventName];
1794 } else {
1795 handlerArray = [];
1796 eventHandlers[eventName] = handlerArray;
1797 }
1798
1799 handlerArray.push(handler);
1800 };
1801
1802 // Removes one (or all) global UI event handler(s) for specified global UI event.
1803 // .removeEventHandler( eventName, [handler(eventData)] )] )
1804 //
1805 // eventName: Name of global event for which handler is being removed.
1806 // handler: Global UI event handler callback function to be removed.
1807 // If omitted, all event handlers for specified event are removed.
1808 this.off = function (eventName, handler) {
1809 if (typeof (handler) != "string") {
1810 throw new Error("first parameter must be specified and must be a string");
1811 }
1812
1813 var removeAll = false;
1814 switch (typeof (handler)) {
1815 case "undefined":
1816 removeAll = true;
1817 break;
1818 case "function":
1819 break;
1820
1821 default:
1822 throw new Error("second parameter must either be a function or left unspecified");
1823 }
1824
1825 if (!eventHandlers.hasOwnProperty(eventName)) {
1826 // If there are no event handlers associated with event, just return
1827 return;
1828 }
1829
1830 if (removeAll) {
1831 eventHandlers[eventName] = [];
1832 } else {
1833 var index = eventHandlers[eventName].indexOf(handler);
1834 if (index >= 0) {
1835 eventHandlers[eventName].slice(index, index + 1);
1836 }
1837 }
1838 };
1839
1840 // Configures the specified DOM element array to act as a Kinect UI button
1841 // .promoteButtons([elements])
1842 //
1843 // elements: DOM element array or HTMLCollection where each element will be configured
1844 // to act as a Kinect UI button.
1845 // If left unspecified, all document elements with class equal to
1846 // KinectUI.BUTTON_CLASS will be promoted to buttons.
1847 this.promoteButtons = function (elements) {
1848 var type = typeof elements;
1849 var error = new Error("first parameter must be left unspecified or be an array equivalent");
1850 switch (type) {
1851 case "undefined":
1852 elements = document.getElementsByClassName(uiController.BUTTON_CLASS);
1853 break;
1854 case "object":
1855 if (elements == null) {
1856 // If null, treat as if an empty array was specified
1857 return;
1858 }
1859
1860 if (!("length" in elements)) {
1861 throw error;
1862 }
1863 break;
1864
1865 default:
1866 throw error;
1867 }
1868
1869 for (var i = 0; i < elements.length; ++i) {
1870 var element = elements[i];
1871 if ((typeof element != "object") || (element.nodeType != Node.ELEMENT_NODE)) {
1872 throw new Error("each array element must be a DOM element");
1873 }
1874
1875 promoteToButton(elements[i]);
1876 }
1877 };
1878
1879 // Creates the default cursor UI elements with associated behavior
1880 // .createDefaultCursor()
1881 //
1882 // Returns an object with the following properties/functions:
1883 // - element: property referencing the DOM element that represents the cursor
1884 // - hide(): function used to hide the cursor
1885 // - show(): function used to show the cursor
1886 this.createDefaultCursor = function() {
1887 // First check if cursor element has already been created
1888 var cursorElement = document.getElementById(uiController.CURSOR_ID);
1889 if (cursorElement != null) {
1890 return jQuery.data(cursorElement, uiController.CURSOR_ID);
1891 }
1892
1893 var showCursor = true;
1894 cursorElement = document.createElement("div");
1895 cursorElement.id = uiController.CURSOR_ID;
1896 cursorElement.innerHTML = '\
1897 <svg viewBox="0 0 250 250">\
1898 <defs>\
1899 <radialGradient id="kinect-pressing-gradient" r="150%">\
1900 <stop stop-color="#663085" offset="0.0%"></stop>\
1901 <stop stop-color="white" offset="100.0%" ></stop>\
1902 </radialGradient>\
1903 <radialGradient id="kinect-extended-gradient">\
1904 <stop stop-color="#01b3ff" offset="0.0%"></stop>\
1905 <stop stop-color="#04e5ff" offset="98.1%" ></stop>\
1906 <stop stop-color="#04e5ff" offset="99.2%" ></stop>\
1907 <stop stop-color="#04e5ff" offset="100.0%" ></stop>\
1908 </radialGradient>\
1909 <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>\
1910 <clipPath id="kinect-cursor-press-clip">\
1911 <circle id="kinect-cursor-press-clip-circle" cx="125.0" cy="250.0" r="0.0"></circle>\
1912 </clipPath>\
1913 </defs>\
1914 <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>\
1915 <use id="kinect-cursor-normal" xlink:href="#kinect-open-hand-base" fill="white" stroke="black"></use>\
1916 <g id="kinect-cursor-progress" clip-path="url(#kinect-cursor-press-clip)">\
1917 <use xlink:href="#kinect-open-hand-base" fill="url(#kinect-pressing-gradient)" stroke="none"></use>\
1918 <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>\
1919 <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>\
1920 <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>\
1921 <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>\
1922 <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>\
1923 <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>\
1924 <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>\
1925 <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>\
1926 <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>\
1927 <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>\
1928 <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>\
1929 <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>\
1930 <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>\
1931 <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>\
1932 <use xlink:href="#kinect-open-hand-base" fill="none" stroke="black"></use>\
1933 </g>\
1934 <use id="kinect-cursor-extended" xlink:href="#kinect-open-hand-base" fill="url(#kinect-extended-gradient)" stroke="black"></use>\
1935 </svg>';
1936 document.body.appendChild(cursorElement);
1937
1938 var cursorData = {
1939 "element": cursorElement,
1940 "show": function () {
1941 // Cursor will be shown next time we get a valid hand pointer
1942 showCursor = true;
1943 },
1944 "hide": function () {
1945 // Hide cursor immediately
1946 showCursor = false;
1947 $(cursorElement).hide();
1948 }
1949 };
1950 jQuery.data(cursorElement, uiController.CURSOR_ID, cursorData);
1951
1952 var wasHovering = false;
1953 var wasPressing = false;
1954 uiAdapter.addHitTestExclusion(cursorElement);
1955
1956 uiAdapter.on(uiController.HANDPOINTERS_UPDATED, function(handPointers) {
1957 var MAXIMUM_CURSOR_SCALE = 1.0;
1958 var MINIMUM_CURSOR_SCALE = 0.8;
1959 var MINIMUM_PROGRESS_CLIP_RADIUS = 8.0;
1960 var RANGE_PROGRESS_CLIP_RADIUS = 240.0;
1961
1962 // If cursor is not supposed to be showing, bail out immediately
1963 if (!showCursor) {
1964 return;
1965 }
1966
1967 var handPointer = null;
1968
1969 for (var iPointer = 0; iPointer < handPointers.length; ++iPointer) {
1970 var curPointer = handPointers[iPointer];
1971
1972 if (curPointer.getIsPrimaryHandOfPrimaryUser()) {
1973 handPointer = curPointer;
1974 break;
1975 }
1976 }
1977
1978 if (cursorElement == null) {
1979 return;
1980 }
1981
1982 function clamp(value, min, max) {
1983 return Math.max(min, Math.min(max, value));
1984 }
1985
1986 if (handPointer != null) {
1987 var isOpen = !handPointer.isInGripInteraction;
1988
1989 // Get information about what this hand pointer is over
1990 var isHovering = false;
1991 var isOverPressTarget = false;
1992 var enteredElements = handPointer.getEnteredElements();
1993 for (var iEnteredElement = 0; iEnteredElement < enteredElements.length; ++iEnteredElement) {
1994 var enteredElement = enteredElements[iEnteredElement];
1995 if (KinectUI.getIsPressTarget(enteredElement)) {
1996 isHovering = true;
1997 isOverPressTarget = true;
1998 break;
1999 }
2000
2001 if (KinectUI.getIsGripTarget(enteredElement)) {
2002 isHovering = true;
2003 }
2004 }
2005
2006 var isPressing = isOverPressTarget && handPointer.isPressed && !handPointer.isInGripInteraction;
2007
2008 if (isHovering != wasHovering) {
2009 if (isHovering) {
2010 $(cursorElement).addClass("cursor-hover");
2011 } else {
2012 $(cursorElement).removeClass("cursor-hover");
2013 }
2014 wasHovering = isHovering;
2015 }
2016
2017 if (isPressing != wasPressing) {
2018 if (isPressing) {
2019 $(cursorElement).addClass("cursor-pressed");
2020 } else {
2021 $(cursorElement).removeClass("cursor-pressed");
2022 }
2023 wasPressing = isPressing;
2024 }
2025
2026 var artworkWidth = $(cursorElement).width();
2027 var artworkHeight = $(cursorElement).height();
2028 var adjustedPressExtent = isOverPressTarget ? handPointer.pressExtent : 0.0;
2029
2030 document.getElementById("kinect-cursor-press-clip-circle").r.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, MINIMUM_PROGRESS_CLIP_RADIUS + (RANGE_PROGRESS_CLIP_RADIUS * adjustedPressExtent));
2031
2032 var scale = (1.0 - (adjustedPressExtent * ((MAXIMUM_CURSOR_SCALE - MINIMUM_CURSOR_SCALE) / 2.0)));
2033 var xScale = (handPointer.handType == "Left") ? -scale : scale;
2034 var yScale = scale;
2035 $(cursorElement).css("transform", "scale(" + xScale + "," + yScale + ")");
2036
2037 var xPos = clamp(handPointer.x, 0, window.innerWidth);
2038 var yPos = clamp(handPointer.y, 0, window.innerHeight);
2039 var isClamped = (xPos != handPointer.x) || (yPos != handPointer.y);
2040 var xDelta = (artworkWidth / 2) * scale;
2041 var yDelta = (artworkHeight / 2) * scale;
2042 var opacity = !isClamped ? 1.0 : 0.3;
2043
2044 $(cursorElement).show();
2045 $(cursorElement).offset({ left: xPos - xDelta, top: yPos - yDelta });
2046 $(cursorElement).css("opacity", opacity);
2047 } else {
2048 $(cursorElement).hide();
2049 }
2050 });
2051
2052 return cursorData;
2053 };
2054
2055 // Enable triggering of UI events as interaction frames are received.
2056 // .enableEvents()
2057 //
2058 // Remarks
2059 // This and .disableEvents() function serve as a global on/off switch
2060 // so that clients don't have to be registering and unregistering all
2061 // event handlers as they enter/exit a state where they don't want or
2062 // need to process Kinect UI events.
2063 // Events are enabled by default.
2064 this.enableEvents = function() {
2065 handPointerEventsEnabled = true;
2066 };
2067
2068 // Disable triggering of UI events as interaction frames are received.
2069 // .disableEvents()
2070 //
2071 // Remarks
2072 // This and .enableEvents() function serve as a global on/off switch
2073 // so that clients don't have to be registering and unregistering all
2074 // event handlers as they enter/exit a state where they don't want or
2075 // need to process Kinect UI events.
2076 // Events are enabled by default.
2077 this.disableEvents = function () {
2078 handPointerEventsEnabled = false;
2079 };
2080 }
2081
2082 //////////////////////////////////////////////////////////////
2083 // KinectUIController object constructor
2084 function KinectUIController() {
2085
2086 //////////////////////////////////////////////////////////////
2087 // Public KinectUIController methods
2088
2089 // Creates a new Kinect UI adapter object optionally associated with a Kinect sensor.
2090 // .createAdapter( [sensor] )
2091 //
2092 // sensor: sensor object associated with the new UI adapter
2093 this.createAdapter = function (sensor) {
2094 if (typeof sensor != 'object') {
2095 throw new Error("first parameter must be either a sensor object or left unspecified");
2096 }
2097
2098 var uiAdapter = new KinectUIAdapter();
2099
2100 if (sensor != null) {
2101 sensor.addStreamFrameHandler(uiAdapter.streamFrameHandler);
2102 sensor.setHitTestHandler(uiAdapter.hitTestHandler);
2103 }
2104
2105 return uiAdapter;
2106 };
2107
2108 // Determines whether the specified element has been designated as a press target
2109 // .getIsPressTarget( element )
2110 //
2111 // element: DOM element for which we're querying press target status.
2112 this.getIsPressTarget = function (element) {
2113 return jQuery.data(element, "isPressTarget") ? true : false;
2114 };
2115
2116 // Specifies whether the specified element should be designated as a press target
2117 // .setIsPressTarget( element, isPressTarget )
2118 //
2119 // element: DOM element for which we're specifying press target status.
2120 // isPressTarget: true if element should be designated as a press target.
2121 // false otherwise.
2122 this.setIsPressTarget = function (element, isPressTarget) {
2123 jQuery.data(element, "isPressTarget", isPressTarget);
2124 };
2125
2126 // Determines whether the specified element has been designated as a grip target
2127 // .getIsPressTarget( element )
2128 //
2129 // element: DOM element for which we're querying grip target status.
2130 this.getIsGripTarget = function (element) {
2131 return jQuery.data(element, "isGripTarget") ? true : false;
2132 };
2133
2134 // Specifies whether the specified element should be designated as a grip target
2135 // .setIsGripTarget( element, isGripTarget )
2136 //
2137 // element: DOM element for which we're specifying grip target status.
2138 // isGripTarget: true if element should be designated as a grip target.
2139 // false otherwise.
2140 this.setIsGripTarget = function (element, isGripTarget) {
2141 jQuery.data(element, "isGripTarget", isGripTarget);
2142 };
2143
2144 // Determines whether the primary hand pointer is over the specified element
2145 // .getIsPrimaryHandPointerOver( element )
2146 //
2147 // element: DOM element for which we're querying if primary hand pointer is over it.
2148 this.getIsPrimaryHandPointerOver = function (element) {
2149 return jQuery.data(element, "isPrimaryHandPointerOver") ? true : false;
2150 };
2151
2152 // Specifies whether the primary hand pointer is over the specified element
2153 // .setIsPrimaryHandPointerOver( element )
2154 //
2155 // element: DOM element for which we're specifying if primary hand pointer is over it.
2156 // over: true if primary hand pointer is over the specified element. false otherwise.
2157 this.setIsPrimaryHandPointerOver = function (element, over) {
2158 jQuery.data(element, "isPrimaryHandPointerOver", over);
2159 };
2160
2161 //////////////////////////////////////////////////////////////
2162 // Public KinectUIController properties
2163
2164 // Element event names
2165 this.HANDPOINTER_MOVE = "handPointerMove";
2166 this.HANDPOINTER_ENTER = "handPointerEnter";
2167 this.HANDPOINTER_LEAVE = "handPointerLeave";
2168 this.HANDPOINTER_PRESS = "handPointerPress";
2169 this.HANDPOINTER_PRESSRELEASE = "handPointerPressRelease";
2170 this.HANDPOINTER_GRIP = "handPointerGrip";
2171 this.HANDPOINTER_GRIPRELEASE = "handPointerGripRelease";
2172 this.HANDPOINTER_GOTCAPTURE = "handPointerGotCapture";
2173 this.HANDPOINTER_LOSTCAPTURE = "handPointerLostCapture";
2174 this.QUERY_INTERACTION_STATUS = "queryInteractionStatus";
2175
2176 // Global UI event names
2177 this.HANDPOINTERS_UPDATED = "handPointersUpdated";
2178
2179 // Global UI id/class names
2180 this.BUTTON_CLASS = "kinect-button";
2181 this.BUTTON_TEXT_CLASS = "kinect-button-text";
2182 this.BUTTON_HOVER_CLASS = "button-hover";
2183 this.BUTTON_PRESSED_CLASS = "button-pressed";
2184
2185 this.CURSOR_ID = "kinect-cursor";
2186 };
2187
2188 //////////////////////////////////////////////////////////////
2189 // Private global properties common to all KinectUI objects
2190
2191 // Get the path common to all script files
2192 var scriptRootURIPath = (function () {
2193 var rootPath = "";
2194
2195 try {
2196 var scriptFileName = document.scripts[document.scripts.length - 1].src;
2197 var pathSeparatorIndex = scriptFileName.lastIndexOf("/");
2198
2199 if (pathSeparatorIndex >= 0) {
2200 // If pathSeparator is found, return path up to and including separator
2201 rootPath = scriptFileName.substring(0, pathSeparatorIndex + 1);
2202 }
2203 } catch(error) {
2204 }
2205
2206 return rootPath;
2207 })();
2208
2209 // The global Kinect UI controller object is a singleton instance of our internal KinectUIController type
2210 var uiController = new KinectUIController();
2211
2212 // Prefix for element IDs assigned by Kinect UI layer for purposes of hit test detection
2213 var kinectIdPrefix = "kinectId";
2214
2215 // Id in sequence to assign to controls that don't already have an assigned DOM element id
2216 var lastAssignedControlId = 0;
2217
2218 // return singleton
2219 return uiController;
2220})();
2221
2222// Guarantee that Kinect and KinectUI objects are available from the global scope even if
2223// this file was loaded on-demand, in a restricted functional scope, a long time after web
2224// document was initially loaded.
2225window.Kinect = Kinect;
2226window.KinectUI = KinectUI;
Note: See TracBrowser for help on using the repository browser.