1 /* This file is part of the Pangolin Project.
2 * http://github.com/stevenlovegrove/Pangolin
3 *
4 * Copyright (c) 2011 Steven Lovegrove
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without
9 * restriction, including without limitation the rights to use,
10 * copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following
13 * conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 * OTHER DEALINGS IN THE SOFTWARE.
26 */
27
28 #include <pangolin/display/display.h>
29 #include <pangolin/display/display_internal.h>
30 #include <pangolin/display/opengl_render_state.h>
31 #include <pangolin/display/view.h>
32 #include <pangolin/display/viewport.h>
33 #include <pangolin/gl/gl.h>
34 #include <pangolin/platform.h>
35
36 #include <stdexcept>
37
38 namespace pangolin
39 {
40
41 // Pointer to context defined in display.cpp
42 extern __thread PangolinGl* context;
43
44 const int panal_v_margin = 6;
45
AttachAbs(int low,int high,Attach a)46 int AttachAbs( int low, int high, Attach a)
47 {
48 if( a.unit == Pixel ) return low + (int)a.p;
49 if( a.unit == ReversePixel ) return high - (int)a.p;
50 return (int)(low + a.p * (high - low));
51 }
52
AspectAreaWithinTarget(double target,double test)53 double AspectAreaWithinTarget(double target, double test)
54 {
55 if( test < target )
56 return test / target;
57 else
58 return target / test;
59 }
60
SaveViewFromFbo(std::string prefix,View & view,float scale)61 void SaveViewFromFbo(std::string prefix, View& view, float scale)
62 {
63 PANGOLIN_UNUSED(prefix);
64
65 #ifndef HAVE_GLES
66 const Viewport orig = view.v;
67 view.v.l = 0;
68 view.v.b = 0;
69 view.v.w = (int)(view.v.w * scale);
70 view.v.h = (int)(view.v.h * scale);
71
72 const int w = view.v.w;
73 const int h = view.v.h;
74
75 float origLineWidth;
76 glGetFloatv(GL_LINE_WIDTH, &origLineWidth);
77 glLineWidth(origLineWidth * scale);
78
79 float origPointSize;
80 glGetFloatv(GL_POINT_SIZE, &origPointSize);
81 glPointSize(origPointSize * scale);
82
83 // Create FBO
84 GlTexture color(w,h);
85 GlRenderBuffer depth(w,h);
86 GlFramebuffer fbo(color, depth);
87
88 // Render into FBO
89 fbo.Bind();
90 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
91 view.Render();
92 glFlush();
93
94 #ifdef HAVE_PNG
95 const PixelFormat fmt = PixelFormatFromString("RGBA32");
96 TypedImage buffer(w, h, fmt );
97 glReadBuffer(GL_BACK);
98 glPixelStorei(GL_PACK_ALIGNMENT, 1); // TODO: Avoid this?
99 glReadPixels(0,0,w,h, GL_RGBA, GL_UNSIGNED_BYTE, buffer.ptr );
100 SaveImage(buffer, fmt, prefix + ".png", false);
101 #endif // HAVE_PNG
102
103 // unbind FBO
104 fbo.Unbind();
105
106 // restore viewport / line width
107 view.v = orig;
108 glLineWidth(origLineWidth);
109 glPointSize(origPointSize);
110 #endif // HAVE_GLES
111 }
112
Resize(const Viewport & p)113 void View::Resize(const Viewport& p)
114 {
115 // Compute Bounds based on specification
116 v.l = AttachAbs(p.l,p.r(),left);
117 v.b = AttachAbs(p.b,p.t(),bottom);
118 int r = AttachAbs(p.l,p.r(),right);
119 int t = AttachAbs(p.b,p.t(),top);
120
121 // Make sure left and right, top and bottom are correct order
122 if( t < v.b ) std::swap(t,v.b);
123 if( r < v.l ) std::swap(r,v.l);
124
125 v.w = r - v.l;
126 v.h = t - v.b;
127
128 vp = v;
129
130 // Adjust based on aspect requirements
131 if( aspect != 0 )
132 {
133 const float current_aspect = (float)v.w / (float)v.h;
134 if( aspect > 0 )
135 {
136 // Fit to space
137 if( current_aspect < aspect )
138 {
139 //Adjust height
140 const int nh = (int)(v.w / aspect);
141 v.b += vlock == LockBottom ? 0 : (vlock == LockCenter ? (v.h-nh)/2 : (v.h-nh) );
142 v.h = nh;
143 }else if( current_aspect > aspect )
144 {
145 //Adjust width
146 const int nw = (int)(v.h * aspect);
147 v.l += hlock == LockLeft? 0 : (hlock == LockCenter ? (v.w-nw)/2 : (v.w-nw) );
148 v.w = nw;
149 }
150 }else{
151 // Overfit
152 double true_aspect = -aspect;
153 if( current_aspect < true_aspect )
154 {
155 //Adjust width
156 const int nw = (int)(v.h * true_aspect);
157 v.l += hlock == LockLeft? 0 : (hlock == LockCenter ? (v.w-nw)/2 : (v.w-nw) );
158 v.w = nw;
159 }else if( current_aspect > true_aspect )
160 {
161 //Adjust height
162 const int nh = (int)(v.w / true_aspect);
163 v.b += vlock == LockBottom ? 0 : (vlock == LockCenter ? (v.h-nh)/2 : (v.h-nh) );
164 v.h = nh;
165 }
166 }
167 }
168
169 ResizeChildren();
170 }
171
zcompare(const View * lhs,const View * rhs)172 inline int zcompare(const View* lhs, const View* rhs)
173 {
174 return lhs->zorder < rhs->zorder;
175 }
176
ResizeChildren()177 void View::ResizeChildren()
178 {
179 if( layout == LayoutOverlay )
180 {
181 // Sort children into z-order
182 std::sort(views.begin(), views.end(), zcompare);
183
184 for(std::vector<View*>::iterator iv = views.begin(); iv != views.end(); ++iv ) {
185 (*iv)->Resize(v);
186 }
187 }else if( layout == LayoutVertical )
188 {
189 // Allocate space incrementally
190 Viewport space = v.Inset(panal_v_margin);
191 int num_children = 0;
192 for(std::vector<View*>::iterator iv = views.begin(); iv != views.end(); ++iv )
193 {
194 if (!(*iv)->show) continue;
195 num_children++;
196 if(scroll_offset >= num_children ) {
197 (*iv)->scroll_show = false;
198 }else{
199 (*iv)->scroll_show = true;
200 (*iv)->Resize(space);
201 space.h = (*iv)->v.b - panal_v_margin - space.b;
202 }
203 }
204 }else if(layout == LayoutHorizontal )
205 {
206 // Allocate space incrementally
207 const int margin = 8;
208 Viewport space = v.Inset(margin);
209 for(std::vector<View*>::iterator iv = views.begin(); iv != views.end(); ++iv )
210 {
211 (*iv)->Resize(space);
212 space.w = (*iv)->v.l + margin + space.l;
213 }
214 }else if(layout == LayoutEqualVertical )
215 {
216 // Allocate vertical space equally
217 const size_t visiblechildren = NumVisibleChildren();
218 const float height = (float)v.h / (float)visiblechildren;
219
220 for( size_t i=0; i < visiblechildren; ++i) {
221 Viewport space(v.l, (GLint)(v.b+(visiblechildren-1-i)*height), v.w, (GLint)(height) );
222 VisibleChild(i).Resize(space);
223 }
224 }else if(layout == LayoutEqualHorizontal )
225 {
226 // Allocate vertical space equally
227 const size_t visiblechildren = NumVisibleChildren();
228 const float width = (float)v.w / (float)visiblechildren;
229
230 for( size_t i=0; i < visiblechildren; ++i) {
231 Viewport space( (GLint)(v.l+i*width), v.b, (GLint)width, v.h);
232 VisibleChild(i).Resize(space);
233 }
234 }else if(layout == LayoutEqual )
235 {
236 const size_t visiblechildren = NumVisibleChildren();
237 // TODO: Make this neater, and make fewer assumptions!
238 if( visiblechildren > 0 )
239 {
240 // This containers aspect
241 const double this_a = std::fabs(v.aspect());
242
243 // Use first child with fixed aspect for all children
244 double child_a = std::fabs(VisibleChild(0).aspect);
245 for(size_t i=1; (child_a==0) && i < visiblechildren; ++i ) {
246 child_a = std::fabs(VisibleChild(i).aspect);
247 }
248
249 if(child_a == 0) {
250 child_a = 1;
251 }
252
253 double a = visiblechildren*child_a;
254 double area = AspectAreaWithinTarget(this_a, a);
255
256 size_t cols = visiblechildren-1;
257 for(; cols > 0; --cols)
258 {
259 const size_t rows = visiblechildren / cols + (visiblechildren % cols == 0 ? 0 : 1);
260 const double na = cols * child_a / rows;
261 const double new_area = visiblechildren*AspectAreaWithinTarget(this_a,na)/(rows*cols);
262 if( new_area <= area )
263 break;
264 area = new_area;
265 a = na;
266 }
267
268 cols++;
269 const size_t rows = visiblechildren / cols + (visiblechildren % cols == 0 ? 0 : 1);
270 size_t cw, ch;
271 if( a > this_a )
272 {
273 cw = v.w / cols;
274 ch = (int)(cw / child_a); //v.h / rows;
275 }else{
276 ch = v.h / rows;
277 cw = (int)(ch * child_a);
278 }
279
280 for(size_t i=0; i< visiblechildren; ++i )
281 {
282 size_t c = i % cols;
283 size_t r = i / cols;
284 Viewport space( GLint(v.l + c*cw), GLint(v.t() - (r+1)*ch), GLint(cw), GLint(ch) );
285 VisibleChild(i).Resize(space);
286 }
287 }
288 }
289
290 }
291
Render()292 void View::Render()
293 {
294 if(extern_draw_function && show && scroll_show) {
295 extern_draw_function(*this);
296 }
297 RenderChildren();
298 }
299
RenderChildren()300 void View::RenderChildren()
301 {
302 for(std::vector<View*>::iterator iv = views.begin(); iv != views.end(); ++iv )
303 {
304 if((*iv)->show && (*iv)->scroll_show) (*iv)->Render();
305 }
306 }
307
Activate() const308 void View::Activate() const
309 {
310 v.Activate();
311 }
312
ActivateAndScissor() const313 void View::ActivateAndScissor() const
314 {
315 vp.Scissor();
316 v.Activate();
317 }
318
ActivateScissorAndClear() const319 void View::ActivateScissorAndClear() const
320 {
321 vp.Scissor();
322 v.Activate();
323 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
324 }
325
Activate(const OpenGlRenderState & state) const326 void View::Activate(const OpenGlRenderState& state ) const
327 {
328 v.Activate();
329 state.Apply();
330 }
331
ActivateAndScissor(const OpenGlRenderState & state) const332 void View::ActivateAndScissor(const OpenGlRenderState& state) const
333 {
334 vp.Scissor();
335 v.Activate();
336 state.Apply();
337 }
338
ActivateScissorAndClear(const OpenGlRenderState & state) const339 void View::ActivateScissorAndClear(const OpenGlRenderState& state ) const
340 {
341 vp.Scissor();
342 v.Activate();
343 state.Apply();
344 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
345 }
346
ActivatePixelOrthographic() const347 void View::ActivatePixelOrthographic() const
348 {
349 v.ActivatePixelOrthographic();
350 }
351
ActivateIdentity() const352 void View::ActivateIdentity() const
353 {
354 v.ActivateIdentity();
355 }
356
GetClosestDepth(int x,int y,int radius) const357 GLfloat View::GetClosestDepth(int x, int y, int radius) const
358 {
359 // TODO: Get to work on android
360
361 #ifdef _MSVC_
362 // MSVC Requires fixed sized arrays on stack
363 radius = 5;
364 const int zl = (5*2+1);
365 #else
366 const int zl = (radius*2+1);
367 #endif
368
369 const int zsize = zl*zl;
370 GLfloat zs[zsize];
371
372 #ifndef HAVE_GLES
373 glReadBuffer(GL_FRONT);
374 glReadPixels(x-radius,y-radius,zl,zl,GL_DEPTH_COMPONENT,GL_FLOAT,zs);
375 #else
376 std::fill(zs,zs+zsize, 0.8);
377 #endif
378
379 const GLfloat mindepth = *(std::min_element(zs,zs+zsize));
380 return mindepth;
381 }
382
GetObjectCoordinates(const OpenGlRenderState & cam_state,double winx,double winy,double winzdepth,GLdouble & x,GLdouble & y,GLdouble & z) const383 void View::GetObjectCoordinates(const OpenGlRenderState& cam_state, double winx, double winy, double winzdepth, GLdouble& x, GLdouble& y, GLdouble& z) const
384 {
385 const GLint viewport[4] = {v.l,v.b,v.w,v.h};
386 const OpenGlMatrix proj = cam_state.GetProjectionMatrix();
387 const OpenGlMatrix mv = cam_state.GetModelViewMatrix();
388 glUnProject(winx, winy, winzdepth, mv.m, proj.m, viewport, &x, &y, &z);
389 }
390
GetCamCoordinates(const OpenGlRenderState & cam_state,double winx,double winy,double winzdepth,GLdouble & x,GLdouble & y,GLdouble & z) const391 void View::GetCamCoordinates(const OpenGlRenderState& cam_state, double winx, double winy, double winzdepth, GLdouble& x, GLdouble& y, GLdouble& z) const
392 {
393 v.GetCamCoordinates(cam_state, winx, winy, winzdepth, x, y, z);
394 }
395
SetFocus()396 View& View::SetFocus()
397 {
398 context->activeDisplay = this;
399 return *this;
400 }
401
HasFocus() const402 bool View::HasFocus() const
403 {
404 return context->activeDisplay == this;
405 }
406
SetBounds(Attach bottom,Attach top,Attach left,Attach right)407 View& View::SetBounds(Attach bottom, Attach top, Attach left, Attach right)
408 {
409 this->left = left;
410 this->top = top;
411 this->right = right;
412 this->bottom = bottom;
413 context->base.ResizeChildren();
414 return *this;
415 }
416
SetBounds(Attach bottom,Attach top,Attach left,Attach right,bool keep_aspect)417 View& View::SetBounds(Attach bottom, Attach top, Attach left, Attach right, bool keep_aspect)
418 {
419 aspect = keep_aspect ? v.aspect() : 0;
420 SetBounds(top,bottom,left,right);
421 return *this;
422 }
423
SetBounds(Attach bottom,Attach top,Attach left,Attach right,double aspect)424 View& View::SetBounds(Attach bottom, Attach top, Attach left, Attach right, double aspect)
425 {
426 this->aspect = aspect;
427 SetBounds(top,bottom,left,right);
428 return *this;
429 }
430
SetAspect(double aspect)431 View& View::SetAspect(double aspect)
432 {
433 this->aspect = aspect;
434 context->base.ResizeChildren();
435 return *this;
436 }
437
SetLock(Lock horizontal,Lock vertical)438 View& View::SetLock(Lock horizontal, Lock vertical )
439 {
440 vlock = vertical;
441 hlock = horizontal;
442 return *this;
443 }
444
SetLayout(Layout l)445 View& View::SetLayout(Layout l)
446 {
447 layout = l;
448 return *this;
449 }
450
451
AddDisplay(View & child)452 View& View::AddDisplay(View& child)
453 {
454 // detach child from any other view, and add to this
455 std::vector<View*>::iterator f = std::find(
456 context->base.views.begin(), context->base.views.end(), &child
457 );
458
459 if( f != context->base.views.end() )
460 context->base.views.erase(f);
461
462 views.push_back(&child);
463 context->base.ResizeChildren();
464 return *this;
465 }
466
Show(bool show)467 View& View::Show(bool show)
468 {
469 this->show = show;
470 context->base.ResizeChildren();
471 return *this;
472 }
473
ToggleShow()474 void View::ToggleShow()
475 {
476 Show(!show);
477 }
478
IsShown() const479 bool View::IsShown() const
480 {
481 return show;
482 }
483
GetBounds() const484 Viewport View::GetBounds() const
485 {
486 return Viewport( std::max(v.l, vp.l), std::max(v.b, vp.b), std::min(v.w, vp.w), std::min(v.h, vp.h) );
487 }
488
SaveOnRender(const std::string & filename_prefix)489 void View::SaveOnRender(const std::string& filename_prefix)
490 {
491 const Viewport tosave = this->v.Intersect(this->vp);
492 context->screen_capture.push(std::pair<std::string,Viewport>(filename_prefix,tosave ) );
493 }
494
RecordOnRender(const std::string & record_uri)495 void View::RecordOnRender(const std::string& record_uri)
496 {
497 PANGOLIN_UNUSED(record_uri);
498
499 #ifdef BUILD_PANGOLIN_VIDEO
500 if(!context->recorder.IsOpen()) {
501 Viewport area = GetBounds();
502 context->record_view = this;
503 try{
504 context->recorder.Open(record_uri);
505 std::vector<StreamInfo> streams;
506 const PixelFormat fmt = PixelFormatFromString("RGB24");
507 streams.push_back( StreamInfo(fmt, area.w, area.h, area.w * fmt.bpp / 8) );
508 context->recorder.SetStreams(streams);
509 }catch(const std::exception& e) {
510 pango_print_error("Unable to open VideoRecorder:\n\t%s\n", e.what());
511 }
512 }else{
513 context->recorder.Close();
514 }
515 #else
516 std::cerr << "Error: Video Support hasn't been built into this library." << std::endl;
517 #endif // BUILD_PANGOLIN_VIDEO
518 }
519
SaveRenderNow(const std::string & filename_prefix,float scale)520 void View::SaveRenderNow(const std::string& filename_prefix, float scale)
521 {
522 SaveViewFromFbo(filename_prefix, *this, scale);
523 }
524
operator [](size_t i)525 View& View::operator[](size_t i)
526 {
527 return *views[i];
528 }
529
NumChildren() const530 size_t View::NumChildren() const
531 {
532 return views.size();
533 }
534
NumVisibleChildren() const535 size_t View::NumVisibleChildren() const
536 {
537 int numvis = 0;
538 for(std::vector<View*>::const_iterator i=views.begin(); i!=views.end(); ++i)
539 {
540 if((*i)->show) {
541 numvis++;
542 }
543 }
544 return numvis;
545 }
546
VisibleChild(size_t i)547 View& View::VisibleChild(size_t i)
548 {
549 size_t numvis = 0;
550 for(size_t v=0; v < views.size(); ++v ) {
551 if(views[v]->show) {
552 if( i == numvis ) {
553 return *views[v];
554 }
555 numvis++;
556 }
557 }
558 // Shouldn't get here
559 throw std::out_of_range("No such child.");
560 }
561
FindChild(int x,int y)562 View* View::FindChild(int x, int y)
563 {
564 // Find in reverse order to mirror draw order
565 for( std::vector<View*>::const_reverse_iterator i = views.rbegin(); i != views.rend(); ++i )
566 if( (*i)->show && (*i)->GetBounds().Contains(x,y) )
567 return (*i);
568 return 0;
569 }
570
SetHandler(Handler * h)571 View& View::SetHandler(Handler* h)
572 {
573 handler = h;
574 return *this;
575 }
576
SetDrawFunction(const std::function<void (View &)> & drawFunc)577 View& View::SetDrawFunction(const std::function<void(View&)>& drawFunc)
578 {
579 extern_draw_function = drawFunc;
580 return *this;
581 }
582
583 }
584