slice preview, localStorage, slice saving
s-ol
3 years ago
35 | 35 | const camera = new T.PerspectiveCamera(75, 1, 0.1, 1000); |
36 | 36 | camera.position.set(-30, 400, 80); |
37 | 37 | |
38 | let fingers; | |
38 | 39 | const orbit = new OrbitControls(camera, canvas); |
39 | 40 | const transform = new TransformControls(camera, canvas); |
41 | transform.space = 'local'; | |
40 | 42 | transform.addEventListener('dragging-changed', e => { orbit.enabled = !e.value }); |
43 | transform.addEventListener('objectChange', e => fingers.forEach(finger => finger.updateSlice())); | |
44 | ||
41 | 45 | scene.add(transform); |
42 | 46 | |
43 | 47 | window.onresize = () => { |
52 | 56 | static tipGeom = new T.SphereBufferGeometry(4, 16, 16); |
53 | 57 | static trackGeom = new T.SphereBufferGeometry(2, 4, 4); |
54 | 58 | static sliceMaterial = new T.MeshLambertMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 }); |
59 | static trackMaterial = new T.MeshLambertMaterial({ color: 0xff0000 }); | |
55 | 60 | static MAX_TRACK = 1 << 10; |
56 | 61 | |
57 | 62 | constructor(i) { |
58 | 63 | super(); |
59 | 64 | |
60 | 65 | 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); | |
65 | 71 | this.add(this.tip); |
66 | 72 | |
67 | 73 | this.lineGeometry = new T.Geometry(); |
68 | 74 | 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); | |
70 | 76 | this.add(this.skeleton); |
71 | 77 | |
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); | |
73 | 79 | this.add(this.trackers); |
74 | 80 | |
75 | 81 | this.dataPoints = []; |
114 | 120 | this.slice.position.copy(average); |
115 | 121 | this.add(this.slice); |
116 | 122 | transform.attach(this.slice); |
123 | ||
124 | this.updateSlice(); | |
117 | 125 | } else if (transform.object === this.slice) { |
118 | 126 | transform.detach(); |
119 | 127 | } else { |
122 | 130 | } |
123 | 131 | |
124 | 132 | 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 => { | |
127 | 139 | const tip = new T.Vector3(...point.tip); |
128 | 140 | |
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); | |
134 | 152 | } |
135 | 153 | |
136 | 154 | removeSlice() { |
142 | 160 | |
143 | 161 | this.remove(this.slice); |
144 | 162 | this.slice = null; |
163 | if (this.slicedTrackers) { | |
164 | this.remove(this.slicedTrackers); | |
165 | this.slicedTrackers = null; | |
166 | } | |
145 | 167 | } |
146 | 168 | |
147 | 169 | update(finger) { |
173 | 195 | } |
174 | 196 | |
175 | 197 | 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) { | |
181 | 210 | this.trackers.setMatrixAt(this.dataPoints.length % Finger.MAX_TRACK, new T.Matrix4().makeTranslation(...dp.tip)); |
182 | 211 | this.dataPoints.push(dp); |
183 | 212 | } |
184 | 213 | |
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 | } | |
185 | 220 | this.trackers.instanceMatrix.needsUpdate = true; |
186 | 221 | } |
187 | 222 | } |
193 | 228 | const palm = new T.Mesh(new T.BoxBufferGeometry(8, 2, 8), material); |
194 | 229 | scene.add(palm); |
195 | 230 | |
196 | const fingers = [0,1,2,3,4].map(i => { | |
231 | fingers = [0,1,2,3,4].map(i => { | |
197 | 232 | const finger = new Finger(i); |
198 | 233 | scene.add(finger); |
199 | 234 | return finger; |
200 | 235 | }); |
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; | |
203 | 246 | window.onkeydown = (e) => { |
204 | 247 | if (e.key === 'r') { |
205 | 248 | fingers.forEach(finger => finger.clear()); |
206 | return; | |
207 | 249 | } else if (e.key === 's') { |
208 | 250 | 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'); | |
210 | 252 | } else if (e.key === 'l') { |
211 | 253 | const input = document.createElement('input'); |
212 | 254 | input.type = 'file'; |
214 | 256 | |
215 | 257 | input.onchange = e => { |
216 | 258 | 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); | |
221 | 260 | reader.readAsText(e.target.files[0]); |
222 | 261 | } |
223 | 262 | } else if (e.key === 'Shift') { |
224 | 263 | transform.setMode('rotate'); |
225 | 264 | } else if (e.key === 'Alt') { |
226 | 265 | transform.setMode('scale'); |
266 | } else if (e.key === ' ') { | |
267 | viewSliceOnly = !viewSliceOnly; | |
268 | fingers.forEach(finger => { finger.trackers.visible = !viewSliceOnly }); | |
227 | 269 | } |
228 | 270 | |
229 | 271 | const i = +e.key; |
237 | 279 | window.onkeyup = (e) => { |
238 | 280 | const i = +e.key; |
239 | 281 | |
240 | if (e.key === 'Shift' || e.key === 'Alt') { | |
282 | if (e.key === 'Shift' || e.key === 'Alt') | |
241 | 283 | transform.setMode('translate'); |
242 | } | |
243 | 284 | |
244 | 285 | if (i > -1 && i < 6) { |
245 | 286 | e.preventDefault(); |
260 | 301 | fingers[i].update(finger); |
261 | 302 | } |
262 | 303 | } |
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 | ||
263 | 311 | |
264 | 312 | const controller = Leap.loop({ |
265 | 313 | frameEventName: 'animationFrame', |