/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 1992-2012 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 */ /** * @file 3d_draw_basic_functions.cpp */ #include #include #include #include <3d_viewer.h> #include #include <3d_draw_basic_functions.h> #include // Number of segments to approximate a circle by segments #define SEGM_PER_CIRCLE 16 #ifndef CALLBACK #define CALLBACK #endif // Variables used to pass a value to call back openGL functions static float s_textureScale; static double s_currentZpos; static double s_biuTo3Dunits; bool s_useTextures; // CALLBACK functions for GLU_TESS static void CALLBACK tessBeginCB( GLenum which ); static void CALLBACK tessEndCB(); static void CALLBACK tessErrorCB( GLenum errorCode ); static void CALLBACK tessCPolyPt2Vertex( const GLvoid* data ); // 2 helper functions to set the current normal vector for gle items static inline void SetNormalZpos() { //glNormal3f( 0.0, 0.0, 1.0 ); } static inline void SetNormalZneg() { //glNormal3f( 0.0, 0.0, -1.0 ); } void TransfertToGLlist( std::vector< S3D_VERTEX >& aVertices, double aBiuTo3DUnits ); /* Draw3D_VerticalPolygonalCylinder is a helper function. * * draws a "vertical cylinder" having a polygon shape * from Z position = aZpos to aZpos + aHeight * Used to create the vertical sides of 3D horizontal shapes with thickness. */ static void Draw3D_VerticalPolygonalCylinder( const CPOLYGONS_LIST& aPolysList, int aHeight, int aZpos, bool aInside, double aBiuTo3DUnits ) { if( aHeight == 0 ) return; std::vector coords; coords.resize( 4 ); // Init Z position of the 4 points of a GL_QUAD if( aInside ) { coords[0].z = aZpos; coords[1].z = aZpos + aHeight; } else { coords[0].z = aZpos + aHeight; coords[1].z = aZpos; } coords[2].z = coords[1].z; coords[3].z = coords[0].z; // Draw the vertical polygonal side int startContour = 0; for( unsigned ii = 0; ii < aPolysList.GetCornersCount(); ii++ ) { unsigned jj = ii + 1; if( aPolysList.IsEndContour( ii ) || jj >= aPolysList.GetCornersCount() ) { jj = startContour; startContour = ii + 1; } // Build the 4 vertices of each GL_QUAD coords[0].x = aPolysList.GetX( ii ); coords[0].y = -aPolysList.GetY( ii ); coords[1].x = coords[0].x; coords[1].y = coords[0].y; // only z change coords[2].x = aPolysList.GetX( jj ); coords[2].y = -aPolysList.GetY( jj ); coords[3].x = coords[2].x; coords[3].y = coords[2].y; // only z change // Creates the GL_QUAD TransfertToGLlist( coords, aBiuTo3DUnits ); } } void SetGLColor( EDA_COLOR_T color, double alpha ) { const StructColors &colordata = g_ColorRefs[ColorGetBase( color )]; double red = colordata.m_Red / 255.0; double blue = colordata.m_Blue / 255.0; double green = colordata.m_Green / 255.0; glColor4f( red, green, blue, alpha ); } void SetGLColor( S3D_COLOR& aColor, float aTransparency ) { glColor4f( aColor.m_Red, aColor.m_Green, aColor.m_Blue, aTransparency ); } void SetGLTexture( GLuint text_id, float scale ) { glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, text_id ); s_textureScale = scale; // for Tess callback functions } /* draw all solid polygons found in aPolysList * aZpos = z position in board internal units * aThickness = thickness in board internal units * If aThickness = 0, a polygon area is drawn in a XY plane at Z position = aZpos. * If aThickness > 0, a solid object is drawn. * The top side is located at aZpos + aThickness / 2 * The bottom side is located at aZpos - aThickness / 2 */ void Draw3D_SolidHorizontalPolyPolygons( const CPOLYGONS_LIST& aPolysList, int aZpos, int aThickness, double aBiuTo3DUnits, bool aUseTextures ) { // for Tess callback functions: s_biuTo3Dunits = aBiuTo3DUnits; s_useTextures = aUseTextures; GLUtesselator* tess = gluNewTess(); gluTessCallback( tess, GLU_TESS_BEGIN, ( void (CALLBACK*) () )tessBeginCB ); gluTessCallback( tess, GLU_TESS_END, ( void (CALLBACK*) () )tessEndCB ); gluTessCallback( tess, GLU_TESS_ERROR, ( void (CALLBACK*) () )tessErrorCB ); gluTessCallback( tess, GLU_TESS_VERTEX, ( void (CALLBACK*) () )tessCPolyPt2Vertex ); GLdouble v_data[3]; double zpos = ( aZpos + (aThickness / 2.0) ) * aBiuTo3DUnits; s_currentZpos = zpos; // for Tess callback functions v_data[2] = aZpos + (aThickness / 2.0); // Set normal toward positive Z axis, for a solid object on the top side if( aThickness ) SetNormalZpos(); // gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); // Draw solid areas contained in this list CPOLYGONS_LIST polylist = aPolysList; // temporary copy for gluTessVertex int startContour; for( int side = 0; side < 2; side++ ) { startContour = 1; for( unsigned ii = 0; ii < polylist.GetCornersCount(); ii++ ) { if( startContour == 1 ) { gluTessBeginPolygon( tess, NULL ); gluTessBeginContour( tess ); startContour = 0; } // https://www.opengl.org/sdk/docs/man2/xhtml/gluTessNormal.xml gluTessNormal( tess, 0.0, 0.0, 0.0 ); v_data[0] = polylist.GetX( ii ) * aBiuTo3DUnits; v_data[1] = -polylist.GetY( ii ) * aBiuTo3DUnits; // gluTessVertex store pointers on data, not data, so do not store // different corners values in a temporary variable // but send pointer on each CPolyPt value in polylist // before calling gluDeleteTess gluTessVertex( tess, v_data, &polylist[ii] ); if( polylist.IsEndContour( ii ) ) { gluTessEndContour( tess ); gluTessEndPolygon( tess ); startContour = 1; } } if( aThickness == 0 ) break; // Prepare the bottom side of solid areas zpos = ( aZpos - (aThickness / 2.0) ) * aBiuTo3DUnits; s_currentZpos = zpos; // for Tess callback functions v_data[2] = zpos; // Set normal toward negative Z axis, for a solid object on bottom side SetNormalZneg(); } if( startContour == 0 ) { gluTessEndContour( tess ); gluTessEndPolygon( tess ); } gluDeleteTess( tess ); if( aThickness == 0 ) { return; } // Build the 3D data : vertical side Draw3D_VerticalPolygonalCylinder( polylist, aThickness, aZpos - (aThickness / 2.0), false, aBiuTo3DUnits ); } /* draw the solid polygon found in aPolysList * The first polygon is the main polygon, others are holes * See Draw3D_SolidHorizontalPolyPolygons for more info */ void Draw3D_SolidHorizontalPolygonWithHoles( const CPOLYGONS_LIST& aPolysList, int aZpos, int aThickness, double aBiuTo3DUnits, bool aUseTextures ) { CPOLYGONS_LIST polygon; ConvertPolysListWithHolesToOnePolygon( aPolysList, polygon ); Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos, aThickness, aBiuTo3DUnits, aUseTextures ); } /* draw a cylinder (a tube) using 3D primitives. * the cylinder axis is parallel to the Z axis * If aHeight = height of the cylinder is 0, only one ring will be drawn * If aThickness = 0, only one cylinder will be drawn */ void Draw3D_ZaxisCylinder( wxPoint aCenterPos, int aRadius, int aHeight, int aThickness, int aZpos, double aBiuTo3DUnits ) { const int slice = SEGM_PER_CIRCLE; CPOLYGONS_LIST outer_cornerBuffer; TransformCircleToPolygon( outer_cornerBuffer, aCenterPos, aRadius + (aThickness / 2), slice ); std::vector coords; coords.resize( 4 ); CPOLYGONS_LIST inner_cornerBuffer; if( aThickness ) // build the the vertical inner polygon (hole) TransformCircleToPolygon( inner_cornerBuffer, aCenterPos, aRadius - (aThickness / 2), slice ); if( aHeight ) { // Draw the vertical outer side Draw3D_VerticalPolygonalCylinder( outer_cornerBuffer, aHeight, aZpos, false, aBiuTo3DUnits ); if( aThickness ) // Draws the vertical inner side (hole) Draw3D_VerticalPolygonalCylinder( inner_cornerBuffer, aHeight, aZpos, true, aBiuTo3DUnits ); } if( aThickness ) { // draw top (front) and bottom (back) horizontal sides (rings) SetNormalZpos(); outer_cornerBuffer.Append( inner_cornerBuffer ); CPOLYGONS_LIST polygon; ConvertPolysListWithHolesToOnePolygon( outer_cornerBuffer, polygon ); // draw top (front) horizontal ring Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos + aHeight, 0, aBiuTo3DUnits, false ); if( aHeight ) { // draw bottom (back) horizontal ring SetNormalZneg(); Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos, 0, aBiuTo3DUnits, false ); } } SetNormalZpos(); } /* * Function Draw3D_ZaxisOblongCylinder: * draw a segment with an oblong hole. * Used to draw oblong holes * If aHeight = height of the cylinder is 0, only one ring will be drawn * If aThickness = 0, only one cylinder will be drawn */ void Draw3D_ZaxisOblongCylinder( wxPoint aAxis1Pos, wxPoint aAxis2Pos, int aRadius, int aHeight, int aThickness, int aZpos, double aBiuTo3DUnits ) { const int slice = SEGM_PER_CIRCLE; // Build the points to approximate oblong cylinder by segments CPOLYGONS_LIST outer_cornerBuffer; int segm_width = (aRadius * 2) + aThickness; TransformRoundedEndsSegmentToPolygon( outer_cornerBuffer, aAxis1Pos, aAxis2Pos, slice, segm_width ); // Draw the oblong outer cylinder if( aHeight ) Draw3D_VerticalPolygonalCylinder( outer_cornerBuffer, aHeight, aZpos, false, aBiuTo3DUnits ); if( aThickness ) { CPOLYGONS_LIST inner_cornerBuffer; segm_width = aRadius * 2; TransformRoundedEndsSegmentToPolygon( inner_cornerBuffer, aAxis1Pos, aAxis2Pos, slice, segm_width ); // Draw the oblong inner cylinder if( aHeight ) Draw3D_VerticalPolygonalCylinder( inner_cornerBuffer, aHeight, aZpos, true, aBiuTo3DUnits ); // Build the horizontal full polygon shape // (outer polygon shape - inner polygon shape) outer_cornerBuffer.Append( inner_cornerBuffer ); CPOLYGONS_LIST polygon; ConvertPolysListWithHolesToOnePolygon( outer_cornerBuffer, polygon ); // draw top (front) horizontal side (ring) SetNormalZpos(); Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos + aHeight, 0, aBiuTo3DUnits, false ); if( aHeight ) { // draw bottom (back) horizontal side (ring) SetNormalZneg(); Draw3D_SolidHorizontalPolyPolygons( polygon, aZpos, 0, aBiuTo3DUnits, false ); } } SetNormalZpos(); } /* draw a thick segment using 3D primitives, in a XY plane * wxPoint aStart, wxPoint aEnd = YX position of end in board units * aWidth = width of segment in board units * aThickness = thickness of segment in board units * aZpos = z position of segment in board units */ void Draw3D_SolidSegment( const wxPoint& aStart, const wxPoint& aEnd, int aWidth, int aThickness, int aZpos, double aBiuTo3DUnits ) { CPOLYGONS_LIST cornerBuffer; const int slice = SEGM_PER_CIRCLE; TransformRoundedEndsSegmentToPolygon( cornerBuffer, aStart, aEnd, slice, aWidth ); Draw3D_SolidHorizontalPolyPolygons( cornerBuffer, aZpos, aThickness, aBiuTo3DUnits, false ); } void Draw3D_ArcSegment( const wxPoint& aCenterPos, const wxPoint& aStartPoint, double aArcAngle, int aWidth, int aThickness, int aZpos, double aBiuTo3DUnits ) { const int slice = SEGM_PER_CIRCLE; CPOLYGONS_LIST cornerBuffer; TransformArcToPolygon( cornerBuffer, aCenterPos, aStartPoint, aArcAngle, slice, aWidth ); Draw3D_SolidHorizontalPolyPolygons( cornerBuffer, aZpos, aThickness, aBiuTo3DUnits, false ); } // ///////////////////////////////////////////////////////////////////////////// // GLU_TESS CALLBACKS // ///////////////////////////////////////////////////////////////////////////// void CALLBACK tessBeginCB( GLenum which ) { glBegin( which ); } void CALLBACK tessEndCB() { glEnd(); } void CALLBACK tessCPolyPt2Vertex( const GLvoid* data ) { // cast back to double type const CPolyPt* ptr = (const CPolyPt*) data; if( s_useTextures ) { glTexCoord2f( ptr->x * s_biuTo3Dunits * s_textureScale, -ptr->y * s_biuTo3Dunits * s_textureScale); } glVertex3d( ptr->x * s_biuTo3Dunits, -ptr->y * s_biuTo3Dunits, s_currentZpos ); } void CALLBACK tessErrorCB( GLenum errorCode ) { #if defined(DEBUG) const GLubyte* errorStr; errorStr = gluErrorString( errorCode ); // DEBUG // DBG( printf( "Tess ERROR: %s\n", errorStr ); ) #endif }