0 | 0 |
import Leap from 'leapjs';
|
1 | 1 |
import * as T from 'three';
|
2 | 2 |
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
|
3 |
import { TransformControls } from 'three/examples/jsm/controls/TransformControls';
|
3 | 4 |
import { saveAs } from 'file-saver';
|
4 | 5 |
|
5 | 6 |
const clamp = (val, min=0, max=1) => Math.min(max, Math.max(min, val));
|
|
11 | 12 |
const $ = x => document.getElementById(x);
|
12 | 13 |
|
13 | 14 |
const canvas = $('main');
|
|
15 |
const renderer = new T.WebGLRenderer({ canvas });
|
14 | 16 |
|
15 | 17 |
const scene = new T.Scene();
|
16 | 18 |
scene.background = new T.Color().setHSL(0.7, .95, .93);
|
|
32 | 34 |
|
33 | 35 |
const camera = new T.PerspectiveCamera(75, 1, 0.1, 1000);
|
34 | 36 |
camera.position.set(-30, 400, 80);
|
35 | |
const controls = new OrbitControls(camera, canvas);
|
36 | |
// const context = canvas.getContext('webgl2', { alpha: false });
|
37 | |
const renderer = new T.WebGLRenderer({ canvas }); // , context });
|
|
37 |
|
|
38 |
const orbit = new OrbitControls(camera, canvas);
|
|
39 |
const transform = new TransformControls(camera, canvas);
|
|
40 |
transform.addEventListener('dragging-changed', e => { orbit.enabled = !e.value });
|
|
41 |
scene.add(transform);
|
38 | 42 |
|
39 | 43 |
window.onresize = () => {
|
40 | 44 |
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
|
41 | 45 |
camera.aspect = canvas.offsetWidth / canvas.offsetHeight;
|
42 | 46 |
camera.updateProjectionMatrix();
|
43 | |
controls.update();
|
|
47 |
orbit.update();
|
44 | 48 |
};
|
45 | 49 |
window.onresize();
|
46 | 50 |
|
47 | 51 |
class Finger extends T.Group {
|
48 | 52 |
static tipGeom = new T.SphereBufferGeometry(4, 16, 16);
|
49 | 53 |
static trackGeom = new T.SphereBufferGeometry(2, 4, 4);
|
|
54 |
static sliceMaterial = new T.MeshLambertMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 });
|
50 | 55 |
static MAX_TRACK = 1 << 10;
|
51 | 56 |
|
52 | |
constructor(hue) {
|
|
57 |
constructor(i) {
|
53 | 58 |
super();
|
54 | 59 |
|
55 | |
const color = new T.Color().setHSL(hue || Math.random(), .8, .6);
|
56 | |
const meshMaterial = new T.MeshLambertMaterial({ color });
|
57 | |
const lineMaterial = new T.LineBasicMaterial({ color, linewidth: 2 });
|
58 | |
|
59 | |
this.tip = new T.Mesh(Finger.tipGeom, meshMaterial);
|
|
60 |
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);
|
60 | 65 |
this.add(this.tip);
|
61 | 66 |
|
62 | 67 |
this.lineGeometry = new T.Geometry();
|
63 | 68 |
this.lineGeometry.vertices.push(new T.Vector3(), new T.Vector3(), new T.Vector3());
|
64 | |
this.skeleton = new T.Line(this.lineGeometry, lineMaterial);
|
|
69 |
this.skeleton = new T.Line(this.lineGeometry, this.lineMaterial);
|
65 | 70 |
this.add(this.skeleton);
|
66 | 71 |
|
67 | |
this.trackers = new T.InstancedMesh(Finger.trackGeom.clone(), meshMaterial.clone(), Finger.MAX_TRACK);
|
|
72 |
this.trackers = new T.InstancedMesh(Finger.trackGeom.clone(), this.meshMaterial.clone(), Finger.MAX_TRACK);
|
68 | 73 |
this.add(this.trackers);
|
69 | 74 |
|
70 | 75 |
this.dataPoints = [];
|
71 | 76 |
this.track = false;
|
|
77 |
|
|
78 |
$('header').append(this.createControls(i));
|
|
79 |
}
|
|
80 |
|
|
81 |
createControls(i) {
|
|
82 |
const title = document.createElement('span');
|
|
83 |
title.innerText = `slice ${i}`;
|
|
84 |
|
|
85 |
const select = document.createElement('button');
|
|
86 |
select.innerText = 'select';
|
|
87 |
select.onclick = e => {
|
|
88 |
e.preventDefault();
|
|
89 |
this.selectSlice();
|
|
90 |
}
|
|
91 |
|
|
92 |
const remove = document.createElement('button');
|
|
93 |
remove.innerText = 'remove';
|
|
94 |
remove.onclick = e => {
|
|
95 |
e.preventDefault();
|
|
96 |
this.removeSlice();
|
|
97 |
}
|
|
98 |
|
|
99 |
const controls = document.createElement('div');
|
|
100 |
controls.append(title);
|
|
101 |
controls.append(select);
|
|
102 |
controls.append(remove);
|
|
103 |
return controls;
|
|
104 |
}
|
|
105 |
|
|
106 |
selectSlice() {
|
|
107 |
if (!this.slice) {
|
|
108 |
const average = this.dataPoints.reduce((sum, p) => sum.add(new T.Vector3(...p.dip)), new T.Vector3());
|
|
109 |
if (this.dataPoints.length)
|
|
110 |
average.divideScalar(this.dataPoints.length);
|
|
111 |
|
|
112 |
this.slice = new T.Mesh(new T.BoxGeometry(), Finger.sliceMaterial);
|
|
113 |
this.slice.scale.set(25, 5, 50);
|
|
114 |
this.slice.position.copy(average);
|
|
115 |
this.add(this.slice);
|
|
116 |
transform.attach(this.slice);
|
|
117 |
} else if (transform.object === this.slice) {
|
|
118 |
transform.detach();
|
|
119 |
} else {
|
|
120 |
transform.attach(this.slice);
|
|
121 |
}
|
|
122 |
}
|
|
123 |
|
|
124 |
updateSlice() {
|
|
125 |
const sliceTransform = this.slice.matrixWorld.getInverse();
|
|
126 |
const points = this.dataPoints.reduce((list, point) => {
|
|
127 |
const tip = new T.Vector3(...point.tip);
|
|
128 |
|
|
129 |
tip.applyMatrix4(sliceTransform);
|
|
130 |
if (Math.abs(tip.x) <= 1 && Math.abs(tip.y) <= 1 && Math.abs(tip.z) <= 1) {
|
|
131 |
|
|
132 |
}
|
|
133 |
}, []);
|
|
134 |
}
|
|
135 |
|
|
136 |
removeSlice() {
|
|
137 |
if (!this.slice)
|
|
138 |
return;
|
|
139 |
|
|
140 |
if (transform.object === this.slice)
|
|
141 |
transform.detach();
|
|
142 |
|
|
143 |
this.remove(this.slice);
|
|
144 |
this.slice = null;
|
72 | 145 |
}
|
73 | 146 |
|
74 | 147 |
update(finger) {
|
|
90 | 163 |
}
|
91 | 164 |
|
92 | 165 |
clear() {
|
|
166 |
this.removeSlice();
|
93 | 167 |
this.dataPoints = [];
|
94 | 168 |
for (let i = 0; i < Finger.MAX_TRACK; i++) {
|
95 | 169 |
this.trackers.setMatrixAt(i, new T.Matrix4());
|
|
120 | 194 |
scene.add(palm);
|
121 | 195 |
|
122 | 196 |
const fingers = [0,1,2,3,4].map(i => {
|
123 | |
const finger = new Finger(i / 5);
|
|
197 |
const finger = new Finger(i);
|
124 | 198 |
scene.add(finger);
|
125 | 199 |
return finger;
|
126 | 200 |
});
|
|
128 | 202 |
|
129 | 203 |
window.onkeydown = (e) => {
|
130 | 204 |
if (e.key === 'r') {
|
131 | |
e.preventDefault();
|
132 | 205 |
fingers.forEach(finger => finger.clear());
|
133 | 206 |
return;
|
134 | 207 |
} else if (e.key === 's') {
|
135 | |
e.preventDefault();
|
136 | 208 |
const data = fingers.map(finger => finger.store());
|
137 | 209 |
saveAs(new Blob([JSON.stringify(data)]), 'text/json');
|
138 | 210 |
} else if (e.key === 'l') {
|
139 | |
e.preventDefault();
|
140 | 211 |
const input = document.createElement('input');
|
141 | 212 |
input.type = 'file';
|
142 | 213 |
input.click();
|
|
149 | 220 |
}
|
150 | 221 |
reader.readAsText(e.target.files[0]);
|
151 | 222 |
}
|
|
223 |
} else if (e.key === 'Shift') {
|
|
224 |
transform.setMode('rotate');
|
|
225 |
} else if (e.key === 'Alt') {
|
|
226 |
transform.setMode('scale');
|
152 | 227 |
}
|
153 | 228 |
|
154 | 229 |
const i = +e.key;
|
|
161 | 236 |
|
162 | 237 |
window.onkeyup = (e) => {
|
163 | 238 |
const i = +e.key;
|
|
239 |
|
|
240 |
if (e.key === 'Shift' || e.key === 'Alt') {
|
|
241 |
transform.setMode('translate');
|
|
242 |
}
|
164 | 243 |
|
165 | 244 |
if (i > -1 && i < 6) {
|
166 | 245 |
e.preventDefault();
|
|
190 | 269 |
}, frame => {
|
191 | 270 |
update(frame);
|
192 | 271 |
|
193 | |
controls.update();
|
|
272 |
orbit.update();
|
194 | 273 |
renderer.render(scene, camera);
|
195 | 274 |
});
|
196 | 275 |
|