git.s-ol.nu leap-finger-scan / e956433
slice preview, localStorage, slice saving s-ol 2 years ago
2 changed file(s) with 78 addition(s) and 29 deletion(s). Raw diff Collapse all Expand all
3535 const camera = new T.PerspectiveCamera(75, 1, 0.1, 1000);
3636 camera.position.set(-30, 400, 80);
3737
38 let fingers;
3839 const orbit = new OrbitControls(camera, canvas);
3940 const transform = new TransformControls(camera, canvas);
41 transform.space = 'local';
4042 transform.addEventListener('dragging-changed', e => { orbit.enabled = !e.value });
43 transform.addEventListener('objectChange', e => fingers.forEach(finger => finger.updateSlice()));
44
4145 scene.add(transform);
4246
4347 window.onresize = () => {
5256 static tipGeom = new T.SphereBufferGeometry(4, 16, 16);
5357 static trackGeom = new T.SphereBufferGeometry(2, 4, 4);
5458 static sliceMaterial = new T.MeshLambertMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 });
59 static trackMaterial = new T.MeshLambertMaterial({ color: 0xff0000 });
5560 static MAX_TRACK = 1 << 10;
5661
5762 constructor(i) {
5863 super();
5964
6065 const color = new T.Color().setHSL(i / 5, .8, .6);
61 this.meshMaterial = new T.MeshLambertMaterial({ color });
62 this.lineMaterial = new T.LineBasicMaterial({ color, linewidth: 2 });
63
64 this.tip = new T.Mesh(Finger.tipGeom, this.meshMaterial);
66 const meshMaterial = new T.MeshLambertMaterial({ color });
67 const trackMaterial = new T.MeshLambertMaterial({ color, transparent: true, opacity: 0.7 });
68 const lineMaterial = new T.LineBasicMaterial({ color, linewidth: 2 });
69
70 this.tip = new T.Mesh(Finger.tipGeom, meshMaterial);
6571 this.add(this.tip);
6672
6773 this.lineGeometry = new T.Geometry();
6874 this.lineGeometry.vertices.push(new T.Vector3(), new T.Vector3(), new T.Vector3());
69 this.skeleton = new T.Line(this.lineGeometry, this.lineMaterial);
75 this.skeleton = new T.Line(this.lineGeometry, lineMaterial);
7076 this.add(this.skeleton);
7177
72 this.trackers = new T.InstancedMesh(Finger.trackGeom.clone(), this.meshMaterial.clone(), Finger.MAX_TRACK);
78 this.trackers = new T.InstancedMesh(Finger.trackGeom.clone(), trackMaterial, Finger.MAX_TRACK);
7379 this.add(this.trackers);
7480
7581 this.dataPoints = [];
114120 this.slice.position.copy(average);
115121 this.add(this.slice);
116122 transform.attach(this.slice);
123
124 this.updateSlice();
117125 } else if (transform.object === this.slice) {
118126 transform.detach();
119127 } else {
122130 }
123131
124132 updateSlice() {
125 const sliceTransform = this.slice.matrixWorld.getInverse();
126 const points = this.dataPoints.reduce((list, point) => {
133 if (!this.slice)
134 return;
135
136 const sliceTransform = new T.Matrix4().getInverse(this.slice.matrixWorld);
137 const points = [];
138 this.dataPoints.map(point => {
127139 const tip = new T.Vector3(...point.tip);
128140
129 tip.applyMatrix4(sliceTransform);
130 if (Math.abs(tip.x) <= 1 && Math.abs(tip.y) <= 1 && Math.abs(tip.z) <= 1) {
131
132 }
133 }, []);
141 const norm = tip.clone().applyMatrix4(sliceTransform);
142 if (Math.abs(norm.x) <= .5 && Math.abs(norm.y) <= .5 && Math.abs(norm.z) <= .5)
143 points.push(tip);
144 });
145
146 if (this.slicedTrackers)
147 this.remove(this.slicedTrackers);
148
149 this.slicedTrackers = new T.InstancedMesh(Finger.trackGeom.clone(), Finger.trackMaterial.clone(), points.length);
150 points.forEach((point, i) => this.slicedTrackers.setMatrixAt(i, new T.Matrix4().setPosition(point)));
151 this.add(this.slicedTrackers);
134152 }
135153
136154 removeSlice() {
142160
143161 this.remove(this.slice);
144162 this.slice = null;
163 if (this.slicedTrackers) {
164 this.remove(this.slicedTrackers);
165 this.slicedTrackers = null;
166 }
145167 }
146168
147169 update(finger) {
173195 }
174196
175197 store() {
176 return this.dataPoints;
177 }
178
179 load(dataPoints) {
180 for (const dp of dataPoints) {
198 return {
199 dataPoints: this.dataPoints,
200 slice: this.slice && {
201 position: this.slice.position.toArray(),
202 rotation: this.slice.rotation.toArray(),
203 scale: this.slice.scale.toArray(),
204 },
205 };
206 }
207
208 load(data) {
209 for (const dp of data.dataPoints) {
181210 this.trackers.setMatrixAt(this.dataPoints.length % Finger.MAX_TRACK, new T.Matrix4().makeTranslation(...dp.tip));
182211 this.dataPoints.push(dp);
183212 }
184213
214 if (data.slice) {
215 this.selectSlice();
216 this.slice.position.fromArray(data.slice.position);
217 this.slice.rotation.fromArray(data.slice.rotation);
218 this.slice.scale.fromArray(data.slice.scale);
219 }
185220 this.trackers.instanceMatrix.needsUpdate = true;
186221 }
187222 }
193228 const palm = new T.Mesh(new T.BoxBufferGeometry(8, 2, 8), material);
194229 scene.add(palm);
195230
196 const fingers = [0,1,2,3,4].map(i => {
231 fingers = [0,1,2,3,4].map(i => {
197232 const finger = new Finger(i);
198233 scene.add(finger);
199234 return finger;
200235 });
201 window.F = fingers;
202
236
237 const restore = (str) => {
238 if (!str)
239 return;
240
241 const data = JSON.parse(str);
242 data.forEach((data, i) => fingers[i].load(data));
243 }
244
245 let viewSliceOnly = false;
203246 window.onkeydown = (e) => {
204247 if (e.key === 'r') {
205248 fingers.forEach(finger => finger.clear());
206 return;
207249 } else if (e.key === 's') {
208250 const data = fingers.map(finger => finger.store());
209 saveAs(new Blob([JSON.stringify(data)]), 'text/json');
251 saveAs(new Blob([JSON.stringify(data)]), 'finger-scan.json');
210252 } else if (e.key === 'l') {
211253 const input = document.createElement('input');
212254 input.type = 'file';
214256
215257 input.onchange = e => {
216258 const reader = new FileReader();
217 reader.onload = e => {
218 const data = JSON.parse(e.target.result);
219 data.forEach((data, i) => fingers[i].load(data));
220 }
259 reader.onload = e => restore(e.target.result);
221260 reader.readAsText(e.target.files[0]);
222261 }
223262 } else if (e.key === 'Shift') {
224263 transform.setMode('rotate');
225264 } else if (e.key === 'Alt') {
226265 transform.setMode('scale');
266 } else if (e.key === ' ') {
267 viewSliceOnly = !viewSliceOnly;
268 fingers.forEach(finger => { finger.trackers.visible = !viewSliceOnly });
227269 }
228270
229271 const i = +e.key;
237279 window.onkeyup = (e) => {
238280 const i = +e.key;
239281
240 if (e.key === 'Shift' || e.key === 'Alt') {
282 if (e.key === 'Shift' || e.key === 'Alt')
241283 transform.setMode('translate');
242 }
243284
244285 if (i > -1 && i < 6) {
245286 e.preventDefault();
260301 fingers[i].update(finger);
261302 }
262303 }
304
305 restore(localStorage.getItem('data'));
306 setInterval(() => {
307 const data = fingers.map(finger => finger.store());
308 localStorage.setItem('data', JSON.stringify(data));
309 }, 2000);
310
263311
264312 const controller = Leap.loop({
265313 frameEventName: 'animationFrame',
8585 <div>
8686 <b>hotkeys &mdash;</b>
8787 <key>1-5</key> track fingers &mdash;
88 <key>space</key> toggle slice data view &mdash;
8889 <key>shift</key> rotate slice &mdash;
8990 <key>alt</key> scale slice &mdash;
9091 <key>S</key> export data &mdash;