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
|
---|
10 | var 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
|
---|
680 | var 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.
|
---|
2227 | window.Kinect = Kinect;
|
---|
2228 | window.KinectUI = KinectUI; |
---|