kicad/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp

1500 lines
51 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) 2015-2021 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 <gal/opengl/kiglew.h> // Must be included first
#include "render_3d_opengl.h"
#include "opengl_utils.h"
#include "common_ogl/ogl_utils.h"
#include <footprint.h>
#include <3d_math.h>
#include <math/util.h> // for KiROUND
#include <wx/log.h>
#include <base_units.h>
/**
* Scale conversion from 3d model units to pcb units
*/
#define UNITS3D_TO_UNITSPCB (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_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 = {};
if( m_boardAdapter.m_Cfg->m_Render.realistic )
{
// 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 );
}
else // Technical Mode
{
const SFVEC3F matAmbientColor = SFVEC3F( 0.10f );
const SFVEC3F matSpecularColor = SFVEC3F( 0.10f );
const float matShininess = 0.1f * 128.0f;
// Copper material
m_materials.m_Copper.m_Ambient = matAmbientColor;
m_materials.m_Copper.m_Specular = matSpecularColor;
m_materials.m_Copper.m_Shininess = matShininess;
m_materials.m_Copper.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Paste material
m_materials.m_Paste.m_Ambient = matAmbientColor;
m_materials.m_Paste.m_Specular = matSpecularColor;
m_materials.m_Paste.m_Shininess = matShininess;
m_materials.m_Paste.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Silk screen material
m_materials.m_SilkSTop.m_Ambient = matAmbientColor;
m_materials.m_SilkSTop.m_Specular = matSpecularColor;
m_materials.m_SilkSTop.m_Shininess = matShininess;
m_materials.m_SilkSTop.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Silk screen material
m_materials.m_SilkSBot.m_Ambient = matAmbientColor;
m_materials.m_SilkSBot.m_Specular = matSpecularColor;
m_materials.m_SilkSBot.m_Shininess = matShininess;
m_materials.m_SilkSBot.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Solder mask material
m_materials.m_SolderMask.m_Ambient = matAmbientColor;
m_materials.m_SolderMask.m_Specular = matSpecularColor;
m_materials.m_SolderMask.m_Shininess = matShininess;
m_materials.m_SolderMask.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Epoxy material
m_materials.m_EpoxyBoard.m_Ambient = matAmbientColor;
m_materials.m_EpoxyBoard.m_Specular = matSpecularColor;
m_materials.m_EpoxyBoard.m_Shininess = matShininess;
m_materials.m_EpoxyBoard.m_Emissive = SFVEC3F( 0.0f, 0.0f, 0.0f );
// Gray material (used for example in technical vias and pad holes)
m_materials.m_GrayMaterial.m_Ambient = SFVEC3F( 0.8f, 0.8f, 0.8f );
m_materials.m_GrayMaterial.m_Diffuse = SFVEC3F( 0.3f, 0.3f, 0.3f );
m_materials.m_GrayMaterial.m_Specular = SFVEC3F( 0.4f, 0.4f, 0.4f );
m_materials.m_GrayMaterial.m_Shininess = 0.01f * 128.0f;
m_materials.m_GrayMaterial.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 = getLayerColor( aLayerID );
m_materials.m_SolderMask.m_Diffuse = layerColor;
// Convert Opacity to Transparency
m_materials.m_SolderMask.m_Transparency = 1.0f - layerColor.a;
if( m_boardAdapter.m_Cfg->m_Render.realistic )
{
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 = getLayerColor( aLayerID );
OglSetMaterial( m_materials.m_Paste, 1.0f );
break;
case B_SilkS:
m_materials.m_SilkSBot.m_Diffuse = getLayerColor( aLayerID );
OglSetMaterial( m_materials.m_SilkSBot, 1.0f );
break;
case F_SilkS:
m_materials.m_SilkSTop.m_Diffuse = getLayerColor( aLayerID );
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:
m_materials.m_Plastic.m_Diffuse = getLayerColor( aLayerID );
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 = getLayerColor( aLayerID );
OglSetMaterial( m_materials.m_Copper, 1.0f );
break;
}
}
SFVEC4F RENDER_3D_OPENGL::getLayerColor( PCB_LAYER_ID aLayerID )
{
SFVEC4F layerColor = m_boardAdapter.GetLayerColor( aLayerID );
if( m_boardAdapter.m_Cfg->m_Render.realistic )
{
switch( aLayerID )
{
case B_Adhes:
case F_Adhes:
break;
case B_Mask:
layerColor = m_boardAdapter.m_SolderMaskColorBot;
break;
case F_Mask:
layerColor = m_boardAdapter.m_SolderMaskColorTop;
break;
case B_Paste:
case F_Paste:
layerColor = m_boardAdapter.m_SolderPasteColor;
break;
case B_SilkS:
layerColor = m_boardAdapter.m_SilkScreenColorBot;
break;
case F_SilkS:
layerColor = m_boardAdapter.m_SilkScreenColorTop;
break;
case Dwgs_User:
case Cmts_User:
case Eco1_User:
case Eco2_User:
case Edge_Cuts:
case Margin:
break;
case B_CrtYd:
case F_CrtYd:
break;
case B_Fab:
case F_Fab:
break;
default:
layerColor = m_boardAdapter.m_CopperColor;
break;
}
}
return layerColor;
}
void init_lights( void )
{
// 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();
}
}
bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter,
REPORTER* aWarningReporter )
{
// Initialize OpenGL
if( !m_is_opengl_initialized )
{
if( !initializeOpenGL() )
return false;
}
if( m_reloadRequested )
{
std::unique_ptr<BUSY_INDICATOR> 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<GRID3D_TYPE>( m_boardAdapter.m_Cfg->m_Render.grid_type );
generate3dGrid( m_lastGridType );
}
else
{
// Check if grid was changed
if( m_boardAdapter.m_Cfg->m_Render.grid_type != m_lastGridType )
{
// and generate a new one
m_lastGridType = static_cast<GRID3D_TYPE>( m_boardAdapter.m_Cfg->m_Render.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 && m_boardAdapter.m_Cfg->m_Render.opengl_AA_disableOnMove )
glDisable( GL_MULTISAMPLE );
else
glEnable( GL_MULTISAMPLE );
// clear color and depth buffers
glClearColor( 0.0f, 0.0f, 0.0f, 1.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( SFVEC3F( m_boardAdapter.m_BgColorTop ),
SFVEC3F( 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 && m_boardAdapter.m_Cfg->m_Render.opengl_thickness_disableOnMove;
bool skipRenderHoles = aIsMoving && m_boardAdapter.m_Cfg->m_Render.opengl_holes_disableOnMove;
bool skipRenderVias = aIsMoving && m_boardAdapter.m_Cfg->m_Render.opengl_vias_disableOnMove;
bool drawMiddleSegments = !skipThickness;
if( m_boardAdapter.m_Cfg->m_Render.realistic )
{
// Draw vias and pad holes with copper material
setLayerMaterial( B_Cu );
}
else
{
OglSetMaterial( m_materials.m_GrayMaterial, 1.0f );
}
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_id = ( PCB_LAYER_ID )( ii->first );
// Mask layers are not processed here because they are a special case
if( ( layer_id == B_Mask ) || ( layer_id == F_Mask ) )
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( m_boardAdapter.m_Cfg->m_Render.show_board_body
&& m_boardAdapter.m_BoardBodyColor.a > opacity_min )
{
if( ( layer_id > F_Cu ) && ( layer_id < B_Cu ) )
continue;
}
glPushMatrix();
OPENGL_RENDER_LIST* pLayerDispList = static_cast<OPENGL_RENDER_LIST*>( ii->second );
if( ( layer_id >= F_Cu ) && ( layer_id <= B_Cu ) )
{
if( m_boardAdapter.m_Cfg->m_Render.renderPlatedPadsAsPlated
&& m_boardAdapter.m_Cfg->m_Render.realistic )
{
setCopperMaterial();
}
else
{
setLayerMaterial( layer_id );
}
if( skipRenderHoles )
{
pLayerDispList->DrawAllCameraCulled( m_camera.GetPos().z, drawMiddleSegments );
// Draw plated pads
if( layer_id == F_Cu && m_platedPadsFront )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsFront->DrawAllCameraCulled( m_camera.GetPos().z,
drawMiddleSegments );
}
else if( layer_id == B_Cu && m_platedPadsBack )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsBack->DrawAllCameraCulled( m_camera.GetPos().z,
drawMiddleSegments );
}
unsetDepthOffset();
}
else
{
if( m_outerThroughHoles )
{
m_outerThroughHoles->ApplyScalePosition( pLayerDispList->GetZBot(),
pLayerDispList->GetZTop()
- pLayerDispList->GetZBot() );
}
if( m_antiBoard )
{
m_antiBoard->ApplyScalePosition( pLayerDispList->GetZBot(),
pLayerDispList->GetZTop()
- pLayerDispList->GetZBot() );
}
if( m_outerLayerHoles.find( layer_id ) != m_outerLayerHoles.end() )
{
const OPENGL_RENDER_LIST* viasHolesLayer = m_outerLayerHoles.at( layer_id );
wxASSERT( viasHolesLayer != nullptr );
if( viasHolesLayer != nullptr )
{
pLayerDispList->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
m_outerThroughHoles,
viasHolesLayer,
m_antiBoard );
// Draw plated pads
if( layer_id == F_Cu && m_platedPadsFront )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsFront->DrawAllCameraCulledSubtractLayer(
drawMiddleSegments,
m_outerThroughHoles,
viasHolesLayer,
m_antiBoard );
}
else if( layer_id == B_Cu && m_platedPadsBack )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsBack->DrawAllCameraCulledSubtractLayer(
drawMiddleSegments,
m_outerThroughHoles,
viasHolesLayer,
m_antiBoard );
}
unsetDepthOffset();
}
}
else
{
pLayerDispList->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
m_outerThroughHoles,
m_antiBoard );
if( layer_id == F_Cu && m_platedPadsFront )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsFront->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
m_outerThroughHoles,
m_antiBoard );
}
else if( layer_id == B_Cu && m_platedPadsBack )
{
setPlatedCopperAndDepthOffset( layer_id );
m_platedPadsBack->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
m_outerThroughHoles,
m_antiBoard );
}
unsetDepthOffset();
}
}
}
else
{
setLayerMaterial( layer_id );
OPENGL_RENDER_LIST* throughHolesOuter =
m_boardAdapter.m_Cfg->m_Render.clip_silk_on_via_annulus
&& m_boardAdapter.m_Cfg->m_Render.realistic
&& ( layer_id == B_SilkS || layer_id == F_SilkS ) ? m_outerThroughHoleRings
: m_outerThroughHoles;
if( throughHolesOuter )
{
throughHolesOuter->ApplyScalePosition( pLayerDispList->GetZBot(),
pLayerDispList->GetZTop()
- pLayerDispList->GetZBot() );
}
OPENGL_RENDER_LIST* anti_board = m_antiBoard;
if( anti_board )
{
anti_board->ApplyScalePosition( pLayerDispList->GetZBot(),
pLayerDispList->GetZTop()
- pLayerDispList->GetZBot() );
}
if( !skipRenderHoles
&& m_boardAdapter.m_Cfg->m_Render.subtract_mask_from_silk
&& m_boardAdapter.m_Cfg->m_Render.realistic
&& ( ( layer_id == B_SilkS && m_layers.find( B_Mask ) != m_layers.end() )
|| ( layer_id == F_SilkS && m_layers.find( F_Mask ) != m_layers.end() ) ) )
{
const PCB_LAYER_ID layerMask_id = (layer_id == B_SilkS) ? B_Mask : F_Mask;
const OPENGL_RENDER_LIST* pLayerDispListMask = m_layers.at( layerMask_id );
pLayerDispList->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
pLayerDispListMask,
throughHolesOuter, anti_board );
}
else
{
if( !skipRenderHoles && throughHolesOuter
&& ( layer_id == B_SilkS || layer_id == F_SilkS ) )
{
pLayerDispList->DrawAllCameraCulledSubtractLayer( drawMiddleSegments, nullptr,
throughHolesOuter,
anti_board );
}
else
{
// Do not render Paste layers when skipRenderHoles is enabled
// otherwise it will cause z-fight issues
if( !( skipRenderHoles && ( layer_id == B_Paste || layer_id == F_Paste ) ) )
{
pLayerDispList->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
anti_board );
}
}
}
}
glPopMatrix();
}
// Render 3D Models (Non-transparent)
render3dModels( false, false );
render3dModels( true, false );
// Display board body
if( m_boardAdapter.m_Cfg->m_Render.show_board_body )
renderBoardBody( skipRenderHoles );
// Display transparent mask layers
if( m_boardAdapter.m_Cfg->m_Render.show_soldermask )
{
// 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 )
{
renderSolderMaskLayer( B_Mask, m_boardAdapter.GetLayerTopZPos( B_Mask ),
drawMiddleSegments, skipRenderHoles );
renderSolderMaskLayer( F_Mask, m_boardAdapter.GetLayerBottomZPos( F_Mask ),
drawMiddleSegments, skipRenderHoles );
}
else
{
renderSolderMaskLayer( F_Mask, m_boardAdapter.GetLayerBottomZPos( F_Mask ),
drawMiddleSegments, skipRenderHoles );
renderSolderMaskLayer( B_Mask, m_boardAdapter.GetLayerTopZPos( B_Mask ),
drawMiddleSegments, 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 );
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_CONSTANT );
render3dModels( false, true );
render3dModels( true, true );
glDisable( GL_BLEND );
OglResetTextureState();
glDepthMask( GL_TRUE );
// Render Grid
if( m_boardAdapter.m_Cfg->m_Render.grid_type != GRID3D_TYPE::NONE )
{
glDisable( GL_LIGHTING );
if( glIsList( m_grid ) )
glCallList( m_grid );
glEnable( GL_LIGHTING );
}
// Render 3D arrows
if( m_boardAdapter.m_Cfg->m_Render.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()
{
if( glIsList( m_grid ) )
glDeleteLists( m_grid, 1 );
m_grid = 0;
for( MAP_OGL_DISP_LISTS::const_iterator ii = m_layers.begin(); ii != m_layers.end(); ++ii )
{
OPENGL_RENDER_LIST* pLayerDispList = static_cast<OPENGL_RENDER_LIST*>( ii->second );
delete pLayerDispList;
}
m_layers.clear();
delete m_platedPadsFront;
m_platedPadsFront = nullptr;
delete m_platedPadsBack;
m_platedPadsBack = nullptr;
for( const std::pair<const PCB_LAYER_ID, OPENGL_RENDER_LIST*> entry : m_outerLayerHoles )
delete entry.second;
m_outerLayerHoles.clear();
for( const std::pair<const PCB_LAYER_ID, OPENGL_RENDER_LIST*> entry : m_innerLayerHoles )
delete entry.second;
m_innerLayerHoles.clear();
for( LIST_TRIANGLES::const_iterator ii = m_triangles.begin(); ii != m_triangles.end(); ++ii )
delete *ii;
m_triangles.clear();
for( const std::pair<const wxString&, MODEL_3D*>& entry : m_3dModelMap )
delete entry.second;
m_3dModelMap.clear();
m_3dModelMatrixMap.clear();
delete m_board;
m_board = nullptr;
delete m_boardWithHoles;
m_boardWithHoles = nullptr;
delete m_antiBoard;
m_antiBoard = nullptr;
delete m_outerThroughHoles;
m_outerThroughHoles = nullptr;
delete m_outerViaThroughHoles;
m_outerViaThroughHoles = nullptr;
delete m_outerThroughHoleRings;
m_outerThroughHoleRings = nullptr;
delete m_vias;
m_vias = nullptr;
delete m_padHoles;
m_padHoles = nullptr;
}
void RENDER_3D_OPENGL::renderSolderMaskLayer( PCB_LAYER_ID aLayerID, float aZPosition,
bool aDrawMiddleSegments, bool aSkipRenderHoles )
{
wxASSERT( (aLayerID == B_Mask) || (aLayerID == F_Mask) );
float nonCopperThickness = m_boardAdapter.GetNonCopperLayerThickness();
if( m_board )
{
if( m_layers.find( aLayerID ) != m_layers.end() )
{
OPENGL_RENDER_LIST* pLayerDispListMask = m_layers.at( aLayerID );
if( m_outerViaThroughHoles )
m_outerViaThroughHoles->ApplyScalePosition( aZPosition, nonCopperThickness );
m_board->ApplyScalePosition( aZPosition, nonCopperThickness );
setLayerMaterial( aLayerID );
m_board->SetItIsTransparent( true );
if( aSkipRenderHoles )
{
m_board->DrawAllCameraCulled( m_camera.GetPos().z, aDrawMiddleSegments );
}
else
{
m_board->DrawAllCameraCulledSubtractLayer( aDrawMiddleSegments, pLayerDispListMask,
m_outerViaThroughHoles );
}
}
else
{
// This case there is no layer with mask, so we will render the full board as mask
if( m_outerViaThroughHoles )
m_outerViaThroughHoles->ApplyScalePosition( aZPosition, nonCopperThickness );
m_board->ApplyScalePosition( aZPosition, nonCopperThickness );
setLayerMaterial( aLayerID );
m_board->SetItIsTransparent( true );
if( aSkipRenderHoles )
{
m_board->DrawAllCameraCulled( m_camera.GetPos().z, aDrawMiddleSegments );
}
else
{
m_board->DrawAllCameraCulledSubtractLayer( aDrawMiddleSegments,
m_outerViaThroughHoles );
}
}
}
}
void RENDER_3D_OPENGL::render3dModelsSelected( bool aRenderTopOrBot, bool aRenderTransparentOnly,
bool aRenderSelectedOnly )
{
if( !m_boardAdapter.GetBoard() )
return;
MODEL_3D::BeginDrawMulti( !aRenderSelectedOnly );
// 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( m_boardAdapter.m_Cfg->m_Render.opengl_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() ) )
{
if( aRenderTopOrBot == !fp->IsFlipped() )
renderFootprint( fp, aRenderTransparentOnly, highlight );
}
}
}
MODEL_3D::EndDrawMulti();
}
void RENDER_3D_OPENGL::render3dModels( bool aRenderTopOrBot, bool aRenderTransparentOnly )
{
if( m_boardAdapter.m_IsBoardView )
render3dModelsSelected( aRenderTopOrBot, aRenderTransparentOnly, true );
render3dModelsSelected( aRenderTopOrBot, aRenderTransparentOnly, false );
}
void RENDER_3D_OPENGL::renderFootprint( const FOOTPRINT* aFootprint, bool aRenderTransparentOnly,
bool aIsSelected )
{
if( !aFootprint->Models().empty() )
{
const double zpos = m_boardAdapter.GetFootprintZPos( aFootprint->IsFlipped() );
SFVEC3F selColor = m_boardAdapter.GetColor( m_boardAdapter.m_Cfg->m_Render.opengl_selection_color );
glPushMatrix();
VECTOR2I pos = aFootprint->GetPosition();
glTranslatef( pos.x * m_boardAdapter.BiuTo3dUnits(), -pos.y * m_boardAdapter.BiuTo3dUnits(),
zpos );
if( !aFootprint->GetOrientation().IsZero() )
glRotated( aFootprint->GetOrientation().AsDegrees(), 0.0, 0.0, 1.0 );
if( aFootprint->IsFlipped() )
{
glRotatef( 180.0f, 0.0f, 1.0f, 0.0f );
glRotatef( 180.0f, 0.0f, 0.0f, 1.0f );
}
double modelunit_to_3d_units_factor = m_boardAdapter.BiuTo3dUnits() * UNITS3D_TO_UNITSPCB;
glScaled( modelunit_to_3d_units_factor, modelunit_to_3d_units_factor,
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 ) ) )
{
glPushMatrix();
std::vector<double> key = { sM.m_Offset.x, sM.m_Offset.y, sM.m_Offset.z,
sM.m_Rotation.x, sM.m_Rotation.y, sM.m_Rotation.z,
sM.m_Scale.x, sM.m_Scale.y, sM.m_Scale.z };
auto it = m_3dModelMatrixMap.find( key );
if( it != m_3dModelMatrixMap.end() )
{
glMultMatrixf( glm::value_ptr( it->second ) );
}
else
{
glm::mat4 mtx( 1 );
mtx = glm::translate( mtx, { sM.m_Offset.x, sM.m_Offset.y, sM.m_Offset.z } );
mtx = glm::rotate( mtx, glm::radians( (float) -sM.m_Rotation.z ), { 0.0f, 0.0f, 1.0f } );
mtx = glm::rotate( mtx, glm::radians( (float) -sM.m_Rotation.y ), { 0.0f, 1.0f, 0.0f } );
mtx = glm::rotate( mtx, glm::radians( (float) -sM.m_Rotation.x ), { 1.0f, 0.0f, 0.0f } );
mtx = glm::scale( mtx, { sM.m_Scale.x, sM.m_Scale.y, sM.m_Scale.z } );
m_3dModelMatrixMap[ key ] = mtx;
glMultMatrixf( glm::value_ptr( mtx ) );
}
if( aRenderTransparentOnly )
{
modelPtr->DrawTransparent( sM.m_Opacity,
aFootprint->IsSelected() || aIsSelected,
selColor );
}
else
{
modelPtr->DrawOpaque( aFootprint->IsSelected() || aIsSelected, selColor );
}
if( m_boardAdapter.m_Cfg->m_Render.opengl_show_model_bbox )
{
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glDisable( GL_LIGHTING );
glLineWidth( 1 );
modelPtr->DrawBboxes();
glLineWidth( 4 );
modelPtr->DrawBbox();
glEnable( GL_LIGHTING );
glDisable( GL_BLEND );
}
glPopMatrix();
}
}
}
glPopMatrix();
}
}
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 )
{
default:
case GRID3D_TYPE::NONE:
return;
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;
}
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, Millimeter2iu( 100 ) ) * 1.2;
const int ysize = std::max( brd_size.y, Millimeter2iu( 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 = Millimeter2iu( -50 ) * scale;
double zmax = Millimeter2iu( 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 * 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 * 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 * 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();
}