-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpinch.go
More file actions
358 lines (296 loc) · 9.02 KB
/
pinch.go
File metadata and controls
358 lines (296 loc) · 9.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
package pinch
import (
"fmt"
"log"
"math"
"os"
"time"
"github.com/stuntgoat/circbuf"
"github.com/whoisjake/gomotion"
)
const (
// threshold between 2 pointables to register a pinch
PINCH_DISTANCE_THRESHOLD = 1900
// number of previous pointable objects to store in a circular buffer
// for each finger per hand.
MAX_POINTABLES_PER_HISTORY = 15
// number of times that the last distance between 2 points
// can be greater than the current distance, when checking convergence.
CONVERGENCE_THRESHOLD = 6
DEBUG = false
DEBUG_COLLECT = false
DEBUG_DISTANCE = false
DEBUG_CONVERGENCE = false
DEBUG_PINCH = false
)
var logger = log.New(os.Stderr, "[leap pinch] ", log.LstdFlags|log.Lshortfile)
func Debug(msg string) {
if DEBUG {
logger.Println(msg)
}
}
// MyPointable holds a circular buffer of Pointable objects
type MyPointable struct {
History *circbuf.Circ
lastUpdate time.Time
}
// calculateConvergence takes a MyPointable object and calculates
// the difference between the last number of respective
// pointable object coordinates.
func (mp *MyPointable) CalculateConvergence(p *MyPointable) bool {
// get last point data to confirm if the points converging.
var myA interface{}
var myB interface{}
var dCurrent float64
var dLast float64
var failThreshold int
var p1Len = mp.History.Added
var p2Len = p.History.Added
var max int64
var maxCount int
if p1Len == p2Len || p1Len < p2Len {
maxCount = int(p1Len)
} else {
maxCount = int(p2Len)
}
if max > MAX_POINTABLES_PER_HISTORY {
maxCount = MAX_POINTABLES_PER_HISTORY
}
for i := 0; i < maxCount; i += 2 { // skip every other frame
myA, _ = mp.History.ReadFromStart(i)
myAp, okA := myA.(gomotion.Pointable)
myB, _ = p.History.ReadFromStart(i)
myBp, okB := myB.(gomotion.Pointable)
if okA && okB {
dCurrent = DistanceBetweenPointables(&myAp, &myBp)
} else {
logger.Fatal("fail to cast interface from circular buffer to a Pointable")
}
if dLast < dCurrent && dLast != 0 {
failThreshold++
}
dLast = dCurrent
}
if failThreshold > CONVERGENCE_THRESHOLD {
if DEBUG_CONVERGENCE {
Debug(fmt.Sprintf("failThreshold: %d", failThreshold))
}
return false
}
return true
}
// HandPinchCheck is an object that represents a hand.
// hands can change ids if they disappear and come back into the LeapMotion's
// view. A Hand can have 1 - 5 Pointables. We keep track the last 15 frames
// seen for each pointable. Pointable ids can/will change as pointables
// enter and leave the LeapMotion's view.
type HandPinchCheck struct {
// unique per hand in each frame
HandId int
// listens for pointables
PointableChan chan gomotion.Pointable
// sends a pinch event to the listener
PinchChan *chan *Pinch
// pointable id to last update time
LastUpdate map[int]time.Time
// listen if a finger disappeared
FingerDisappeared chan bool
// list of pointables per hand and their history
Pointables map[int]*MyPointable
}
func (hPinchCheck *HandPinchCheck) ListenForPointables() {
var pair []*MyPointable
var myP *MyPointable
var ok bool
var converging bool
var args []*gomotion.Pointable
for {
select {
case p := <- hPinchCheck.PointableChan:
hPinchCheck.LastUpdate[p.Id] = time.Now()
// create this MyPointable object if it doesn't exist
myP, ok = hPinchCheck.Pointables[p.Id]
if ok == false {
cb := circbuf.NewCircBuf(MAX_POINTABLES_PER_HISTORY)
myP = &MyPointable{
History: cb,
}
}
hPinchCheck.Pointables[p.Id] = myP
myP.History.AddItem(p)
myP.lastUpdate = time.Now()
case <- hPinchCheck.FingerDisappeared:
// Check if we have only 2 pointables that have been seen
// recently.
pair = make([]*MyPointable, 0)
for _, pntbl := range hPinchCheck.Pointables {
// Check the number of pointables that are new
isNew := bool(time.Since(pntbl.lastUpdate) < time.Duration(50 * time.Millisecond))
// at least a few frames in history?
if isNew && (pntbl.History.Added >= 6) {
pair = append(pair, pntbl)
}
if DEBUG_COLLECT {
msg := fmt.Sprintf("pointable is new: %t", isNew)
Debug(msg)
msg = fmt.Sprintf("pointable had added %d frames", pntbl.History.Added)
Debug(msg)
}
}
// two fingers might mean a pinch occured
if len(pair) == 2 {
// extract the Pointable from the circular buffer
var lastItem interface{}
var err error
args = make([]*gomotion.Pointable, 0)
for _, pntbl := range pair {
lastItem, err = pntbl.History.ReadFromEnd(0)
if err != nil {
logger.Fatal(err.Error)
}
goP, ok := lastItem.(gomotion.Pointable)
if ok {
args = append(args, &goP)
}
}
dist := DistanceBetweenPointables(args[0], args[1])
if dist < PINCH_DISTANCE_THRESHOLD {
converging = pair[0].CalculateConvergence(pair[1])
if converging {
Debug("sending pinch on pinch channel")
pinch := new(Pinch)
pinch.SetFrom2Pointables(args[0], args[1])
*hPinchCheck.PinchChan <- pinch
goto REMOVE_OLD
}
if DEBUG_CONVERGENCE {
Debug("failed to converge")
}
} else {
if DEBUG_DISTANCE {
logger.Printf("PINCH_DISTANCE_THRESHOLD: %d\tdistance: %f\n", PINCH_DISTANCE_THRESHOLD, dist)
}
}
} else {
if DEBUG_COLLECT {
logger.Printf("could not find 2 pointables. Found %d", len(pair))
}
}
goto REMOVE_OLD
}
REMOVE_OLD:
for id, mpt := range hPinchCheck.Pointables {
if time.Since(mpt.lastUpdate) > time.Duration(60 * time.Millisecond) {
delete(hPinchCheck.Pointables, id)
}
}
}
}
// HandPinchCheck reads LeapMotion frames sends Pinch objects shortly
// after they occur.
type HandPinchRouter struct {
// listens for frames
FrameChan chan *gomotion.Frame
// map of hand ids to objects that calculate Pinch events for that hand.
PinchChecks map[int]HandPinchCheck
// emits Pinch object pointers
PinchChan chan *Pinch
}
// PPH stands for Pointables Per Hand; used for counting pointables
// per hand within the HandPinchRouter
type PPH struct {
HandId int
NumPointables int
}
// RouteHands will route a Pointable to the respective hand pinch
// channel.
func (hPinchRouter *HandPinchRouter) RouteHand() {
var currPerHand = map[int]*PPH{}
var pastPerHand = map[int]*PPH{}
var handId int
for frame := range hPinchRouter.FrameChan {
for _, pointable := range frame.Pointables {
handId = pointable.HandId
// sometimes we receive a frame with an unknown hand id
if handId == -1 {
continue
}
// check the current hand id pointables; 'pointables per hand'.
// If one is not found, we create one
if pph, ok := currPerHand[handId]; ok {
pph.NumPointables += 1
} else {
pph = &PPH{
HandId: handId,
NumPointables: 0,
}
pph.NumPointables++
currPerHand[handId] = pph
}
pc, ok := hPinchRouter.PinchChecks[handId];
if ok {
pc.PointableChan <- pointable
} else {
hpc := HandPinchCheck{
HandId: pointable.HandId,
PointableChan: make(chan gomotion.Pointable),
LastUpdate: make(map[int]time.Time),
Pointables: make(map[int]*MyPointable),
FingerDisappeared: make(chan bool),
PinchChan: &hPinchRouter.PinchChan,
}
go hpc.ListenForPointables()
hpc.PointableChan <- pointable
hPinchRouter.PinchChecks[handId] = hpc
}
}
// calculate the current pointables per hand and see if any disappeared,
// if so, send the FingerDisappeared to the hand's FingerDisappeared chan.
for pHandId, pph := range pastPerHand {
if cHnum, ok := currPerHand[pHandId]; ok {
if cHnum.NumPointables < pph.NumPointables {
hPinchRouter.PinchChecks[pHandId].FingerDisappeared <- true;
}
}
}
pastPerHand = currPerHand
currPerHand = map[int]*PPH{}
}
}
// Pinch represents a pinch event in 3 dimensional space for a particular hand.
type Pinch struct {
X float64
Y float64
Z float64
HandId int
}
// SetFrom2Pointables sets the fields in a Pinch object from 2 gomotion.Pointables.
func (p *Pinch) SetFrom2Pointables(p1, p2 *gomotion.Pointable) {
p1X, p1Y, p1Z := XYZFromPointable(p1)
p2X, p2Y, p2Z := XYZFromPointable(p2)
p.X = Halfway(p1X, p2X)
p.Y = Halfway(p1Y, p2Y)
p.Z = Halfway(p1Z, p2Z)
p.HandId = p1.HandId
}
func XYZFromPointable(p *gomotion.Pointable) (x, y, z float64) {
return float64(p.TipPosition[0]), float64(p.TipPosition[1]), float64(p.TipPosition[2])
}
func DistanceBetweenPointables(pointable1, pointable2 *gomotion.Pointable) float64 {
p1X, p1Y, p1Z := XYZFromPointable(pointable1)
p2X, p2Y, p2Z := XYZFromPointable(pointable2)
return DistanceBetween(p1X, p1Y, p1Z, p2X, p2Y, p2Z)
}
// Halfway returns the coordinate between 2 coordinates in the same dimension.
func Halfway(a, b float64) float64 {
return float64(a + (0.5 * (b - a)))
}
// DistanceBetween takes the distance between 2 3D points and
// caluclates the total distance between them by calculating the
// sum of the squares of the difference of each x, y and z coordinate.
func DistanceBetween(x, y, z, x2, y2, z2 float64) float64 {
xSqDiff := math.Pow(x - x2, 2)
ySqDiff := math.Pow(y - y2, 2)
zSqDiff := math.Pow(z - z2, 2)
return xSqDiff + ySqDiff + zSqDiff
}