@@ -35,7 +35,7 @@ def __init__(self):
35
35
self .prevLookAtEye = None
36
36
self .prevLookAtTarget = None
37
37
38
- def compute (self , eye , target , paused ):
38
+ def update (self , eye , target , paused ):
39
39
if self .prevLookAtEye is not None :
40
40
if paused : return self .prevLookAtEye , self .prevLookAtTarget
41
41
eyeSmooth = self .alpha * eye + (1.0 - self .alpha ) * self .prevLookAtEye
@@ -53,19 +53,130 @@ def reset(self):
53
53
self .prevLookAtEye = None
54
54
self .prevLookAtTarget = None
55
55
56
+ class CameraControls2D :
57
+ def __init__ (self ):
58
+ self .zoomSpeed = 1.0
59
+ self .translateSpeed = 1.0
60
+ self .reset ()
61
+
62
+ def reset (self ):
63
+ self .lastMousePos = None
64
+ self .draggingRight = False
65
+ self .camera_pos = np .array ([0.0 , 0.0 , 0.0 ])
66
+ self .zoom = 1.0
67
+
68
+ def update (self , event ):
69
+ if event .type == pygame .MOUSEBUTTONDOWN :
70
+ if event .button == 3 : # Right mouse button
71
+ self .draggingRight = True
72
+ elif event .button == 4 : # Scroll up
73
+ self .zoom *= 0.95 * self .zoomSpeed
74
+ elif event .button == 5 : # Scroll down
75
+ self .zoom *= 1.05 * self .zoomSpeed
76
+ self .lastMousePos = pygame .mouse .get_pos ()
77
+ elif event .type == pygame .MOUSEBUTTONUP :
78
+ if event .button == 1 :
79
+ self .draggingLeft = False
80
+ elif event .button == 3 :
81
+ self .draggingRight = False
82
+ elif event .type == pygame .MOUSEMOTION :
83
+ if self .draggingRight :
84
+ # Drag to move
85
+ mouse_pos = pygame .mouse .get_pos ()
86
+ dx = mouse_pos [0 ] - self .lastMousePos [0 ]
87
+ dy = mouse_pos [1 ] - self .lastMousePos [1 ]
88
+ self .camera_pos [0 ] += 0.01 * dx * self .translateSpeed
89
+ self .camera_pos [1 ] += 0.01 * dy * self .translateSpeed
90
+ self .lastMousePos = pygame .mouse .get_pos ()
91
+
92
+ def transformViewMatrix (self , viewMatrix ):
93
+ viewMatrix [:3 , 3 ] += self .camera_pos
94
+ return viewMatrix
95
+
96
+ class CameraControls3D :
97
+ def __init__ (self ):
98
+ self .zoomSpeed = 1.0
99
+ self .translateSpeed = 1.0
100
+ self .rotateSpeed = 1.0
101
+ self .reset ()
102
+
103
+ def __rotationMatrixX (self , a ):
104
+ return np .array ([
105
+ [1 , 0 , 0 ],
106
+ [0 , np .cos (a ), - np .sin (a )],
107
+ [0 , np .sin (a ), np .cos (a )]
108
+ ])
109
+
110
+ def __rotationMatrixZ (self , a ):
111
+ return np .array ([
112
+ [np .cos (a ), - np .sin (a ), 0 ],
113
+ [np .sin (a ), np .cos (a ), 0 ],
114
+ [0 , 0 , 1 ]
115
+ ])
116
+
117
+ def reset (self ):
118
+ self .lastMousePos = None
119
+ self .draggingLeft = False
120
+ self .draggingRight = False
121
+ self .camera_pos = np .array ([0.0 , 0.0 , 0.0 ])
122
+ self .yaw = 0
123
+ self .pitch = 0
124
+ self .zoom = 1.0
125
+
126
+ def update (self , event ):
127
+ if event .type == pygame .MOUSEBUTTONDOWN :
128
+ if event .button == 1 : # Left mouse button
129
+ self .draggingLeft = True
130
+ elif event .button == 3 : # Right mouse button
131
+ self .draggingRight = True
132
+ elif event .button == 4 : # Scroll up
133
+ self .camera_pos [2 ] -= 0.25 * self .zoomSpeed
134
+ elif event .button == 5 : # Scroll down
135
+ self .camera_pos [2 ] += 0.25 * self .zoomSpeed
136
+ self .lastMousePos = pygame .mouse .get_pos ()
137
+ elif event .type == pygame .MOUSEBUTTONUP :
138
+ if event .button == 1 :
139
+ self .draggingLeft = False
140
+ elif event .button == 3 :
141
+ self .draggingRight = False
142
+ elif event .type == pygame .MOUSEMOTION :
143
+ if self .draggingRight :
144
+ # Drag to move
145
+ mouse_pos = pygame .mouse .get_pos ()
146
+ dx = mouse_pos [0 ] - self .lastMousePos [0 ]
147
+ dy = mouse_pos [1 ] - self .lastMousePos [1 ]
148
+ self .camera_pos [0 ] += 0.01 * dx * self .translateSpeed
149
+ self .camera_pos [1 ] += 0.01 * dy * self .translateSpeed
150
+ elif self .draggingLeft :
151
+ # Drag to rotate (yaw and pitch)
152
+ mouse_pos = pygame .mouse .get_pos ()
153
+ dx = mouse_pos [0 ] - self .lastMousePos [0 ]
154
+ dy = mouse_pos [1 ] - self .lastMousePos [1 ]
155
+ self .yaw += 0.001 * dx * self .rotateSpeed
156
+ self .pitch += 0.003 * dy * self .rotateSpeed
157
+ self .lastMousePos = pygame .mouse .get_pos ()
158
+
159
+ def transformViewMatrix (self , viewMatrix ):
160
+ viewMatrix [:3 , 3 ] += self .camera_pos
161
+ viewMatrix [:3 , :3 ] = self .__rotationMatrixX (self .pitch ) @ viewMatrix [:3 , :3 ] # rotate around camera y-axis
162
+ viewMatrix [:3 , :3 ] = viewMatrix [:3 , :3 ] @ self .__rotationMatrixZ (self .yaw ) # rotate around world z-axis
163
+ return viewMatrix
164
+
56
165
class VisualizerArgs :
57
166
# Window
58
167
resolution = "1280x720" # Window resolution
59
168
fullScreen = False # Full screen mode
60
169
visualizationScale = 10.0 # Generic scale of visualizations. Affects color maps, camera size, etc.
61
170
backGroundColor = [1 , 1 , 1 ] # Background color RGB color (0-1).
62
171
keepOpenAfterFinalMap = False # If false, window is automatically closed on final mapper output
172
+ targetFps = 0 # 0 = render when vio output is available, otherwise tries to render at specified target fps
63
173
64
174
# Camera
65
175
cameraNear = 0.01 # Camera near plane (m)
66
176
cameraFar = 100.0 # Camera far plane (m)
67
177
cameraMode = CameraMode .THIRD_PERSON # Camera mode (options: AR, 3rd person, 2D). Note: AR mode should have 'useRectification: True'
68
178
cameraSmooth = True # Enable camera smoothing in 3rd person mode
179
+ cameraFollow = True # When true, camera follows estimated camera pose. Otherwise, use free camera (3rd person, 2D)
69
180
flip = False # Vertically flip image in AR mode
70
181
71
182
# Initial state for visualization components
@@ -133,6 +244,7 @@ def __init__(self, args=VisualizerArgs()):
133
244
self .displayInitialized = False
134
245
self .outputQueue = []
135
246
self .outputQueueMutex = Lock ()
247
+ self .clock = pygame .time .Clock ()
136
248
137
249
# Window
138
250
self .fullScreen = args .fullScreen
@@ -144,8 +256,8 @@ def __init__(self, args=VisualizerArgs()):
144
256
# Camera
145
257
self .cameraMode = self .args .cameraMode
146
258
self .cameraSmooth = CameraSmooth () if args .cameraSmooth else None
147
- self .initialZoom = args . visualizationScale / 10.0
148
- self .zoom = self . initialZoom
259
+ self .cameraControls2D = CameraControls2D ()
260
+ self .cameraControls3D = CameraControls3D ()
149
261
150
262
# Toggle visualization components
151
263
self .showGrid = args .showGrid
@@ -218,36 +330,41 @@ def __render(self, cameraPose, width, height, image=None, colorFormat=None):
218
330
glClearColor (* self .args .backGroundColor , 1.0 )
219
331
glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
220
332
333
+ if self .args .cameraFollow :
334
+ cameraToWorld = cameraPose .getCameraToWorldMatrix ()
335
+ else :
336
+ cameraToWorld = np .array ([
337
+ [0 , 0 , 1 , 0 ],
338
+ [- 1 , 0 , 0 , 0 ],
339
+ [ 0 , - 1 , 0 , 0 ],
340
+ [0 , 0 , 0 , 1 ]]
341
+ )
342
+
221
343
near , far = self .args .cameraNear , self .args .cameraFar
222
344
if self .cameraMode == CameraMode .AR :
223
345
if image is not None :
224
346
# draw AR background
225
347
glDrawPixels (width , height , GL_LUMINANCE if colorFormat == spectacularAI .ColorFormat .GRAY else GL_RGB , GL_UNSIGNED_BYTE , image .data )
226
348
viewMatrix = cameraPose .getWorldToCameraMatrix ()
227
349
projectionMatrix = cameraPose .camera .getProjectionMatrixOpenGL (near , far )
228
- if self .args .flip : projectionMatrix = np .array ([1 , 0 , 0 , 0 ], [0 , - 1 , 0 , 0 ], [0 , 0 , 1 , 0 ], [0 , 0 , 0 , 1 ]) @ projectionMatrix
350
+ if self .args .flip : projectionMatrix = np .array ([[ 1 , 0 , 0 , 0 ], [0 , - 1 , 0 , 0 ], [0 , 0 , 1 , 0 ], [0 , 0 , 0 , 1 ] ]) @ projectionMatrix
229
351
elif self .cameraMode == CameraMode .THIRD_PERSON :
230
- # TODO: implement mouse controls
231
- cameraToWorld = cameraPose .getCameraToWorldMatrix ()
232
352
up = np .array ([0.0 , 0.0 , 1.0 ])
233
353
forward = cameraToWorld [0 :3 , 2 ]
234
354
eye = cameraToWorld [0 :3 , 3 ] - 10.0 * forward + 5.0 * up
235
355
target = cameraToWorld [0 :3 , 3 ]
236
- if self .cameraSmooth : eye , target = self .cameraSmooth .compute (eye , target , self .shouldPause )
237
- viewMatrix = lookAt (eye , target , up )
356
+ if self .cameraSmooth : eye , target = self .cameraSmooth .update (eye , target , self .shouldPause )
357
+ viewMatrix = self . cameraControls3D . transformViewMatrix ( lookAt (eye , target , up ) )
238
358
projectionMatrix = cameraPose .camera .getProjectionMatrixOpenGL (near , far )
239
- projectionMatrix [0 , 0 ] *= 1.0 / self .zoom
240
- projectionMatrix [1 , 1 ] *= 1.0 / self .zoom
241
359
elif self .cameraMode == CameraMode .TOP_VIEW :
242
- cameraToWorld = cameraPose .getCameraToWorldMatrix ()
243
360
eye = cameraToWorld [0 :3 , 3 ] + np .array ([0 , 0 , 15 ])
244
361
target = cameraToWorld [0 :3 , 3 ]
245
362
up = np .array ([- 1.0 , 0.0 , 0.0 ])
246
- viewMatrix = lookAt (eye , target , up )
247
- left = - 25.0 * self .zoom
248
- right = 25.0 * self .zoom
249
- bottom = - 25.0 * self .zoom / self .aspectRatio # divide by aspect ratio to avoid strecthing (i.e. x and y directions have equal scale)
250
- top = 25.0 * self .zoom / self .aspectRatio
363
+ viewMatrix = self . cameraControls2D . transformViewMatrix ( lookAt (eye , target , up ) )
364
+ left = - 25.0 * self .cameraControls2D . zoom
365
+ right = 25.0 * self .cameraControls2D . zoom
366
+ bottom = - 25.0 * self .cameraControls2D . zoom / self .aspectRatio # divide by aspect ratio to avoid strecthing (i.e. x and y directions have equal scale)
367
+ top = 25.0 * self .cameraControls2D . zoom / self .aspectRatio
251
368
projectionMatrix = getOrthographicProjectionMatrixOpenGL (left , right , bottom , top , - 1000.0 , 1000.0 )
252
369
253
370
self .map .render (cameraPose .getPosition (), viewMatrix , projectionMatrix )
@@ -267,12 +384,13 @@ def __processUserInput(self):
267
384
for event in pygame .event .get ():
268
385
if event .type == pygame .QUIT :
269
386
self .shouldQuit = True
270
- if event .type == pygame .KEYDOWN :
387
+ elif event .type == pygame .KEYDOWN :
271
388
if event .key == pygame .K_q : self .shouldQuit = True
272
389
elif event .key == pygame .K_SPACE : self .shouldPause = not self .shouldPause
273
390
elif event .key == pygame .K_c :
274
391
self .cameraMode = self .cameraMode .next ()
275
- self .zoom = self .initialZoom
392
+ self .cameraControls2D .reset ()
393
+ self .cameraControls3D .reset ()
276
394
if self .cameraSmooth : self .cameraSmooth .reset ()
277
395
elif event .key == pygame .K_PLUS :
278
396
self .map .setPointSize (np .clip (self .map .pointSize * 1.05 , 0.0 , 10.0 ))
@@ -308,11 +426,9 @@ def __processUserInput(self):
308
426
else : pygame .display .set_mode ((w , h ), DOUBLEBUF | OPENGL )
309
427
elif event .key == pygame .K_h :
310
428
self .printHelp ()
311
- if event .type == pygame .MOUSEBUTTONDOWN :
312
- if event .button == 4 : # Mouse wheel up
313
- self .zoom = min (10.0 * self .initialZoom , self .zoom * 1.05 )
314
- elif event .button == 5 : # Mouse wheel down
315
- self .zoom = max (0.1 * self .initialZoom , self .zoom * 0.95 )
429
+ else :
430
+ if self .cameraMode is CameraMode .THIRD_PERSON : self .cameraControls3D .update (event )
431
+ if self .cameraMode is CameraMode .TOP_VIEW : self .cameraControls2D .update (event )
316
432
317
433
def onVioOutput (self , cameraPose , image = None , width = None , height = None , colorFormat = None ):
318
434
if self .shouldQuit : return
@@ -358,22 +474,22 @@ def onMappingOutput(self, mapperOutput):
358
474
self .outputQueue .append (output )
359
475
360
476
def run (self ):
477
+ vioOutput = None
478
+
361
479
while not self .shouldQuit :
362
480
self .__processUserInput ()
363
481
364
- if self .shouldPause or len (self .outputQueue ) == 0 :
365
- time .sleep (0.01 )
366
- continue
367
-
368
482
# Process VIO & Mapping API outputs
369
- vioOutput = None
370
483
while self .outputQueueMutex and len (self .outputQueue ) > 0 :
484
+ if self .shouldPause : break
485
+
371
486
output = self .outputQueue .pop (0 )
372
487
if output ["type" ] == "vio" :
373
488
vioOutput = output
374
489
cameraPose = vioOutput ["cameraPose" ]
375
490
self .poseTrail .append (cameraPose .getPosition ())
376
- break
491
+ # Drop vio outputs if target fps is too low
492
+ if self .args .targetFps == 0 : break
377
493
elif output ["type" ] == "slam" :
378
494
mapperOutput = output ["mapperOutput" ]
379
495
self .map .onMappingOutput (mapperOutput )
@@ -390,6 +506,14 @@ def run(self):
390
506
vioOutput ["image" ],
391
507
vioOutput ["colorFormat" ])
392
508
509
+ if self .args .targetFps > 0 :
510
+ # Try to render at target fps
511
+ self .clock .tick (self .args .targetFps )
512
+ else :
513
+ # Render whenever vioOutput is available
514
+ vioOutput = None
515
+ time .sleep (0.01 )
516
+
393
517
self .__close ()
394
518
395
519
def printHelp (self ):
0 commit comments