# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: mrluzeiro@ua.pt-20150309204000-a3kv7k2qdwt3nqwf # target_branch: http://bazaar.launchpad.net/~kicad-product-\ # committers/kicad/product/ # testament_sha1: 09a55fa9db9c3d281a41ab4b5646630c85546ff3 # timestamp: 2015-03-09 21:41:27 +0100 # base_revision_id: maciej.suminski@cern.ch-20150309100654-\ # 8446qxznudbtthvi # # Begin patch === modified file '3d-viewer/3d_canvas.cpp' --- 3d-viewer/3d_canvas.cpp 2015-02-26 10:33:15 +0000 +++ 3d-viewer/3d_canvas.cpp 2015-03-05 10:26:17 +0000 @@ -563,7 +563,7 @@ void EDA_3D_CANVAS::SetLights() { // activate light. the source is above the xy plane, at source_pos - GLfloat source_pos[4] = { 0.0, 0.0, 30.0, 0.0 }; + GLfloat source_pos[4] = { 0.0, 0.0, 1000.0, 0.0 }; GLfloat light_color[4]; // color of lights (RGBA values) light_color[3] = 1.0; === modified file '3d-viewer/3d_draw.cpp' --- 3d-viewer/3d_draw.cpp 2014-08-24 13:29:59 +0000 +++ 3d-viewer/3d_draw.cpp 2015-03-09 20:40:00 +0000 @@ -342,16 +342,9 @@ glClearStencil( 0 ); glClearDepth( 1.0 ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); - - if( isEnabled( FL_RENDER_SMOOTH ) ) - { - glShadeModel( GL_SMOOTH ); - } - else - { - glShadeModel( GL_FLAT ); - } - + + glShadeModel( GL_SMOOTH ); + // Draw background glMatrixMode( GL_PROJECTION ); glLoadIdentity(); === modified file '3d-viewer/3d_frame.cpp' --- 3d-viewer/3d_frame.cpp 2014-08-21 11:59:57 +0000 +++ 3d-viewer/3d_frame.cpp 2015-03-09 20:40:00 +0000 @@ -54,7 +54,8 @@ static const wxChar keyRenderShadows[] = wxT( "Render_Shadows" ); static const wxChar keyRenderRemoveHoles[] = wxT( "Render_RemoveHoles" ); static const wxChar keyRenderTextures[] = wxT( "Render_Textures" ); -static const wxChar keyRenderSmooth[] = wxT( "Render_Smooth" ); +static const wxChar keyRenderSmoothNormals[] = wxT( "Render_Smooth_Normals" ); +static const wxChar keyRenderUseModelNormals[] =wxT( "Render_Use_Model_Normals" ); static const wxChar keyRenderMaterial[] = wxT( "Render_Material" ); static const wxChar keyShowAxis[] = wxT( "ShowAxis" ); @@ -247,8 +248,11 @@ aCfg->Read( keyRenderTextures, &tmp, false ); prms.SetFlag( FL_RENDER_TEXTURES, tmp ); - aCfg->Read( keyRenderSmooth, &tmp, false ); - prms.SetFlag( FL_RENDER_SMOOTH, tmp ); + aCfg->Read( keyRenderSmoothNormals, &tmp, false ); + prms.SetFlag( FL_RENDER_SMOOTH_NORMALS, tmp ); + + aCfg->Read( keyRenderUseModelNormals, &tmp, false ); + prms.SetFlag( FL_RENDER_USE_MODEL_NORMALS, tmp ); aCfg->Read( keyRenderMaterial, &tmp, false ); prms.SetFlag( FL_RENDER_MATERIAL, tmp ); @@ -312,7 +316,8 @@ aCfg->Write( keyRenderShadows, prms.GetFlag( FL_RENDER_SHADOWS ) ); aCfg->Write( keyRenderRemoveHoles, prms.GetFlag( FL_RENDER_SHOW_HOLES_IN_ZONES ) ); aCfg->Write( keyRenderTextures, prms.GetFlag( FL_RENDER_TEXTURES ) ); - aCfg->Write( keyRenderSmooth, prms.GetFlag( FL_RENDER_SMOOTH ) ); + aCfg->Write( keyRenderSmoothNormals, prms.GetFlag( FL_RENDER_SMOOTH_NORMALS ) ); + aCfg->Write( keyRenderUseModelNormals, prms.GetFlag( FL_RENDER_USE_MODEL_NORMALS ) ); aCfg->Write( keyRenderMaterial, prms.GetFlag( FL_RENDER_MATERIAL ) ); aCfg->Write( keyShowAxis, prms.GetFlag( FL_AXIS ) ); @@ -488,8 +493,13 @@ NewDisplay(GL_ID_TECH_LAYERS); return; - case ID_MENU3D_FL_RENDER_SMOOTH: - GetPrm3DVisu().SetFlag( FL_RENDER_SMOOTH, isChecked ); + case ID_MENU3D_FL_RENDER_SMOOTH_NORMALS: + GetPrm3DVisu().SetFlag( FL_RENDER_SMOOTH_NORMALS, isChecked ); + NewDisplay(); + return; + + case ID_MENU3D_FL_RENDER_USE_MODEL_NORMALS: + GetPrm3DVisu().SetFlag( FL_RENDER_USE_MODEL_NORMALS, isChecked ); NewDisplay(); return; === modified file '3d-viewer/3d_mesh_model.cpp' --- 3d-viewer/3d_mesh_model.cpp 2015-02-15 21:30:34 +0000 +++ 3d-viewer/3d_mesh_model.cpp 2015-03-09 20:40:00 +0000 @@ -40,6 +40,7 @@ isPerFaceNormalsComputed = false; isPointNormalizedComputed = false; isPerPointNormalsComputed = false; + isPerVertexNormalsVerified = false; m_Materials = NULL; childs.clear(); @@ -91,10 +92,7 @@ //DBG( printf( "openGL_Render" ) ); bool useMaterial = g_Parm_3D_Visu.GetFlag( FL_RENDER_MATERIAL ); bool smoothShapes = g_Parm_3D_Visu.IsRealisticMode() - && g_Parm_3D_Visu.GetFlag( FL_RENDER_SMOOTH ); - - if( m_Materials ) - m_Materials->SetOpenGLMaterial( 0, useMaterial ); + && g_Parm_3D_Visu.GetFlag( FL_RENDER_SMOOTH_NORMALS ); if( m_CoordIndex.size() == 0 ) return; @@ -109,19 +107,23 @@ calcPointNormalized(); calcPerFaceNormals(); - if( m_PerVertexNormalsNormalized.size() == 0 ) + if( smoothShapes ) { - if( smoothShapes ) + if( (m_PerVertexNormalsNormalized.size() > 0) && + g_Parm_3D_Visu.GetFlag( FL_RENDER_USE_MODEL_NORMALS ) ) + perVertexNormalsVerify_and_Repair(); + else calcPerPointNormals(); + } for( unsigned int idx = 0; idx < m_CoordIndex.size(); idx++ ) { - if( m_MaterialIndex.size() > 1 ) - { + if( m_MaterialIndex.size() == 0 ) + m_Materials->SetOpenGLMaterial( 0, useMaterial ); + else if( m_Materials ) m_Materials->SetOpenGLMaterial( m_MaterialIndex[idx], useMaterial ); - } switch( m_CoordIndex[idx].size() ) @@ -138,42 +140,69 @@ } - if( m_PerVertexNormalsNormalized.size() > 0 ) + if( smoothShapes ) { - for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + if( (m_PerVertexNormalsNormalized.size() > 0) && + g_Parm_3D_Visu.GetFlag( FL_RENDER_USE_MODEL_NORMALS ) ) { - glm::vec3 normal = m_PerVertexNormalsNormalized[m_NormalIndex[idx][ii]]; - glNormal3fv( &normal.x ); - - glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; - glVertex3fv( &point.x ); + for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + { + glm::vec3 normal = m_PerVertexNormalsNormalized[m_NormalIndex[idx][ii]]; + glNormal3fv( &normal.x ); + + // Flag error vertices + if ((normal.x == 0.0) && (normal.y == 0.0) && (normal.z == 0.0)) + glColor3f( 1.0, 0.0, 0.0 ); + + glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; + glVertex3fv( &point.x ); + } } - } - else if( smoothShapes ) - { - std::vector< glm::vec3 > normals_list; - normals_list = m_PerFaceVertexNormals[idx]; - - for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + else { - glm::vec3 normal = normals_list[ii]; - glNormal3fv( &normal.x ); - - glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; - glVertex3fv( &point.x ); + std::vector< glm::vec3 > normals_list; + normals_list = m_PerFaceVertexNormals[idx]; + + for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + { + glm::vec3 normal = normals_list[ii]; + glNormal3fv( &normal.x ); + + // Flag error vertices + if ((normal.x == 0.0) && (normal.y == 0.0) && (normal.z == 0.0)) + glColor3f( 1.0, 0.0, 0.0 ); + + glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; + glVertex3fv( &point.x ); + } } } else { // Flat - glm::vec3 normal = m_PerFaceNormalsNormalized[idx]; - - for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) - { - glNormal3fv( &normal.x ); - - glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; - glVertex3fv( &point.x ); + if( m_PerFaceNormalsNormalized.size() > 0 ) + { + glm::vec3 normal = m_PerFaceNormalsNormalized[idx]; + + for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + { + glNormal3fv( &normal.x ); + + // Flag error vertices + if ((normal.x == 0.0) && (normal.y == 0.0) && (normal.z == 0.0)) + glColor3f( 1.0, 0.0, 0.0 ); + + glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; + glVertex3fv( &point.x ); + } + } + else + { + for( unsigned int ii = 0; ii < m_CoordIndex[idx].size(); ii++ ) + { + glm::vec3 point = m_Point[m_CoordIndex[idx][ii]]; + glVertex3fv( &point.x ); + } } } @@ -184,6 +213,68 @@ } +void S3D_MESH::perVertexNormalsVerify_and_Repair() +{ + if( isPerVertexNormalsVerified == true ) + return; + + isPerVertexNormalsVerified = true; + + //DBG( printf( "perVertexNormalsVerify_and_Repair\n" ) ); + + for( unsigned int idx = 0; idx < m_PerVertexNormalsNormalized.size(); idx++ ) + { + glm::vec3 normal = m_PerVertexNormalsNormalized[idx]; + + if( (normal.x == 1.0) && ((normal.y != 0.0) || (normal.z != 0.0)) ) + { + normal.y = 0.0; + normal.z = 0.0; + } + else + if( (normal.y == 1.0) && ((normal.x != 0.0) || (normal.z != 0.0)) ) + { + normal.x = 0.0; + normal.z = 0.0; + } + else + if( (normal.z == 1.0) && ((normal.x != 0.0) || (normal.y != 0.0)) ) + { + normal.x = 0.0; + normal.y = 0.0; + } + else + if( (normal.x < FLT_EPSILON) && (normal.x > -FLT_EPSILON) ) + { + normal.x = 0.0; + } + else + if( (normal.y < FLT_EPSILON) && (normal.y > -FLT_EPSILON) ) + { + normal.y = 0.0; + } + else + if( (normal.z < FLT_EPSILON) && (normal.z > -FLT_EPSILON) ) + { + normal.z = 0.0; + } + + float l = glm::length( normal ); + + if( l > FLT_EPSILON ) // avoid division by zero + { + normal = normal / l; + } + else + { + //DBG( printf( "idx:%u\n", idx ) ); + } + + m_PerVertexNormalsNormalized[idx] = normal; + } +} + + void S3D_MESH::calcPointNormalized() { //DBG( printf( "calcPointNormalized\n" ) ); @@ -193,13 +284,10 @@ isPointNormalizedComputed = true; - if( m_PerVertexNormalsNormalized.size() > 0 ) - return; - m_PointNormalized.clear(); float biggerPoint = 0.0f; - for( unsigned int i = 0; i< m_Point.size(); i++ ) + for( unsigned int i = 0; i < m_Point.size(); i++ ) { if( fabs( m_Point[i].x ) > biggerPoint ) biggerPoint = fabs( m_Point[i].x ); @@ -245,20 +333,17 @@ isPerFaceNormalsComputed = true; - if( m_PerVertexNormalsNormalized.size() > 0 ) - return; - bool haveAlreadyNormals_from_model_file = false; - if( m_PerFaceNormalsNormalized.size() > 0 ) + if( ( m_PerFaceNormalsNormalized.size() > 0 ) && + g_Parm_3D_Visu.GetFlag( FL_RENDER_USE_MODEL_NORMALS ) ) haveAlreadyNormals_from_model_file = true; + else + m_PerFaceNormalsNormalized.clear(); m_PerFaceNormalsRaw.clear(); m_PerFaceSquaredArea.clear(); - //DBG( printf("m_CoordIndex.size %u\n", m_CoordIndex.size()) ); - //DBG( printf("m_PointNormalized.size %u\n", m_PointNormalized.size()) ); - // There are no points defined for the coordIndex if( m_PointNormalized.size() == 0 ) { @@ -273,13 +358,6 @@ glm::vec3 v1 = m_PointNormalized[m_CoordIndex[idx][1]]; glm::vec3 v2 = m_PointNormalized[m_CoordIndex[idx][m_CoordIndex[idx].size() - 1]]; - /* - // !TODO: improove and check what is best to calc the normal (check what have more resolution) - glm::vec3 v0 = m_Point[m_CoordIndex[idx][0]]; - glm::vec3 v1 = m_Point[m_CoordIndex[idx][1]]; - glm::vec3 v2 = m_Point[m_CoordIndex[idx][m_CoordIndex[idx].size() - 1]]; - */ - glm::vec3 cross_prod; /* @@ -317,11 +395,11 @@ } else { - // Cannot calc normal + //DBG( printf( "Cannot calc normal idx: %u", idx ) ); if( ( cross_prod.x > cross_prod.y ) && ( cross_prod.x > cross_prod.z ) ) { - cross_prod.x = 1.0; - cross_prod.y = 0.0; + cross_prod.x = 0.0; + cross_prod.y = 1.0; cross_prod.z = 0.0; } else if( ( cross_prod.y > cross_prod.x ) && ( cross_prod.y > cross_prod.z ) ) @@ -355,9 +433,6 @@ isPerPointNormalsComputed = true; - if( m_PerVertexNormalsNormalized.size() > 0 ) - return; - m_PerFaceVertexNormals.clear(); // Pre-allocate space for the entire vector of vertex normals so we can do parallel writes @@ -389,9 +464,18 @@ //if A != B { // ignore self if( each_face_A_idx != each_face_B_idx ) { - if( (m_CoordIndex[each_face_B_idx][0] == vertexIndex) - || (m_CoordIndex[each_face_B_idx][1] == vertexIndex) - || (m_CoordIndex[each_face_B_idx][2] == vertexIndex) ) + bool addThisVertex = false; + + for( unsigned int ii = 0; ii < m_CoordIndex[each_face_B_idx].size(); ii++ ) + { + if( m_CoordIndex[each_face_B_idx][ii] == vertexIndex ) + { + addThisVertex = true; + break; + } + } + + if( addThisVertex ) { glm::vec3 vector_face_B = m_PerFaceNormalsNormalized[each_face_B_idx]; === modified file '3d-viewer/3d_mesh_model.h' --- 3d-viewer/3d_mesh_model.h 2014-07-31 07:01:30 +0000 +++ 3d-viewer/3d_mesh_model.h 2015-03-09 20:40:00 +0000 @@ -95,6 +95,9 @@ bool isPerPointNormalsComputed; void calcPerPointNormals(); + + bool isPerVertexNormalsVerified; + void perVertexNormalsVerify_and_Repair(); }; === modified file '3d-viewer/3d_toolbar.cpp' --- 3d-viewer/3d_toolbar.cpp 2014-08-25 16:31:32 +0000 +++ 3d-viewer/3d_toolbar.cpp 2015-03-09 20:40:00 +0000 @@ -175,9 +175,13 @@ _( "Apply a grid/cloud textures to Board, Solder Mask and Silkscreen" ), KiBitmap( green_xpm ), wxITEM_CHECK ); - AddMenuItem( renderOptionsMenu, ID_MENU3D_FL_RENDER_SMOOTH, + AddMenuItem( renderOptionsMenu, ID_MENU3D_FL_RENDER_SMOOTH_NORMALS, _( "Render Smooth Normals" ), KiBitmap( green_xpm ), wxITEM_CHECK ); + + AddMenuItem( renderOptionsMenu, ID_MENU3D_FL_RENDER_USE_MODEL_NORMALS, + _( "Use Model Normals" ), + KiBitmap( green_xpm ), wxITEM_CHECK ); AddMenuItem( renderOptionsMenu, ID_MENU3D_FL_RENDER_MATERIAL, _( "Render Material Properties" ), @@ -287,8 +291,11 @@ item = menuBar->FindItem( ID_MENU3D_FL_RENDER_TEXTURES ); item->Check( GetPrm3DVisu().GetFlag( FL_RENDER_TEXTURES ) ); - item = menuBar->FindItem( ID_MENU3D_FL_RENDER_SMOOTH ); - item->Check( GetPrm3DVisu().GetFlag( FL_RENDER_SMOOTH ) ); + item = menuBar->FindItem( ID_MENU3D_FL_RENDER_SMOOTH_NORMALS ); + item->Check( GetPrm3DVisu().GetFlag( FL_RENDER_SMOOTH_NORMALS ) ); + + item = menuBar->FindItem( ID_MENU3D_FL_RENDER_USE_MODEL_NORMALS ); + item->Check( GetPrm3DVisu().GetFlag( FL_RENDER_USE_MODEL_NORMALS ) ); item = menuBar->FindItem( ID_MENU3D_FL_RENDER_MATERIAL ); item->Check( GetPrm3DVisu().GetFlag( FL_RENDER_MATERIAL ) ); === modified file '3d-viewer/3d_viewer_id.h' --- 3d-viewer/3d_viewer_id.h 2014-08-20 08:39:19 +0000 +++ 3d-viewer/3d_viewer_id.h 2015-03-09 20:40:00 +0000 @@ -49,7 +49,8 @@ ID_MENU3D_FL_RENDER_SHADOWS, ID_MENU3D_FL_RENDER_SHOW_HOLES_IN_ZONES, ID_MENU3D_FL_RENDER_TEXTURES, - ID_MENU3D_FL_RENDER_SMOOTH, + ID_MENU3D_FL_RENDER_SMOOTH_NORMALS, + ID_MENU3D_FL_RENDER_USE_MODEL_NORMALS, ID_MENU3D_FL_RENDER_MATERIAL, ID_END_COMMAND_3D, === modified file '3d-viewer/info3d_visu.h' --- 3d-viewer/info3d_visu.h 2014-08-22 10:24:14 +0000 +++ 3d-viewer/info3d_visu.h 2015-03-09 20:40:00 +0000 @@ -75,7 +75,8 @@ FL_RENDER_SHADOWS, FL_RENDER_SHOW_HOLES_IN_ZONES, FL_RENDER_TEXTURES, - FL_RENDER_SMOOTH, + FL_RENDER_SMOOTH_NORMALS, + FL_RENDER_USE_MODEL_NORMALS, FL_RENDER_MATERIAL, FL_LAST }; # Begin bundle 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 7H+TKEikJAg8Tx6hCo+T/i7kinChICYcjNw=