Accepts a THREE.scene. If undefined, a default scene will be created with some defaults (transparent canvas, three lights, mm units),
79 | and rendered on every animation frame. If null, the plugin will wait for the scene to be set explicitly at a future point.
80 |
81 |
With the default scene, the renderer, camera, and scene will all be made available on the plugin scope. E.g., `myController.plugins.boneHand.scene`
82 |
83 |
84 |
85 |
targetEl
86 |
undefined
87 |
Accepts an element, such as `document.body`.
88 |
89 |
90 |
boneScale
91 |
1 / 6
92 |
The radius of the bones relative to the length of the proximal bone on the middle finger
93 |
94 |
95 |
jointScale
96 |
1 / 5
97 |
The radius of the bones relative to the length of the proximal bone on the middle finger
98 |
99 |
100 |
boneColor
101 |
(new THREE.Color).setHex(0xffffff)
102 |
103 |
104 |
105 |
jointColor
106 |
(new THREE.Color).setHex(0x5daa00)
107 |
108 |
109 |
110 |
111 |
Addendum
112 |
113 |
114 | THREE.js shadows are fully supported.
115 |
116 |
117 |
If using the default scene, simply set yourMesh.receivesShadow = true, as
118 | demonstrated in the source of this page.
119 |
If working with your own scene, be sure to have your renderer.shadowMapEnabled = true
120 | and then, if you choose, call myController.plugins.boneHand.addShadowCamera().
121 |
122 |
123 |
124 | See the source for details. scope.light.shadowCameraVisible is a nice tool to know about.
125 |
On the four corners, clockwise: name, max, min, and current values.
89 |
At its core:
plotter.plot('height', hand.palmPosition[1]);
90 |
91 |
92 |
93 |
94 |
95 |
Live Example
96 |
97 | (insert hand)
98 |
99 |
100 |
101 |
102 |
Screenshot
103 |
104 |
105 |
106 |
107 |
108 |
Usage:
109 |
110 |
111 |
112 |
113 | // call this once per page, after the DOM is ready.
114 | window.plotter = new LeapDataPlotter();
115 |
116 |
117 | Leap.loop(function(frame){
118 | var hand = frame.hands[0];
119 | if (!hand) return;
120 |
121 |
122 | // call this once per frame per plot
123 | plotter.plot('height', hand.palmPosition[1], {
124 | precision: 3,
125 | units: 'mm'
126 | });
127 |
128 |
129 | plotter.plot('y velocity', hand.palmVelocity[1], {
130 | precision: 4,
131 | units: 'mm/s'
132 | });
133 |
134 |
135 | // call this once per frame
136 | plotter.update()
137 |
138 | });
139 |
140 |
141 |
142 |
143 |
144 |
145 |
LeapDataPlotter:
146 |
147 |
148 | Calling plot will add a canvas to the page. The canvas will simply be appended to the body of `document.body`,
149 | with a classname of `leap-data-plotter`. Alternatively, you can pass in your own canvas element:
150 |
171 | The name of the graph, persistent between frames
172 | - The plot automatically resizes based upon input data
173 |
174 |
175 |
176 |
data
177 |
178 | The datapoint to be added to the plot
179 |
180 |
181 |
182 |
opts
183 |
184 |
185 |
186 |
precision
187 |
decimal places to display in outputs [default: 5]
188 |
189 |
190 |
units
191 |
String suffix to print after the numeric output [default: '']
192 |
193 |
194 |
length
195 |
The number of data points which fit on to a graph. [default: 600]
196 |
197 |
198 |
width
199 |
[default: canvas width]
200 |
201 |
202 |
height
203 |
[default: 50]
204 |
205 |
206 |
color
207 |
Color of plotted line. Hex string. [default: auto]
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
LeapDataPlotter#update()
216 |
217 |
218 | Redraws the canvas, updating the plot. Nothing will be visible without this being called.
219 |
220 |
221 |
222 |
Authors
223 |
224 |
225 | Thanks to @nashira for authoring this library.
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/main/transform/leap.transform.js:
--------------------------------------------------------------------------------
1 | //CoffeeScript generated from main/transform/leap.transform.coffee
2 | (function() {
3 | Leap.plugin('transform', function(scope) {
4 | var noop, transformDirections, transformMat4Implicit0, transformPositions, transformWithMatrices, _directionTransform;
5 | if (scope == null) {
6 | scope = {};
7 | }
8 | noop = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
9 | _directionTransform = new THREE.Matrix4;
10 | if (scope.vr === true) {
11 | this.setOptimizeHMD(true);
12 | scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix((new THREE.Matrix4).set(-1, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1));
13 | scope.scale = 0.001;
14 | scope.position = new THREE.Vector3(0, 0, -0.08);
15 | }
16 | if (scope.vr === 'desktop') {
17 | scope.scale = 0.001;
18 | }
19 | scope.getTransform = function(hand) {
20 | var matrix;
21 | if (scope.matrix) {
22 | matrix = typeof scope.matrix === 'function' ? scope.matrix(hand) : scope.matrix;
23 | if (window['THREE'] && matrix instanceof THREE.Matrix4) {
24 | return matrix.elements;
25 | } else {
26 | return matrix;
27 | }
28 | } else if (scope.position || scope.quaternion || scope.scale) {
29 | _directionTransform.set.apply(_directionTransform, noop);
30 | if (scope.quaternion) {
31 | _directionTransform.makeRotationFromQuaternion(typeof scope.quaternion === 'function' ? scope.quaternion(hand) : scope.quaternion);
32 | }
33 | if (scope.position) {
34 | _directionTransform.setPosition(typeof scope.position === 'function' ? scope.position(hand) : scope.position);
35 | }
36 | return _directionTransform.elements;
37 | } else {
38 | return noop;
39 | }
40 | };
41 | scope.getScale = function(hand) {
42 | if (!isNaN(scope.scale)) {
43 | scope.scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale);
44 | }
45 | if (typeof scope.scale === 'function') {
46 | return scope.scale(hand);
47 | } else {
48 | return scope.scale;
49 | }
50 | };
51 | transformPositions = function(matrix, vec3s) {
52 | var vec3, _i, _len, _results;
53 | _results = [];
54 | for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
55 | vec3 = vec3s[_i];
56 | if (vec3) {
57 | _results.push(Leap.vec3.transformMat4(vec3, vec3, matrix));
58 | } else {
59 | _results.push(void 0);
60 | }
61 | }
62 | return _results;
63 | };
64 | transformMat4Implicit0 = function(out, a, m) {
65 | var x, y, z;
66 | x = a[0];
67 | y = a[1];
68 | z = a[2];
69 | out[0] = m[0] * x + m[4] * y + m[8] * z;
70 | out[1] = m[1] * x + m[5] * y + m[9] * z;
71 | out[2] = m[2] * x + m[6] * y + m[10] * z;
72 | return out;
73 | };
74 | transformDirections = function(matrix, vec3s) {
75 | var vec3, _i, _len, _results;
76 | _results = [];
77 | for (_i = 0, _len = vec3s.length; _i < _len; _i++) {
78 | vec3 = vec3s[_i];
79 | if (vec3) {
80 | _results.push(transformMat4Implicit0(vec3, vec3, matrix));
81 | } else {
82 | _results.push(void 0);
83 | }
84 | }
85 | return _results;
86 | };
87 | transformWithMatrices = function(hand, transform, scale) {
88 | var finger, scalarScale, _i, _j, _len, _len1, _ref, _ref1;
89 | transformDirections(transform, [hand.direction, hand.palmNormal, hand.palmVelocity, hand.arm.basis[0], hand.arm.basis[1], hand.arm.basis[2]]);
90 | _ref = hand.fingers;
91 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
92 | finger = _ref[_i];
93 | transformDirections(transform, [finger.direction, finger.metacarpal.basis[0], finger.metacarpal.basis[1], finger.metacarpal.basis[2], finger.proximal.basis[0], finger.proximal.basis[1], finger.proximal.basis[2], finger.medial.basis[0], finger.medial.basis[1], finger.medial.basis[2], finger.distal.basis[0], finger.distal.basis[1], finger.distal.basis[2]]);
94 | }
95 | Leap.glMatrix.mat4.scale(transform, transform, scale);
96 | transformPositions(transform, [hand.palmPosition, hand.stabilizedPalmPosition, hand.sphereCenter, hand.arm.nextJoint, hand.arm.prevJoint]);
97 | _ref1 = hand.fingers;
98 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
99 | finger = _ref1[_j];
100 | transformPositions(transform, [finger.carpPosition, finger.mcpPosition, finger.pipPosition, finger.dipPosition, finger.distal.nextJoint, finger.tipPosition]);
101 | }
102 | scalarScale = (scale[0] + scale[1] + scale[2]) / 3;
103 | return hand.arm.width *= scalarScale;
104 | };
105 | return {
106 | frame: function(frame) {
107 | var finger, hand, len, _i, _j, _len, _len1, _ref, _ref1, _results;
108 | if (!frame.valid || frame.data.transformed) {
109 | return;
110 | }
111 | frame.data.transformed = true;
112 | _ref = frame.hands;
113 | _results = [];
114 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
115 | hand = _ref[_i];
116 | transformWithMatrices(hand, scope.getTransform(hand), (scope.getScale(hand) || new THREE.Vector3(1, 1, 1)).toArray());
117 | if (scope.effectiveParent) {
118 | transformWithMatrices(hand, scope.effectiveParent.matrixWorld.elements, scope.effectiveParent.scale.toArray());
119 | }
120 | len = null;
121 | _ref1 = hand.fingers;
122 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
123 | finger = _ref1[_j];
124 | len = Leap.vec3.create();
125 | Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition);
126 | finger.metacarpal.length = Leap.vec3.length(len);
127 | Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition);
128 | finger.proximal.length = Leap.vec3.length(len);
129 | Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition);
130 | finger.medial.length = Leap.vec3.length(len);
131 | Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition);
132 | finger.distal.length = Leap.vec3.length(len);
133 | }
134 | Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint);
135 | _results.push(hand.arm.length = Leap.vec3.length(len));
136 | }
137 | return _results;
138 | }
139 | };
140 | });
141 |
142 | }).call(this);
143 |
--------------------------------------------------------------------------------
/main/transform/leap.transform.coffee:
--------------------------------------------------------------------------------
1 | # Allows arbitrary transforms to be easily applied to hands in the Leap Frame
2 | # This requires THREE.js
3 |
4 | # configuration:
5 | # if transform is set, all other properties will be ignored
6 | # transform: a THREE.Matrix4 directly. This can be either an array of 16-length, or a THREE.matrix4
7 | # quaternion: a THREE.Quaternion
8 | # position: a THREE.Vector3
9 | # scale: a THREE.Vector3 or a number.
10 |
11 |
12 | Leap.plugin 'transform', (scope = {})->
13 | noop = [
14 | 1, 0, 0, 0,
15 | 0, 1, 0, 0,
16 | 0, 0, 1, 0,
17 | 0, 0, 0, 1
18 | ]
19 | _directionTransform = new THREE.Matrix4
20 |
21 | if scope.vr == true
22 |
23 | this.setOptimizeHMD(true)
24 |
25 | # This matrix flips the x, y, and z axis.
26 | scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix(
27 | (new THREE.Matrix4).set(
28 | -1, 0, 0, 0,
29 | 0, 0, -1, 0,
30 | 0, -1, 0, 0,
31 | 0, 0, 0, 1
32 | )
33 | )
34 |
35 | # Scales to meters.
36 | scope.scale = 0.001
37 |
38 | scope.position = new THREE.Vector3(0,0,-0.08)
39 |
40 | if scope.vr == 'desktop'
41 | scope.scale = 0.001
42 |
43 | # no scale
44 | scope.getTransform = (hand)->
45 | if scope.matrix
46 | matrix = if typeof scope.matrix == 'function' then scope.matrix(hand) else scope.matrix
47 |
48 | if window['THREE'] && matrix instanceof THREE.Matrix4
49 | return matrix.elements
50 | else
51 | return matrix
52 |
53 | else if scope.position || scope.quaternion || scope.scale
54 | _directionTransform.set.apply(_directionTransform, noop)
55 |
56 | if scope.quaternion
57 | _directionTransform.makeRotationFromQuaternion(
58 | if typeof scope.quaternion == 'function' then scope.quaternion(hand) else scope.quaternion
59 | )
60 |
61 | if scope.position
62 | _directionTransform.setPosition(
63 | if typeof scope.position == 'function' then scope.position(hand) else scope.position
64 | )
65 |
66 | return _directionTransform.elements
67 |
68 | else
69 | return noop
70 |
71 |
72 | scope.getScale = (hand)->
73 | if !isNaN(scope.scale)
74 | scope.scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale)
75 |
76 | return if typeof scope.scale == 'function' then scope.scale(hand) else scope.scale
77 |
78 |
79 | # implicitly appends 1 to the vec3s, applying both translation and rotation
80 | transformPositions = (matrix, vec3s)->
81 | for vec3 in vec3s
82 | if vec3 # some recordings may not have all fields
83 | Leap.vec3.transformMat4(vec3, vec3, matrix)
84 |
85 | transformMat4Implicit0 = (out, a, m) ->
86 | x = a[0]
87 | y = a[1]
88 | z = a[2]
89 |
90 | out[0] = m[0] * x + m[4] * y + m[8] * z
91 | out[1] = m[1] * x + m[5] * y + m[9] * z
92 | out[2] = m[2] * x + m[6] * y + m[10] * z
93 | return out
94 |
95 | # appends 0 to the vec3s, applying only rotation
96 | transformDirections = (matrix, vec3s)->
97 | for vec3 in vec3s
98 | if vec3 # some recordings may not have all fields
99 | transformMat4Implicit0(vec3, vec3, matrix)
100 |
101 | # expects a hand, an array mat4 and an array scale.
102 | transformWithMatrices = (hand, transform, scale) ->
103 | transformDirections(
104 | transform,
105 | [
106 | hand.direction,
107 | hand.palmNormal,
108 | hand.palmVelocity,
109 | hand.arm.basis[0],
110 | hand.arm.basis[1],
111 | hand.arm.basis[2],
112 | ]
113 | )
114 |
115 | for finger in hand.fingers
116 | transformDirections(
117 | transform,
118 | [
119 | finger.direction,
120 | finger.metacarpal.basis[0],
121 | finger.metacarpal.basis[1],
122 | finger.metacarpal.basis[2],
123 | finger.proximal.basis[0],
124 | finger.proximal.basis[1],
125 | finger.proximal.basis[2],
126 | finger.medial.basis[0],
127 | finger.medial.basis[1],
128 | finger.medial.basis[2],
129 | finger.distal.basis[0],
130 | finger.distal.basis[1],
131 | finger.distal.basis[2]
132 | ]
133 | )
134 |
135 | Leap.glMatrix.mat4.scale(transform, transform, scale)
136 |
137 | transformPositions(
138 | transform,
139 | [
140 | hand.palmPosition,
141 | hand.stabilizedPalmPosition,
142 | hand.sphereCenter,
143 | hand.arm.nextJoint,
144 | hand.arm.prevJoint
145 | ]
146 | )
147 |
148 | for finger in hand.fingers
149 | transformPositions(
150 | transform,
151 | [
152 | finger.carpPosition,
153 | finger.mcpPosition,
154 | finger.pipPosition,
155 | finger.dipPosition,
156 | finger.distal.nextJoint,
157 | finger.tipPosition
158 | ]
159 | )
160 |
161 | scalarScale = ( scale[0] + scale[1] + scale[2] ) / 3;
162 | hand.arm.width *= scalarScale
163 |
164 | # todo - expose function to transform a frame
165 | {
166 | frame: (frame)->
167 |
168 | return if !frame.valid || frame.data.transformed
169 |
170 | frame.data.transformed = true
171 |
172 | for hand in frame.hands
173 |
174 | transformWithMatrices(hand, scope.getTransform(hand), (scope.getScale(hand) || new THREE.Vector3(1,1,1)).toArray() )
175 |
176 | if scope.effectiveParent
177 | # as long as parent doesn't have scale, we're good.
178 | # could refactor to extract scale from mat4 and do the two separately
179 | # e.g., decompose in to pos/rot/scale, recompose from pos/rot/defaultScale
180 | transformWithMatrices( hand, scope.effectiveParent.matrixWorld.elements, scope.effectiveParent.scale.toArray() )
181 |
182 | len = null
183 | for finger in hand.fingers
184 | # recalculate lengths
185 | len = Leap.vec3.create()
186 | Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition)
187 | finger.metacarpal.length = Leap.vec3.length(len)
188 |
189 | Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition)
190 | finger.proximal.length = Leap.vec3.length(len)
191 |
192 | Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition)
193 | finger.medial.length = Leap.vec3.length(len)
194 |
195 | Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition)
196 | finger.distal.length = Leap.vec3.length(len)
197 |
198 | Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint)
199 | hand.arm.length = Leap.vec3.length(len)
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | Leap Motion Individual Contributor License Agreement, v1.0
2 |
3 | In order to clarify the intellectual property license granted with
4 | Contributions from any person or entity, Leap Motion, Inc. ("Leap
5 | Motion") must have a Contributor License Agreement ("CLA") on file that
6 | has been signed by each Contributor, indicating agreement to the license
7 | terms below. This license is for your protection as a Contributor as
8 | well as the protection of Leap Motion; it does not change your rights to
9 | use your own Contributions for any other purpose.
10 |
11 | If you wish to submit a pull request, please read the terms of this agreement
12 | and indicate your acceptance of it by adding your name, email, and github account name
13 | to the bottom of this file. Pull requests submitted without accepting this agreement
14 | will not be reviewed.
15 |
16 | By adding your information to this file, you accept and agree to the following
17 | terms and conditions for Your present and future Contributions submitted to
18 | Leap Motion. Except for the license granted herein to Leap Motion and recipients of
19 | software distributed by Leap Motion, You reserve all right, title, and interest
20 | in and to Your Contributions.
21 |
22 | 1. Definitions.
23 |
24 | "You" (or "Your") shall mean the copyright owner or legal entity
25 | authorized by the copyright owner that is making this Agreement with
26 | Leap Motion.
27 |
28 | For legal entities, the entity making a Contribution and all other
29 | entities that control, are controlled by, or are under common control
30 | with that entity are considered to be a single Contributor.
31 |
32 | For the purposes of this definition, "control" means (i) the power,
33 | direct or indirect, to cause the direction or management of such entity,
34 | whether by contract or otherwise, or (ii) ownership of fifty percent
35 | (50%) or more of the outstanding shares, or (iii) beneficial ownership
36 | of such entity.
37 |
38 | “Contribution" shall mean any original work of authorship, including
39 | any modifications or additions to an existing work, that is
40 | intentionally submitted by You to Leap Motion for inclusion in, or
41 | documentation of, any of the products owned or managed by Leap Motion
42 | (the "Work"). For the purposes of this definition, "submitted" means any
43 | form of electronic, verbal, or written communication sent to Leap Motion
44 | or its representatives, including but not limited to communication on
45 | electronic mailing lists, source code control systems, and issue
46 | tracking systems that are managed by, or on behalf of, Leap Motion for
47 | the purpose of discussing and improving the Work, but excluding
48 | communication that is conspicuously marked or otherwise designated in
49 | writing by You as "Not a Contribution."
50 |
51 | 2. Grant of Copyright License.
52 |
53 | Subject to the terms and conditions of this Agreement, You hereby grant
54 | to Leap Motion and to recipients of software distributed by Leap Motion
55 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
56 | irrevocable copyright license to reproduce, prepare derivative works of,
57 | publicly display, publicly perform, sublicense, and distribute Your
58 | Contributions and such derivative works.
59 |
60 | 3. Grant of Patent License.
61 |
62 | Subject to the terms and conditions of this Agreement, You hereby grant
63 | to Leap Motion and to recipients of software distributed by Leap Motion
64 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable (except as stated in this section) patent license to make,
66 | have made, use, offer to sell, import, and otherwise transfer the Work,
67 | where such license applies only to those patent claims licensable by You
68 | that are necessarily infringed by Your Contribution(s) alone or by
69 | combination of Your Contribution(s) with the Work to which such
70 | Contribution(s) was submitted. If any entity institutes patent
71 | litigation against You or any other entity (including a cross-claim or
72 | counterclaim in a lawsuit) alleging that your Contribution, or the Work
73 | to which you have contributed, constitutes direct or contributory patent
74 | infringement, then any patent licenses granted to that entity under this
75 | Agreement for that Contribution or Work shall terminate as of the date
76 | such litigation is filed.
77 |
78 | 4. You represent that you are legally entitled to grant the above
79 | license.
80 |
81 | If your employer(s) has rights to intellectual property that you create
82 | that includes your Contributions, you represent that you have received
83 | permission to make Contributions on behalf of that employer, that your
84 | employer has waived such rights for your Contributions to Leap Motion,
85 | or that your employer has executed a separate Corporate CLA with Leap
86 | Motion.
87 |
88 | 5. You represent that each of Your Contributions is Your original
89 | creation (see section 7 for submissions on behalf of others).
90 |
91 | You represent that Your Contribution submissions include complete
92 | details of any third-party license or other restriction (including, but
93 | not limited to, related patents and trademarks) of which you are
94 | personally aware and which are associated with any part of Your
95 | Contributions.
96 |
97 | 6. You are not expected to provide support for Your Contributions,
98 | except to the extent You desire to provide support. You may provide
99 | support for free, for a fee, or not at all. Unless required by
100 | applicable law or agreed to in writing, You provide Your Contributions
101 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
102 | either express or implied, including, without limitation, any warranties
103 | or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS
104 | FOR A PARTICULAR PURPOSE.
105 |
106 | 7. Should You wish to submit work that is not Your original creation,
107 | You may submit it to Leap Motion separately from any Contribution,
108 | identifying the complete details of its source and of any license or
109 | other restriction (including, but not limited to, related patents,
110 | trademarks, and license agreements) of which you are personally aware,
111 | and conspicuously marking the work as "Submitted on behalf of a
112 | third-party: [[]named here]".
113 |
114 | 8. You agree to notify Leap Motion of any facts or circumstances of
115 | which you become aware that would make these representations inaccurate
116 | in any respect.
117 |
118 | ======================================================
119 |
120 | 1. Peter Ehrlich
121 | Github account: pehrlich
122 | Email: pehrlich@leapmotion.com
123 |
124 | 2. Max Sills
125 | Github account: MaxSillsLM
126 | Email: max@leapmotion.com
127 |
128 | 3. Paul Mandel
129 | Github account: paulmand3l
130 | Email: pmandel@leapmotion.com
131 |
132 | 4. Robin Böhm
133 | Github account: robinboehm
134 | Email: robinboehm@gmail.com
135 |
136 | 5. Martin Naumann
137 | Github account: AVGP
138 | Email: martin@geekonaut.de
139 |
140 | 6. Wenchen Li (Neo)
141 | Github account: neolwc
142 | Email: neolwc@gmail.com
143 |
--------------------------------------------------------------------------------
/main/transform/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Leap Transform Plugin
6 |
7 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | View Source
54 |
55 |
Transform Plugin
56 |
57 | Transforms Leap data based off of a rotation matrix or a THREE.js rotation and vectors.
58 |
59 |
60 | Here we use the riggedHand to visualize the transformations, but it is not required. All data in the frame is altered
61 | by the transform plugin.
62 |
63 |
64 | Parameters can be either static objects or methods which transform on every frame.
65 |
66 |
67 | In this way, you can then transform individual hands based upon their id.
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | // At the simplest:
96 | Leap.loop()
97 | .use('transform', {
98 | // move 20 cm back.
99 | position: new THREE.Vector3(0,0,-200)
100 | });
101 |
102 |
103 |
104 |
105 |
Options
106 |
107 |
108 |
109 |
Name
110 |
Default
111 |
Description
112 |
113 |
114 |
VR
115 |
false
116 |
117 | Sets scale, position, and rotation transforms for HMD mode. These are: units in meters, 8cm forward, and all x, y, and z axis flipped. Also tells tracking to use head-mounted device mode.
118 |
119 |
120 |
121 |
position
122 |
undefined
123 |
A THREE.Vector3 position offset vector
124 |
125 |
126 |
quaternion
127 |
undefined
128 |
A THREE.Quaternion rotation
129 |
130 |
131 |
scale
132 |
undefined
133 |
A THREE.Vector scale vector, or a single scalar to be applied to all three axis.
134 |
135 |
136 |
effectiveParent
137 |
undefined
138 |
A THREE.Object3d, or anything which responds to `matrixWorld` and `scale`. This matrixWorld is applied to the
139 | hand data as well as any transformations specified through the other options. This allows hands to be "attached"
140 | to a camera.
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/utils/data-plotter/LeapDataPlotter.js:
--------------------------------------------------------------------------------
1 | // this allows RequireJS without necessitating it.
2 | // see http://bob.yexley.net/umd-javascript-that-runs-anywhere/
3 | (function (root, factory) {
4 |
5 | if (typeof define === "function" && define.amd) {
6 | define([], factory);
7 | } else {
8 | root.LeapDataPlotter = factory();
9 | }
10 |
11 | }(this, function () {
12 |
13 | var LeapDataPlotter, TimeSeries;
14 |
15 | var colors = ['#900', '#090', '#009', '#990', '#909', '#099'];
16 | var colorIndex = 0;
17 |
18 | LeapDataPlotter = function (options) {
19 | this.options = options || (options = {});
20 | this.seriesHash = {};
21 | this.series = [];
22 | this.init(options.el);
23 | }
24 |
25 | LeapDataPlotter.prototype.init = function(el) {
26 |
27 | if (el){
28 | var canvas = el;
29 | }else {
30 | var canvas = document.createElement('canvas');
31 | canvas.className = "leap-data-plotter";
32 | document.body.appendChild(canvas);
33 | }
34 |
35 |
36 | this.canvas = canvas;
37 | this.context = canvas.getContext('2d');
38 |
39 | this.rescale();
40 | }
41 |
42 | // this method must be called any time the canvas changes size.
43 | LeapDataPlotter.prototype.rescale = function(){
44 | var styles = getComputedStyle(this.canvas);
45 | var windowWidth = parseInt(styles.width, 10);
46 | var windowHeight = parseInt(styles.height, 10);
47 | this.width = windowWidth;
48 | this.height = windowHeight;
49 |
50 | var devicePixelRatio = window.devicePixelRatio || 1;
51 | var backingStoreRatio = this.context.webkitBackingStorePixelRatio ||
52 | this.context.mozBackingStorePixelRatio ||
53 | this.context.msBackingStorePixelRatio ||
54 | this.context.oBackingStorePixelRatio ||
55 | this.context.backingStorePixelRatio || 1;
56 |
57 | var ratio = devicePixelRatio / backingStoreRatio;
58 | if (devicePixelRatio !== backingStoreRatio) {
59 |
60 | var oldWidth = this.canvas.width;
61 | var oldHeight = this.canvas.height;
62 |
63 | this.canvas.width = oldWidth * ratio;
64 | this.canvas.height = oldHeight * ratio;
65 |
66 | this.canvas.style.width = oldWidth + 'px';
67 | this.canvas.style.height = oldHeight + 'px';
68 |
69 | this.context.scale(ratio, ratio);
70 | }
71 |
72 | this.clear();
73 | this.draw();
74 | }
75 |
76 | // pushes a data point on to the plot
77 | // data can either be a number
78 | // or an array [x,y,z], which will be plotted in three graphs.
79 | // options:
80 | // - y: the graph index on which to plot this datapoint
81 | // - color: hex code
82 | // - name: name of the plot
83 | // - precision: how many decimals to show (for max, min, current value)
84 | LeapDataPlotter.prototype.plot = function (id, data, opts) {
85 | // console.assert(!isNaN(data), "No plotting data received");
86 |
87 | opts || (opts = {});
88 |
89 | if (data.length) {
90 |
91 | for (var i = 0, c = 120; i < data.length; i++, c=++c>122?97:c) {
92 | this.getTimeSeries( id + '.' + String.fromCharCode(c), opts )
93 | .push( data[i], {pointColor: opts.pointColor} );
94 | }
95 |
96 | } else {
97 |
98 | this.getTimeSeries(id, opts)
99 | .push(data, {pointColor: opts.pointColor});
100 |
101 | }
102 |
103 | }
104 |
105 | LeapDataPlotter.prototype.getTimeSeries = function (id, opts) {
106 | var ts = this.seriesHash[id];
107 |
108 | if (!ts) {
109 |
110 | var defaultOpts = this.getOptions(id);
111 | for (key in opts){
112 | defaultOpts[key] = opts[key];
113 | }
114 |
115 | ts = new TimeSeries(defaultOpts);
116 | this.series.push(ts);
117 | this.seriesHash[id] = ts;
118 |
119 | }
120 |
121 | return ts;
122 | }
123 |
124 | LeapDataPlotter.prototype.getOptions = function (name) {
125 | var c = colorIndex;
126 | colorIndex = (colorIndex + 1) % colors.length;
127 | var len = this.series.length;
128 | var y = len ? this.series[len - 1].y + 50 : 0;
129 | return {
130 | y: y,
131 | width: this.width,
132 | color: colors[c],
133 | name: name
134 | }
135 | }
136 |
137 | LeapDataPlotter.prototype.clear = function() {
138 | this.context.clearRect(0, 0, this.width, this.height);
139 | }
140 |
141 | LeapDataPlotter.prototype.draw = function() {
142 | var context = this.context;
143 | this.series.forEach(function (s) {
144 | s.draw(context);
145 | });
146 | }
147 |
148 | LeapDataPlotter.prototype.update = function(){
149 | this.clear();
150 | this.draw();
151 | }
152 |
153 | TimeSeries = function (opts) {
154 | opts = opts || {};
155 | this.x = opts.x || 0;
156 | this.y = opts.y || 0;
157 | this.precision = opts.precision || 5;
158 | this.units = opts.units || '';
159 | this.width = opts.width || 1000;
160 | this.height = opts.height || 50;
161 | this.length = opts.length || 600;
162 | this.color = opts.color || '#000';
163 | this.name = opts.name || "";
164 | this.frameHandler = opts.frameHandler;
165 |
166 | this.max = -Infinity;
167 | this.min = Infinity;
168 | this.data = [];
169 | this.pointColors = [];
170 | }
171 |
172 | TimeSeries.prototype.push = function (value, opts) {
173 | this.data.push(value);
174 |
175 | if (this.data.length >= this.length) {
176 | this.data.shift();
177 | }
178 |
179 | if (opts && opts.pointColor){
180 | this.pointColors.push(opts.pointColor);
181 |
182 | // note: this can get out of sync if a point color is not set for every point.
183 | if (this.pointColors.length >= this.length) {
184 | this.pointColors.shift();
185 | }
186 | }
187 |
188 | return this;
189 | }
190 |
191 | TimeSeries.prototype.draw = function (context) {
192 | var self = this;
193 | var xScale = (this.width - 10) / (this.length - 1);
194 | var yScale = -(this.height - 10) / (this.max - this.min);
195 |
196 | var padding = 5;
197 | var top = (this.max - this.min) * yScale + 10;
198 |
199 | context.save();
200 | context.strokeRect(this.x, this.y, this.width, this.height);
201 | context.translate(this.x, this.y + this.height - padding);
202 | context.strokeStyle = this.color;
203 |
204 | context.beginPath();
205 |
206 | var max = -Infinity;
207 | var min = Infinity;
208 | this.data.forEach(function (d, i) {
209 | if (d > max) max = d;
210 | if (d < min) min = d;
211 |
212 | if (isNaN(d)) {
213 | context.stroke();
214 | context.beginPath();
215 | } else {
216 | context.lineTo(i * xScale, (d - self.min) * yScale);
217 | if (self.pointColors[i] && (self.pointColors[i] != self.pointColors[i - 1]) ){
218 | context.stroke();
219 | context.strokeStyle = self.pointColors[i];
220 | context.beginPath();
221 | context.lineTo(i * xScale, (d - self.min) * yScale);
222 | }
223 | }
224 | });
225 | context.stroke();
226 |
227 | // draw labels
228 | context.fillText( this.name, padding, top);
229 | context.fillText( this.data[this.data.length - 1].toPrecision(this.precision) + this.units, padding, 0 );
230 |
231 | context.textAlign="end";
232 | context.fillText( this.min.toPrecision(this.precision) + this.units, this.width - padding, 0 );
233 | context.fillText( this.max.toPrecision(this.precision) + this.units, this.width - padding, top );
234 | context.textAlign="left";
235 | // end draw labels
236 |
237 | context.restore();
238 | this.min = min;
239 | this.max = max;
240 | }
241 |
242 | return LeapDataPlotter;
243 |
244 | }));
245 |
--------------------------------------------------------------------------------
/utils/leap-plugins-0.1.12-utils.js:
--------------------------------------------------------------------------------
1 | // this allows RequireJS without necessitating it.
2 | // see http://bob.yexley.net/umd-javascript-that-runs-anywhere/
3 | (function (root, factory) {
4 |
5 | if (typeof define === "function" && define.amd) {
6 | define([], factory);
7 | } else {
8 | root.LeapDataPlotter = factory();
9 | }
10 |
11 | }(this, function () {
12 |
13 | var LeapDataPlotter, TimeSeries;
14 |
15 | var colors = ['#900', '#090', '#009', '#990', '#909', '#099'];
16 | var colorIndex = 0;
17 |
18 | LeapDataPlotter = function (options) {
19 | this.options = options || (options = {});
20 | this.seriesHash = {};
21 | this.series = [];
22 | this.init(options.el);
23 | }
24 |
25 | LeapDataPlotter.prototype.init = function(el) {
26 |
27 | if (el){
28 | var canvas = el;
29 | }else {
30 | var canvas = document.createElement('canvas');
31 | canvas.className = "leap-data-plotter";
32 | document.body.appendChild(canvas);
33 | }
34 |
35 |
36 | this.canvas = canvas;
37 | this.context = canvas.getContext('2d');
38 |
39 | this.rescale();
40 | }
41 |
42 | // this method must be called any time the canvas changes size.
43 | LeapDataPlotter.prototype.rescale = function(){
44 | var styles = getComputedStyle(this.canvas);
45 | var windowWidth = parseInt(styles.width, 10);
46 | var windowHeight = parseInt(styles.height, 10);
47 | this.width = windowWidth;
48 | this.height = windowHeight;
49 |
50 | var devicePixelRatio = window.devicePixelRatio || 1;
51 | var backingStoreRatio = this.context.webkitBackingStorePixelRatio ||
52 | this.context.mozBackingStorePixelRatio ||
53 | this.context.msBackingStorePixelRatio ||
54 | this.context.oBackingStorePixelRatio ||
55 | this.context.backingStorePixelRatio || 1;
56 |
57 | var ratio = devicePixelRatio / backingStoreRatio;
58 | if (devicePixelRatio !== backingStoreRatio) {
59 |
60 | var oldWidth = this.canvas.width;
61 | var oldHeight = this.canvas.height;
62 |
63 | this.canvas.width = oldWidth * ratio;
64 | this.canvas.height = oldHeight * ratio;
65 |
66 | this.canvas.style.width = oldWidth + 'px';
67 | this.canvas.style.height = oldHeight + 'px';
68 |
69 | this.context.scale(ratio, ratio);
70 | }
71 |
72 | this.clear();
73 | this.draw();
74 | }
75 |
76 | // pushes a data point on to the plot
77 | // data can either be a number
78 | // or an array [x,y,z], which will be plotted in three graphs.
79 | // options:
80 | // - y: the graph index on which to plot this datapoint
81 | // - color: hex code
82 | // - name: name of the plot
83 | // - precision: how many decimals to show (for max, min, current value)
84 | LeapDataPlotter.prototype.plot = function (id, data, opts) {
85 | // console.assert(!isNaN(data), "No plotting data received");
86 |
87 | opts || (opts = {});
88 |
89 | if (data.length) {
90 |
91 | for (var i = 0, c = 120; i < data.length; i++, c=++c>122?97:c) {
92 | this.getTimeSeries( id + '.' + String.fromCharCode(c), opts )
93 | .push( data[i], {pointColor: opts.pointColor} );
94 | }
95 |
96 | } else {
97 |
98 | this.getTimeSeries(id, opts)
99 | .push(data, {pointColor: opts.pointColor});
100 |
101 | }
102 |
103 | }
104 |
105 | LeapDataPlotter.prototype.getTimeSeries = function (id, opts) {
106 | var ts = this.seriesHash[id];
107 |
108 | if (!ts) {
109 |
110 | var defaultOpts = this.getOptions(id);
111 | for (key in opts){
112 | defaultOpts[key] = opts[key];
113 | }
114 |
115 | ts = new TimeSeries(defaultOpts);
116 | this.series.push(ts);
117 | this.seriesHash[id] = ts;
118 |
119 | }
120 |
121 | return ts;
122 | }
123 |
124 | LeapDataPlotter.prototype.getOptions = function (name) {
125 | var c = colorIndex;
126 | colorIndex = (colorIndex + 1) % colors.length;
127 | var len = this.series.length;
128 | var y = len ? this.series[len - 1].y + 50 : 0;
129 | return {
130 | y: y,
131 | width: this.width,
132 | color: colors[c],
133 | name: name
134 | }
135 | }
136 |
137 | LeapDataPlotter.prototype.clear = function() {
138 | this.context.clearRect(0, 0, this.width, this.height);
139 | }
140 |
141 | LeapDataPlotter.prototype.draw = function() {
142 | var context = this.context;
143 | this.series.forEach(function (s) {
144 | s.draw(context);
145 | });
146 | }
147 |
148 | LeapDataPlotter.prototype.update = function(){
149 | this.clear();
150 | this.draw();
151 | }
152 |
153 | TimeSeries = function (opts) {
154 | opts = opts || {};
155 | this.x = opts.x || 0;
156 | this.y = opts.y || 0;
157 | this.precision = opts.precision || 5;
158 | this.units = opts.units || '';
159 | this.width = opts.width || 1000;
160 | this.height = opts.height || 50;
161 | this.length = opts.length || 600;
162 | this.color = opts.color || '#000';
163 | this.name = opts.name || "";
164 | this.frameHandler = opts.frameHandler;
165 |
166 | this.max = -Infinity;
167 | this.min = Infinity;
168 | this.data = [];
169 | this.pointColors = [];
170 | }
171 |
172 | TimeSeries.prototype.push = function (value, opts) {
173 | this.data.push(value);
174 |
175 | if (this.data.length >= this.length) {
176 | this.data.shift();
177 | }
178 |
179 | if (opts && opts.pointColor){
180 | this.pointColors.push(opts.pointColor);
181 |
182 | // note: this can get out of sync if a point color is not set for every point.
183 | if (this.pointColors.length >= this.length) {
184 | this.pointColors.shift();
185 | }
186 | }
187 |
188 | return this;
189 | }
190 |
191 | TimeSeries.prototype.draw = function (context) {
192 | var self = this;
193 | var xScale = (this.width - 10) / (this.length - 1);
194 | var yScale = -(this.height - 10) / (this.max - this.min);
195 |
196 | var padding = 5;
197 | var top = (this.max - this.min) * yScale + 10;
198 |
199 | context.save();
200 | context.strokeRect(this.x, this.y, this.width, this.height);
201 | context.translate(this.x, this.y + this.height - padding);
202 | context.strokeStyle = this.color;
203 |
204 | context.beginPath();
205 |
206 | var max = -Infinity;
207 | var min = Infinity;
208 | this.data.forEach(function (d, i) {
209 | if (d > max) max = d;
210 | if (d < min) min = d;
211 |
212 | if (isNaN(d)) {
213 | context.stroke();
214 | context.beginPath();
215 | } else {
216 | context.lineTo(i * xScale, (d - self.min) * yScale);
217 | if (self.pointColors[i] && (self.pointColors[i] != self.pointColors[i - 1]) ){
218 | context.stroke();
219 | context.strokeStyle = self.pointColors[i];
220 | context.beginPath();
221 | context.lineTo(i * xScale, (d - self.min) * yScale);
222 | }
223 | }
224 | });
225 | context.stroke();
226 |
227 | // draw labels
228 | context.fillText( this.name, padding, top);
229 | context.fillText( this.data[this.data.length - 1].toPrecision(this.precision) + this.units, padding, 0 );
230 |
231 | context.textAlign="end";
232 | context.fillText( this.min.toPrecision(this.precision) + this.units, this.width - padding, 0 );
233 | context.fillText( this.max.toPrecision(this.precision) + this.units, this.width - padding, top );
234 | context.textAlign="left";
235 | // end draw labels
236 |
237 | context.restore();
238 | this.min = min;
239 | this.max = max;
240 | }
241 |
242 | return LeapDataPlotter;
243 |
244 | }));
245 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 |
--------------------------------------------------------------------------------
/main/bone-hand/leap.bone-hand.coffee:
--------------------------------------------------------------------------------
1 | scope = null
2 |
3 | THREE = if typeof require != 'undefined' then require('three') else window.THREE
4 |
5 | initScene = (targetEl, scale)->
6 |
7 | # scene and renderer
8 |
9 | scope.scene = new THREE.Scene()
10 | scope.rendererOps ||= {}
11 | if scope.rendererOps.alpha == undefined then scope.rendererOps.alpha = true
12 | scope.renderer = renderer = new THREE.WebGLRenderer(scope.rendererOps)
13 |
14 | width = scope.width || window.innerWidth
15 | height = scope.height || window.innerHeight
16 |
17 | renderer.setClearColor(0x000000, 0)
18 | renderer.setSize(width, height)
19 |
20 | renderer.domElement.className = "leap-boneHand"
21 |
22 | renderer.shadowMap.enabled = true
23 | renderer.shadowMap.type = THREE.PCFSoftShadowMap
24 |
25 | targetEl.appendChild(renderer.domElement)
26 |
27 | # camera
28 |
29 | near = 1 # 1 mm
30 | far = 10000 # 10 m
31 |
32 | if scale
33 | near *= scale
34 | far *= scale
35 |
36 | scope.camera = camera = new THREE.PerspectiveCamera(45, width / height, near, far)
37 |
38 | camera.position.set(0, 300, 500);
39 | camera.lookAt(new THREE.Vector3(0, 160, 0));
40 |
41 | scope.scene.add(camera)
42 |
43 | if !scope.width && !scope.height
44 | window.addEventListener 'resize', ->
45 | width = window.innerWidth
46 | height = window.innerHeight
47 |
48 | camera.aspect = width / height
49 | camera.updateProjectionMatrix()
50 |
51 | renderer.setSize( width, height )
52 |
53 | renderer.render(scope.scene, camera)
54 | , false
55 |
56 |
57 | scope.render ||= (timestamp)->
58 | renderer.render(scope.scene, scope.camera);
59 |
60 | scope.render()
61 |
62 | baseBoneRotation = null
63 | jointColor = null
64 | boneColor = null
65 | boneScale = null
66 | jointScale = null
67 | boneRadius = null
68 | jointRadius = null
69 | material = null
70 | armTopAndBottomRotation = null
71 |
72 |
73 |
74 | class HandMesh
75 | # when a hand enters the scene, it takes a mesh out of here, or creates a new one
76 | @unusedHandMeshes: []
77 |
78 | # gets or creates a handmesh
79 | # makes it visible
80 | @get: ->
81 | if HandMesh.unusedHandMeshes.length == 0
82 | handMesh = HandMesh.create()
83 |
84 | handMesh = HandMesh.unusedHandMeshes.pop()
85 |
86 | handMesh.show()
87 |
88 | return handMesh
89 |
90 | # replaces a handMesh in the cache
91 | # makes it invisible.
92 | replace: ->
93 | @hide()
94 | HandMesh.unusedHandMeshes.push(this)
95 |
96 |
97 | # adds hand meshes to the scene
98 | # stores them in unusedhandMesh
99 | @create: ->
100 | mesh = new HandMesh
101 | mesh.setVisibility(false)
102 | HandMesh.unusedHandMeshes.push(mesh)
103 |
104 |
105 | if HandMesh.onMeshCreated
106 | HandMesh.onMeshCreated(mesh)
107 |
108 | return mesh
109 |
110 | constructor: ->
111 |
112 | material = if !isNaN(scope.opacity)
113 | new THREE.MeshPhongMaterial(fog: false, transparent: true, opacity: scope.opacity)
114 | else
115 | new THREE.MeshPhongMaterial(fog: false)
116 |
117 | boneRadius = 40 * boneScale # 40 is typical mm middle finger proximal bone length. This can be anything, as it gets rescaled later.
118 | jointRadius = 40 * jointScale
119 |
120 | @fingerMeshes = []
121 | for i in [0...5]
122 | finger = []
123 | boneCount = if i == 0 then 3 else 4 # thumb has one fewer
124 |
125 | for j in [0...boneCount]
126 | # we keep one array with bone and joint meshes, and iterate through it later for position.. easy
127 |
128 | #joint
129 | mesh = new THREE.Mesh(
130 | new THREE.SphereGeometry(jointRadius, 32, 32),
131 | material.clone()
132 | )
133 | mesh.name = "hand-bone-#{j}"
134 | mesh.material.color.copy(jointColor)
135 | mesh.renderOrder = ((i * 9) + (2 * j) ) / 36
136 | mesh.castShadow = true
137 | scope.scene.add mesh
138 | finger.push mesh
139 |
140 | # bone
141 | # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
142 | mesh = new THREE.Mesh(
143 | new THREE.CylinderGeometry(boneRadius, boneRadius, 40, 32),
144 | material.clone()
145 | )
146 | mesh.name = "hand-joint-#{j}"
147 | mesh.material.color.copy(boneColor)
148 | mesh.renderOrder = ((i * 9) + (2 * j) + 1 ) / 36 # might fuckup opaque objects?
149 | mesh.castShadow = true
150 | scope.scene.add mesh
151 | finger.push mesh
152 |
153 |
154 | #joint - end cap
155 | mesh = new THREE.Mesh(
156 | new THREE.SphereGeometry(jointRadius, 32, 32),
157 | material.clone()
158 | )
159 | mesh.material.color.copy(jointColor)
160 |
161 | mesh.castShadow = true
162 |
163 | scope.scene.add mesh
164 | finger.push mesh
165 |
166 |
167 | @fingerMeshes.push(finger)
168 |
169 |
170 | if scope.arm
171 | @armMesh = new THREE.Object3D;
172 | @armBones = []
173 | @armSpheres = []
174 |
175 | for i in [0..3]
176 | @armBones.push(new THREE.Mesh(
177 | # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
178 | new THREE.CylinderGeometry(boneRadius, boneRadius,
179 | ( if i < 2 then 1000 else 100 )
180 | , 32),
181 | material.clone()
182 | ))
183 | @armBones[i].material.color.copy(boneColor)
184 | @armBones[i].castShadow = true
185 | @armBones[i].name= "ArmBone#{i}"
186 |
187 | if i > 1
188 | @armBones[i].quaternion.multiply(armTopAndBottomRotation)
189 |
190 | @armMesh.add(@armBones[i])
191 |
192 | @armSpheres = []
193 | for i in [0..3]
194 | @armSpheres.push(new THREE.Mesh(
195 | new THREE.SphereGeometry(jointRadius, 32, 32),
196 | material.clone()
197 | ))
198 | @armSpheres[i].material.color.copy(jointColor)
199 | @armSpheres[i].castShadow = true
200 | @armSpheres[i].name= "ArmSphere#{i}"
201 | @armMesh.add(@armSpheres[i])
202 |
203 | scope.scene.add(@armMesh);
204 |
205 | traverse: (callback)->
206 | for i in [0...5]
207 | for mesh in @fingerMeshes[i]
208 | callback(mesh)
209 |
210 | @armMesh && @armMesh.traverse(callback)
211 |
212 |
213 |
214 | # scales the meshes appropriately
215 | scaleTo: (hand)->
216 |
217 | # bone radius changes with overall hand size, but not joint length
218 | baseScale = hand.middleFinger.proximal.length / @fingerMeshes[2][1].geometry.parameters.height
219 |
220 | for i in [0...5]
221 | finger = hand.fingers[i]
222 | j = 0
223 | # iterate backwards as first bone in the thumb is bunk.
224 | while true
225 | if j == @fingerMeshes[i].length - 1
226 | mesh = @fingerMeshes[i][j]
227 | mesh.scale.set(baseScale, baseScale, baseScale)
228 | break
229 |
230 | bone = finger.bones[ 3 - (j / 2) ]
231 |
232 | # joints
233 | mesh = @fingerMeshes[i][j]
234 | mesh.scale.set(baseScale, baseScale, baseScale)
235 |
236 | j++
237 |
238 | # bones
239 | mesh = @fingerMeshes[i][j]
240 | fingerBoneLengthScale = bone.length / mesh.geometry.parameters.height
241 | mesh.scale.set(baseScale, fingerBoneLengthScale, baseScale)
242 | j++
243 |
244 | if scope.arm
245 | armLenScale = hand.arm.length / ( @armBones[0].geometry.parameters.height + @armBones[0].geometry.parameters.radiusTop )
246 | armWidthScale = hand.arm.width / ( @armBones[2].geometry.parameters.height + @armBones[2].geometry.parameters.radiusTop )
247 |
248 | for i in [0..3]
249 | @armBones[i].scale.set(baseScale,
250 | ( if i < 2 then armLenScale else armWidthScale )
251 | , baseScale)
252 |
253 | @armSpheres[i].scale.set(baseScale, baseScale, baseScale)
254 |
255 | boneXOffset = (hand.arm.width / 2) * 0.85
256 | halfArmLength = hand.arm.length / 2
257 |
258 |
259 | # CW from Top center
260 | @armBones[0].position.setX(boneXOffset) # radius
261 | @armBones[1].position.setX(-boneXOffset) # ulna
262 | @armBones[2].position.setY(halfArmLength)
263 | @armBones[3].position.setY(-halfArmLength)
264 |
265 | # CW from TL
266 | @armSpheres[0].position.set( - boneXOffset, halfArmLength, 0)
267 | @armSpheres[1].position.set( boneXOffset, halfArmLength, 0)
268 | @armSpheres[2].position.set( boneXOffset, - halfArmLength, 0)
269 | @armSpheres[3].position.set( - boneXOffset, - halfArmLength, 0)
270 |
271 |
272 |
273 | return this
274 |
275 | # positions and rotates the meshes appropriately
276 | formTo: (hand)->
277 | for i in [0...5]
278 | finger = hand.fingers[i]
279 | j = 0
280 | while true
281 |
282 | if j == @fingerMeshes[i].length - 1
283 | mesh = @fingerMeshes[i][j] # alternating, joint-bone-joint-bone...
284 | mesh.position.fromArray bone.prevJoint
285 | break
286 |
287 | bone = finger.bones[ 3 - (j / 2) ]
288 |
289 | mesh = @fingerMeshes[i][j]
290 | mesh.position.fromArray bone.nextJoint
291 | ++j
292 |
293 | mesh = @fingerMeshes[i][j]
294 |
295 | mesh.position.fromArray bone.center()
296 | mesh.setRotationFromMatrix (new THREE.Matrix4).fromArray(bone.matrix())
297 | mesh.quaternion.multiply baseBoneRotation
298 | ++j
299 |
300 | if @armMesh
301 | @armMesh.position.fromArray(hand.arm.center())
302 | @armMesh.setRotationFromMatrix(
303 | (new THREE.Matrix4).fromArray( hand.arm.matrix() )
304 | );
305 | @armMesh.quaternion.multiply(baseBoneRotation)
306 |
307 | return this
308 |
309 | setVisibility: (visible)->
310 | for i in [0...5]
311 | j = 0
312 | while true
313 | @fingerMeshes[i][j].visible = visible
314 | ++j
315 |
316 | break if j == @fingerMeshes[i].length
317 |
318 | if scope.arm
319 | for i in [0..3]
320 | @armBones[i].visible = visible
321 | @armSpheres[i].visible = visible
322 |
323 |
324 | show: ->
325 | @setVisibility(true)
326 |
327 | hide: ->
328 | @setVisibility(false)
329 |
330 |
331 |
332 | onHand = (hand) ->
333 | return if !scope.scene
334 |
335 | handMesh = hand.data('handMesh')
336 |
337 | # the handFound listener doesn't actually fire if in live mode with hand-in-screen
338 | # we manually check for finger meshes and initialize if necessary
339 | if !handMesh
340 | handMesh = HandMesh.get().scaleTo(hand)
341 | hand.data('handMesh', handMesh)
342 | if HandMesh.onMeshUsed
343 | HandMesh.onMeshUsed(handMesh)
344 |
345 | handMesh.formTo(hand)
346 |
347 |
348 | boneHandLost = (hand) ->
349 | handMesh = hand.data('handMesh')
350 | if handMesh
351 | handMesh.replace()
352 |
353 | handMesh = hand.data('handMesh', null)
354 |
355 | Leap.plugin 'boneHand', (options = {}) ->
356 | # make sure scope is globally available
357 | scope = options
358 | controller = this;
359 |
360 | jointColor = (new THREE.Color).setHex(0x5daa00)
361 | boneColor = (new THREE.Color).setHex(0xffffff)
362 |
363 | scope.boneScale && boneScale = scope.boneScale
364 | scope.jointScale && jointScale = scope.jointScale
365 |
366 | scope.boneColor && boneColor = scope.boneColor
367 | scope.jointColor && jointColor = scope.jointColor
368 |
369 | scope.HandMesh = HandMesh
370 |
371 | # this method enhances scope.scene with camera and lighting
372 | # it is split from initScene so as to allow it being called with non-default scenes.
373 | # expects camera to be added to the scene
374 | scope.addShadowCamera = ->
375 |
376 | scope.light = new THREE.SpotLight( 0xffffff, 1 )
377 | scope.light.castShadow = true
378 | # scope.light.shadowCameraVisible = true # This makes for excellent debugging
379 | # scope.light.shadowDarkness = 0.8 # THREE.Light: .shadowDarkness has been removed.
380 | scope.light.shadow.mapSize.width = 1024
381 | scope.light.shadow.mapSize.height = 1024
382 | scope.light.shadow.camera.near = 0.5 / 0.001
383 | scope.light.shadow.camera.far = 3 / 0.001
384 |
385 | # fixed hand position..
386 | scope.light.position.set(0,1000,1000); # up and behind
387 | scope.light.target.position.set(0,0,-1000); # one meter forward
388 |
389 | # see https:#github.com/mrdoob/three.js/issues/2251
390 | scope.camera.add(scope.light.target);
391 | scope.camera.add(scope.light);
392 |
393 | if controller.plugins.transform
394 |
395 | if controller.plugins.transform.getScale()
396 | # this somewhat confusingly-named method also ensures that scale is a Vector3
397 | # for VR, this would be 0.001 m/mm
398 |
399 | scope.light.shadowCameraNear *= controller.plugins.transform.scale.x
400 | scope.light.shadowCameraFar *= controller.plugins.transform.scale.x
401 | scope.light.target.position.multiply(controller.plugins.transform.scale)
402 | scope.light.position.multiply(controller.plugins.transform.scale)
403 |
404 | if controller.plugins.transform.vr == true
405 | scope.camera.position.set(0,0,0)
406 |
407 | if controller.plugins.transform.vr == 'desktop'
408 | scope.camera.position.set(0,0.15,0.3)
409 |
410 |
411 |
412 |
413 | baseBoneRotation = (new THREE.Quaternion).setFromEuler(
414 | new THREE.Euler(Math.PI / 2, 0, 0)
415 | );
416 |
417 |
418 |
419 | boneScale = 1 / 6
420 | jointScale = 1 / 5
421 |
422 | boneRadius = null
423 | jointRadius = null
424 |
425 | material = null
426 |
427 | armTopAndBottomRotation = (new THREE.Quaternion).setFromEuler(
428 | new THREE.Euler(0, 0, Math.PI / 2)
429 | );
430 |
431 |
432 | HandMesh.onMeshCreated = (mesh)->
433 | controller.emit('handMeshCreated', mesh)
434 |
435 | HandMesh.onMeshUsed = (mesh)->
436 | controller.emit('handMeshUsed', mesh)
437 |
438 |
439 |
440 |
441 | @use('handEntry')
442 | @use('handHold')
443 |
444 | # this allows a null scene to be passed in for delayed-initialization.
445 | if scope.scene == undefined
446 | console.assert(scope.targetEl)
447 |
448 | if @plugins.transform && @plugins.transform.getScale()
449 | scale = @plugins.transform.scale.x # we just grab one value, as they're usually all the same.
450 |
451 | initScene(scope.targetEl, scale)
452 | scope.addShadowCamera()
453 |
454 |
455 |
456 | # Preload two hands
457 | if scope.scene
458 | HandMesh.create()
459 | HandMesh.create()
460 |
461 | # have rendered called by leap, rather than animation frame, to make sure there's no one-frame-delay.
462 | # we wrap this to allow the render method to be replaced
463 | if Leap.version.major == 0 && Leap.version.minor < 7 && Leap.version.dot < 4
464 | console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have #{Leap.version.full}")
465 |
466 | @on 'frameEnd', (timestamp)->
467 | if scope.render
468 | scope.render(timestamp)
469 |
470 | @on 'handLost', boneHandLost
471 |
472 |
473 | {
474 |
475 | hand: onHand
476 |
477 | }
478 |
--------------------------------------------------------------------------------
/main/bone-hand/leap.bone-hand.js:
--------------------------------------------------------------------------------
1 | //CoffeeScript generated from main/bone-hand/leap.bone-hand.coffee
2 | (function() {
3 | var HandMesh, THREE, armTopAndBottomRotation, baseBoneRotation, boneColor, boneHandLost, boneRadius, boneScale, initScene, jointColor, jointRadius, jointScale, material, onHand, scope;
4 |
5 | scope = null;
6 |
7 | THREE = typeof require !== 'undefined' ? require('three') : window.THREE;
8 |
9 | initScene = function(targetEl, scale) {
10 | var camera, far, height, near, renderer, width;
11 | scope.scene = new THREE.Scene();
12 | scope.rendererOps || (scope.rendererOps = {});
13 | if (scope.rendererOps.alpha === void 0) {
14 | scope.rendererOps.alpha = true;
15 | }
16 | scope.renderer = renderer = new THREE.WebGLRenderer(scope.rendererOps);
17 | width = scope.width || window.innerWidth;
18 | height = scope.height || window.innerHeight;
19 | renderer.setClearColor(0x000000, 0);
20 | renderer.setSize(width, height);
21 | renderer.domElement.className = "leap-boneHand";
22 | renderer.shadowMap.enabled = true;
23 | renderer.shadowMap.type = THREE.PCFSoftShadowMap;
24 | targetEl.appendChild(renderer.domElement);
25 | near = 1;
26 | far = 10000;
27 | if (scale) {
28 | near *= scale;
29 | far *= scale;
30 | }
31 | scope.camera = camera = new THREE.PerspectiveCamera(45, width / height, near, far);
32 | camera.position.set(0, 300, 500);
33 | camera.lookAt(new THREE.Vector3(0, 160, 0));
34 | scope.scene.add(camera);
35 | if (!scope.width && !scope.height) {
36 | window.addEventListener('resize', function() {
37 | width = window.innerWidth;
38 | height = window.innerHeight;
39 | camera.aspect = width / height;
40 | camera.updateProjectionMatrix();
41 | renderer.setSize(width, height);
42 | return renderer.render(scope.scene, camera);
43 | }, false);
44 | }
45 | scope.render || (scope.render = function(timestamp) {
46 | return renderer.render(scope.scene, scope.camera);
47 | });
48 | return scope.render();
49 | };
50 |
51 | baseBoneRotation = null;
52 |
53 | jointColor = null;
54 |
55 | boneColor = null;
56 |
57 | boneScale = null;
58 |
59 | jointScale = null;
60 |
61 | boneRadius = null;
62 |
63 | jointRadius = null;
64 |
65 | material = null;
66 |
67 | armTopAndBottomRotation = null;
68 |
69 | HandMesh = (function() {
70 | HandMesh.unusedHandMeshes = [];
71 |
72 | HandMesh.get = function() {
73 | var handMesh;
74 | if (HandMesh.unusedHandMeshes.length === 0) {
75 | handMesh = HandMesh.create();
76 | }
77 | handMesh = HandMesh.unusedHandMeshes.pop();
78 | handMesh.show();
79 | return handMesh;
80 | };
81 |
82 | HandMesh.prototype.replace = function() {
83 | this.hide();
84 | return HandMesh.unusedHandMeshes.push(this);
85 | };
86 |
87 | HandMesh.create = function() {
88 | var mesh;
89 | mesh = new HandMesh;
90 | mesh.setVisibility(false);
91 | HandMesh.unusedHandMeshes.push(mesh);
92 | if (HandMesh.onMeshCreated) {
93 | HandMesh.onMeshCreated(mesh);
94 | }
95 | return mesh;
96 | };
97 |
98 | function HandMesh() {
99 | var boneCount, finger, i, j, mesh, _i, _j, _k, _l;
100 | material = !isNaN(scope.opacity) ? new THREE.MeshPhongMaterial({
101 | fog: false,
102 | transparent: true,
103 | opacity: scope.opacity
104 | }) : new THREE.MeshPhongMaterial({
105 | fog: false
106 | });
107 | boneRadius = 40 * boneScale;
108 | jointRadius = 40 * jointScale;
109 | this.fingerMeshes = [];
110 | for (i = _i = 0; _i < 5; i = ++_i) {
111 | finger = [];
112 | boneCount = i === 0 ? 3 : 4;
113 | for (j = _j = 0; 0 <= boneCount ? _j < boneCount : _j > boneCount; j = 0 <= boneCount ? ++_j : --_j) {
114 | mesh = new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone());
115 | mesh.name = "hand-bone-" + j;
116 | mesh.material.color.copy(jointColor);
117 | mesh.renderOrder = ((i * 9) + (2 * j)) / 36;
118 | mesh.castShadow = true;
119 | scope.scene.add(mesh);
120 | finger.push(mesh);
121 | mesh = new THREE.Mesh(new THREE.CylinderGeometry(boneRadius, boneRadius, 40, 32), material.clone());
122 | mesh.name = "hand-joint-" + j;
123 | mesh.material.color.copy(boneColor);
124 | mesh.renderOrder = ((i * 9) + (2 * j) + 1) / 36;
125 | mesh.castShadow = true;
126 | scope.scene.add(mesh);
127 | finger.push(mesh);
128 | }
129 | mesh = new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone());
130 | mesh.material.color.copy(jointColor);
131 | mesh.castShadow = true;
132 | scope.scene.add(mesh);
133 | finger.push(mesh);
134 | this.fingerMeshes.push(finger);
135 | }
136 | if (scope.arm) {
137 | this.armMesh = new THREE.Object3D;
138 | this.armBones = [];
139 | this.armSpheres = [];
140 | for (i = _k = 0; _k <= 3; i = ++_k) {
141 | this.armBones.push(new THREE.Mesh(new THREE.CylinderGeometry(boneRadius, boneRadius, (i < 2 ? 1000 : 100), 32), material.clone()));
142 | this.armBones[i].material.color.copy(boneColor);
143 | this.armBones[i].castShadow = true;
144 | this.armBones[i].name = "ArmBone" + i;
145 | if (i > 1) {
146 | this.armBones[i].quaternion.multiply(armTopAndBottomRotation);
147 | }
148 | this.armMesh.add(this.armBones[i]);
149 | }
150 | this.armSpheres = [];
151 | for (i = _l = 0; _l <= 3; i = ++_l) {
152 | this.armSpheres.push(new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone()));
153 | this.armSpheres[i].material.color.copy(jointColor);
154 | this.armSpheres[i].castShadow = true;
155 | this.armSpheres[i].name = "ArmSphere" + i;
156 | this.armMesh.add(this.armSpheres[i]);
157 | }
158 | scope.scene.add(this.armMesh);
159 | }
160 | }
161 |
162 | HandMesh.prototype.traverse = function(callback) {
163 | var i, mesh, _i, _j, _len, _ref;
164 | for (i = _i = 0; _i < 5; i = ++_i) {
165 | _ref = this.fingerMeshes[i];
166 | for (_j = 0, _len = _ref.length; _j < _len; _j++) {
167 | mesh = _ref[_j];
168 | callback(mesh);
169 | }
170 | }
171 | return this.armMesh && this.armMesh.traverse(callback);
172 | };
173 |
174 | HandMesh.prototype.scaleTo = function(hand) {
175 | var armLenScale, armWidthScale, baseScale, bone, boneXOffset, finger, fingerBoneLengthScale, halfArmLength, i, j, mesh, _i, _j;
176 | baseScale = hand.middleFinger.proximal.length / this.fingerMeshes[2][1].geometry.parameters.height;
177 | for (i = _i = 0; _i < 5; i = ++_i) {
178 | finger = hand.fingers[i];
179 | j = 0;
180 | while (true) {
181 | if (j === this.fingerMeshes[i].length - 1) {
182 | mesh = this.fingerMeshes[i][j];
183 | mesh.scale.set(baseScale, baseScale, baseScale);
184 | break;
185 | }
186 | bone = finger.bones[3 - (j / 2)];
187 | mesh = this.fingerMeshes[i][j];
188 | mesh.scale.set(baseScale, baseScale, baseScale);
189 | j++;
190 | mesh = this.fingerMeshes[i][j];
191 | fingerBoneLengthScale = bone.length / mesh.geometry.parameters.height;
192 | mesh.scale.set(baseScale, fingerBoneLengthScale, baseScale);
193 | j++;
194 | }
195 | }
196 | if (scope.arm) {
197 | armLenScale = hand.arm.length / (this.armBones[0].geometry.parameters.height + this.armBones[0].geometry.parameters.radiusTop);
198 | armWidthScale = hand.arm.width / (this.armBones[2].geometry.parameters.height + this.armBones[2].geometry.parameters.radiusTop);
199 | for (i = _j = 0; _j <= 3; i = ++_j) {
200 | this.armBones[i].scale.set(baseScale, (i < 2 ? armLenScale : armWidthScale), baseScale);
201 | this.armSpheres[i].scale.set(baseScale, baseScale, baseScale);
202 | }
203 | boneXOffset = (hand.arm.width / 2) * 0.85;
204 | halfArmLength = hand.arm.length / 2;
205 | this.armBones[0].position.setX(boneXOffset);
206 | this.armBones[1].position.setX(-boneXOffset);
207 | this.armBones[2].position.setY(halfArmLength);
208 | this.armBones[3].position.setY(-halfArmLength);
209 | this.armSpheres[0].position.set(-boneXOffset, halfArmLength, 0);
210 | this.armSpheres[1].position.set(boneXOffset, halfArmLength, 0);
211 | this.armSpheres[2].position.set(boneXOffset, -halfArmLength, 0);
212 | this.armSpheres[3].position.set(-boneXOffset, -halfArmLength, 0);
213 | }
214 | return this;
215 | };
216 |
217 | HandMesh.prototype.formTo = function(hand) {
218 | var bone, finger, i, j, mesh, _i;
219 | for (i = _i = 0; _i < 5; i = ++_i) {
220 | finger = hand.fingers[i];
221 | j = 0;
222 | while (true) {
223 | if (j === this.fingerMeshes[i].length - 1) {
224 | mesh = this.fingerMeshes[i][j];
225 | mesh.position.fromArray(bone.prevJoint);
226 | break;
227 | }
228 | bone = finger.bones[3 - (j / 2)];
229 | mesh = this.fingerMeshes[i][j];
230 | mesh.position.fromArray(bone.nextJoint);
231 | ++j;
232 | mesh = this.fingerMeshes[i][j];
233 | mesh.position.fromArray(bone.center());
234 | mesh.setRotationFromMatrix((new THREE.Matrix4).fromArray(bone.matrix()));
235 | mesh.quaternion.multiply(baseBoneRotation);
236 | ++j;
237 | }
238 | }
239 | if (this.armMesh) {
240 | this.armMesh.position.fromArray(hand.arm.center());
241 | this.armMesh.setRotationFromMatrix((new THREE.Matrix4).fromArray(hand.arm.matrix()));
242 | this.armMesh.quaternion.multiply(baseBoneRotation);
243 | }
244 | return this;
245 | };
246 |
247 | HandMesh.prototype.setVisibility = function(visible) {
248 | var i, j, _i, _j, _results;
249 | for (i = _i = 0; _i < 5; i = ++_i) {
250 | j = 0;
251 | while (true) {
252 | this.fingerMeshes[i][j].visible = visible;
253 | ++j;
254 | if (j === this.fingerMeshes[i].length) {
255 | break;
256 | }
257 | }
258 | }
259 | if (scope.arm) {
260 | _results = [];
261 | for (i = _j = 0; _j <= 3; i = ++_j) {
262 | this.armBones[i].visible = visible;
263 | _results.push(this.armSpheres[i].visible = visible);
264 | }
265 | return _results;
266 | }
267 | };
268 |
269 | HandMesh.prototype.show = function() {
270 | return this.setVisibility(true);
271 | };
272 |
273 | HandMesh.prototype.hide = function() {
274 | return this.setVisibility(false);
275 | };
276 |
277 | return HandMesh;
278 |
279 | })();
280 |
281 | onHand = function(hand) {
282 | var handMesh;
283 | if (!scope.scene) {
284 | return;
285 | }
286 | handMesh = hand.data('handMesh');
287 | if (!handMesh) {
288 | handMesh = HandMesh.get().scaleTo(hand);
289 | hand.data('handMesh', handMesh);
290 | if (HandMesh.onMeshUsed) {
291 | HandMesh.onMeshUsed(handMesh);
292 | }
293 | }
294 | return handMesh.formTo(hand);
295 | };
296 |
297 | boneHandLost = function(hand) {
298 | var handMesh;
299 | handMesh = hand.data('handMesh');
300 | if (handMesh) {
301 | handMesh.replace();
302 | }
303 | return handMesh = hand.data('handMesh', null);
304 | };
305 |
306 | Leap.plugin('boneHand', function(options) {
307 | var controller, scale;
308 | if (options == null) {
309 | options = {};
310 | }
311 | scope = options;
312 | controller = this;
313 | jointColor = (new THREE.Color).setHex(0x5daa00);
314 | boneColor = (new THREE.Color).setHex(0xffffff);
315 | scope.boneScale && (boneScale = scope.boneScale);
316 | scope.jointScale && (jointScale = scope.jointScale);
317 | scope.boneColor && (boneColor = scope.boneColor);
318 | scope.jointColor && (jointColor = scope.jointColor);
319 | scope.HandMesh = HandMesh;
320 | scope.addShadowCamera = function() {
321 | scope.light = new THREE.SpotLight(0xffffff, 1);
322 | scope.light.castShadow = true;
323 | scope.light.shadow.mapSize.width = 1024;
324 | scope.light.shadow.mapSize.height = 1024;
325 | scope.light.shadow.camera.near = 0.5 / 0.001;
326 | scope.light.shadow.camera.far = 3 / 0.001;
327 | scope.light.position.set(0, 1000, 1000);
328 | scope.light.target.position.set(0, 0, -1000);
329 | scope.camera.add(scope.light.target);
330 | scope.camera.add(scope.light);
331 | if (controller.plugins.transform) {
332 | if (controller.plugins.transform.getScale()) {
333 | scope.light.shadowCameraNear *= controller.plugins.transform.scale.x;
334 | scope.light.shadowCameraFar *= controller.plugins.transform.scale.x;
335 | scope.light.target.position.multiply(controller.plugins.transform.scale);
336 | scope.light.position.multiply(controller.plugins.transform.scale);
337 | }
338 | if (controller.plugins.transform.vr === true) {
339 | scope.camera.position.set(0, 0, 0);
340 | }
341 | if (controller.plugins.transform.vr === 'desktop') {
342 | return scope.camera.position.set(0, 0.15, 0.3);
343 | }
344 | }
345 | };
346 | baseBoneRotation = (new THREE.Quaternion).setFromEuler(new THREE.Euler(Math.PI / 2, 0, 0));
347 | boneScale = 1 / 6;
348 | jointScale = 1 / 5;
349 | boneRadius = null;
350 | jointRadius = null;
351 | material = null;
352 | armTopAndBottomRotation = (new THREE.Quaternion).setFromEuler(new THREE.Euler(0, 0, Math.PI / 2));
353 | HandMesh.onMeshCreated = function(mesh) {
354 | return controller.emit('handMeshCreated', mesh);
355 | };
356 | HandMesh.onMeshUsed = function(mesh) {
357 | return controller.emit('handMeshUsed', mesh);
358 | };
359 | this.use('handEntry');
360 | this.use('handHold');
361 | if (scope.scene === void 0) {
362 | console.assert(scope.targetEl);
363 | if (this.plugins.transform && this.plugins.transform.getScale()) {
364 | scale = this.plugins.transform.scale.x;
365 | }
366 | initScene(scope.targetEl, scale);
367 | scope.addShadowCamera();
368 | }
369 | if (scope.scene) {
370 | HandMesh.create();
371 | HandMesh.create();
372 | if (Leap.version.major === 0 && Leap.version.minor < 7 && Leap.version.dot < 4) {
373 | console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have " + Leap.version.full);
374 | }
375 | this.on('frameEnd', function(timestamp) {
376 | if (scope.render) {
377 | return scope.render(timestamp);
378 | }
379 | });
380 | }
381 | this.on('handLost', boneHandLost);
382 | return {
383 | hand: onHand
384 | };
385 | });
386 |
387 | }).call(this);
388 |
--------------------------------------------------------------------------------
/main/bone-hand/lib/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 | /*global THREE, console */
9 |
10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains
11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
12 | // supported.
13 | //
14 | // Orbit - left mouse / touch: one finger move
15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
16 | // Pan - right mouse, or arrow keys / touch: three finter swipe
17 | //
18 | // This is a drop-in replacement for (most) TrackballControls used in examples.
19 | // That is, include this js file and wherever you see:
20 | // controls = new THREE.TrackballControls( camera );
21 | // controls.target.z = 150;
22 | // Simple substitute "OrbitControls" and the control should work as-is.
23 |
24 | THREE.OrbitControls = function ( object, domElement ) {
25 |
26 | this.object = object;
27 | this.domElement = ( domElement !== undefined ) ? domElement : document;
28 |
29 | // API
30 |
31 | // Set to false to disable this control
32 | this.enabled = true;
33 |
34 | // "target" sets the location of focus, where the control orbits around
35 | // and where it pans with respect to.
36 | this.target = new THREE.Vector3();
37 |
38 | // center is old, deprecated; use "target" instead
39 | this.center = this.target;
40 |
41 | // This option actually enables dollying in and out; left as "zoom" for
42 | // backwards compatibility
43 | this.noZoom = false;
44 | this.zoomSpeed = 1.0;
45 |
46 | // Limits to how far you can dolly in and out
47 | this.minDistance = 0;
48 | this.maxDistance = Infinity;
49 |
50 | // Set to true to disable this control
51 | this.noRotate = false;
52 | this.rotateSpeed = 1.0;
53 |
54 | // Set to true to disable this control
55 | this.noPan = false;
56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
57 |
58 | // Set to true to automatically rotate around the target
59 | this.autoRotate = false;
60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
61 |
62 | // How far you can orbit vertically, upper and lower limits.
63 | // Range is 0 to Math.PI radians.
64 | this.minPolarAngle = 0; // radians
65 | this.maxPolarAngle = Math.PI; // radians
66 |
67 | // Set to true to disable use of the keys
68 | this.noKeys = false;
69 |
70 | // The four arrow keys
71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
72 |
73 | ////////////
74 | // internals
75 |
76 | var scope = this;
77 |
78 | var EPS = 0.000001;
79 |
80 | var rotateStart = new THREE.Vector2();
81 | var rotateEnd = new THREE.Vector2();
82 | var rotateDelta = new THREE.Vector2();
83 |
84 | var panStart = new THREE.Vector2();
85 | var panEnd = new THREE.Vector2();
86 | var panDelta = new THREE.Vector2();
87 | var panOffset = new THREE.Vector3();
88 |
89 | var offset = new THREE.Vector3();
90 |
91 | var dollyStart = new THREE.Vector2();
92 | var dollyEnd = new THREE.Vector2();
93 | var dollyDelta = new THREE.Vector2();
94 |
95 | var phiDelta = 0;
96 | var thetaDelta = 0;
97 | var scale = 1;
98 | var pan = new THREE.Vector3();
99 |
100 | var lastPosition = new THREE.Vector3();
101 | var lastQuaternion = new THREE.Quaternion();
102 |
103 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
104 |
105 | var state = STATE.NONE;
106 |
107 | // for reset
108 |
109 | this.target0 = this.target.clone();
110 | this.position0 = this.object.position.clone();
111 |
112 | // so camera.up is the orbit axis
113 |
114 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
115 | var quatInverse = quat.clone().inverse();
116 |
117 | // events
118 |
119 | var changeEvent = { type: 'change' };
120 | var startEvent = { type: 'start'};
121 | var endEvent = { type: 'end'};
122 |
123 | this.rotateLeft = function ( angle ) {
124 |
125 | if ( angle === undefined ) {
126 |
127 | angle = getAutoRotationAngle();
128 |
129 | }
130 |
131 | thetaDelta -= angle;
132 |
133 | };
134 |
135 | this.rotateUp = function ( angle ) {
136 |
137 | if ( angle === undefined ) {
138 |
139 | angle = getAutoRotationAngle();
140 |
141 | }
142 |
143 | phiDelta -= angle;
144 |
145 | };
146 |
147 | // pass in distance in world space to move left
148 | this.panLeft = function ( distance ) {
149 |
150 | var te = this.object.matrix.elements;
151 |
152 | // get X column of matrix
153 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
154 | panOffset.multiplyScalar( - distance );
155 |
156 | pan.add( panOffset );
157 |
158 | };
159 |
160 | // pass in distance in world space to move up
161 | this.panUp = function ( distance ) {
162 |
163 | var te = this.object.matrix.elements;
164 |
165 | // get Y column of matrix
166 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
167 | panOffset.multiplyScalar( distance );
168 |
169 | pan.add( panOffset );
170 |
171 | };
172 |
173 | // pass in x,y of change desired in pixel space,
174 | // right and down are positive
175 | this.pan = function ( deltaX, deltaY ) {
176 |
177 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
178 |
179 | if ( scope.object.fov !== undefined ) {
180 |
181 | // perspective
182 | var position = scope.object.position;
183 | var offset = position.clone().sub( scope.target );
184 | var targetDistance = offset.length();
185 |
186 | // half of the fov is center to top of screen
187 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
188 |
189 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
190 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
191 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
192 |
193 | } else if ( scope.object.top !== undefined ) {
194 |
195 | // orthographic
196 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
197 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
198 |
199 | } else {
200 |
201 | // camera neither orthographic or perspective
202 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
203 |
204 | }
205 |
206 | };
207 |
208 | this.dollyIn = function ( dollyScale ) {
209 |
210 | if ( dollyScale === undefined ) {
211 |
212 | dollyScale = getZoomScale();
213 |
214 | }
215 |
216 | scale /= dollyScale;
217 |
218 | };
219 |
220 | this.dollyOut = function ( dollyScale ) {
221 |
222 | if ( dollyScale === undefined ) {
223 |
224 | dollyScale = getZoomScale();
225 |
226 | }
227 |
228 | scale *= dollyScale;
229 |
230 | };
231 |
232 | this.update = function () {
233 |
234 | var position = this.object.position;
235 |
236 | offset.copy( position ).sub( this.target );
237 |
238 | // rotate offset to "y-axis-is-up" space
239 | offset.applyQuaternion( quat );
240 |
241 | // angle from z-axis around y-axis
242 |
243 | var theta = Math.atan2( offset.x, offset.z );
244 |
245 | // angle from y-axis
246 |
247 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
248 |
249 | if ( this.autoRotate ) {
250 |
251 | this.rotateLeft( getAutoRotationAngle() );
252 |
253 | }
254 |
255 | theta += thetaDelta;
256 | phi += phiDelta;
257 |
258 | // restrict phi to be between desired limits
259 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
260 |
261 | // restrict phi to be betwee EPS and PI-EPS
262 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
263 |
264 | var radius = offset.length() * scale;
265 |
266 | // restrict radius to be between desired limits
267 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
268 |
269 | // move target to panned location
270 | this.target.add( pan );
271 |
272 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
273 | offset.y = radius * Math.cos( phi );
274 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
275 |
276 | // rotate offset back to "camera-up-vector-is-up" space
277 | offset.applyQuaternion( quatInverse );
278 |
279 | position.copy( this.target ).add( offset );
280 |
281 | this.object.lookAt( this.target );
282 |
283 | thetaDelta = 0;
284 | phiDelta = 0;
285 | scale = 1;
286 | pan.set( 0, 0, 0 );
287 |
288 | // update condition is:
289 | // min(camera displacement, camera rotation in radians)^2 > EPS
290 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
291 |
292 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS
293 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) {
294 |
295 | this.dispatchEvent( changeEvent );
296 |
297 | lastPosition.copy( this.object.position );
298 | lastQuaternion.copy (this.object.quaternion );
299 |
300 | }
301 |
302 | };
303 |
304 |
305 | this.reset = function () {
306 |
307 | state = STATE.NONE;
308 |
309 | this.target.copy( this.target0 );
310 | this.object.position.copy( this.position0 );
311 |
312 | this.update();
313 |
314 | };
315 |
316 | function getAutoRotationAngle() {
317 |
318 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
319 |
320 | }
321 |
322 | function getZoomScale() {
323 |
324 | return Math.pow( 0.95, scope.zoomSpeed );
325 |
326 | }
327 |
328 | function onMouseDown( event ) {
329 |
330 | if ( scope.enabled === false ) return;
331 | event.preventDefault();
332 |
333 | if ( event.button === 0 ) {
334 | if ( scope.noRotate === true ) return;
335 |
336 | state = STATE.ROTATE;
337 |
338 | rotateStart.set( event.clientX, event.clientY );
339 |
340 | } else if ( event.button === 1 ) {
341 | if ( scope.noZoom === true ) return;
342 |
343 | state = STATE.DOLLY;
344 |
345 | dollyStart.set( event.clientX, event.clientY );
346 |
347 | } else if ( event.button === 2 ) {
348 | if ( scope.noPan === true ) return;
349 |
350 | state = STATE.PAN;
351 |
352 | panStart.set( event.clientX, event.clientY );
353 |
354 | }
355 |
356 | document.addEventListener( 'mousemove', onMouseMove, false );
357 | document.addEventListener( 'mouseup', onMouseUp, false );
358 | scope.dispatchEvent( startEvent );
359 |
360 | }
361 |
362 | function onMouseMove( event ) {
363 |
364 | if ( scope.enabled === false ) return;
365 |
366 | event.preventDefault();
367 |
368 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
369 |
370 | if ( state === STATE.ROTATE ) {
371 |
372 | if ( scope.noRotate === true ) return;
373 |
374 | rotateEnd.set( event.clientX, event.clientY );
375 | rotateDelta.subVectors( rotateEnd, rotateStart );
376 |
377 | // rotating across whole screen goes 360 degrees around
378 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
379 |
380 | // rotating up and down along whole screen attempts to go 360, but limited to 180
381 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
382 |
383 | rotateStart.copy( rotateEnd );
384 |
385 | } else if ( state === STATE.DOLLY ) {
386 |
387 | if ( scope.noZoom === true ) return;
388 |
389 | dollyEnd.set( event.clientX, event.clientY );
390 | dollyDelta.subVectors( dollyEnd, dollyStart );
391 |
392 | if ( dollyDelta.y > 0 ) {
393 |
394 | scope.dollyIn();
395 |
396 | } else {
397 |
398 | scope.dollyOut();
399 |
400 | }
401 |
402 | dollyStart.copy( dollyEnd );
403 |
404 | } else if ( state === STATE.PAN ) {
405 |
406 | if ( scope.noPan === true ) return;
407 |
408 | panEnd.set( event.clientX, event.clientY );
409 | panDelta.subVectors( panEnd, panStart );
410 |
411 | scope.pan( panDelta.x, panDelta.y );
412 |
413 | panStart.copy( panEnd );
414 |
415 | }
416 |
417 | scope.update();
418 |
419 | }
420 |
421 | function onMouseUp( /* event */ ) {
422 |
423 | if ( scope.enabled === false ) return;
424 |
425 | document.removeEventListener( 'mousemove', onMouseMove, false );
426 | document.removeEventListener( 'mouseup', onMouseUp, false );
427 | scope.dispatchEvent( endEvent );
428 | state = STATE.NONE;
429 |
430 | }
431 |
432 | function onMouseWheel( event ) {
433 |
434 | if ( scope.enabled === false || scope.noZoom === true ) return;
435 |
436 | event.preventDefault();
437 | event.stopPropagation();
438 |
439 | var delta = 0;
440 |
441 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
442 |
443 | delta = event.wheelDelta;
444 |
445 | } else if ( event.detail !== undefined ) { // Firefox
446 |
447 | delta = - event.detail;
448 |
449 | }
450 |
451 | if ( delta > 0 ) {
452 |
453 | scope.dollyOut();
454 |
455 | } else {
456 |
457 | scope.dollyIn();
458 |
459 | }
460 |
461 | scope.update();
462 | scope.dispatchEvent( startEvent );
463 | scope.dispatchEvent( endEvent );
464 |
465 | }
466 |
467 | function onKeyDown( event ) {
468 |
469 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
470 |
471 | switch ( event.keyCode ) {
472 |
473 | case scope.keys.UP:
474 | scope.pan( 0, scope.keyPanSpeed );
475 | scope.update();
476 | break;
477 |
478 | case scope.keys.BOTTOM:
479 | scope.pan( 0, - scope.keyPanSpeed );
480 | scope.update();
481 | break;
482 |
483 | case scope.keys.LEFT:
484 | scope.pan( scope.keyPanSpeed, 0 );
485 | scope.update();
486 | break;
487 |
488 | case scope.keys.RIGHT:
489 | scope.pan( - scope.keyPanSpeed, 0 );
490 | scope.update();
491 | break;
492 |
493 | }
494 |
495 | }
496 |
497 | function touchstart( event ) {
498 |
499 | if ( scope.enabled === false ) return;
500 |
501 | switch ( event.touches.length ) {
502 |
503 | case 1: // one-fingered touch: rotate
504 |
505 | if ( scope.noRotate === true ) return;
506 |
507 | state = STATE.TOUCH_ROTATE;
508 |
509 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
510 | break;
511 |
512 | case 2: // two-fingered touch: dolly
513 |
514 | if ( scope.noZoom === true ) return;
515 |
516 | state = STATE.TOUCH_DOLLY;
517 |
518 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
519 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
520 | var distance = Math.sqrt( dx * dx + dy * dy );
521 | dollyStart.set( 0, distance );
522 | break;
523 |
524 | case 3: // three-fingered touch: pan
525 |
526 | if ( scope.noPan === true ) return;
527 |
528 | state = STATE.TOUCH_PAN;
529 |
530 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
531 | break;
532 |
533 | default:
534 |
535 | state = STATE.NONE;
536 |
537 | }
538 |
539 | scope.dispatchEvent( startEvent );
540 |
541 | }
542 |
543 | function touchmove( event ) {
544 |
545 | if ( scope.enabled === false ) return;
546 |
547 | event.preventDefault();
548 | event.stopPropagation();
549 |
550 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
551 |
552 | switch ( event.touches.length ) {
553 |
554 | case 1: // one-fingered touch: rotate
555 |
556 | if ( scope.noRotate === true ) return;
557 | if ( state !== STATE.TOUCH_ROTATE ) return;
558 |
559 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
560 | rotateDelta.subVectors( rotateEnd, rotateStart );
561 |
562 | // rotating across whole screen goes 360 degrees around
563 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
564 | // rotating up and down along whole screen attempts to go 360, but limited to 180
565 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
566 |
567 | rotateStart.copy( rotateEnd );
568 |
569 | scope.update();
570 | break;
571 |
572 | case 2: // two-fingered touch: dolly
573 |
574 | if ( scope.noZoom === true ) return;
575 | if ( state !== STATE.TOUCH_DOLLY ) return;
576 |
577 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
578 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
579 | var distance = Math.sqrt( dx * dx + dy * dy );
580 |
581 | dollyEnd.set( 0, distance );
582 | dollyDelta.subVectors( dollyEnd, dollyStart );
583 |
584 | if ( dollyDelta.y > 0 ) {
585 |
586 | scope.dollyOut();
587 |
588 | } else {
589 |
590 | scope.dollyIn();
591 |
592 | }
593 |
594 | dollyStart.copy( dollyEnd );
595 |
596 | scope.update();
597 | break;
598 |
599 | case 3: // three-fingered touch: pan
600 |
601 | if ( scope.noPan === true ) return;
602 | if ( state !== STATE.TOUCH_PAN ) return;
603 |
604 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
605 | panDelta.subVectors( panEnd, panStart );
606 |
607 | scope.pan( panDelta.x, panDelta.y );
608 |
609 | panStart.copy( panEnd );
610 |
611 | scope.update();
612 | break;
613 |
614 | default:
615 |
616 | state = STATE.NONE;
617 |
618 | }
619 |
620 | }
621 |
622 | function touchend( /* event */ ) {
623 |
624 | if ( scope.enabled === false ) return;
625 |
626 | scope.dispatchEvent( endEvent );
627 | state = STATE.NONE;
628 |
629 | }
630 |
631 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
632 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
633 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
634 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
635 |
636 | this.domElement.addEventListener( 'touchstart', touchstart, false );
637 | this.domElement.addEventListener( 'touchend', touchend, false );
638 | this.domElement.addEventListener( 'touchmove', touchmove, false );
639 |
640 | window.addEventListener( 'keydown', onKeyDown, false );
641 |
642 | // force an update at start
643 | this.update();
644 |
645 | };
646 |
647 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
648 |
--------------------------------------------------------------------------------
/main/leap-plugins-0.1.12.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LeapJS-Plugins - v0.1.12 - 2016-11-16
3 | * http://github.com/leapmotion/leapjs-plugins/
4 | *
5 | * Copyright 2016 LeapMotion, Inc
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | *
19 | */
20 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;o=null,b="undefined"!=typeof require?require("three"):window.THREE,i=function(a,c){var d,e,f,g,h,i;return o.scene=new b.Scene,o.rendererOps||(o.rendererOps={}),void 0===o.rendererOps.alpha&&(o.rendererOps.alpha=!0),o.renderer=h=new b.WebGLRenderer(o.rendererOps),i=o.width||window.innerWidth,f=o.height||window.innerHeight,h.setClearColor(0,0),h.setSize(i,f),h.domElement.className="leap-boneHand",h.shadowMap.enabled=!0,h.shadowMap.type=b.PCFSoftShadowMap,a.appendChild(h.domElement),g=1,e=1e4,c&&(g*=c,e*=c),o.camera=d=new b.PerspectiveCamera(45,i/f,g,e),d.position.set(0,300,500),d.lookAt(new b.Vector3(0,160,0)),o.scene.add(d),o.width||o.height||window.addEventListener("resize",function(){return i=window.innerWidth,f=window.innerHeight,d.aspect=i/f,d.updateProjectionMatrix(),h.setSize(i,f),h.render(o.scene,d)},!1),o.render||(o.render=function(a){return h.render(o.scene,o.camera)}),o.render()},d=null,j=null,e=null,h=null,l=null,g=null,k=null,m=null,c=null,a=function(){function a(){var a,d,f,i,n,p,q,r,s;for(m=isNaN(o.opacity)?new b.MeshPhongMaterial({fog:!1}):new b.MeshPhongMaterial({fog:!1,transparent:!0,opacity:o.opacity}),g=40*h,k=40*l,this.fingerMeshes=[],f=p=0;5>p;f=++p){for(d=[],a=0===f?3:4,i=q=0;a>=0?a>q:q>a;i=a>=0?++q:--q)n=new b.Mesh(new b.SphereGeometry(k,32,32),m.clone()),n.name="hand-bone-"+i,n.material.color.copy(j),n.renderOrder=(9*f+2*i)/36,n.castShadow=!0,o.scene.add(n),d.push(n),n=new b.Mesh(new b.CylinderGeometry(g,g,40,32),m.clone()),n.name="hand-joint-"+i,n.material.color.copy(e),n.renderOrder=(9*f+2*i+1)/36,n.castShadow=!0,o.scene.add(n),d.push(n);n=new b.Mesh(new b.SphereGeometry(k,32,32),m.clone()),n.material.color.copy(j),n.castShadow=!0,o.scene.add(n),d.push(n),this.fingerMeshes.push(d)}if(o.arm){for(this.armMesh=new b.Object3D,this.armBones=[],this.armSpheres=[],f=r=0;3>=r;f=++r)this.armBones.push(new b.Mesh(new b.CylinderGeometry(g,g,2>f?1e3:100,32),m.clone())),this.armBones[f].material.color.copy(e),this.armBones[f].castShadow=!0,this.armBones[f].name="ArmBone"+f,f>1&&this.armBones[f].quaternion.multiply(c),this.armMesh.add(this.armBones[f]);for(this.armSpheres=[],f=s=0;3>=s;f=++s)this.armSpheres.push(new b.Mesh(new b.SphereGeometry(k,32,32),m.clone())),this.armSpheres[f].material.color.copy(j),this.armSpheres[f].castShadow=!0,this.armSpheres[f].name="ArmSphere"+f,this.armMesh.add(this.armSpheres[f]);o.scene.add(this.armMesh)}}return a.unusedHandMeshes=[],a.get=function(){var b;return 0===a.unusedHandMeshes.length&&(b=a.create()),b=a.unusedHandMeshes.pop(),b.show(),b},a.prototype.replace=function(){return this.hide(),a.unusedHandMeshes.push(this)},a.create=function(){var b;return b=new a,b.setVisibility(!1),a.unusedHandMeshes.push(b),a.onMeshCreated&&a.onMeshCreated(b),b},a.prototype.traverse=function(a){var b,c,d,e,f,g;for(b=d=0;5>d;b=++d)for(g=this.fingerMeshes[b],e=0,f=g.length;f>e;e++)c=g[e],a(c);return this.armMesh&&this.armMesh.traverse(a)},a.prototype.scaleTo=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;for(d=a.middleFinger.proximal.length/this.fingerMeshes[2][1].geometry.parameters.height,j=m=0;5>m;j=++m)for(g=a.fingers[j],k=0;;){if(k===this.fingerMeshes[j].length-1){l=this.fingerMeshes[j][k],l.scale.set(d,d,d);break}e=g.bones[3-k/2],l=this.fingerMeshes[j][k],l.scale.set(d,d,d),k++,l=this.fingerMeshes[j][k],h=e.length/l.geometry.parameters.height,l.scale.set(d,h,d),k++}if(o.arm){for(b=a.arm.length/(this.armBones[0].geometry.parameters.height+this.armBones[0].geometry.parameters.radiusTop),c=a.arm.width/(this.armBones[2].geometry.parameters.height+this.armBones[2].geometry.parameters.radiusTop),j=n=0;3>=n;j=++n)this.armBones[j].scale.set(d,2>j?b:c,d),this.armSpheres[j].scale.set(d,d,d);f=a.arm.width/2*.85,i=a.arm.length/2,this.armBones[0].position.setX(f),this.armBones[1].position.setX(-f),this.armBones[2].position.setY(i),this.armBones[3].position.setY(-i),this.armSpheres[0].position.set(-f,i,0),this.armSpheres[1].position.set(f,i,0),this.armSpheres[2].position.set(f,-i,0),this.armSpheres[3].position.set(-f,-i,0)}return this},a.prototype.formTo=function(a){var c,e,f,g,h,i;for(f=i=0;5>i;f=++i)for(e=a.fingers[f],g=0;;){if(g===this.fingerMeshes[f].length-1){h=this.fingerMeshes[f][g],h.position.fromArray(c.prevJoint);break}c=e.bones[3-g/2],h=this.fingerMeshes[f][g],h.position.fromArray(c.nextJoint),++g,h=this.fingerMeshes[f][g],h.position.fromArray(c.center()),h.setRotationFromMatrix((new b.Matrix4).fromArray(c.matrix())),h.quaternion.multiply(d),++g}return this.armMesh&&(this.armMesh.position.fromArray(a.arm.center()),this.armMesh.setRotationFromMatrix((new b.Matrix4).fromArray(a.arm.matrix())),this.armMesh.quaternion.multiply(d)),this},a.prototype.setVisibility=function(a){var b,c,d,e,f;for(b=d=0;5>d;b=++d)for(c=0;;)if(this.fingerMeshes[b][c].visible=a,++c,c===this.fingerMeshes[b].length)break;if(o.arm){for(f=[],b=e=0;3>=e;b=++e)this.armBones[b].visible=a,f.push(this.armSpheres[b].visible=a);return f}},a.prototype.show=function(){return this.setVisibility(!0)},a.prototype.hide=function(){return this.setVisibility(!1)},a}(),n=function(b){var c;if(o.scene)return c=b.data("handMesh"),c||(c=a.get().scaleTo(b),b.data("handMesh",c),a.onMeshUsed&&a.onMeshUsed(c)),c.formTo(b)},f=function(a){var b;return b=a.data("handMesh"),b&&b.replace(),b=a.data("handMesh",null)},Leap.plugin("boneHand",function(p){var q,r;return null==p&&(p={}),o=p,q=this,j=(new b.Color).setHex(6138368),e=(new b.Color).setHex(16777215),o.boneScale&&(h=o.boneScale),o.jointScale&&(l=o.jointScale),o.boneColor&&(e=o.boneColor),o.jointColor&&(j=o.jointColor),o.HandMesh=a,o.addShadowCamera=function(){return o.light=new b.SpotLight(16777215,1),o.light.castShadow=!0,o.light.shadow.mapSize.width=1024,o.light.shadow.mapSize.height=1024,o.light.shadow.camera.near=500,o.light.shadow.camera.far=3e3,o.light.position.set(0,1e3,1e3),o.light.target.position.set(0,0,-1e3),o.camera.add(o.light.target),o.camera.add(o.light),q.plugins.transform&&(q.plugins.transform.getScale()&&(o.light.shadowCameraNear*=q.plugins.transform.scale.x,o.light.shadowCameraFar*=q.plugins.transform.scale.x,o.light.target.position.multiply(q.plugins.transform.scale),o.light.position.multiply(q.plugins.transform.scale)),q.plugins.transform.vr===!0&&o.camera.position.set(0,0,0),"desktop"===q.plugins.transform.vr)?o.camera.position.set(0,.15,.3):void 0},d=(new b.Quaternion).setFromEuler(new b.Euler(Math.PI/2,0,0)),h=1/6,l=.2,g=null,k=null,m=null,c=(new b.Quaternion).setFromEuler(new b.Euler(0,0,Math.PI/2)),a.onMeshCreated=function(a){return q.emit("handMeshCreated",a)},a.onMeshUsed=function(a){return q.emit("handMeshUsed",a)},this.use("handEntry"),this.use("handHold"),void 0===o.scene&&(console.assert(o.targetEl),this.plugins.transform&&this.plugins.transform.getScale()&&(r=this.plugins.transform.scale.x),i(o.targetEl,r),o.addShadowCamera()),o.scene&&(a.create(),a.create(),0===Leap.version.major&&Leap.version.minor<7&&Leap.version.dot<4&&console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have "+Leap.version.full),this.on("frameEnd",function(a){return o.render?o.render(a):void 0})),this.on("handLost",f),{hand:n}})}).call(this),function(){var a;if(a=function(){var a;return a=[],0===Leap.version.major&&Leap.version.minor<5&&console.warn("The hand entry plugin requires LeapJS 0.5.0 or newer."),this.on("deviceStopped",function(){for(var b=0,c=a.length;c>b;b++)id=a[b],a.splice(b,1),this.emit("handLost",this.lastConnectionFrame.hand(id)),b--,c--}),{frame:function(b){var c,d,e,f,g;d=b.hands.map(function(a){return a.id});for(var h=0,i=a.length;i>h;h++)c=a[h],-1==d.indexOf(c)&&(a.splice(h,1),this.emit("handLost",this.frame(1).hand(c)),h--,i--);for(g=[],e=0,f=d.length;f>e;e++)c=d[e],-1===a.indexOf(c)?(a.push(c),g.push(this.emit("handFound",b.hand(c)))):g.push(void 0);return g}}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("handEntry",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.handEntry=a}}.call(this),function(){var a;if(a=function(){var a,b;return b={},a=function(a,c,d){var e,f,g,h;if(b[g=a+this.id]||(b[g]=[]),e=b[a+this.id],void 0!==d)return e[c]=d;if("[object String]"==={}.toString.call(c))return e[c];h=[];for(f in c)d=c[f],void 0===d?h.push(delete e[f]):h.push(e[f]=d);return h},{hand:{data:function(b,c){return a.call(this,"h",b,c)},hold:function(a){return a?this.data({holding:a}):this.hold(this.hovering())},holding:function(){return this.data("holding")},release:function(){var a;return a=this.data("holding"),this.data({holding:void 0}),a},hoverFn:function(a){return this.data({getHover:a})},hovering:function(){var a;return(a=this.data("getHover"))?this._hovering||(this._hovering=a.call(this)):void 0}},pointable:{data:function(b,c){return a.call(this,"p",b,c)}}}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("handHold",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.handHold=a}}.call(this),function(a,b){"use strict";function c(a){this.options=a||(a={}),this.loading=!1,this.timeBetweenLoops=a.timeBetweenLoops||50,this.packingStructure=["id","timestamp",{hands:[["id","type","direction","palmNormal","palmPosition","palmVelocity","stabilizedPalmPosition","pinchStrength","grabStrength","confidence","armBasis","armWidth","elbow","wrist"]]},{pointables:[["id","direction","handId","length","stabilizedTipPosition","tipPosition","tipVelocity","tool","carpPosition","mcpPosition","pipPosition","dipPosition","btipPosition","bases","type"]]},{interactionBox:["center","size"]}],this.setFrames(a.frames||[])}var d={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_f:String.fromCharCode,compressToBase64:function(a){if(null==a)return"";var b,c,e,f,g,h,i,j="",k=0;for(a=d.compress(a);k<2*a.length;)k%2==0?(b=a.charCodeAt(k/2)>>8,c=255&a.charCodeAt(k/2),e=k/2+1>8:NaN):(b=255&a.charCodeAt((k-1)/2),(k+1)/2>8,e=255&a.charCodeAt((k+1)/2)):c=e=NaN),k+=3,f=b>>2,g=(3&b)<<4|c>>4,h=(15&c)<<2|e>>6,i=63&e,isNaN(c)?h=i=64:isNaN(e)&&(i=64),j=j+d._keyStr.charAt(f)+d._keyStr.charAt(g)+d._keyStr.charAt(h)+d._keyStr.charAt(i);return j},decompressFromBase64:function(a){if(null==a)return"";var b,c,e,f,g,h,i,j,k="",l=0,m=0,n=d._f;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");m>4,e=(15&h)<<4|i>>2,f=(3&i)<<6|j,l%2==0?(b=c<<8,64!=i&&(k+=n(b|e)),64!=j&&(b=f<<8)):(k+=n(b|c),64!=i&&(b=e<<8),64!=j&&(k+=n(b|f))),l+=3;return d.decompress(k)},compressToUTF16:function(a){if(null==a)return"";var b,c,e,f="",g=0,h=d._f;for(a=d.compress(a),b=0;b>1)+32),e=(1&c)<<14;break;case 1:f+=h(e+(c>>2)+32),e=(3&c)<<13;break;case 2:f+=h(e+(c>>3)+32),e=(7&c)<<12;break;case 3:f+=h(e+(c>>4)+32),e=(15&c)<<11;break;case 4:f+=h(e+(c>>5)+32),e=(31&c)<<10;break;case 5:f+=h(e+(c>>6)+32),e=(63&c)<<9;break;case 6:f+=h(e+(c>>7)+32),e=(127&c)<<8;break;case 7:f+=h(e+(c>>8)+32),e=(255&c)<<7;break;case 8:f+=h(e+(c>>9)+32),e=(511&c)<<6;break;case 9:f+=h(e+(c>>10)+32),e=(1023&c)<<5;break;case 10:f+=h(e+(c>>11)+32),e=(2047&c)<<4;break;case 11:f+=h(e+(c>>12)+32),e=(4095&c)<<3;break;case 12:f+=h(e+(c>>13)+32),e=(8191&c)<<2;break;case 13:f+=h(e+(c>>14)+32),e=(16383&c)<<1;break;case 14:f+=h(e+(c>>15)+32,(32767&c)+32),g=0}return f+h(e+32)},decompressFromUTF16:function(a){if(null==a)return"";for(var b,c,e="",f=0,g=0,h=d._f;g>14),b=(16383&c)<<2;break;case 2:e+=h(b|c>>13),b=(8191&c)<<3;break;case 3:e+=h(b|c>>12),b=(4095&c)<<4;break;case 4:e+=h(b|c>>11),b=(2047&c)<<5;break;case 5:e+=h(b|c>>10),b=(1023&c)<<6;break;case 6:e+=h(b|c>>9),b=(511&c)<<7;break;case 7:e+=h(b|c>>8),b=(255&c)<<8;break;case 8:e+=h(b|c>>7),b=(127&c)<<9;break;case 9:e+=h(b|c>>6),b=(63&c)<<10;break;case 10:e+=h(b|c>>5),b=(31&c)<<11;break;case 11:e+=h(b|c>>4),b=(15&c)<<12;break;case 12:e+=h(b|c>>3),b=(7&c)<<13;break;case 13:e+=h(b|c>>2),b=(3&c)<<14;break;case 14:e+=h(b|c>>1),b=(1&c)<<15;break;case 15:e+=h(b|c),f=0}g++}return d.decompress(e)},compress:function(a){if(null==a)return"";var b,c,e,f={},g={},h="",i="",j="",k=2,l=3,m=2,n="",o=0,p=0,q=d._f;for(e=0;eb;b++)o<<=1,15==p?(p=0,n+=q(o),o=0):p++;for(c=j.charCodeAt(0),b=0;8>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}else{for(c=1,b=0;m>b;b++)o=o<<1|c,15==p?(p=0,n+=q(o),o=0):p++,c=0;for(c=j.charCodeAt(0),b=0;16>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}k--,0==k&&(k=Math.pow(2,m),m++),delete g[j]}else for(c=f[j],b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;k--,0==k&&(k=Math.pow(2,m),m++),f[i]=l++,j=String(h)}if(""!==j){if(Object.prototype.hasOwnProperty.call(g,j)){if(j.charCodeAt(0)<256){for(b=0;m>b;b++)o<<=1,15==p?(p=0,n+=q(o),o=0):p++;for(c=j.charCodeAt(0),b=0;8>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}else{for(c=1,b=0;m>b;b++)o=o<<1|c,15==p?(p=0,n+=q(o),o=0):p++,c=0;for(c=j.charCodeAt(0),b=0;16>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}k--,0==k&&(k=Math.pow(2,m),m++),delete g[j]}else for(c=f[j],b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;k--,0==k&&(k=Math.pow(2,m),m++)}for(c=2,b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;for(;;){if(o<<=1,15==p){n+=q(o);break}p++}return n},decompress:function(a){if(null==a)return"";if(""==a)return null;var b,c,e,f,g,h,i,j,k=[],l=4,m=4,n=3,o="",p="",q=d._f,r={string:a,val:a.charCodeAt(0),position:32768,index:1};for(c=0;3>c;c+=1)k[c]=c;for(f=0,h=Math.pow(2,2),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;switch(b=f){case 0:for(f=0,h=Math.pow(2,8),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;j=q(f);break;case 1:for(f=0,h=Math.pow(2,16),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;j=q(f);break;case 2:return""}for(k[3]=j,e=p=j;;){if(r.index>r.string.length)return"";for(f=0,h=Math.pow(2,n),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;switch(j=f){case 0:for(f=0,h=Math.pow(2,8),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;k[m++]=q(f),j=m-1,l--;break;case 1:for(f=0,h=Math.pow(2,16),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;k[m++]=q(f),j=m-1,l--;break;case 2:return p}if(0==l&&(l=Math.pow(2,n),n++),k[j])o=k[j];else{if(j!==m)return null;o=e+e.charAt(0)}p+=o,k[m++]=e+o.charAt(0),l--,e=o,0==l&&(l=Math.pow(2,n),n++)}}};"undefined"!=typeof module&&null!=module&&(module.exports=d),function(){if("undefined"==typeof a.performance&&(a.performance={}),!a.performance.now){var b=Date.now();performance.timing&&performance.timing.navigationStart&&(b=performance.timing.navigationStart),a.performance.now=function(){return Date.now()-b}}}(),c.prototype={setFrames:function(a){this.frameData=a,this.frameIndex=0,this.frameCount=a.length,this.leftCropPosition=0,this.rightCropPosition=this.frameCount,this.setMetaData()},addFrame:function(a){this.frameData.push(a)},currentFrame:function(){return this.frameData[this.frameIndex]},nextFrame:function(){var a=this.frameIndex+1;return a%=this.rightCropPosition||1,a=this.rightCropPosition&&!this.options.loop?(this.frameIndex--,!1):(this.frameIndex=this.frameIndex%(this.rightCropPosition||1),this.frameIndex=this.rightCropPosition?(this.frameIndex=this.frameIndex%(this.rightCropPosition||1),this.frameIndexn;n++){c=i.hands[n];for(var o=0;l>o;o++)b=g[o],e.hands[n][b]&&f.hands[n]&&Leap.vec3.lerp(c[b],e.hands[n][b],f.hands[n][b],a)}for(n=0;k>n;n++)for(d=i.pointables[n],o=0;m>o;o++)b=h[o],e.pointables[n][b]&&f.hands[n]&&Leap.vec3.lerp(d[b],e.pointables[n][b],f.pointables[n][b],0);return i},timeToNextFrame:function(){var a=(this.nextFrame().timestamp-this.currentFrame().timestamp)/1e3;return 0>a&&(a=this.timeBetweenLoops),a},blank:function(){return 0===this.frameData.length},leftCrop:function(){this.leftCropPosition=this.frameIndex},rightCrop:function(){this.rightCropPosition=this.frameIndex},cullFrames:function(a){console.log("cull frames",a),a||(a=1);for(var b=0;bd;d++)a=b[d],c.push(this.packArray(this.packingStructure,a));return c},packArray:function(a,b){for(var c,d=[],e=0,f=a.length;f>e;e++)if(c=a[e],"string"==typeof c)d.push(b[c]);else if("[object Array]"==Object.prototype.toString.call(c))for(var g=0,h=b.length;h>g;g++)d.push(this.packArray(c,b[g]));else{for(var i in c)break;d.push(this.packArray(c[i],b[i]))}return d},unPackFrameData:function(a){for(var b,c=a[0],d=[],e=1,f=a.length;f>e;e++)b=a[e],d.push(this.unPackArray(c,b));return d},unPackArray:function(a,b){for(var c,d={},e=0,f=a.length;f>e;e++)if(c=a[e],"string"==typeof c)d[c]=b[e];else{if("[object Array]"==Object.prototype.toString.call(c)){for(var g=[],h=0,i=b.length;i>h;h++)g.push(this.unPackArray(c,b[h]));return g}for(var j in c)break;d[j]=this.unPackArray(c[j],b[e])}return d},toHash:function(){return this.setMetaData(),{metadata:this.metadata,frames:this.packedFrameData()}},"export":function(a){var b=JSON.stringify(this.toHash());return"json"==a?b:d.compressToBase64(b)},save:function(a){var b;b=this.metadata.title?this.metadata.title.replace(/\s/g,""):"leap-playback-recording",this.metadata.frameRate&&(b+="-"+Math.round(this.metadata.frameRate)+"fps"),"json"===a?saveAs(new Blob([this["export"]("json")],{type:"text/JSON;charset=utf-8"}),b+".json"):saveAs(new Blob([this["export"]("lz")],{type:"application/x-gzip;charset=utf-8"}),b+".json.lz")},decompress:function(a){return d.decompressFromBase64(a)},loaded:function(){return!(!this.frameData||!this.frameData.length)},loadFrameData:function(a){var b=new XMLHttpRequest,c=this.url,d=this;b.onreadystatechange=function(){b.readyState===b.DONE&&(200===b.status||0===b.status?b.responseText?d.finishLoad(b.responseText,a):console.error('Leap Playback: "'+c+'" seems to be unreachable or the file is empty.'):console.error("Leap Playback: Couldn't load \""+c+'" ('+b.status+")"))},b.addEventListener("progress",function(a){if(d.options.loadProgress&&a.lengthComputable){var b=a.loaded/a.total;d.options.loadProgress(d,b,a)}}),this.loading=!0,b.open("GET",c,!0),b.send(null)},finishLoad:function(a,b){var c=this.url;"lz"==c.split(".")[c.split(".").length-1]&&(a=this.decompress(a)),a=JSON.parse(a),2==a.metadata.formatVersion&&(a.frames=this.unPackFrameData(a.frames)),this.metadata=a.metadata,console.log("Recording loaded:",this.metadata),this.loading=!1,b&&b.call(this,a.frames)},loadCompressedRecording:function(a,b){var c=this.decompress(a);c=JSON.parse(c),2==c.metadata.formatVersion&&(c.frames=this.unPackFrameData(c.frames)),this.metadata=c.metadata,console.log("Recording loaded:",this.metadata),this.loading=!1,b&&b.call(this,c.frames)}},function(){function d(a,b){var c=this;b||(b={}),this.options=b,this.recording=b.recording,this.controller=a,this.resetTimers(),this.setupLoops(),this.controller.connection.on("ready",function(){c.setupProtocols()}),this.userHasControl=!1,b.recording&&("[object String]"==Object.prototype.toString.call(b.recording)&&(b.recording={url:b.recording}),this.setRecording(b.recording)),document.addEventListener("DOMContentLoaded",function(a){document.body.addEventListener("keydown",function(a){a.which===c.options.pauseHotkey&&c.toggle()},!1)})}var e='',f='';
21 | d.prototype={resetTimers:function(){this.timeSinceLastFrame=0,this.lastFrameTime=null},setupLoops:function(){var a=this;this.stepFrameLoop=function(b){"playing"==a.state&&(a.sendFrameAt(b||performance.now()),requestAnimationFrame(a.stepFrameLoop))}},setupProtocols:function(){var a=this;this.stopProtocol=this.controller.connection.protocol,this.playbackProtocol=function(b){var c=a.stopProtocol(b);return c instanceof Leap.Frame?(a.pauseOnHand&&(b.hands.length>0?(a.userHasControl=!0,a.controller.emit("playback.userTakeControl"),a.setGraphic(),a.idle()):0==b.hands.length&&a.userHasControl&&a.resumeOnHandLost&&(a.userHasControl=!1,a.controller.emit("playback.userReleaseControl"),a.setGraphic("wave"))),{type:"playback"}):c},this.recordProtocol=function(b){var c=a.stopProtocol(b);return c instanceof Leap.Frame&&a.recordFrameHandler(b),c};for(var b in this.stopProtocol)this.stopProtocol.hasOwnProperty(b)&&(this.playbackProtocol[b]=this.stopProtocol[b],this.recordProtocol[b]=this.stopProtocol[b]);"playing"==this.state&&(this.controller.connection.protocol=this.playbackProtocol)},sendFrameAt:function(a){this.lastFrameTime&&(a(b=this.recording.timeToNextFrame());)if(this.timeSinceLastFrame-=b,!this.recording.advanceFrame())return this.pause(),void this.controller.emit("playback.playbackFinished",this);this.sendFrame(this.recording.createLerpFrameData(this.timeSinceLastFrame/b))},sendFrame:function(a){if(!a)throw"Frame data not provided";var b=new Leap.Frame(a);return this.controller.processFrame(b),!0},sendImmediateFrame:function(a){if(!a)throw"Frame data not provided";var b=new Leap.Frame(a);return this.controller.processFinishedFrame(b),!0},setFrameIndex:function(a){a!=this.recording.frameIndex&&(this.recording.frameIndex=a%this.recording.frameCount,this.sendFrame(this.recording.currentFrame()))},stop:function(){this.idle(),delete this.recording,this.recording=new c({timeBetweenLoops:this.options.timeBetweenLoops,loop:this.options.loop,requestProtocolVersion:this.controller.connection.opts.requestProtocolVersion,serviceVersion:this.controller.connection.protocol.serviceVersion}),this.controller.emit("playback.stop",this)},pause:function(){this.state="idle",this.hideOverlay(),this.controller.emit("playback.pause",this)},idle:function(){this.state="idle",this.controller.connection.protocol=this.stopProtocol},toggle:function(){"idle"==this.state?this.play():"playing"==this.state&&this.pause()},record:function(){this.clear(),this.stop(),this.state="recording",this.controller.connection.protocol=this.recordProtocol,this.setGraphic("connect"),this.controller.emit("playback.record",this)},clear:function(){if(this.recording&&!this.recording.blank()){var a=this.recording.cloneCurrentFrame();a.hands=[],a.fingers=[],a.pointables=[],a.tools=[],this.sendImmediateFrame(a)}},recordPending:function(){return"recording"==this.state&&this.recording.blank()},isRecording:function(){return"recording"==this.state&&!this.recording.blank()},finishRecording:function(){this.controller.connection.protocol=this.playbackProtocol,this.recording.setFrames(this.recording.frameData),this.controller.emit("playback.recordingFinished",this)},loaded:function(){return this.recording.loaded()},loading:function(){return this.recording.loading},play:function(){if("playing"!==this.state&&!this.loading()&&!this.recording.blank()){this.state="playing",this.controller.connection.protocol=this.playbackProtocol;var a=this;this.controller.connection.removeAllListeners("frame"),this.controller.connection.on("frame",function(b){a.resumeOnHandLost&&a.autoPlay&&"idle"==a.state&&0==b.hands.length&&a.play(),a.controller.processFrame(b)}),this.resetTimers(),this.recording.readyPlay(),this.stepFrameLoop(),this.controller.emit("playback.play",this)}},recordFrameHandler:function(a){this.setGraphic("wave"),a.hands.length>0?(this.recording.addFrame(a),this.hideOverlay()):this.recording.blank()||this.finishRecording()},setRecording:function(a){var b=this;this.pause();var d=function(a){return this.setFrames(a),b.recording!=this?void console.log("recordings changed during load"):(b.autoPlay&&(b.play(),b.pauseOnHand&&!b.controller.streaming()&&b.setGraphic("connect")),void b.controller.emit("playback.recordingSet",this))};return this.recording=a,a instanceof c||(this.recording.__proto__=c.prototype,c.call(this.recording,{timeBetweenLoops:this.options.timeBetweenLoops,loop:this.options.loop,loadProgress:function(a,c,d){b.controller.emit("playback.ajax:progress",a,c,d)}})),this.recording.loaded()?d.call(this.recording,this.recording.frameData):a.url?(this.controller.emit("playback.ajax:begin",this,this.recording),this.recording.loadFrameData(function(a){d.call(this,a),b.controller.emit("playback.ajax:complete",b,this)})):a.compressedRecording&&this.recording.loadCompressedRecording(a.compressedRecording,function(a){d.call(this,a)}),this},hideOverlay:function(){this.overlay&&(this.overlay.style.display="none")},setGraphic:function(a){if(this.overlay&&this.graphicName!=a)switch(this.graphicName=a,a){case"connect":this.overlay.style.display="block",this.overlay.innerHTML=e;break;case"wave":this.overlay.style.display="block",this.overlay.innerHTML=f;break;case b:this.overlay.innerHTML=""}}};var g=function(c){var e=this,f=c.autoPlay;f===b&&(f=!0);var g=c.pauseOnHand;g===b&&(g=!0);var h=c.resumeOnHandLost;h===b&&(h=!0);var i=c.timeBetweenLoops;i===b&&(i=50);var j=c.requiredProtocolVersion,k=c.pauseHotkey;k===b&&(k=32);var l=c.loop;l===b&&(l=!0);var m=c.overlay;m===b&&document.body&&(m=document.createElement("div"),document.body.appendChild(m),m.style.width="100%",m.style.position="absolute",m.style.top="0",m.style.left="-"+a.getComputedStyle(document.body).getPropertyValue("margin"),m.style.padding="10px",m.style.textAlign="center",m.style.fontSize="18px",m.style.opacity="0.8",m.style.display="none",m.style.zIndex="10",m.id="connect-leap",m.style.cursor="pointer",m.addEventListener("click",function(){return this.style.display="none",!1},!1)),c.player=new d(this,{recording:c.recording,loop:l,pauseHotkey:k,timeBetweenLoops:i}),c.player.overlay=m,c.player.pauseOnHand=g,c.player.resumeOnHandLost=h,c.player.requiredProtocolVersion=j,c.player.autoPlay=f;var n=function(){return c.player.pauseOnHand&&e.connection.opts.requestProtocolVersiond;d++)c=b[d],c?f.push(Leap.vec3.transformMat4(c,c,a)):f.push(void 0);return f},d=function(a,b,c){var d,e,f;return d=b[0],e=b[1],f=b[2],a[0]=c[0]*d+c[4]*e+c[8]*f,a[1]=c[1]*d+c[5]*e+c[9]*f,a[2]=c[2]*d+c[6]*e+c[10]*f,a},c=function(a,b){var c,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)c=b[e],c?g.push(d(c,c,a)):g.push(void 0);return g},f=function(a,b,d){var f,g,h,i,j,k,l,m;for(c(b,[a.direction,a.palmNormal,a.palmVelocity,a.arm.basis[0],a.arm.basis[1],a.arm.basis[2]]),l=a.fingers,h=0,j=l.length;j>h;h++)f=l[h],c(b,[f.direction,f.metacarpal.basis[0],f.metacarpal.basis[1],f.metacarpal.basis[2],f.proximal.basis[0],f.proximal.basis[1],f.proximal.basis[2],f.medial.basis[0],f.medial.basis[1],f.medial.basis[2],f.distal.basis[0],f.distal.basis[1],f.distal.basis[2]]);for(Leap.glMatrix.mat4.scale(b,b,d),e(b,[a.palmPosition,a.stabilizedPalmPosition,a.sphereCenter,a.arm.nextJoint,a.arm.prevJoint]),m=a.fingers,i=0,k=m.length;k>i;i++)f=m[i],e(b,[f.carpPosition,f.mcpPosition,f.pipPosition,f.dipPosition,f.distal.nextJoint,f.tipPosition]);return g=(d[0]+d[1]+d[2])/3,a.arm.width*=g},{frame:function(b){var c,d,e,g,h,i,j,k,l,m;if(b.valid&&!b.data.transformed){for(b.data.transformed=!0,k=b.hands,m=[],g=0,i=k.length;i>g;g++){for(d=k[g],f(d,a.getTransform(d),(a.getScale(d)||new THREE.Vector3(1,1,1)).toArray()),a.effectiveParent&&f(d,a.effectiveParent.matrixWorld.elements,a.effectiveParent.scale.toArray()),e=null,l=d.fingers,h=0,j=l.length;j>h;h++)c=l[h],e=Leap.vec3.create(),Leap.vec3.sub(e,c.mcpPosition,c.carpPosition),c.metacarpal.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.pipPosition,c.mcpPosition),c.proximal.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.dipPosition,c.pipPosition),c.medial.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.tipPosition,c.dipPosition),c.distal.length=Leap.vec3.length(e);Leap.vec3.sub(e,d.arm.prevJoint,d.arm.nextJoint),m.push(d.arm.length=Leap.vec3.length(e))}return m}}}})}.call(this),function(){var a;if(a=function(a){return a.alert||(a.alert=!1),a.requiredProtocolVersion||(a.requiredProtocolVersion=6),a.disconnect||(a.disconnect=!0),"undefined"!=typeof Leap&&Leap.Controller&&Leap.version.minor<5&&Leap.version.dot<4&&console.warn("LeapJS Version Check plugin incompatible with LeapJS pre 0.4.4"),this.on("ready",function(){var b,c,d;return d=a.requiredProtocolVersion,b=this.connection.opts.requestProtocolVersion,d>b?(c="Protocol Version too old. v"+d+" required, v"+b+" available.",a.disconnect&&(this.disconnect(),c+=" Disconnecting."),console.warn(c),a.alert&&alert("Your Leap Software version is out of date. Visit http://www.leapmotion.com/setup to update"),this.emit("versionCheck.outdated",{required:d,current:b,disconnect:a.disconnect})):void 0}),{}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("versionCheck",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.versionCheck=a}}.call(this);
--------------------------------------------------------------------------------