mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
The plan goes like this: - eeschema still uses int in decidegrees - all the other things internally use double in decidegrees (or radians in temporaries) - in pcbnew UI the unit is *still* int in decidegrees The idea is to have better precision everywhere while keeping the user with int i angles. Hopefully, if a fractional angle doesn't come in from the outside, everything should *look* like an integer angle (unless I forgot something and it broke) When the time comes, simply updating the UI for allowing doubles from the user should be enough to get arbitrary angles in pcbnew.
659 lines
18 KiB
C++
659 lines
18 KiB
C++
/*
|
||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||
*
|
||
* Copyright (C) 2009 Jean-Pierre Charras, jean-pierre.charras@gipsa-lab.inpg.fr
|
||
* Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
|
||
* Copyright (C) 1992-2011 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 dcode.cpp
|
||
* @brief D_CODE class implementation
|
||
*/
|
||
|
||
#include <fctsys.h>
|
||
#include <common.h>
|
||
#include <class_drawpanel.h>
|
||
#include <confirm.h>
|
||
#include <macros.h>
|
||
#include <trigo.h>
|
||
#include <gr_basic.h>
|
||
#include <base_units.h>
|
||
|
||
#include <gerbview.h>
|
||
#include <class_gerber_draw_item.h>
|
||
#include <class_GERBER.h>
|
||
|
||
#define DEFAULT_SIZE 100
|
||
|
||
/* Format Gerber: NOTES:
|
||
* Tools and D_CODES
|
||
* tool number (identification of shapes)
|
||
* 1 to 999
|
||
*
|
||
* D_CODES:
|
||
* D01 ... D9 = command codes:
|
||
* D01 = activating light (pen down) while moving
|
||
* D02 = light extinction (pen up) while moving
|
||
* D03 = Flash
|
||
* D04 to D09 = non used
|
||
* D10 ... D999 = Identification Tool (Shape id)
|
||
*
|
||
* For tools defining a shape):
|
||
* DCode min = D10
|
||
* DCode max = 999
|
||
*/
|
||
|
||
|
||
/***************/
|
||
/* Class DCODE */
|
||
/***************/
|
||
|
||
|
||
D_CODE::D_CODE( int num_dcode )
|
||
{
|
||
m_Num_Dcode = num_dcode;
|
||
Clear_D_CODE_Data();
|
||
}
|
||
|
||
|
||
D_CODE::~D_CODE()
|
||
{
|
||
}
|
||
|
||
|
||
void D_CODE::Clear_D_CODE_Data()
|
||
{
|
||
m_Size.x = DEFAULT_SIZE;
|
||
m_Size.y = DEFAULT_SIZE;
|
||
m_Shape = APT_CIRCLE;
|
||
m_Drill.x = m_Drill.y = 0;
|
||
m_DrillShape = APT_DEF_NO_HOLE;
|
||
m_InUse = false;
|
||
m_Defined = false;
|
||
m_Macro = NULL;
|
||
m_Rotation = 0.0;
|
||
m_EdgesCount = 0;
|
||
m_PolyCorners.clear();
|
||
}
|
||
|
||
|
||
const wxChar* D_CODE::ShowApertureType( APERTURE_T aType )
|
||
{
|
||
const wxChar* ret;
|
||
|
||
switch( aType )
|
||
{
|
||
case APT_CIRCLE:
|
||
ret = wxT( "Round" ); break;
|
||
|
||
case APT_RECT:
|
||
ret = wxT( "Rect" ); break;
|
||
|
||
case APT_OVAL:
|
||
ret = wxT( "Oval" ); break;
|
||
|
||
case APT_POLYGON:
|
||
ret = wxT( "Poly" ); break;
|
||
|
||
case APT_MACRO:
|
||
ret = wxT( "Macro" ); break;
|
||
|
||
default:
|
||
ret = wxT( "???" ); break;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
int D_CODE::GetShapeDim( GERBER_DRAW_ITEM* aParent )
|
||
{
|
||
int dim = -1;
|
||
switch( m_Shape )
|
||
{
|
||
case APT_CIRCLE:
|
||
dim = m_Size.x;
|
||
break;
|
||
|
||
case APT_RECT:
|
||
case APT_OVAL:
|
||
dim = std::min( m_Size.x, m_Size.y );
|
||
break;
|
||
|
||
case APT_POLYGON:
|
||
dim = std::min( m_Size.x, m_Size.y );
|
||
break;
|
||
|
||
case APT_MACRO:
|
||
if( m_Macro )
|
||
dim = m_Macro->GetShapeDim( aParent );
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return dim;
|
||
}
|
||
|
||
|
||
int GERBVIEW_FRAME::ReadDCodeDefinitionFile( const wxString& D_Code_FullFileName )
|
||
{
|
||
int current_Dcode, ii;
|
||
char* ptcar;
|
||
int dimH, dimV, drill, dummy;
|
||
float fdimH, fdimV, fdrill;
|
||
char c_type_outil[256];
|
||
char line[GERBER_BUFZ];
|
||
wxString msg;
|
||
D_CODE* dcode;
|
||
FILE* dest;
|
||
LAYER_NUM layer = getActiveLayer();
|
||
int type_outil;
|
||
|
||
if( g_GERBER_List[layer] == NULL )
|
||
g_GERBER_List[layer] = new GERBER_IMAGE( this, layer );
|
||
|
||
GERBER_IMAGE* gerber = g_GERBER_List[layer];
|
||
|
||
|
||
/* Updating gerber scale: */
|
||
double dcode_scale = IU_PER_MILS; // By uniting dCode = mil,
|
||
// internal unit = IU_PER_MILS
|
||
current_Dcode = 0;
|
||
|
||
if( D_Code_FullFileName.IsEmpty() )
|
||
return 0;
|
||
|
||
dest = wxFopen( D_Code_FullFileName, wxT( "rt" ) );
|
||
if( dest == 0 )
|
||
{
|
||
msg.Printf( _( "File <%s> not found" ), GetChars( D_Code_FullFileName ) );
|
||
DisplayError( this, msg, 10 );
|
||
return -1;
|
||
}
|
||
|
||
gerber->InitToolTable();
|
||
|
||
while( fgets( line, sizeof(line) - 1, dest ) != NULL )
|
||
{
|
||
if( *line == ';' )
|
||
continue;
|
||
|
||
if( strlen( line ) < 10 )
|
||
continue; /* Skip blank line. */
|
||
|
||
dcode = NULL;
|
||
current_Dcode = 0;
|
||
|
||
/* Determine of the type of file from D_Code. */
|
||
ptcar = line;
|
||
ii = 0;
|
||
|
||
while( *ptcar )
|
||
if( *(ptcar++) == ',' )
|
||
ii++;
|
||
|
||
if( ii >= 6 ) /* value in mils */
|
||
{
|
||
sscanf( line, "%d,%d,%d,%d,%d,%d,%d", &ii,
|
||
&dimH, &dimV, &drill, &dummy, &dummy, &type_outil );
|
||
|
||
dimH = KiROUND( dimH * dcode_scale );
|
||
dimV = KiROUND( dimV * dcode_scale );
|
||
drill = KiROUND( drill * dcode_scale );
|
||
|
||
if( ii < 1 )
|
||
ii = 1;
|
||
|
||
current_Dcode = ii - 1 + FIRST_DCODE;
|
||
}
|
||
else /* Values in inches are converted to mils. */
|
||
{
|
||
fdrill = 0;
|
||
current_Dcode = 0;
|
||
|
||
sscanf( line, "%f,%f,%1s", &fdimV, &fdimH, c_type_outil );
|
||
ptcar = line;
|
||
|
||
while( *ptcar )
|
||
{
|
||
if( *ptcar == 'D' )
|
||
{
|
||
sscanf( ptcar + 1, "%d,%f", ¤t_Dcode, &fdrill );
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
ptcar++;
|
||
}
|
||
}
|
||
|
||
dimH = KiROUND( fdimH * dcode_scale * 1000 );
|
||
dimV = KiROUND( fdimV * dcode_scale * 1000 );
|
||
drill = KiROUND( fdrill * dcode_scale * 1000 );
|
||
|
||
if( strchr( "CLROP", c_type_outil[0] ) )
|
||
{
|
||
type_outil = (APERTURE_T) c_type_outil[0];
|
||
}
|
||
else
|
||
{
|
||
fclose( dest );
|
||
return -2;
|
||
}
|
||
}
|
||
|
||
/* Update the list of d_codes if consistent. */
|
||
if( current_Dcode < FIRST_DCODE )
|
||
continue;
|
||
|
||
if( current_Dcode >= TOOLS_MAX_COUNT )
|
||
continue;
|
||
|
||
dcode = gerber->GetDCODE( current_Dcode );
|
||
dcode->m_Size.x = dimH;
|
||
dcode->m_Size.y = dimV;
|
||
dcode->m_Shape = (APERTURE_T) type_outil;
|
||
dcode->m_Drill.x = dcode->m_Drill.y = drill;
|
||
dcode->m_Defined = true;
|
||
}
|
||
|
||
fclose( dest );
|
||
|
||
return 1;
|
||
}
|
||
|
||
|
||
void GERBVIEW_FRAME::CopyDCodesSizeToItems()
|
||
{
|
||
static D_CODE dummy( 999 ); //Used if D_CODE not found in list
|
||
|
||
GERBER_DRAW_ITEM* gerb_item = GetItemsList();
|
||
for( ; gerb_item; gerb_item = gerb_item->Next() )
|
||
{
|
||
D_CODE* dcode = gerb_item->GetDcodeDescr();
|
||
wxASSERT( dcode );
|
||
if( dcode == NULL )
|
||
dcode = &dummy;
|
||
|
||
dcode->m_InUse = true;
|
||
|
||
gerb_item->m_Size = dcode->m_Size;
|
||
|
||
if( // Line Item
|
||
(gerb_item->m_Shape == GBR_SEGMENT ) /* rectilinear segment */
|
||
|| (gerb_item->m_Shape == GBR_ARC ) /* segment arc (rounded tips) */
|
||
|| (gerb_item->m_Shape == GBR_CIRCLE ) /* segment in a circle (ring) */
|
||
)
|
||
{
|
||
}
|
||
else // Spots ( Flashed Items )
|
||
{
|
||
switch( dcode->m_Shape )
|
||
{
|
||
case APT_CIRCLE: /* spot round */
|
||
gerb_item->m_Shape = GBR_SPOT_CIRCLE;
|
||
break;
|
||
|
||
case APT_OVAL: /* spot oval*/
|
||
gerb_item->m_Shape = GBR_SPOT_OVAL;
|
||
break;
|
||
|
||
case APT_RECT: /* spot rect*/
|
||
gerb_item->m_Shape = GBR_SPOT_RECT;
|
||
break;
|
||
|
||
case APT_POLYGON: /* spot regular polyg 3 to 1<> edges */
|
||
gerb_item->m_Shape = GBR_SPOT_POLY;
|
||
break;
|
||
|
||
case APT_MACRO: /* spot defined by a macro */
|
||
gerb_item->m_Shape = GBR_SPOT_MACRO;
|
||
break;
|
||
|
||
default:
|
||
wxMessageBox( wxT( "GERBVIEW_FRAME::CopyDCodesSizeToItems() error" ) );
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void D_CODE::DrawFlashedShape( GERBER_DRAW_ITEM* aParent,
|
||
EDA_RECT* aClipBox, wxDC* aDC, EDA_COLOR_T aColor,
|
||
EDA_COLOR_T aAltColor,
|
||
wxPoint aShapePos, bool aFilledShape )
|
||
{
|
||
int radius;
|
||
|
||
switch( m_Shape )
|
||
{
|
||
case APT_MACRO:
|
||
GetMacro()->DrawApertureMacroShape( aParent, aClipBox, aDC, aColor, aAltColor,
|
||
aShapePos, aFilledShape);
|
||
break;
|
||
|
||
case APT_CIRCLE:
|
||
radius = m_Size.x >> 1;
|
||
if( !aFilledShape )
|
||
GRCircle( aClipBox, aDC, aParent->GetABPosition(aShapePos), radius, 0, aColor );
|
||
else
|
||
if( m_DrillShape == APT_DEF_NO_HOLE )
|
||
{
|
||
GRFilledCircle( aClipBox, aDC, aParent->GetABPosition(aShapePos),
|
||
radius, aColor );
|
||
}
|
||
else if( APT_DEF_ROUND_HOLE == 1 ) // round hole in shape
|
||
{
|
||
int width = (m_Size.x - m_Drill.x ) / 2;
|
||
GRCircle( aClipBox, aDC, aParent->GetABPosition(aShapePos),
|
||
radius - (width / 2), width, aColor );
|
||
}
|
||
else // rectangular hole
|
||
{
|
||
if( m_PolyCorners.size() == 0 )
|
||
ConvertShapeToPolygon();
|
||
|
||
DrawFlashedPolygon( aParent, aClipBox, aDC, aColor, aFilledShape, aShapePos );
|
||
}
|
||
break;
|
||
|
||
case APT_RECT:
|
||
{
|
||
wxPoint start;
|
||
start.x = aShapePos.x - m_Size.x / 2;
|
||
start.y = aShapePos.y - m_Size.y / 2;
|
||
wxPoint end = start + m_Size;
|
||
start = aParent->GetABPosition( start );
|
||
end = aParent->GetABPosition( end );
|
||
|
||
if( !aFilledShape )
|
||
{
|
||
GRRect( aClipBox, aDC, start.x, start.y, end.x, end.y, 0, aColor );
|
||
}
|
||
else if( m_DrillShape == APT_DEF_NO_HOLE )
|
||
{
|
||
GRFilledRect( aClipBox, aDC, start.x, start.y, end.x, end.y, 0, aColor, aColor );
|
||
}
|
||
else
|
||
{
|
||
if( m_PolyCorners.size() == 0 )
|
||
ConvertShapeToPolygon();
|
||
|
||
DrawFlashedPolygon( aParent, aClipBox, aDC, aColor, aFilledShape, aShapePos );
|
||
}
|
||
}
|
||
break;
|
||
|
||
case APT_OVAL:
|
||
{
|
||
wxPoint start = aShapePos;
|
||
wxPoint end = aShapePos;
|
||
|
||
if( m_Size.x > m_Size.y ) // horizontal oval
|
||
{
|
||
int delta = (m_Size.x - m_Size.y) / 2;
|
||
start.x -= delta;
|
||
end.x += delta;
|
||
radius = m_Size.y;
|
||
}
|
||
else // horizontal oval
|
||
{
|
||
int delta = (m_Size.y - m_Size.x) / 2;
|
||
start.y -= delta;
|
||
end.y += delta;
|
||
radius = m_Size.x;
|
||
}
|
||
|
||
start = aParent->GetABPosition( start );
|
||
end = aParent->GetABPosition( end );
|
||
|
||
if( !aFilledShape )
|
||
{
|
||
GRCSegm( aClipBox, aDC, start.x, start.y, end.x, end.y, radius, aColor );
|
||
}
|
||
else if( m_DrillShape == APT_DEF_NO_HOLE )
|
||
{
|
||
GRFillCSegm( aClipBox, aDC, start.x, start.y, end.x, end.y, radius, aColor );
|
||
}
|
||
else
|
||
{
|
||
if( m_PolyCorners.size() == 0 )
|
||
ConvertShapeToPolygon();
|
||
|
||
DrawFlashedPolygon( aParent, aClipBox, aDC, aColor, aFilledShape, aShapePos );
|
||
}
|
||
}
|
||
break;
|
||
|
||
case APT_POLYGON:
|
||
if( m_PolyCorners.size() == 0 )
|
||
ConvertShapeToPolygon();
|
||
|
||
DrawFlashedPolygon( aParent, aClipBox, aDC, aColor, aFilledShape, aShapePos );
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
void D_CODE::DrawFlashedPolygon( GERBER_DRAW_ITEM* aParent,
|
||
EDA_RECT* aClipBox, wxDC* aDC,
|
||
EDA_COLOR_T aColor, bool aFilled,
|
||
const wxPoint& aPosition )
|
||
{
|
||
if( m_PolyCorners.size() == 0 )
|
||
return;
|
||
|
||
std::vector<wxPoint> points;
|
||
points = m_PolyCorners;
|
||
|
||
for( unsigned ii = 0; ii < points.size(); ii++ )
|
||
{
|
||
points[ii] += aPosition;
|
||
points[ii] = aParent->GetABPosition( points[ii] );
|
||
}
|
||
|
||
GRClosedPoly( aClipBox, aDC, points.size(), &points[0], aFilled, aColor, aColor );
|
||
}
|
||
|
||
|
||
#define SEGS_CNT 32 // number of segments to approximate a circle
|
||
|
||
|
||
// A helper function for D_CODE::ConvertShapeToPolygon(). Add a hole to a polygon
|
||
static void addHoleToPolygon( std::vector<wxPoint>& aBuffer,
|
||
APERTURE_DEF_HOLETYPE aHoleShape,
|
||
wxSize aSize,
|
||
wxPoint aAnchorPos );
|
||
|
||
|
||
void D_CODE::ConvertShapeToPolygon()
|
||
{
|
||
wxPoint initialpos;
|
||
wxPoint currpos;
|
||
|
||
m_PolyCorners.clear();
|
||
|
||
switch( m_Shape )
|
||
{
|
||
case APT_CIRCLE: // creates only a circle with rectangular hole
|
||
currpos.x = m_Size.x >> 1;
|
||
initialpos = currpos;
|
||
|
||
for( unsigned ii = 0; ii <= SEGS_CNT; ii++ )
|
||
{
|
||
currpos = initialpos;
|
||
RotatePoint( &currpos, ii * 3600.0 / SEGS_CNT );
|
||
m_PolyCorners.push_back( currpos );
|
||
}
|
||
|
||
addHoleToPolygon( m_PolyCorners, m_DrillShape, m_Drill, initialpos );
|
||
break;
|
||
|
||
case APT_RECT:
|
||
currpos.x = m_Size.x / 2;
|
||
currpos.y = m_Size.y / 2;
|
||
initialpos = currpos;
|
||
m_PolyCorners.push_back( currpos );
|
||
currpos.x -= m_Size.x;
|
||
m_PolyCorners.push_back( currpos );
|
||
currpos.y -= m_Size.y;
|
||
m_PolyCorners.push_back( currpos );
|
||
currpos.x += m_Size.x;
|
||
m_PolyCorners.push_back( currpos );
|
||
currpos.y += m_Size.y;
|
||
m_PolyCorners.push_back( currpos ); // close polygon
|
||
|
||
addHoleToPolygon( m_PolyCorners, m_DrillShape, m_Drill, initialpos );
|
||
break;
|
||
|
||
case APT_OVAL:
|
||
{
|
||
int delta, radius;
|
||
|
||
// we create an horizontal oval shape. then rotate if needed
|
||
if( m_Size.x > m_Size.y ) // horizontal oval
|
||
{
|
||
delta = (m_Size.x - m_Size.y) / 2;
|
||
radius = m_Size.y / 2;
|
||
}
|
||
else // vertical oval
|
||
{
|
||
delta = (m_Size.y - m_Size.x) / 2;
|
||
radius = m_Size.x / 2;
|
||
}
|
||
|
||
currpos.y = radius;
|
||
initialpos = currpos;
|
||
m_PolyCorners.push_back( currpos );
|
||
|
||
// build the right arc of the shape
|
||
unsigned ii = 0;
|
||
|
||
for( ; ii <= SEGS_CNT / 2; ii++ )
|
||
{
|
||
currpos = initialpos;
|
||
RotatePoint( &currpos, ii * 3600.0 / SEGS_CNT );
|
||
currpos.x += delta;
|
||
m_PolyCorners.push_back( currpos );
|
||
}
|
||
|
||
// build the left arc of the shape
|
||
for( ii = SEGS_CNT / 2; ii <= SEGS_CNT; ii++ )
|
||
{
|
||
currpos = initialpos;
|
||
RotatePoint( &currpos, ii * 3600.0 / SEGS_CNT );
|
||
currpos.x -= delta;
|
||
m_PolyCorners.push_back( currpos );
|
||
}
|
||
|
||
m_PolyCorners.push_back( initialpos ); // close outline
|
||
|
||
if( m_Size.y > m_Size.x ) // vertical oval, rotate polygon.
|
||
{
|
||
for( unsigned jj = 0; jj < m_PolyCorners.size(); jj++ )
|
||
RotatePoint( &m_PolyCorners[jj], 900 );
|
||
}
|
||
|
||
addHoleToPolygon( m_PolyCorners, m_DrillShape, m_Drill, initialpos );
|
||
}
|
||
break;
|
||
|
||
case APT_POLYGON:
|
||
currpos.x = m_Size.x >> 1; // first point is on X axis
|
||
initialpos = currpos;
|
||
|
||
// rs274x said: m_EdgesCount = 3 ... 12
|
||
if( m_EdgesCount < 3 )
|
||
m_EdgesCount = 3;
|
||
|
||
if( m_EdgesCount > 12 )
|
||
m_EdgesCount = 12;
|
||
|
||
for( int ii = 0; ii <= m_EdgesCount; ii++ )
|
||
{
|
||
currpos = initialpos;
|
||
RotatePoint( &currpos, ii * 3600.0 / m_EdgesCount );
|
||
m_PolyCorners.push_back( currpos );
|
||
}
|
||
|
||
addHoleToPolygon( m_PolyCorners, m_DrillShape, m_Drill, initialpos );
|
||
|
||
if( m_Rotation ) // vertical oval, rotate polygon.
|
||
{
|
||
int angle = KiROUND( m_Rotation * 10 );
|
||
|
||
for( unsigned jj = 0; jj < m_PolyCorners.size(); jj++ )
|
||
{
|
||
RotatePoint( &m_PolyCorners[jj], -angle );
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case APT_MACRO:
|
||
|
||
// TODO
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
// The helper function for D_CODE::ConvertShapeToPolygon().
|
||
// Add a hole to a polygon
|
||
static void addHoleToPolygon( std::vector<wxPoint>& aBuffer,
|
||
APERTURE_DEF_HOLETYPE aHoleShape,
|
||
wxSize aSize,
|
||
wxPoint aAnchorPos )
|
||
{
|
||
wxPoint currpos;
|
||
|
||
if( aHoleShape == APT_DEF_ROUND_HOLE ) // build a round hole
|
||
{
|
||
for( int ii = 0; ii <= SEGS_CNT; ii++ )
|
||
{
|
||
currpos.x = 0;
|
||
currpos.y = aSize.x / 2; // aSize.x / 2 is the radius of the hole
|
||
RotatePoint( &currpos, ii * 3600.0 / SEGS_CNT );
|
||
aBuffer.push_back( currpos );
|
||
}
|
||
|
||
aBuffer.push_back( aAnchorPos ); // link to outline
|
||
}
|
||
|
||
if( aHoleShape == APT_DEF_RECT_HOLE ) // Create rectangular hole
|
||
{
|
||
currpos.x = aSize.x / 2;
|
||
currpos.y = aSize.y / 2;
|
||
aBuffer.push_back( currpos ); // link to hole and begin hole
|
||
currpos.x -= aSize.x;
|
||
aBuffer.push_back( currpos );
|
||
currpos.y -= aSize.y;
|
||
aBuffer.push_back( currpos );
|
||
currpos.x += aSize.x;
|
||
aBuffer.push_back( currpos );
|
||
currpos.y += aSize.y;
|
||
aBuffer.push_back( currpos ); // close hole
|
||
aBuffer.push_back( aAnchorPos ); // link to outline
|
||
}
|
||
}
|