kicad-source/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp
Seth Hillbrand 09e30adbb1 Move GL_CONTEXT_MGR into PGM_BASE singleton
This is the second try at 5326c36a5f.  The difference here is that we
have moved GL_CONTEXT_MGR into kicommon first which will hopefully
address some of the Windows linkage issues

The GL context lock needs to be shared across kifaces.  Otherwise, we
can end up blocking the lock from one kiface.  Unfortunately, I can't
find the issue in GitLab right now for where the footprint viewer shows
a blank screen after opening too many contexts.  But that's what this
fixes.
2025-01-09 09:03:08 -08:00

1444 lines
48 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2020 Mario Luzeiro <mrluzeiro@ua.pt>
* Copyright (C) 2023 CERN
* Copyright The 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 <cstdint>
#include <gal/opengl/kiglew.h> // 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 <board.h>
#include <footprint.h>
#include <gal/opengl/gl_context_mgr.h>
#include <3d_math.h>
#include <glm/geometric.hpp>
#include <lset.h>
#include <pgm_base.h>
#include <math/util.h> // for KiROUND
#include <utility>
#include <vector>
#include <wx/log.h>
#include <base_units.h>
/**
* 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( aAdapter, aCamera ),
m_canvas( aCanvas )
{
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 )
{
EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render;
if( cfg.use_board_editor_copper_colors && IsCopperLayer( aLayerID ) )
{
COLOR4D copper_color = m_boardAdapter.m_BoardEditorColors[aLayerID];
m_materials.m_Copper.m_Diffuse = SFVEC3F( copper_color.r, copper_color.g,
copper_color.b );
OglSetMaterial( m_materials.m_Copper, 1.0f );
m_materials.m_NonPlatedCopper.m_Diffuse = m_materials.m_Copper.m_Diffuse;
OglSetMaterial( m_materials.m_NonPlatedCopper, 1.0f );
return;
}
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<float>() * 0.03f,
glm::pi<float>() * 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_canvasInitialized )
{
if( !initializeOpenGL() )
return false;
}
EDA_3D_VIEWER_SETTINGS::RENDER_SETTINGS& cfg = m_boardAdapter.m_Cfg->m_Render;
if( m_reloadRequested )
{
std::unique_ptr<BUSY_INDICATOR> busy = CreateBusyIndicator();
if( aStatusReporter )
aStatusReporter->Report( _( "Loading..." ) );
// Careful here!
// We are in the middle of rendering and the reload method may show
// a dialog box that requires the opengl context for a redraw
Pgm().GetGLContextManager()->RunWithoutCtxLock( [this, aStatusReporter, aWarningReporter]()
{
reload( aStatusReporter, aWarningReporter );
} );
// generate a new 3D grid as the size of the board may had changed
m_lastGridType = static_cast<GRID3D_TYPE>( 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<GRID3D_TYPE>( 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<LAYER_3D_END> 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;
// 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 )
{
// generating internal copper layers is time consumming. so skip them
// if the board body is masking them (i.e. if the opacity is near 1.0)
// B_Cu is layer 2 and all inner layers are higher values
if( layer > B_Cu && IsCopperLayer( layer ) )
continue;
}
glPushMatrix();
OPENGL_RENDER_LIST* pLayerDispList = static_cast<OPENGL_RENDER_LIST*>( ii->second );
if( IsCopperLayer( layer ) )
{
if( cfg.DifferentiatePlatedCopper() )
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_canvasInitialized = 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<MODELTORENDER> &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<MODELTORENDER> &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<float>(), SFVEC3F( 0.0f, 1.0f, 0.0f ) );
fpMatrix = glm::rotate( fpMatrix, glm::pi<float>(), 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<float> 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<MODELTORENDER> 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<MODELTORENDER> 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<std::pair<const MODELTORENDER *, float>> 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<const MODELTORENDER *, float>& a,
std::pair<const MODELTORENDER *, float>& 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<const MODELTORENDER *, float>& 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();
}