/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015-2020 Mario Luzeiro * Copyright (C) 2023 CERN * Copyright (C) 2015-2024 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include // Must be included first #include "plugins/3dapi/xv3d_types.h" #include "render_3d_opengl.h" #include "opengl_utils.h" #include "common_ogl/ogl_utils.h" #include #include <3d_math.h> #include #include // for KiROUND #include #include #include #include /** * Scale conversion from 3d model units to pcb units */ #define UNITS3D_TO_UNITSPCB ( pcbIUScale.IU_PER_MM ) RENDER_3D_OPENGL::RENDER_3D_OPENGL( EDA_3D_CANVAS* aCanvas, BOARD_ADAPTER& aAdapter, CAMERA& aCamera ) : RENDER_3D_BASE( aCanvas, aAdapter, aCamera ) { wxLogTrace( m_logTrace, wxT( "RENDER_3D_OPENGL::RENDER_3D_OPENGL" ) ); m_layers.clear(); m_outerLayerHoles.clear(); m_innerLayerHoles.clear(); m_triangles.clear(); m_board = nullptr; m_antiBoard = nullptr; m_platedPadsFront = nullptr; m_platedPadsBack = nullptr; m_offboardPadsFront = nullptr; m_offboardPadsBack = nullptr; m_outerThroughHoles = nullptr; m_outerThroughHoleRings = nullptr; m_outerViaThroughHoles = nullptr; m_vias = nullptr; m_padHoles = nullptr; m_circleTexture = 0; m_grid = 0; m_lastGridType = GRID3D_TYPE::NONE; m_currentRollOverItem = nullptr; m_boardWithHoles = nullptr; m_3dModelMap.clear(); } RENDER_3D_OPENGL::~RENDER_3D_OPENGL() { wxLogTrace( m_logTrace, wxT( "RENDER_3D_OPENGL::RENDER_3D_OPENGL" ) ); freeAllLists(); glDeleteTextures( 1, &m_circleTexture ); } int RENDER_3D_OPENGL::GetWaitForEditingTimeOut() { return 50; // ms } void RENDER_3D_OPENGL::SetCurWindowSize( const wxSize& aSize ) { if( m_windowSize != aSize ) { m_windowSize = aSize; glViewport( 0, 0, m_windowSize.x, m_windowSize.y ); // Initialize here any screen dependent data here } } void RENDER_3D_OPENGL::setLightFront( bool enabled ) { if( enabled ) glEnable( GL_LIGHT0 ); else glDisable( GL_LIGHT0 ); } void RENDER_3D_OPENGL::setLightTop( bool enabled ) { if( enabled ) glEnable( GL_LIGHT1 ); else glDisable( GL_LIGHT1 ); } void RENDER_3D_OPENGL::setLightBottom( bool enabled ) { if( enabled ) glEnable( GL_LIGHT2 ); else glDisable( GL_LIGHT2 ); } void RENDER_3D_OPENGL::render3dArrows() { const float arrow_size = RANGE_SCALE_3D * 0.30f; glDisable( GL_CULL_FACE ); // YxY squared view port, this is on propose glViewport( 4, 4, m_windowSize.y / 8 , m_windowSize.y / 8 ); glClear( GL_DEPTH_BUFFER_BIT ); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective( 45.0f, 1.0f, 0.001f, RANGE_SCALE_3D ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); const glm::mat4 TranslationMatrix = glm::translate( glm::mat4( 1.0f ), SFVEC3F( 0.0f, 0.0f, -( arrow_size * 2.75f ) ) ); const glm::mat4 ViewMatrix = TranslationMatrix * m_camera.GetRotationMatrix(); glLoadMatrixf( glm::value_ptr( ViewMatrix ) ); setArrowMaterial(); glColor3f( 0.9f, 0.0f, 0.0f ); DrawRoundArrow( SFVEC3F( 0.0f, 0.0f, 0.0f ), SFVEC3F( arrow_size, 0.0f, 0.0f ), 0.275f ); glColor3f( 0.0f, 0.9f, 0.0f ); DrawRoundArrow( SFVEC3F( 0.0f, 0.0f, 0.0f ), SFVEC3F( 0.0f, arrow_size, 0.0f ), 0.275f ); glColor3f( 0.0f, 0.0f, 0.9f ); DrawRoundArrow( SFVEC3F( 0.0f, 0.0f, 0.0f ), SFVEC3F( 0.0f, 0.0f, arrow_size ), 0.275f ); glEnable( GL_CULL_FACE ); } void RENDER_3D_OPENGL::setupMaterials() { m_materials = {}; // http://devernay.free.fr/cours/opengl/materials.html // Plated copper // Copper material mixed with the copper color m_materials.m_Copper.m_Ambient = SFVEC3F( m_boardAdapter.m_CopperColor.r * 0.1f, m_boardAdapter.m_CopperColor.g * 0.1f, m_boardAdapter.m_CopperColor.b * 0.1f); m_materials.m_Copper.m_Specular = SFVEC3F( m_boardAdapter.m_CopperColor.r * 0.75f + 0.25f, m_boardAdapter.m_CopperColor.g * 0.75f + 0.25f, m_boardAdapter.m_CopperColor.b * 0.75f + 0.25f ); // This guess the material type(ex: copper vs gold) to determine the // shininess factor between 0.1 and 0.4 float shininessfactor = 0.40f - mapf( fabs( m_boardAdapter.m_CopperColor.r - m_boardAdapter.m_CopperColor.g ), 0.15f, 1.00f, 0.00f, 0.30f ); m_materials.m_Copper.m_Shininess = shininessfactor * 128.0f; m_materials.m_Copper.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); // Non plated copper (raw copper) m_materials.m_NonPlatedCopper.m_Ambient = SFVEC3F( 0.191f, 0.073f, 0.022f ); m_materials.m_NonPlatedCopper.m_Diffuse = SFVEC3F( 184.0f / 255.0f, 115.0f / 255.0f, 50.0f / 255.0f ); m_materials.m_NonPlatedCopper.m_Specular = SFVEC3F( 0.256f, 0.137f, 0.086f ); m_materials.m_NonPlatedCopper.m_Shininess = 0.1f * 128.0f; m_materials.m_NonPlatedCopper.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); // Paste material mixed with paste color m_materials.m_Paste.m_Ambient = SFVEC3F( m_boardAdapter.m_SolderPasteColor.r, m_boardAdapter.m_SolderPasteColor.g, m_boardAdapter.m_SolderPasteColor.b ); m_materials.m_Paste.m_Specular = SFVEC3F( m_boardAdapter.m_SolderPasteColor.r * m_boardAdapter.m_SolderPasteColor.r, m_boardAdapter.m_SolderPasteColor.g * m_boardAdapter.m_SolderPasteColor.g, m_boardAdapter.m_SolderPasteColor.b * m_boardAdapter.m_SolderPasteColor.b ); m_materials.m_Paste.m_Shininess = 0.1f * 128.0f; m_materials.m_Paste.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); // Silk screen material mixed with silk screen color m_materials.m_SilkSTop.m_Ambient = SFVEC3F( m_boardAdapter.m_SilkScreenColorTop.r, m_boardAdapter.m_SilkScreenColorTop.g, m_boardAdapter.m_SilkScreenColorTop.b ); m_materials.m_SilkSTop.m_Specular = SFVEC3F( m_boardAdapter.m_SilkScreenColorTop.r * m_boardAdapter.m_SilkScreenColorTop.r + 0.10f, m_boardAdapter.m_SilkScreenColorTop.g * m_boardAdapter.m_SilkScreenColorTop.g + 0.10f, m_boardAdapter.m_SilkScreenColorTop.b * m_boardAdapter.m_SilkScreenColorTop.b + 0.10f ); m_materials.m_SilkSTop.m_Shininess = 0.078125f * 128.0f; m_materials.m_SilkSTop.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); // Silk screen material mixed with silk screen color m_materials.m_SilkSBot.m_Ambient = SFVEC3F( m_boardAdapter.m_SilkScreenColorBot.r, m_boardAdapter.m_SilkScreenColorBot.g, m_boardAdapter.m_SilkScreenColorBot.b ); m_materials.m_SilkSBot.m_Specular = SFVEC3F( m_boardAdapter.m_SilkScreenColorBot.r * m_boardAdapter.m_SilkScreenColorBot.r + 0.10f, m_boardAdapter.m_SilkScreenColorBot.g * m_boardAdapter.m_SilkScreenColorBot.g + 0.10f, m_boardAdapter.m_SilkScreenColorBot.b * m_boardAdapter.m_SilkScreenColorBot.b + 0.10f ); m_materials.m_SilkSBot.m_Shininess = 0.078125f * 128.0f; m_materials.m_SilkSBot.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); m_materials.m_SolderMask.m_Shininess = 0.8f * 128.0f; m_materials.m_SolderMask.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); // Epoxy material m_materials.m_EpoxyBoard.m_Ambient = SFVEC3F( 117.0f / 255.0f, 97.0f / 255.0f, 47.0f / 255.0f ); m_materials.m_EpoxyBoard.m_Specular = SFVEC3F( 18.0f / 255.0f, 3.0f / 255.0f, 20.0f / 255.0f ); m_materials.m_EpoxyBoard.m_Shininess = 0.1f * 128.0f; m_materials.m_EpoxyBoard.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); } void RENDER_3D_OPENGL::setLayerMaterial( PCB_LAYER_ID aLayerID ) { switch( aLayerID ) { case F_Mask: case B_Mask: { const SFVEC4F layerColor = aLayerID == F_Mask ? m_boardAdapter.m_SolderMaskColorTop : m_boardAdapter.m_SolderMaskColorBot; m_materials.m_SolderMask.m_Diffuse = layerColor; // Convert Opacity to Transparency m_materials.m_SolderMask.m_Transparency = 1.0f - layerColor.a; m_materials.m_SolderMask.m_Ambient = m_materials.m_SolderMask.m_Diffuse * 0.3f; m_materials.m_SolderMask.m_Specular = m_materials.m_SolderMask.m_Diffuse * m_materials.m_SolderMask.m_Diffuse; OglSetMaterial( m_materials.m_SolderMask, 1.0f ); break; } case B_Paste: case F_Paste: m_materials.m_Paste.m_Diffuse = m_boardAdapter.m_SolderPasteColor; OglSetMaterial( m_materials.m_Paste, 1.0f ); break; case B_SilkS: m_materials.m_SilkSBot.m_Diffuse = m_boardAdapter.m_SilkScreenColorBot; OglSetMaterial( m_materials.m_SilkSBot, 1.0f ); break; case F_SilkS: m_materials.m_SilkSTop.m_Diffuse = m_boardAdapter.m_SilkScreenColorTop; OglSetMaterial( m_materials.m_SilkSTop, 1.0f ); break; case B_Adhes: case F_Adhes: case Dwgs_User: case Cmts_User: case Eco1_User: case Eco2_User: case Edge_Cuts: case Margin: case B_CrtYd: case F_CrtYd: case B_Fab: case F_Fab: switch( aLayerID ) { case Dwgs_User: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_UserDrawingsColor; break; case Cmts_User: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_UserCommentsColor; break; case Eco1_User: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_ECO1Color; break; case Eco2_User: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_ECO2Color; break; case Edge_Cuts: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_UserDrawingsColor; break; case Margin: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.m_UserDrawingsColor; break; default: m_materials.m_Plastic.m_Diffuse = m_boardAdapter.GetLayerColor( aLayerID ); break; } m_materials.m_Plastic.m_Ambient = SFVEC3F( m_materials.m_Plastic.m_Diffuse.r * 0.05f, m_materials.m_Plastic.m_Diffuse.g * 0.05f, m_materials.m_Plastic.m_Diffuse.b * 0.05f ); m_materials.m_Plastic.m_Specular = SFVEC3F( m_materials.m_Plastic.m_Diffuse.r * 0.7f, m_materials.m_Plastic.m_Diffuse.g * 0.7f, m_materials.m_Plastic.m_Diffuse.b * 0.7f ); m_materials.m_Plastic.m_Shininess = 0.078125f * 128.0f; m_materials.m_Plastic.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f ); OglSetMaterial( m_materials.m_Plastic, 1.0f ); break; default: m_materials.m_Copper.m_Diffuse = m_boardAdapter.m_CopperColor; OglSetMaterial( m_materials.m_Copper, 1.0f ); break; } } void init_lights() { // Setup light // https://www.opengl.org/sdk/docs/man2/xhtml/glLight.xml const GLfloat ambient[] = { 0.084f, 0.084f, 0.084f, 1.0f }; const GLfloat diffuse0[] = { 0.3f, 0.3f, 0.3f, 1.0f }; const GLfloat specular0[] = { 0.5f, 0.5f, 0.5f, 1.0f }; glLightfv( GL_LIGHT0, GL_AMBIENT, ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse0 ); glLightfv( GL_LIGHT0, GL_SPECULAR, specular0 ); const GLfloat diffuse12[] = { 0.7f, 0.7f, 0.7f, 1.0f }; const GLfloat specular12[] = { 0.7f, 0.7f, 0.7f, 1.0f }; // defines a directional light that points along the negative z-axis GLfloat position[4] = { 0.0f, 0.0f, 1.0f, 0.0f }; // This makes a vector slight not perpendicular with XZ plane const SFVEC3F vectorLight = SphericalToCartesian( glm::pi() * 0.03f, glm::pi() * 0.25f ); position[0] = vectorLight.x; position[1] = vectorLight.y; position[2] = vectorLight.z; glLightfv( GL_LIGHT1, GL_AMBIENT, ambient ); glLightfv( GL_LIGHT1, GL_DIFFUSE, diffuse12 ); glLightfv( GL_LIGHT1, GL_SPECULAR, specular12 ); glLightfv( GL_LIGHT1, GL_POSITION, position ); // defines a directional light that points along the positive z-axis position[2] = -position[2]; glLightfv( GL_LIGHT2, GL_AMBIENT, ambient ); glLightfv( GL_LIGHT2, GL_DIFFUSE, diffuse12 ); glLightfv( GL_LIGHT2, GL_SPECULAR, specular12 ); glLightfv( GL_LIGHT2, GL_POSITION, position ); const GLfloat lmodel_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f }; glLightModelfv( GL_LIGHT_MODEL_AMBIENT, lmodel_ambient ); glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE ); } void RENDER_3D_OPENGL::setCopperMaterial() { OglSetMaterial( m_materials.m_NonPlatedCopper, 1.0f ); } void RENDER_3D_OPENGL::setPlatedCopperAndDepthOffset( PCB_LAYER_ID aLayer_id ) { glEnable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( -0.1f, -2.0f ); setLayerMaterial( aLayer_id ); } void RENDER_3D_OPENGL::unsetDepthOffset() { glDisable( GL_POLYGON_OFFSET_FILL ); } void RENDER_3D_OPENGL::renderBoardBody( bool aSkipRenderHoles ) { m_materials.m_EpoxyBoard.m_Diffuse = m_boardAdapter.m_BoardBodyColor; // opacity to transparency m_materials.m_EpoxyBoard.m_Transparency = 1.0f - m_boardAdapter.m_BoardBodyColor.a; OglSetMaterial( m_materials.m_EpoxyBoard, 1.0f ); OPENGL_RENDER_LIST* ogl_disp_list = nullptr; if( aSkipRenderHoles ) ogl_disp_list = m_board; else ogl_disp_list = m_boardWithHoles; if( ogl_disp_list ) { ogl_disp_list->ApplyScalePosition( -m_boardAdapter.GetBoardBodyThickness() / 2.0f, m_boardAdapter.GetBoardBodyThickness() ); ogl_disp_list->SetItIsTransparent( true ); ogl_disp_list->DrawAll(); } } static inline SFVEC4F premultiplyAlpha( const SFVEC4F& aInput ) { return SFVEC4F( aInput.r * aInput.a, aInput.g * aInput.a, aInput.b * aInput.a, aInput.a ); } bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter, REPORTER* aWarningReporter ) { // Initialize OpenGL if( !m_is_opengl_initialized ) { if( !initializeOpenGL() ) return false; } EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render; if( m_reloadRequested ) { std::unique_ptr busy = CreateBusyIndicator(); if( aStatusReporter ) aStatusReporter->Report( _( "Loading..." ) ); reload( aStatusReporter, aWarningReporter ); // generate a new 3D grid as the size of the board may had changed m_lastGridType = static_cast( cfg.grid_type ); generate3dGrid( m_lastGridType ); } else { // Check if grid was changed if( cfg.grid_type != m_lastGridType ) { // and generate a new one m_lastGridType = static_cast( cfg.grid_type ); generate3dGrid( m_lastGridType ); } } setupMaterials(); // Initial setup glDepthFunc( GL_LESS ); glEnable( GL_CULL_FACE ); glFrontFace( GL_CCW ); // This is the OpenGL default glEnable( GL_NORMALIZE ); // This allow OpenGL to normalize the normals after transformations glViewport( 0, 0, m_windowSize.x, m_windowSize.y ); if( aIsMoving && cfg.opengl_AA_disableOnMove ) glDisable( GL_MULTISAMPLE ); else glEnable( GL_MULTISAMPLE ); // clear color and depth buffers glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); glClearDepth( 1.0f ); glClearStencil( 0x00 ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); OglResetTextureState(); // Draw the background ( rectangle with color gradient) OglDrawBackground( premultiplyAlpha( m_boardAdapter.m_BgColorTop ), premultiplyAlpha( m_boardAdapter.m_BgColorBot ) ); glEnable( GL_DEPTH_TEST ); // Set projection and modelview matrixes glMatrixMode( GL_PROJECTION ); glLoadMatrixf( glm::value_ptr( m_camera.GetProjectionMatrix() ) ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glLoadMatrixf( glm::value_ptr( m_camera.GetViewMatrix() ) ); // Position the headlight setLightFront( true ); setLightTop( true ); setLightBottom( true ); glEnable( GL_LIGHTING ); { const SFVEC3F& cameraPos = m_camera.GetPos(); // Place the light at a minimum Z so the diffuse factor will not drop // and the board will still look with good light. float zpos; if( cameraPos.z > 0.0f ) zpos = glm::max( cameraPos.z, 0.5f ) + cameraPos.z * cameraPos.z; else zpos = glm::min( cameraPos.z,-0.5f ) - cameraPos.z * cameraPos.z; // This is a point light. const GLfloat headlight_pos[] = { cameraPos.x, cameraPos.y, zpos, 1.0f }; glLightfv( GL_LIGHT0, GL_POSITION, headlight_pos ); } bool skipThickness = aIsMoving && cfg.opengl_thickness_disableOnMove; bool skipRenderHoles = aIsMoving && cfg.opengl_holes_disableOnMove; bool skipRenderVias = aIsMoving && cfg.opengl_vias_disableOnMove; bool showThickness = !skipThickness; std::bitset layerFlags = m_boardAdapter.GetVisibleLayers(); setLayerMaterial( B_Cu ); if( !( skipRenderVias || skipRenderHoles ) && m_vias ) m_vias->DrawAll(); if( !skipRenderHoles && m_padHoles ) m_padHoles->DrawAll(); // Display copper and tech layers for( MAP_OGL_DISP_LISTS::const_iterator ii = m_layers.begin(); ii != m_layers.end(); ++ii ) { const PCB_LAYER_ID layer = ( PCB_LAYER_ID )( ii->first ); bool isSilkLayer = layer == F_SilkS || layer == B_SilkS; bool isMaskLayer = layer == F_Mask || layer == B_Mask; bool isPasteLayer = layer == F_Paste || layer == B_Paste; bool isCopperLayer = layer >= F_Cu && layer <= B_Cu; // Mask layers are not processed here because they are a special case if( isMaskLayer ) continue; // Do not show inner layers when it is displaying the board and board body is opaque // enough: the time to create inner layers can be *really significant*. // So avoid creating them is they are not very visible const double opacity_min = 0.8; if( layerFlags.test( LAYER_3D_BOARD ) && m_boardAdapter.m_BoardBodyColor.a > opacity_min ) { if( layer > F_Cu && layer < B_Cu ) continue; } glPushMatrix(); OPENGL_RENDER_LIST* pLayerDispList = static_cast( ii->second ); if( isCopperLayer ) { if( cfg.differentiate_plated_copper ) setCopperMaterial(); else setLayerMaterial( layer ); OPENGL_RENDER_LIST* outerTH = nullptr; OPENGL_RENDER_LIST* viaHoles = nullptr; if( !skipRenderHoles ) { outerTH = m_outerThroughHoles; viaHoles = m_outerLayerHoles[layer]; } if( m_antiBoard ) m_antiBoard->ApplyScalePosition( pLayerDispList ); if( outerTH ) outerTH->ApplyScalePosition( pLayerDispList ); pLayerDispList->DrawCulled( showThickness, outerTH, viaHoles, m_antiBoard ); // Draw plated & offboard pads if( layer == F_Cu && ( m_platedPadsFront || m_offboardPadsFront ) ) { setPlatedCopperAndDepthOffset( layer ); if( m_platedPadsFront ) m_platedPadsFront->DrawCulled( showThickness, outerTH, viaHoles, m_antiBoard ); if( m_offboardPadsFront ) m_offboardPadsFront->DrawCulled( showThickness, outerTH, viaHoles ); } else if( layer == B_Cu && ( m_platedPadsBack || m_offboardPadsBack ) ) { setPlatedCopperAndDepthOffset( layer ); if( m_platedPadsBack ) m_platedPadsBack->DrawCulled( showThickness, outerTH, viaHoles, m_antiBoard ); if( m_offboardPadsBack ) m_offboardPadsBack->DrawCulled( showThickness, outerTH, viaHoles ); } unsetDepthOffset(); } else if( isPasteLayer && skipRenderHoles ) { // Do not render paste layers when skipRenderHoles is enabled or we get z-fight issues } else { setLayerMaterial( layer ); OPENGL_RENDER_LIST* throughHolesOuter = nullptr; OPENGL_RENDER_LIST* anti_board = nullptr; OPENGL_RENDER_LIST* solder_mask = nullptr; if( isSilkLayer && cfg.clip_silk_on_via_annuli ) throughHolesOuter = m_outerThroughHoleRings; else throughHolesOuter = m_outerThroughHoles; if( isSilkLayer && cfg.show_off_board_silk ) anti_board = nullptr; else if( LSET::PhysicalLayersMask().test( layer ) ) anti_board = m_antiBoard; if( isSilkLayer && cfg.subtract_mask_from_silk && !cfg.show_off_board_silk ) solder_mask = m_layers[ ( layer == B_SilkS) ? B_Mask : F_Mask ]; if( throughHolesOuter ) throughHolesOuter->ApplyScalePosition( pLayerDispList ); if( anti_board ) anti_board->ApplyScalePosition( pLayerDispList ); if( solder_mask ) solder_mask->ApplyScalePosition( pLayerDispList ); pLayerDispList->DrawCulled( showThickness, solder_mask, throughHolesOuter, anti_board ); } glPopMatrix(); } glm::mat4 cameraViewMatrix; glGetFloatv( GL_MODELVIEW_MATRIX, glm::value_ptr( cameraViewMatrix ) ); // Render 3D Models (Non-transparent) renderOpaqueModels( cameraViewMatrix ); // Display board body if( layerFlags.test( LAYER_3D_BOARD ) ) renderBoardBody( skipRenderHoles ); // Display transparent mask layers if( layerFlags.test( LAYER_3D_SOLDERMASK_TOP ) || layerFlags.test( LAYER_3D_SOLDERMASK_BOTTOM ) ) { // add a depth buffer offset, it will help to hide some artifacts // on silkscreen where the SolderMask is removed glEnable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( 0.0f, -2.0f ); if( m_camera.GetPos().z > 0 ) { if( layerFlags.test( LAYER_3D_SOLDERMASK_BOTTOM ) ) { renderSolderMaskLayer( B_Mask, m_boardAdapter.GetLayerTopZPos( B_Mask ), showThickness, skipRenderHoles ); } if( layerFlags.test( LAYER_3D_SOLDERMASK_TOP ) ) { renderSolderMaskLayer( F_Mask, m_boardAdapter.GetLayerBottomZPos( F_Mask ), showThickness, skipRenderHoles ); } } else { if( layerFlags.test( LAYER_3D_SOLDERMASK_TOP ) ) { renderSolderMaskLayer( F_Mask, m_boardAdapter.GetLayerBottomZPos( F_Mask ), showThickness, skipRenderHoles ); } if( layerFlags.test( LAYER_3D_SOLDERMASK_BOTTOM ) ) { renderSolderMaskLayer( B_Mask, m_boardAdapter.GetLayerTopZPos( B_Mask ), showThickness, skipRenderHoles ); } } glDisable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( 0.0f, 0.0f ); } // Render 3D Models (Transparent) // !TODO: this can be optimized. If there are no transparent models (or no opacity), // then there is no need to make this function call. glDepthMask( GL_FALSE ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); // Enables Texture Env so it can combine model transparency with each footprint opacity glEnable( GL_TEXTURE_2D ); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, m_circleTexture ); // Uses an existent texture so the glTexEnv operations will work glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvf( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); glTexEnvf( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE ); glTexEnvi( GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA ); glTexEnvi( GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA ); renderTransparentModels( cameraViewMatrix ); glDisable( GL_BLEND ); OglResetTextureState(); glDepthMask( GL_TRUE ); // Render Grid if( cfg.grid_type != GRID3D_TYPE::NONE ) { glDisable( GL_LIGHTING ); if( glIsList( m_grid ) ) glCallList( m_grid ); glEnable( GL_LIGHTING ); } // Render 3D arrows if( cfg.show_axis ) render3dArrows(); // Return back to the original viewport (this is important if we want // to take a screenshot after the render) glViewport( 0, 0, m_windowSize.x, m_windowSize.y ); return false; } bool RENDER_3D_OPENGL::initializeOpenGL() { glEnable( GL_LINE_SMOOTH ); glShadeModel( GL_SMOOTH ); // 4-byte pixel alignment glPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); // Initialize the open GL texture to draw the filled semi-circle of the segments IMAGE* circleImage = new IMAGE( SIZE_OF_CIRCLE_TEXTURE, SIZE_OF_CIRCLE_TEXTURE ); if( !circleImage ) return false; unsigned int circleRadius = ( SIZE_OF_CIRCLE_TEXTURE / 2 ) - 4; circleImage->CircleFilled( ( SIZE_OF_CIRCLE_TEXTURE / 2 ) - 0, ( SIZE_OF_CIRCLE_TEXTURE / 2 ) - 0, circleRadius, 0xFF ); IMAGE* circleImageBlured = new IMAGE( circleImage->GetWidth(), circleImage->GetHeight() ); circleImageBlured->EfxFilter_SkipCenter( circleImage, IMAGE_FILTER::GAUSSIAN_BLUR, circleRadius - 8 ); m_circleTexture = OglLoadTexture( *circleImageBlured ); delete circleImageBlured; circleImageBlured = nullptr; delete circleImage; circleImage = nullptr; init_lights(); // Use this mode if you want see the triangle lines (debug proposes) //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); m_is_opengl_initialized = true; return true; } void RENDER_3D_OPENGL::setArrowMaterial() { glEnable( GL_COLOR_MATERIAL ); glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); const SFVEC4F ambient = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f ); const SFVEC4F diffuse = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f ); const SFVEC4F emissive = SFVEC4F( 0.0f, 0.0f, 0.0f, 1.0f ); const SFVEC4F specular = SFVEC4F( 0.1f, 0.1f, 0.1f, 1.0f ); glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, &specular.r ); glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, 96.0f ); glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, &ambient.r ); glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, &diffuse.r ); glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, &emissive.r ); } void RENDER_3D_OPENGL::freeAllLists() { #define DELETE_AND_FREE( ptr ) \ { \ delete ptr; \ ptr = nullptr; \ } \ #define DELETE_AND_FREE_MAP( map ) \ { \ for( auto& [ layer, ptr ] : map ) \ delete ptr; \ \ map.clear(); \ } if( glIsList( m_grid ) ) glDeleteLists( m_grid, 1 ); m_grid = 0; DELETE_AND_FREE_MAP( m_layers ) DELETE_AND_FREE( m_platedPadsFront ) DELETE_AND_FREE( m_platedPadsBack ) DELETE_AND_FREE( m_offboardPadsFront ) DELETE_AND_FREE( m_offboardPadsBack ) DELETE_AND_FREE_MAP( m_outerLayerHoles ) DELETE_AND_FREE_MAP( m_innerLayerHoles ) for( TRIANGLE_DISPLAY_LIST* list : m_triangles ) delete list; m_triangles.clear(); DELETE_AND_FREE_MAP( m_3dModelMap ) m_3dModelMatrixMap.clear(); DELETE_AND_FREE( m_board ) DELETE_AND_FREE( m_boardWithHoles ) DELETE_AND_FREE( m_antiBoard ) DELETE_AND_FREE( m_outerThroughHoles ) DELETE_AND_FREE( m_outerViaThroughHoles ) DELETE_AND_FREE( m_outerThroughHoleRings ) DELETE_AND_FREE( m_vias ) DELETE_AND_FREE( m_padHoles ) } void RENDER_3D_OPENGL::renderSolderMaskLayer( PCB_LAYER_ID aLayerID, float aZPos, bool aShowThickness, bool aSkipRenderHoles ) { wxASSERT( (aLayerID == B_Mask) || (aLayerID == F_Mask) ); if( m_board ) { OPENGL_RENDER_LIST* solder_mask = m_layers[ aLayerID ]; OPENGL_RENDER_LIST* via_holes = aSkipRenderHoles ? nullptr : m_outerThroughHoles; if( via_holes ) via_holes->ApplyScalePosition( aZPos, m_boardAdapter.GetNonCopperLayerThickness() ); m_board->ApplyScalePosition( aZPos, m_boardAdapter.GetNonCopperLayerThickness() ); setLayerMaterial( aLayerID ); m_board->SetItIsTransparent( true ); m_board->DrawCulled( aShowThickness, solder_mask, via_holes ); } } void RENDER_3D_OPENGL::get3dModelsSelected( std::list &aDstRenderList, bool aGetTop, bool aGetBot, bool aRenderTransparentOnly, bool aRenderSelectedOnly ) { wxASSERT( ( aGetTop == true ) || ( aGetBot == true ) ); if( !m_boardAdapter.GetBoard() ) return; EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render; // Go for all footprints for( FOOTPRINT* fp : m_boardAdapter.GetBoard()->Footprints() ) { bool highlight = false; if( m_boardAdapter.m_IsBoardView ) { if( fp->IsSelected() ) highlight = true; if( cfg.highlight_on_rollover && fp == m_currentRollOverItem ) highlight = true; if( aRenderSelectedOnly != highlight ) continue; } if( !fp->Models().empty() ) { if( m_boardAdapter.IsFootprintShown( (FOOTPRINT_ATTR_T) fp->GetAttributes() ) ) { const bool isFlipped = fp->IsFlipped(); if( aGetTop == !isFlipped || aGetBot == isFlipped ) get3dModelsFromFootprint( aDstRenderList, fp, aRenderTransparentOnly, highlight ); } } } } void RENDER_3D_OPENGL::get3dModelsFromFootprint( std::list &aDstRenderList, const FOOTPRINT* aFootprint, bool aRenderTransparentOnly, bool aIsSelected ) { if( !aFootprint->Models().empty() ) { const double zpos = m_boardAdapter.GetFootprintZPos( aFootprint->IsFlipped() ); VECTOR2I pos = aFootprint->GetPosition(); glm::mat4 fpMatrix( 1.0f ); fpMatrix = glm::translate( fpMatrix, SFVEC3F( pos.x * m_boardAdapter.BiuTo3dUnits(), -pos.y * m_boardAdapter.BiuTo3dUnits(), zpos ) ); if( !aFootprint->GetOrientation().IsZero() ) { fpMatrix = glm::rotate( fpMatrix, (float) aFootprint->GetOrientation().AsRadians(), SFVEC3F( 0.0f, 0.0f, 1.0f ) ); } if( aFootprint->IsFlipped() ) { fpMatrix = glm::rotate( fpMatrix, glm::pi(), SFVEC3F( 0.0f, 1.0f, 0.0f ) ); fpMatrix = glm::rotate( fpMatrix, glm::pi(), SFVEC3F( 0.0f, 0.0f, 1.0f ) ); } double modelunit_to_3d_units_factor = m_boardAdapter.BiuTo3dUnits() * UNITS3D_TO_UNITSPCB; fpMatrix = glm::scale( fpMatrix, SFVEC3F( modelunit_to_3d_units_factor ) ); // Get the list of model files for this model for( const FP_3DMODEL& sM : aFootprint->Models() ) { if( !sM.m_Show || sM.m_Filename.empty() ) continue; // Check if the model is present in our cache map auto cache_i = m_3dModelMap.find( sM.m_Filename ); if( cache_i == m_3dModelMap.end() ) continue; if( const MODEL_3D* modelPtr = cache_i->second ) { bool opaque = sM.m_Opacity >= 1.0; if( ( !aRenderTransparentOnly && modelPtr->HasOpaqueMeshes() && opaque ) || ( aRenderTransparentOnly && ( modelPtr->HasTransparentMeshes() || !opaque ) ) ) { glm::mat4 modelworldMatrix = fpMatrix; const SFVEC3F offset = SFVEC3F( sM.m_Offset.x, sM.m_Offset.y, sM.m_Offset.z ); const SFVEC3F rotation = SFVEC3F( sM.m_Rotation.x, sM.m_Rotation.y, sM.m_Rotation.z ); const SFVEC3F scale = SFVEC3F( sM.m_Scale.x, sM.m_Scale.y, sM.m_Scale.z ); std::vector key = { offset.x, offset.y, offset.z, rotation.x, rotation.y, rotation.z, scale.x, scale.y, scale.z }; auto it = m_3dModelMatrixMap.find( key ); if( it != m_3dModelMatrixMap.end() ) { modelworldMatrix *= it->second; } else { glm::mat4 mtx( 1.0f ); mtx = glm::translate( mtx, offset ); mtx = glm::rotate( mtx, glm::radians( -rotation.z ), { 0.0f, 0.0f, 1.0f } ); mtx = glm::rotate( mtx, glm::radians( -rotation.y ), { 0.0f, 1.0f, 0.0f } ); mtx = glm::rotate( mtx, glm::radians( -rotation.x ), { 1.0f, 0.0f, 0.0f } ); mtx = glm::scale( mtx, scale ); m_3dModelMatrixMap[ key ] = mtx; modelworldMatrix *= mtx; } aDstRenderList.emplace_back( modelworldMatrix, modelPtr, aRenderTransparentOnly ? sM.m_Opacity : 1.0f, aRenderTransparentOnly, aFootprint->IsSelected() || aIsSelected ); } } } } } void RENDER_3D_OPENGL::renderOpaqueModels( const glm::mat4 &aCameraViewMatrix ) { EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render; const SFVEC3F selColor = m_boardAdapter.GetColor( cfg.opengl_selection_color ); glPushMatrix(); std::list renderList; if( m_boardAdapter.m_IsBoardView ) { renderList.clear(); get3dModelsSelected( renderList, true, true, false, true ); if( !renderList.empty() ) { MODEL_3D::BeginDrawMulti( false ); for( const MODELTORENDER& mtr : renderList ) renderModel( aCameraViewMatrix, mtr, selColor, nullptr ); MODEL_3D::EndDrawMulti(); } } renderList.clear(); get3dModelsSelected( renderList, true, true, false, false ); if( !renderList.empty() ) { MODEL_3D::BeginDrawMulti( true ); for( const MODELTORENDER& mtr : renderList ) renderModel( aCameraViewMatrix, mtr, selColor, nullptr ); MODEL_3D::EndDrawMulti(); } glPopMatrix(); } void RENDER_3D_OPENGL::renderTransparentModels( const glm::mat4 &aCameraViewMatrix ) { EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render; const SFVEC3F selColor = m_boardAdapter.GetColor( cfg.opengl_selection_color ); std::list renderListModels; // do not clear it until this function returns if( m_boardAdapter.m_IsBoardView ) { // Get Transparent Selected get3dModelsSelected( renderListModels, true, true, true, true ); } // Get Transparent Not Selected get3dModelsSelected( renderListModels, true, true, true, false ); if( renderListModels.empty() ) return; std::vector> transparentModelList; transparentModelList.reserve( renderListModels.size() ); // Calculate the distance to the camera for each model const SFVEC3F &cameraPos = m_camera.GetPos(); for( const MODELTORENDER& mtr : renderListModels ) { const BBOX_3D& bBox = mtr.m_model->GetBBox(); const SFVEC3F& bBoxCenter = bBox.GetCenter(); const SFVEC3F bBoxWorld = mtr.m_modelWorldMat * glm::vec4( bBoxCenter, 1.0f ); const float distanceToCamera = glm::length( cameraPos - bBoxWorld ); transparentModelList.emplace_back( &mtr, distanceToCamera ); } // Sort from back to front std::sort( transparentModelList.begin(), transparentModelList.end(), [&]( std::pair& a, std::pair& b ) { if( a.second != b.second ) return a.second > b.second; return a.first > b.first; // use pointers as a last resort } ); // Start rendering calls glPushMatrix(); bool isUsingColorInformation = !( transparentModelList.begin()->first->m_isSelected && m_boardAdapter.m_IsBoardView ); MODEL_3D::BeginDrawMulti( isUsingColorInformation ); for( const std::pair& mtr : transparentModelList ) { if( m_boardAdapter.m_IsBoardView ) { // Toggle between using model color or the select color if( !isUsingColorInformation && !mtr.first->m_isSelected ) { isUsingColorInformation = true; glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glEnable( GL_COLOR_MATERIAL ); } else if( isUsingColorInformation && mtr.first->m_isSelected ) { isUsingColorInformation = false; glDisableClientState( GL_COLOR_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glDisable( GL_COLOR_MATERIAL ); } } // Render model, sort each individuall material group // by passing cameraPos renderModel( aCameraViewMatrix, *mtr.first, selColor, &cameraPos ); } MODEL_3D::EndDrawMulti(); glPopMatrix(); } void RENDER_3D_OPENGL::renderModel( const glm::mat4 &aCameraViewMatrix, const MODELTORENDER &aModelToRender, const SFVEC3F &aSelColor, const SFVEC3F *aCameraWorldPos ) { EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render; const glm::mat4 modelviewMatrix = aCameraViewMatrix * aModelToRender.m_modelWorldMat; glLoadMatrixf( glm::value_ptr( modelviewMatrix ) ); aModelToRender.m_model->Draw( aModelToRender.m_isTransparent, aModelToRender.m_opacity, aModelToRender.m_isSelected, aSelColor, &aModelToRender.m_modelWorldMat, aCameraWorldPos ); if( cfg.show_model_bbox ) { const bool wasBlendEnabled = glIsEnabled( GL_BLEND ); if( !wasBlendEnabled ) { glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); } glDisable( GL_LIGHTING ); glLineWidth( 1 ); aModelToRender.m_model->DrawBboxes(); glLineWidth( 4 ); aModelToRender.m_model->DrawBbox(); glEnable( GL_LIGHTING ); if( !wasBlendEnabled ) glDisable( GL_BLEND ); } } void RENDER_3D_OPENGL::generate3dGrid( GRID3D_TYPE aGridType ) { if( glIsList( m_grid ) ) glDeleteLists( m_grid, 1 ); m_grid = 0; if( aGridType == GRID3D_TYPE::NONE ) return; m_grid = glGenLists( 1 ); if( !glIsList( m_grid ) ) return; glNewList( m_grid, GL_COMPILE ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); const double zpos = 0.0; // Color of grid lines const SFVEC3F gridColor = m_boardAdapter.GetColor( DARKGRAY ); // Color of grid lines every 5 lines const SFVEC3F gridColor_marker = m_boardAdapter.GetColor( LIGHTBLUE ); const double scale = m_boardAdapter.BiuTo3dUnits(); const GLfloat transparency = 0.35f; double griSizeMM = 0.0; switch( aGridType ) { case GRID3D_TYPE::GRID_1MM: griSizeMM = 1.0; break; case GRID3D_TYPE::GRID_2P5MM: griSizeMM = 2.5; break; case GRID3D_TYPE::GRID_5MM: griSizeMM = 5.0; break; case GRID3D_TYPE::GRID_10MM: griSizeMM = 10.0; break; default: case GRID3D_TYPE::NONE: return; } glNormal3f( 0.0, 0.0, 1.0 ); const VECTOR2I brd_size = m_boardAdapter.GetBoardSize(); VECTOR2I brd_center_pos = m_boardAdapter.GetBoardPos(); brd_center_pos.y = -brd_center_pos.y; const int xsize = std::max( brd_size.x, pcbIUScale.mmToIU( 100 ) ) * 1.2; const int ysize = std::max( brd_size.y, pcbIUScale.mmToIU( 100 ) ) * 1.2; // Grid limits, in 3D units double xmin = ( brd_center_pos.x - xsize / 2 ) * scale; double xmax = ( brd_center_pos.x + xsize / 2 ) * scale; double ymin = ( brd_center_pos.y - ysize / 2 ) * scale; double ymax = ( brd_center_pos.y + ysize / 2 ) * scale; double zmin = pcbIUScale.mmToIU( -50 ) * scale; double zmax = pcbIUScale.mmToIU( 100 ) * scale; // Set rasterised line width (min value = 1) glLineWidth( 1 ); // Draw horizontal grid centered on 3D origin (center of the board) for( int ii = 0; ; ii++ ) { if( (ii % 5) ) glColor4f( gridColor.r, gridColor.g, gridColor.b, transparency ); else glColor4f( gridColor_marker.r, gridColor_marker.g, gridColor_marker.b, transparency ); const int delta = KiROUND( ii * griSizeMM * pcbIUScale.IU_PER_MM ); if( delta <= xsize / 2 ) // Draw grid lines parallel to X axis { glBegin( GL_LINES ); glVertex3f( (brd_center_pos.x + delta) * scale, -ymin, zpos ); glVertex3f( (brd_center_pos.x + delta) * scale, -ymax, zpos ); glEnd(); if( ii != 0 ) { glBegin( GL_LINES ); glVertex3f( (brd_center_pos.x - delta) * scale, -ymin, zpos ); glVertex3f( (brd_center_pos.x - delta) * scale, -ymax, zpos ); glEnd(); } } if( delta <= ysize / 2 ) // Draw grid lines parallel to Y axis { glBegin( GL_LINES ); glVertex3f( xmin, -( brd_center_pos.y + delta ) * scale, zpos ); glVertex3f( xmax, -( brd_center_pos.y + delta ) * scale, zpos ); glEnd(); if( ii != 0 ) { glBegin( GL_LINES ); glVertex3f( xmin, -( brd_center_pos.y - delta ) * scale, zpos ); glVertex3f( xmax, -( brd_center_pos.y - delta ) * scale, zpos ); glEnd(); } } if( ( delta > ysize / 2 ) && ( delta > xsize / 2 ) ) break; } // Draw vertical grid on Z axis glNormal3f( 0.0, -1.0, 0.0 ); // Draw vertical grid lines (parallel to Z axis) double posy = -brd_center_pos.y * scale; for( int ii = 0; ; ii++ ) { if( (ii % 5) ) glColor4f( gridColor.r, gridColor.g, gridColor.b, transparency ); else glColor4f( gridColor_marker.r, gridColor_marker.g, gridColor_marker.b, transparency ); const double delta = ii * griSizeMM * pcbIUScale.IU_PER_MM; glBegin( GL_LINES ); xmax = ( brd_center_pos.x + delta ) * scale; glVertex3f( xmax, posy, zmin ); glVertex3f( xmax, posy, zmax ); glEnd(); if( ii != 0 ) { glBegin( GL_LINES ); xmin = ( brd_center_pos.x - delta ) * scale; glVertex3f( xmin, posy, zmin ); glVertex3f( xmin, posy, zmax ); glEnd(); } if( delta > xsize / 2.0f ) break; } // Draw horizontal grid lines on Z axis (parallel to X axis) for( int ii = 0; ; ii++ ) { if( ii % 5 ) glColor4f( gridColor.r, gridColor.g, gridColor.b, transparency ); else glColor4f( gridColor_marker.r, gridColor_marker.g, gridColor_marker.b, transparency ); const double delta = ii * griSizeMM * pcbIUScale.IU_PER_MM * scale; if( delta <= zmax ) { // Draw grid lines on Z axis (positive Z axis coordinates) glBegin( GL_LINES ); glVertex3f( xmin, posy, delta ); glVertex3f( xmax, posy, delta ); glEnd(); } if( delta <= -zmin && ( ii != 0 ) ) { // Draw grid lines on Z axis (negative Z axis coordinates) glBegin( GL_LINES ); glVertex3f( xmin, posy, -delta ); glVertex3f( xmax, posy, -delta ); glEnd(); } if( ( delta > zmax ) && ( delta > -zmin ) ) break; } glDisable( GL_BLEND ); glEndList(); }