2018-05-09 10:47:04 +02:00
#include "GLCanvas3D.hpp"
2018-05-14 14:14:19 +02:00
#include "../../slic3r/GUI/3DScene.hpp"
2018-05-09 10:47:04 +02:00
#include <wx/glcanvas.h>
#include <iostream>
2018-05-15 09:50:01 +02:00
static const bool TURNTABLE_MODE = true ;
2018-05-14 12:08:23 +02:00
static const float GIMBALL_LOCK_THETA_MAX = 180.0f ;
2018-05-09 10:47:04 +02:00
namespace Slic3r {
namespace GUI {
GLCanvas3D :: Camera :: Camera ()
2018-05-14 12:08:23 +02:00
: m_type ( CT_Ortho )
, m_zoom ( 1.0f )
, m_phi ( 45.0f )
, m_theta ( 45.0f )
, m_distance ( 0.0f )
, m_target ( 0.0 , 0.0 , 0.0 )
2018-05-09 10:47:04 +02:00
{
}
2018-05-14 12:08:23 +02:00
GLCanvas3D :: Camera :: EType GLCanvas3D :: Camera :: get_type () const
{
return m_type ;
}
void GLCanvas3D :: Camera :: set_type ( GLCanvas3D :: Camera :: EType type )
{
m_type = type ;
}
2018-05-14 11:31:58 +02:00
std :: string GLCanvas3D :: Camera :: get_type_as_string () const
{
2018-05-14 12:08:23 +02:00
switch ( m_type )
2018-05-14 11:31:58 +02:00
{
default :
case CT_Unknown :
return "unknown" ;
case CT_Perspective :
return "perspective" ;
case CT_Ortho :
return "ortho" ;
};
}
2018-05-14 12:08:23 +02:00
float GLCanvas3D :: Camera :: get_zoom () const
{
return m_zoom ;
}
void GLCanvas3D :: Camera :: set_zoom ( float zoom )
{
m_zoom = zoom ;
}
float GLCanvas3D :: Camera :: get_phi () const
{
return m_phi ;
}
void GLCanvas3D :: Camera :: set_phi ( float phi )
{
m_phi = phi ;
}
float GLCanvas3D :: Camera :: get_theta () const
{
return m_theta ;
}
void GLCanvas3D :: Camera :: set_theta ( float theta )
{
m_theta = theta ;
// clamp angle
if ( m_theta > GIMBALL_LOCK_THETA_MAX )
m_theta = GIMBALL_LOCK_THETA_MAX ;
if ( m_theta < 0.0f )
m_theta = 0.0f ;
}
float GLCanvas3D :: Camera :: get_distance () const
{
return m_distance ;
}
void GLCanvas3D :: Camera :: set_distance ( float distance )
{
m_distance = distance ;
}
const Pointf3 & GLCanvas3D :: Camera :: get_target () const
{
return m_target ;
}
void GLCanvas3D :: Camera :: set_target ( const Pointf3 & target )
{
m_target = target ;
}
2018-05-14 14:14:19 +02:00
const Pointfs & GLCanvas3D :: Bed :: get_shape () const
{
return m_shape ;
}
void GLCanvas3D :: Bed :: set_shape ( const Pointfs & shape )
{
m_shape = shape ;
_calc_bounding_box ();
}
const BoundingBoxf3 & GLCanvas3D :: Bed :: get_bounding_box () const
{
return m_bounding_box ;
}
void GLCanvas3D :: Bed :: _calc_bounding_box ()
{
m_bounding_box = BoundingBoxf3 ();
for ( const Pointf & p : m_shape )
{
m_bounding_box . merge ( Pointf3 ( p . x , p . y , 0.0 ));
}
}
2018-05-09 10:47:04 +02:00
GLCanvas3D :: GLCanvas3D ( wxGLCanvas * canvas , wxGLContext * context )
: m_canvas ( canvas )
, m_context ( context )
2018-05-14 14:14:19 +02:00
, m_volumes ( nullptr )
2018-05-09 10:47:04 +02:00
, m_dirty ( true )
2018-05-14 14:14:19 +02:00
, m_apply_zoom_to_volumes_filter ( false )
2018-05-09 10:47:04 +02:00
{
}
2018-05-15 09:50:01 +02:00
GLCanvas3D ::~ GLCanvas3D ()
{
_deregister_callbacks ();
}
2018-05-09 10:47:04 +02:00
void GLCanvas3D :: set_current ()
{
if (( m_canvas != nullptr ) && ( m_context != nullptr ))
m_canvas -> SetCurrent ( * m_context );
}
2018-05-14 14:14:19 +02:00
bool GLCanvas3D :: is_shown_on_screen () const
{
return ( m_canvas != nullptr ) ? m_canvas -> IsShownOnScreen () : false ;
}
void GLCanvas3D :: resize ( unsigned int w , unsigned int h )
{
if ( m_context == nullptr )
return ;
set_current ();
:: glViewport ( 0 , 0 , w , h );
:: glMatrixMode ( GL_PROJECTION );
:: glLoadIdentity ();
BoundingBoxf3 bbox = max_bounding_box ();
switch ( get_camera_type ())
{
case Camera :: CT_Ortho :
{
float w2 = w ;
float h2 = h ;
float two_zoom = 2.0f * get_camera_zoom ();
if ( two_zoom != 0.0f )
{
float inv_two_zoom = 1.0f / two_zoom ;
w2 *= inv_two_zoom ;
h2 *= inv_two_zoom ;
}
// FIXME: calculate a tighter value for depth will improve z-fighting
2018-05-15 09:50:01 +02:00
float depth = 5.0f * ( float ) bbox . max_size ();
2018-05-14 14:14:19 +02:00
:: glOrtho ( - w2 , w2 , - h2 , h2 , - depth , depth );
break ;
}
case Camera :: CT_Perspective :
{
float bbox_r = ( float ) bbox . radius ();
float fov = PI * 45.0f / 180.0f ;
float fov_tan = tan ( 0.5f * fov );
float cam_distance = 0.5f * bbox_r / fov_tan ;
set_camera_distance ( cam_distance );
float nr = cam_distance - bbox_r * 1.1f ;
float fr = cam_distance + bbox_r * 1.1f ;
if ( nr < 1.0f )
nr = 1.0f ;
if ( fr < nr + 1.0f )
fr = nr + 1.0f ;
float h2 = fov_tan * nr ;
float w2 = h2 * w / h ;
:: glFrustum ( - w2 , w2 , - h2 , h2 , nr , fr );
break ;
}
default :
{
throw std :: runtime_error ( "Invalid camera type." );
break ;
}
}
:: glMatrixMode ( GL_MODELVIEW );
set_dirty ( false );
}
GLVolumeCollection * GLCanvas3D :: get_volumes ()
{
return m_volumes ;
}
void GLCanvas3D :: set_volumes ( GLVolumeCollection * volumes )
{
m_volumes = volumes ;
}
void GLCanvas3D :: set_bed_shape ( const Pointfs & shape )
{
m_bed . set_shape ( shape );
}
2018-05-09 10:47:04 +02:00
bool GLCanvas3D :: is_dirty () const
{
return m_dirty ;
}
void GLCanvas3D :: set_dirty ( bool dirty )
{
m_dirty = dirty ;
}
GLCanvas3D :: Camera :: EType GLCanvas3D :: get_camera_type () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_type ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_type ( GLCanvas3D :: Camera :: EType type )
{
2018-05-14 12:08:23 +02:00
m_camera . set_type ( type );
2018-05-09 10:47:04 +02:00
}
2018-05-14 11:31:58 +02:00
std :: string GLCanvas3D :: get_camera_type_as_string () const
{
return m_camera . get_type_as_string ();
}
2018-05-09 10:47:04 +02:00
float GLCanvas3D :: get_camera_zoom () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_zoom ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_zoom ( float zoom )
{
2018-05-14 12:08:23 +02:00
m_camera . set_zoom ( zoom );
2018-05-09 10:47:04 +02:00
}
float GLCanvas3D :: get_camera_phi () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_phi ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_phi ( float phi )
{
2018-05-14 12:08:23 +02:00
m_camera . set_phi ( phi );
2018-05-09 10:47:04 +02:00
}
float GLCanvas3D :: get_camera_theta () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_theta ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_theta ( float theta )
{
2018-05-14 12:08:23 +02:00
m_camera . set_theta ( theta );
2018-05-09 10:47:04 +02:00
}
float GLCanvas3D :: get_camera_distance () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_distance ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_distance ( float distance )
{
2018-05-14 12:08:23 +02:00
m_camera . set_distance ( distance );
2018-05-09 10:47:04 +02:00
}
const Pointf3 & GLCanvas3D :: get_camera_target () const
{
2018-05-14 12:08:23 +02:00
return m_camera . get_target ();
2018-05-09 10:47:04 +02:00
}
void GLCanvas3D :: set_camera_target ( const Pointf3 & target )
{
2018-05-14 12:08:23 +02:00
m_camera . set_target ( target );
2018-05-09 10:47:04 +02:00
}
2018-05-14 14:14:19 +02:00
BoundingBoxf3 GLCanvas3D :: bed_bounding_box () const
{
return m_bed . get_bounding_box ();
}
BoundingBoxf3 GLCanvas3D :: volumes_bounding_box () const
{
BoundingBoxf3 bb ;
if ( m_volumes != nullptr )
{
for ( const GLVolume * volume : m_volumes -> volumes )
{
if ( ! m_apply_zoom_to_volumes_filter || (( volume != nullptr ) && volume -> zoom_to_volumes ))
bb . merge ( volume -> transformed_bounding_box ());
}
}
return bb ;
}
BoundingBoxf3 GLCanvas3D :: max_bounding_box () const
{
BoundingBoxf3 bb = bed_bounding_box ();
bb . merge ( volumes_bounding_box ());
return bb ;
}
2018-05-15 10:32:38 +02:00
void GLCanvas3D :: zoom_to_bed ()
{
_zoom_to_bounding_box ( bed_bounding_box ());
}
void GLCanvas3D :: zoom_to_volumes ()
{
m_apply_zoom_to_volumes_filter = true ;
_zoom_to_bounding_box ( volumes_bounding_box ());
m_apply_zoom_to_volumes_filter = false ;
}
2018-05-15 09:50:01 +02:00
void GLCanvas3D :: register_on_viewport_changed_callback ( void * callback )
{
if ( callback != nullptr )
m_on_viewport_changed_callback . register_callback ( callback );
}
2018-05-14 14:47:13 +02:00
void GLCanvas3D :: on_size ( wxSizeEvent & evt )
{
set_dirty ( true );
}
void GLCanvas3D :: on_idle ( wxIdleEvent & evt )
{
if ( ! is_dirty () || ! is_shown_on_screen ())
return ;
if ( m_canvas != nullptr )
{
std :: pair < int , int > size = _get_canvas_size ();
resize (( unsigned int ) size . first , ( unsigned int ) size . second );
m_canvas -> Refresh ();
}
}
2018-05-14 14:14:19 +02:00
void GLCanvas3D :: _zoom_to_bounding_box ( const BoundingBoxf3 & bbox )
{
2018-05-15 09:50:01 +02:00
// Calculate the zoom factor needed to adjust viewport to bounding box.
float zoom = _get_zoom_to_bounding_box_factor ( bbox );
if ( zoom > 0.0f )
{
set_camera_zoom ( zoom );
// center view around bounding box center
set_camera_target ( bbox . center ());
m_on_viewport_changed_callback . call ();
if ( is_shown_on_screen ())
{
std :: pair < int , int > size = _get_canvas_size ();
resize (( unsigned int ) size . first , ( unsigned int ) size . second );
if ( m_canvas != nullptr )
m_canvas -> Refresh ();
}
}
2018-05-14 14:14:19 +02:00
}
2018-05-14 14:47:13 +02:00
std :: pair < int , int > GLCanvas3D :: _get_canvas_size () const
{
std :: pair < int , int > ret ( 0 , 0 );
if ( m_canvas != nullptr )
m_canvas -> GetSize ( & ret . first , & ret . second );
return ret ;
}
2018-05-15 09:50:01 +02:00
float GLCanvas3D :: _get_zoom_to_bounding_box_factor ( const BoundingBoxf3 & bbox ) const
{
float max_bb_size = bbox . max_size ();
if ( max_bb_size == 0.0f )
return - 1.0f ;
// project the bbox vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
// we need the view matrix, we let opengl calculate it(same as done in render sub)
:: glMatrixMode ( GL_MODELVIEW );
:: glLoadIdentity ();
if ( TURNTABLE_MODE )
{
// Turntable mode is enabled by default.
:: glRotatef ( - get_camera_theta (), 1.0f , 0.0f , 0.0f ); // pitch
:: glRotatef ( get_camera_phi (), 0.0f , 0.0f , 1.0f ); // yaw
}
else
{
// Shift the perspective camera.
Pointf3 camera_pos ( 0.0 , 0.0 , - ( coordf_t ) get_camera_distance ());
:: glTranslatef (( float ) camera_pos . x , ( float ) camera_pos . y , ( float ) camera_pos . z );
// my @rotmat = quat_to_rotmatrix($self->quat); <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEMPORARY COMMENTED OUT
// glMultMatrixd_p(@rotmat[0..15]); <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEMPORARY COMMENTED OUT
}
const Pointf3 & target = get_camera_target ();
:: glTranslatef ( - ( float ) target . x , - ( float ) target . y , - ( float ) target . z );
// get the view matrix back from opengl
GLfloat matrix [ 16 ];
:: glGetFloatv ( GL_MODELVIEW_MATRIX , matrix );
// camera axes
Pointf3 right (( coordf_t ) matrix [ 0 ], ( coordf_t ) matrix [ 4 ], ( coordf_t ) matrix [ 8 ]);
Pointf3 up (( coordf_t ) matrix [ 1 ], ( coordf_t ) matrix [ 5 ], ( coordf_t ) matrix [ 9 ]);
Pointf3 forward (( coordf_t ) matrix [ 2 ], ( coordf_t ) matrix [ 6 ], ( coordf_t ) matrix [ 10 ]);
Pointf3 bb_min = bbox . min ;
Pointf3 bb_max = bbox . max ;
Pointf3 bb_center = bbox . center ();
// bbox vertices in world space
std :: vector < Pointf3 > vertices ;
vertices . reserve ( 8 );
vertices . push_back ( bb_min );
vertices . emplace_back ( bb_max . x , bb_min . y , bb_min . z );
vertices . emplace_back ( bb_max . x , bb_max . y , bb_min . z );
vertices . emplace_back ( bb_min . x , bb_max . y , bb_min . z );
vertices . emplace_back ( bb_min . x , bb_min . y , bb_max . z );
vertices . emplace_back ( bb_max . x , bb_min . y , bb_max . z );
vertices . push_back ( bb_max );
vertices . emplace_back ( bb_min . x , bb_max . y , bb_max . z );
coordf_t max_x = 0.0 ;
coordf_t max_y = 0.0 ;
// margin factor to give some empty space around the bbox
coordf_t margin_factor = 1.25 ;
for ( const Pointf3 v : vertices )
{
// project vertex on the plane perpendicular to camera forward axis
Pointf3 pos ( v . x - bb_center . x , v . y - bb_center . y , v . z - bb_center . z );
Pointf3 proj_on_plane = pos - dot ( pos , forward ) * forward ;
// calculates vertex coordinate along camera xy axes
coordf_t x_on_plane = dot ( proj_on_plane , right );
coordf_t y_on_plane = dot ( proj_on_plane , up );
max_x = std :: max ( max_x , margin_factor * std :: abs ( x_on_plane ));
max_y = std :: max ( max_y , margin_factor * std :: abs ( y_on_plane ));
}
if (( max_x == 0.0 ) || ( max_y == 0.0 ))
return - 1.0f ;
max_x *= 2.0 ;
max_y *= 2.0 ;
std :: pair < int , int > cvs_size = _get_canvas_size ();
return ( float ) std :: min (( coordf_t ) cvs_size . first / max_x , ( coordf_t ) cvs_size . second / max_y );
}
void GLCanvas3D :: _deregister_callbacks ()
{
m_on_viewport_changed_callback . deregister_callback ();
}
2018-05-09 10:47:04 +02:00
} // namespace GUI
} // namespace Slic3r