7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-21 00:21:25 +00:00

Snapping: Add construction geometry snapping

This is a pretty major rework of the snapping system.
The GRID_HELPERs now have a separate CONSTRUCTION_MANAGER
which handles some of the state involving "construction
geometry".

This is fed with 'extended' geometry (e.g. "infinite" lines from
segments) for use in generating things like intersection points.
It also handles adding this geoemtry to a GAL view item
(CONSTRUCTION_GEOM) for display to the user.

The process is:

* A TOOL creates a GRID_HELPER
* Optionally, it pre-loads a "persistent" batch of construction
  geometry (e.g. for an item's original position)
* The grid helper finds useful snap 'anchors' as before, including
  those involving the construction items.
* Other items on the board can be 'activated' by snapping to one
  of their main points. Then, if it has construction geometry,
  it will be added to the display. At most 2 items of this kind of
  geometry are shown, plus the original item, to reduce avoid
  too much clutter.

The dashed snap lines state machine is also handled in the
CONSTRUCTION_MANAGER and displayed in the CONSTRUCTION_GEOM item.
This commit is contained in:
John Beard 2024-09-06 00:00:00 +01:00
parent 72e48aed52
commit b2be0d39bd
35 changed files with 2808 additions and 268 deletions

View File

@ -457,7 +457,9 @@ set( COMMON_PREVIEW_ITEMS_SRCS
preview_items/arc_assistant.cpp
preview_items/arc_geom_manager.cpp
preview_items/centreline_rect_item.cpp
preview_items/construction_geom.cpp
preview_items/draw_context.cpp
preview_items/item_drawing_utils.cpp
preview_items/polygon_geom_manager.cpp
preview_items/polygon_item.cpp
preview_items/preview_utils.cpp
@ -603,7 +605,7 @@ set( COMMON_SRCS
tool/action_toolbar.cpp
tool/actions.cpp
tool/common_control.cpp
tool/library_editor_control.cpp
tool/construction_manager.cpp
tool/common_tools.cpp
tool/conditional_menu.cpp
tool/edit_constraints.cpp
@ -612,6 +614,7 @@ set( COMMON_SRCS
tool/embed_tool.cpp
tool/grid_helper.cpp
tool/grid_menu.cpp
tool/library_editor_control.cpp
tool/picker_tool.cpp
tool/properties_tool.cpp
tool/selection.cpp

View File

@ -0,0 +1,161 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "preview_items/construction_geom.h"
#include <layer_ids.h>
#include <gal/graphics_abstraction_layer.h>
#include <geometry/shape_utils.h>
#include <preview_items/item_drawing_utils.h>
#include <view/view.h>
using namespace KIGFX;
CONSTRUCTION_GEOM::CONSTRUCTION_GEOM() :
EDA_ITEM( nullptr, NOT_USED ), // Never added to a BOARD/SCHEMATIC so it needs no type
m_color( COLOR4D::WHITE ), m_persistentColor( COLOR4D::WHITE )
{
}
void CONSTRUCTION_GEOM::AddDrawable( const DRAWABLE& aItem, bool aPersistent )
{
m_drawables.push_back( { aItem, aPersistent } );
}
void CONSTRUCTION_GEOM::ClearDrawables()
{
m_drawables.clear();
}
const BOX2I CONSTRUCTION_GEOM::ViewBBox() const
{
// We could be a bit more circumspect here, but much of the time the
// enxtended lines go across the whole screen anyway
BOX2I bbox;
bbox.SetMaximum();
return bbox;
}
void CONSTRUCTION_GEOM::ViewDraw( int aLayer, VIEW* aView ) const
{
GAL& gal = *aView->GetGAL();
gal.SetIsFill( false );
gal.SetIsStroke( true );
gal.SetLineWidth( 1 );
BOX2D viewportD = aView->GetViewport();
BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
const bool haveSnapLine = m_snapLine && m_snapLine->Length() != 0;
// Avoid fighting with the snap line
const auto drawLineIfNotAlsoSnapLine = [&]( const SEG& aLine )
{
if( !haveSnapLine || !aLine.ApproxCollinear( *m_snapLine, 1 ) )
{
gal.DrawLine( aLine.A, aLine.B );
}
};
// Draw all the items
for( const DRAWABLE_INFO& drawable : m_drawables )
{
gal.SetStrokeColor( drawable.IsPersistent ? m_persistentColor : m_color );
std::visit(
[&]( const auto& visited )
{
using ItemType = std::decay_t<decltype( visited )>;
if constexpr( std::is_same_v<ItemType, LINE> )
{
// Extend the segment to the viewport boundary
std::optional<SEG> segToBoundary =
KIGEOM::ClipLineToBox( visited, viewport );
if( segToBoundary )
{
drawLineIfNotAlsoSnapLine( *segToBoundary );
}
}
else if constexpr( std::is_same_v<ItemType, HALF_LINE> )
{
// Extend the ray to the viewport boundary
std::optional<SEG> segToBoundary =
KIGEOM::ClipHalfLineToBox( visited, viewport );
if( segToBoundary )
{
drawLineIfNotAlsoSnapLine( *segToBoundary );
}
}
else if constexpr( std::is_same_v<ItemType, SEG> )
{
drawLineIfNotAlsoSnapLine( visited );
}
else if constexpr( std::is_same_v<ItemType, CIRCLE> )
{
gal.DrawCircle( visited.Center, visited.Radius );
}
else if constexpr( std::is_same_v<ItemType, SHAPE_ARC> )
{
gal.DrawArc( visited.GetCenter(), visited.GetRadius(),
visited.GetStartAngle(), visited.GetCentralAngle() );
}
else if constexpr( std::is_same_v<ItemType, VECTOR2I> )
{
KIGFX::DrawCross( gal, visited, aView->ToWorld( 16 ) );
}
},
drawable.Item );
}
if( haveSnapLine )
{
gal.SetStrokeColor( m_persistentColor );
const int dashSizeBasis = aView->ToWorld( 12 );
const int snapOriginMarkerSize = aView->ToWorld( 16 );
// Avoid clash with the snap marker if very close
const int omitStartMarkerIfWithinLength = aView->ToWorld( 8 );
// The line itself
KIGFX::DrawDashedLine( gal, *m_snapLine, dashSizeBasis );
// The line origin marker if the line is long enough
if( m_snapLine->A.Distance( m_snapLine->B ) > omitStartMarkerIfWithinLength )
{
KIGFX::DrawCross( gal, m_snapLine->A, snapOriginMarkerSize );
gal.DrawCircle( m_snapLine->A, snapOriginMarkerSize / 2 );
}
}
}
void CONSTRUCTION_GEOM::ViewGetLayers( int aLayers[], int& aCount ) const
{
aLayers[0] = LAYER_GP_OVERLAY;
aCount = 1;
}

View File

@ -0,0 +1,76 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "preview_items/item_drawing_utils.h"
#include <array>
#include <geometry/geometry_utils.h>
#include <geometry/seg.h>
using namespace KIGFX;
void KIGFX::DrawCross( GAL& aGal, const VECTOR2I& aPosition, int aSize )
{
const int size = aSize / 2;
aGal.DrawLine( aPosition - VECTOR2I( size, 0 ), aPosition + VECTOR2I( size, 0 ) );
aGal.DrawLine( aPosition - VECTOR2I( 0, size ), aPosition + VECTOR2I( 0, size ) );
}
void KIGFX::DrawDashedLine( GAL& aGal, const SEG& aSeg, double aDashSize )
{
const std::array<double, 2> strokes = { aDashSize, aDashSize / 2 };
const double dashCycleLen = strokes[0] + strokes[1];
// The dash cycle length must be at least 1 pixel.
wxASSERT( dashCycleLen * aGal.GetWorldScale() > 1 );
const BOX2I clip = BOX2I::ByCorners( aSeg.A, aSeg.B );
const double theta = atan2( aSeg.B.y - aSeg.A.y, aSeg.B.x - aSeg.A.x );
const VECTOR2D cycleVec{
dashCycleLen * cos( theta ),
dashCycleLen * sin( theta ),
};
const VECTOR2D dashVec{
strokes[0] * cos( theta ),
strokes[0] * sin( theta ),
};
unsigned cyclei = 0;
while( true )
{
const VECTOR2D dashStart = aSeg.A + cycleVec * cyclei;
const VECTOR2D dashEnd = dashStart + dashVec;
// Drawing each segment can be done rounded to ints.
SEG dashSeg{ KiROUND( dashStart ), KiROUND( dashEnd ) };
if( ClipLine( &clip, dashSeg.A.x, dashSeg.A.y, dashSeg.B.x, dashSeg.B.y ) )
break;
aGal.DrawLine( dashSeg.A, dashSeg.B );
++cyclei;
}
}

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 20204 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <preview_items/snap_indicator.h>
#include "preview_items/snap_indicator.h"
#include <gal/graphics_abstraction_layer.h>
@ -124,6 +124,16 @@ static void DrawIntersectionIcon( GAL& aGal, const VECTOR2I& aPosition, int aSiz
aGal.DrawLine( aPosition - xLeg, aPosition + xLeg );
}
static void DrawOnElementIcon( GAL& aGal, const VECTOR2I& aPosition, int aSize )
{
const int nodeRadius = aSize / 8;
// A bit like midpoint by off to one side
DrawSnapNode( aGal, aPosition + VECTOR2I( aSize / 4, 0 ), nodeRadius );
aGal.DrawLine( aPosition - VECTOR2I( aSize / 2, 0 ), aPosition + VECTOR2I( aSize / 2, 0 ) );
}
void SNAP_INDICATOR::ViewDraw( int, VIEW* aView ) const
{
@ -134,19 +144,9 @@ void SNAP_INDICATOR::ViewDraw( int, VIEW* aView ) const
gal.SetFillColor( m_color );
const auto scaleSize = [&]( double aSize ) -> int
{
return aView->ToWorld( aSize );
};
const auto scaleVec = [&]( const VECTOR2I& aVec ) -> VECTOR2I
{
return aView->ToWorld( aVec, false );
};
// Put the icon near the x-line, so it doesn't overlap with the ruler helpers
const VECTOR2I typeIconPos = m_position + scaleVec( { 24, 10 } );
const int size = scaleSize( 16 );
const VECTOR2I typeIconPos = m_position + aView->ToWorld( { 24, 10 }, false );
const int size = aView->ToWorld( 16 );
// For now, choose the first type that is set
if( m_snapTypes & POINT_TYPE::PT_CORNER )
@ -173,4 +173,8 @@ void SNAP_INDICATOR::ViewDraw( int, VIEW* aView ) const
{
DrawIntersectionIcon( gal, typeIconPos, size );
}
else if( m_snapTypes & POINT_TYPE::PT_ON_ELEMENT )
{
DrawOnElementIcon( gal, typeIconPos, size );
}
}

View File

@ -0,0 +1,300 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <tool/construction_manager.h>
CONSTRUCTION_MANAGER::CONSTRUCTION_MANAGER( KIGFX::CONSTRUCTION_GEOM& aHelper ) :
m_constructionGeomPreview( aHelper )
{
}
void CONSTRUCTION_MANAGER::updateView()
{
if( m_updateCallback )
{
bool showAnything = m_persistentConstructionBatch || !m_temporaryConstructionBatches.empty()
|| ( m_snapLineOrigin && m_snapLineEnd );
m_updateCallback( showAnything );
}
}
void CONSTRUCTION_MANAGER::AddConstructionItems( CONSTRUCTION_ITEM_BATCH aBatch,
bool aIsPersistent )
{
if( aIsPersistent )
{
// We only keep one previous persistent batch for the moment
m_persistentConstructionBatch = std::move( aBatch );
}
else
{
bool anyNewItems = false;
for( CONSTRUCTION_ITEM& item : aBatch )
{
if( m_involvedItems.count( item.Item ) == 0 )
{
anyNewItems = true;
break;
}
}
// If there are no new items involved, don't bother adding the batch
if( !anyNewItems )
{
return;
}
// We only keep up to one previous temporary batch and the current one
// we could make this a setting if we want to keep more, but it gets cluttered
const int maxTempItems = 2;
while( m_temporaryConstructionBatches.size() >= maxTempItems )
{
m_temporaryConstructionBatches.pop_front();
}
m_temporaryConstructionBatches.emplace_back( std::move( aBatch ) );
}
// Refresh what items are drawn
m_constructionGeomPreview.ClearDrawables();
m_involvedItems.clear();
const auto addBatchItems = [&]( const CONSTRUCTION_ITEM_BATCH& aBatchToAdd, bool aPersistent )
{
for( const CONSTRUCTION_ITEM& item : aBatchToAdd )
{
// Only show the item if it's not already involved
// (avoid double-drawing the same item)
if( m_involvedItems.count( item.Item ) == 0 )
{
m_involvedItems.insert( item.Item );
for( const KIGFX::CONSTRUCTION_GEOM::DRAWABLE& construction : item.Constructions )
{
m_constructionGeomPreview.AddDrawable( construction, aPersistent );
}
}
}
};
if( m_persistentConstructionBatch )
{
addBatchItems( *m_persistentConstructionBatch, true );
}
for( const CONSTRUCTION_ITEM_BATCH& batch : m_temporaryConstructionBatches )
{
addBatchItems( batch, false );
}
updateView();
}
bool CONSTRUCTION_MANAGER::InvolvesAllGivenRealItems( const std::vector<EDA_ITEM*>& aItems ) const
{
for( EDA_ITEM* item : aItems )
{
// Null items (i.e. construction items) are always considered involved
if( item && m_involvedItems.count( item ) == 0 )
{
return false;
}
}
return true;
}
void CONSTRUCTION_MANAGER::SetSnapLineOrigin( const VECTOR2I& aOrigin )
{
// Setting the origin clears the snap line as the end point is no longer valid
ClearSnapLine();
m_snapLineOrigin = aOrigin;
}
void CONSTRUCTION_MANAGER::SetSnapLineEnd( const OPT_VECTOR2I& aSnapEnd )
{
if( m_snapLineOrigin && aSnapEnd != m_snapLineEnd )
{
m_snapLineEnd = aSnapEnd;
if( m_snapLineEnd )
m_constructionGeomPreview.SetSnapLine( SEG{ *m_snapLineOrigin, *m_snapLineEnd } );
else
m_constructionGeomPreview.ClearSnapLine();
updateView();
}
}
void CONSTRUCTION_MANAGER::ClearSnapLine()
{
m_snapLineOrigin.reset();
m_snapLineEnd.reset();
m_constructionGeomPreview.ClearSnapLine();
updateView();
}
void CONSTRUCTION_MANAGER::SetSnappedAnchor( const VECTOR2I& aAnchorPos )
{
if( m_snapLineOrigin )
{
if( aAnchorPos.x == m_snapLineOrigin->x || aAnchorPos.y == m_snapLineOrigin->y )
{
SetSnapLineEnd( aAnchorPos );
}
else
{
// Snapped to something that is not the snap line origin, so
// this anchor is now the new snap line origin
SetSnapLineOrigin( aAnchorPos );
}
}
else
{
// If there's no snap line, start one
m_snapLineOrigin = aAnchorPos;
}
}
std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>
CONSTRUCTION_MANAGER::GetConstructionItems() const
{
std::vector<CONSTRUCTION_ITEM_BATCH> batches;
if( m_persistentConstructionBatch )
{
batches.push_back( *m_persistentConstructionBatch );
}
for( const CONSTRUCTION_ITEM_BATCH& batch : m_temporaryConstructionBatches )
{
batches.push_back( batch );
}
if( m_snapLineOrigin )
{
CONSTRUCTION_ITEM_BATCH batch;
CONSTRUCTION_ITEM& snapPointItem = batch.emplace_back( CONSTRUCTION_ITEM{
SOURCE::FROM_SNAP_LINE,
nullptr,
{},
} );
snapPointItem.Constructions.push_back(
LINE{ *m_snapLineOrigin, *m_snapLineOrigin + VECTOR2I( 100000, 0 ) } );
snapPointItem.Constructions.push_back(
LINE{ *m_snapLineOrigin, *m_snapLineOrigin + VECTOR2I( 0, 100000 ) } );
batches.push_back( std::move( batch ) );
}
return batches;
}
/**
* Check if the cursor has moved far enough away from the snap line origin to escape snapping
* in the X direction.
*
* This is defined as within aEscapeRange of the snap line origin, and within aLongRangeEscapeAngle
* of the vertical line passing through the snap line origin.
*/
static bool pointHasEscapedSnapLineX( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin,
int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle )
{
if( std::abs( aCursor.x - aSnapLineOrigin.x ) < aEscapeRange )
{
return false;
}
EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin ) + EDA_ANGLE( 90, DEGREES_T );
return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle;
}
/**
* As above, but for the Y direction.
*/
static bool pointHasEscapedSnapLineY( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin,
int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle )
{
if( std::abs( aCursor.y - aSnapLineOrigin.y ) < aEscapeRange )
{
return false;
}
EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin );
return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle;
}
OPT_VECTOR2I CONSTRUCTION_MANAGER::GetNearestSnapLinePoint( const VECTOR2I& aCursor,
const VECTOR2I& aNearestGrid,
std::optional<int> aDistToNearest,
int aSnapRange ) const
{
// return std::nullopt;
if( m_snapLineOrigin )
{
bool snapLine = false;
VECTOR2I bestSnapPoint = aNearestGrid;
// If there's no snap anchor, or it's too far away, prefer the grid
const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange;
// The escape range is how far you go before the snap line is de-activated.
// Make this a bit more forgiving than the snap range, as you can easily cancel
// deliberately with a mouse move.
// These are both a bit arbitrary, and can be adjusted as preferred
const int escapeRange = 2 * aSnapRange;
const EDA_ANGLE longRangeEscapeAngle( 3, DEGREES_T );
const bool escapedX = pointHasEscapedSnapLineX( aCursor, *m_snapLineOrigin, escapeRange,
longRangeEscapeAngle );
const bool escapedY = pointHasEscapedSnapLineY( aCursor, *m_snapLineOrigin, escapeRange,
longRangeEscapeAngle );
/// Allows de-snapping from the line if you are closer to another snap point
/// Or if you have moved far enough away from the line
if( !escapedX && gridBetterThanNearest )
{
bestSnapPoint.x = m_snapLineOrigin->x;
snapLine = true;
}
if( !escapedY && gridBetterThanNearest )
{
bestSnapPoint.y = m_snapLineOrigin->y;
snapLine = true;
}
if( snapLine )
{
return bestSnapPoint;
}
}
return std::nullopt;
}

View File

@ -21,32 +21,78 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "tool/grid_helper.h"
#include <functional>
using namespace std::placeholders;
#include <gal/graphics_abstraction_layer.h>
#include <gal/painter.h>
#include <math/util.h> // for KiROUND
#include <math/vector2d.h>
#include <render_settings.h>
#include <tool/tool_manager.h>
#include <view/view.h>
#include <tool/grid_helper.h>
#include <settings/app_settings.h>
GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
m_toolMgr( aToolMgr )
GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
m_toolMgr( aToolMgr ), m_constructionManager( m_constructionGeomPreview )
{
m_maskTypes = ALL;
m_enableSnap = true;
m_enableSnapLine = true;
m_enableGrid = true;
m_snapItem = std::nullopt;
KIGFX::VIEW* view = m_toolMgr->GetView();
KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
const KIGFX::COLOR4D constructionColour = settings->GetLayerColor( aConstructionLayer );
m_constructionGeomPreview.SetPersistentColor( constructionColour );
m_constructionGeomPreview.SetColor( constructionColour.WithAlpha( 0.7 ) );
view->Add( &m_constructionGeomPreview );
view->SetVisible( &m_constructionGeomPreview, false );
m_constructionManager.SetUpdateCallback(
[view, this]( bool aAnythingShown )
{
const bool currentlyVisible = view->IsVisible( &m_constructionGeomPreview );
if( currentlyVisible && aAnythingShown )
{
view->Update( &m_constructionGeomPreview, KIGFX::GEOMETRY );
}
else
{
view->SetVisible( &m_constructionGeomPreview, aAnythingShown );
}
} );
}
GRID_HELPER::~GRID_HELPER()
{
KIGFX::VIEW* view = m_toolMgr->GetView();
view->Remove( &m_constructionGeomPreview );
}
void GRID_HELPER::showConstructionGeometry( bool aShow )
{
m_toolMgr->GetView()->SetVisible( &m_constructionGeomPreview, aShow );
}
void GRID_HELPER::updateSnapPoint( const TYPED_POINT2I& aPoint )
{
m_viewSnapPoint.SetPosition( aPoint.m_point );
m_viewSnapPoint.SetSnapTypes( aPoint.m_types );
if( m_toolMgr->GetView()->IsVisible( &m_viewSnapPoint ) )
m_toolMgr->GetView()->Update( &m_viewSnapPoint, KIGFX::GEOMETRY );
else
m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, true );
}

View File

@ -39,7 +39,7 @@
EE_GRID_HELPER::EE_GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
GRID_HELPER( aToolMgr )
GRID_HELPER( aToolMgr, LAYER_SCHEMATIC_ANCHOR )
{
KIGFX::VIEW* view = m_toolMgr->GetView();
@ -55,12 +55,6 @@ EE_GRID_HELPER::EE_GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
m_viewSnapPoint.SetDrawAtZero( true );
view->Add( &m_viewSnapPoint );
view->SetVisible( &m_viewSnapPoint, false );
m_viewSnapLine.SetStyle( KIGFX::ORIGIN_VIEWITEM::DASH_LINE );
m_viewSnapLine.SetColor( COLOR4D( 0.33, 0.55, 0.95, 1.0 ) );
m_viewSnapLine.SetDrawAtZero( true );
view->Add( &m_viewSnapLine );
view->SetVisible( &m_viewSnapLine, false );
}
@ -70,7 +64,6 @@ EE_GRID_HELPER::~EE_GRID_HELPER()
view->Remove( &m_viewAxis );
view->Remove( &m_viewSnapPoint );
view->Remove( &m_viewSnapLine );
}
@ -167,19 +160,24 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR
ANCHOR* nearest = nearestAnchor( aOrigin, SNAPPABLE, aGrid );
VECTOR2I nearestGrid = Align( aOrigin, aGrid );
if( m_enableSnapLine && m_snapItem && m_skipPoint != VECTOR2I( m_viewSnapLine.GetPosition() ) )
showConstructionGeometry( m_enableSnap );
std::optional<VECTOR2I> snapLineOrigin = getConstructionManager().GetSnapLineOrigin();
if( m_enableSnapLine && m_snapItem && snapLineOrigin.has_value()
&& m_skipPoint != *snapLineOrigin )
{
if( std::abs( m_viewSnapLine.GetPosition().x - aOrigin.x ) < snapDist.x )
if( std::abs( snapLineOrigin->x - aOrigin.x ) < snapDist.x )
{
pt.x = m_viewSnapLine.GetPosition().x;
snapDist.x = std::abs( m_viewSnapLine.GetPosition().x - aOrigin.x );
pt.x = snapLineOrigin->x;
snapDist.x = std::abs( pt.x - aOrigin.x );
snapLineX = true;
}
if( std::abs( m_viewSnapLine.GetPosition().y - aOrigin.y ) < snapDist.y )
if( std::abs( snapLineOrigin->y - aOrigin.y ) < snapDist.y )
{
pt.y = m_viewSnapLine.GetPosition().y;
snapDist.y = std::abs( m_viewSnapLine.GetPosition().y - aOrigin.y );
pt.y = snapLineOrigin->y;
snapDist.y = std::abs( pt.y - aOrigin.y );
snapLineY = true;
}
@ -218,7 +216,8 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR
snapPoint = true;
}
snapLineX = snapLineY = false;
snapLineX = false;
snapLineY = false;
gridChecked = true;
}
@ -227,20 +226,14 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR
if( snapLineX || snapLineY )
{
m_viewSnapLine.SetEndPosition( pt );
if( m_toolMgr->GetView()->IsVisible( &m_viewSnapLine ) )
m_toolMgr->GetView()->Update( &m_viewSnapLine, KIGFX::GEOMETRY );
else
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, true );
getConstructionManager().SetSnapLineEnd( pt );
}
else if( snapPoint )
{
m_snapItem = *nearest;
m_viewSnapPoint.SetPosition( pt );
m_viewSnapLine.SetPosition( pt );
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
getConstructionManager().SetSnapLineOrigin( pt );
if( m_toolMgr->GetView()->IsVisible( &m_viewSnapPoint ) )
m_toolMgr->GetView()->Update( &m_viewSnapPoint, KIGFX::GEOMETRY);
@ -249,8 +242,8 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR
}
else
{
getConstructionManager().ClearSnapLine();
m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
}
return pt;
@ -309,7 +302,10 @@ SCH_ITEM* EE_GRID_HELPER::GetSnapped() const
if( !m_snapItem )
return nullptr;
return static_cast<SCH_ITEM*>( m_snapItem->item );
if( m_snapItem->items.empty() )
return nullptr;
return static_cast<SCH_ITEM*>( m_snapItem->items[0] );
}
@ -511,15 +507,20 @@ EE_GRID_HELPER::ANCHOR* EE_GRID_HELPER::nearestAnchor( const VECTOR2I& aPos, int
for( ANCHOR& a : m_anchors )
{
SCH_ITEM* item = static_cast<SCH_ITEM*>( a.item );
if( ( aFlags & a.flags ) != aFlags )
continue;
if( aGrid == GRID_CONNECTABLE && !item->IsConnectable() )
continue;
else if( aGrid == GRID_GRAPHICS && item->IsConnectable() )
continue;
// A "virtual" anchor with no real items associated shouldn't be filtered out
if( !a.items.empty() )
{
// Filter using the first item
SCH_ITEM* item = static_cast<SCH_ITEM*>( a.items[0] );
if( aGrid == GRID_CONNECTABLE && !item->IsConnectable() )
continue;
else if( aGrid == GRID_GRAPHICS && item->IsConnectable() )
continue;
}
double dist = a.Distance( aPos );

View File

@ -0,0 +1,84 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <variant>
#include <vector>
#include <eda_item.h>
#include <gal/color4d.h>
#include <geometry/circle.h>
#include <geometry/half_line.h>
#include <geometry/line.h>
#include <geometry/seg.h>
#include <geometry/shape_arc.h>
namespace KIGFX
{
/**
* Shows construction geometry for things like line extensions, arc centers, etc.
*/
class CONSTRUCTION_GEOM : public EDA_ITEM
{
public:
// Supported items
using DRAWABLE = std::variant<SEG, LINE, HALF_LINE, CIRCLE, SHAPE_ARC, VECTOR2I>;
CONSTRUCTION_GEOM();
wxString GetClass() const override { return wxT( "CONSTRUCTION_GEOM" ); }
const BOX2I ViewBBox() const override;
void ViewDraw( int aLayer, VIEW* aView ) const override;
void ViewGetLayers( int aLayers[], int& aCount ) const override;
void SetColor( const COLOR4D& aColor ) { m_color = aColor; }
void SetPersistentColor( const COLOR4D& aColor ) { m_persistentColor = aColor; }
void AddDrawable( const DRAWABLE& aItem, bool aIsPersistent );
void ClearDrawables();
void SetSnapLine( const SEG& aLine ) { m_snapLine = aLine; }
void ClearSnapLine() { m_snapLine.reset(); }
private:
COLOR4D m_color;
COLOR4D m_persistentColor;
struct DRAWABLE_INFO
{
DRAWABLE Item;
bool IsPersistent;
};
// The items to draw
std::vector<DRAWABLE_INFO> m_drawables;
// The snap line to draw
std::optional<SEG> m_snapLine;
};
} // namespace KIGFX

View File

@ -0,0 +1,57 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <gal/graphics_abstraction_layer.h>
#include <math/vector2d.h>
/**
* @file item_drawing_utils.h
*
* Utility functions for drawing compound items (i.e. items that
* need more than one GAL Draw call to render) the might be used be more
* than one VIEW_ITEM Draw function.
*/
namespace KIGFX
{
/**
* Draw a cross at a given position.
*
* @param aGal The graphics abstraction layer to draw with.
* @param aPosition The position to draw the cross at.
* @param aSize The size of the cross.
*/
void DrawCross( GAL& aGal, const VECTOR2I& aPosition, int aSize );
/**
* Draw a dashed line.
*
* @param aGal The graphics abstraction layer to draw with.
* @param aSeg The line to draw.
* @param aDashSize The size of the dashes.
*/
void DrawDashedLine( GAL& aGal, const SEG& aSeg, double aDashSize );
} // namespace KIGFX

View File

@ -0,0 +1,179 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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
*/
#pragma once
#include <vector>
#include <deque>
#include <preview_items/construction_geom.h>
class EDA_ITEM;
/**
* A class that mananges "construction" objects and geometry.
*
* Probably only used by GRID_HELPERs, but it's neater to keep it separate,
* as there's quite a bit of state to manage.
*/
class CONSTRUCTION_MANAGER
{
public:
CONSTRUCTION_MANAGER( KIGFX::CONSTRUCTION_GEOM& aHelper );
/**
* Items to be used for the construction of "virtual" anchors, for example, when snapping to
* a point involving an _extension_ of an existing line or arc.
*
* One item can have multiple construction items (e.g. an arc can have a circle and centre point).
*/
enum class SOURCE
{
FROM_ITEMS,
FROM_SNAP_LINE,
};
struct CONSTRUCTION_ITEM
{
SOURCE Source;
EDA_ITEM* Item;
std::vector<KIGFX::CONSTRUCTION_GEOM::DRAWABLE> Constructions;
};
// A single batch of construction items. Once batch contains all the items (and associated
// construction geometry) that should be shown for one point of interest.
// More than one batch may be shown on the screen at the same time.
using CONSTRUCTION_ITEM_BATCH = std::vector<CONSTRUCTION_ITEM>;
/**
* Add a batch of construction items to the helper.
*
* @param aBatch The batch of construction items to add.
* @param aIsPersistent If true, the batch is considered "persistent" and will always be shown
* (and it will replace any previous persistent batch).
* If false, the batch is temporary and may be pushed out by other batches.
*/
void AddConstructionItems( CONSTRUCTION_ITEM_BATCH aBatch, bool aIsPersistent );
/**
* Check if all 'real' (non-null = constructed) the items in the batch are in the list of items
* currently 'involved' in an active construction.
*/
bool InvolvesAllGivenRealItems( const std::vector<EDA_ITEM*>& aItems ) const;
/**
* Set the reference-only points - these are points that are not snapped to, but can still
* be used for connection to the snap line.
*/
void SetReferenceOnlyPoints( std::vector<VECTOR2I> aPoints )
{
m_referenceOnlyPoints = std::move( aPoints );
}
const std::vector<VECTOR2I>& GetReferenceOnlyPoints() const { return m_referenceOnlyPoints; }
/**
* The snap point is a special point that is located at the last point the cursor
* snapped to. If it is set, the construction manager may add extra construction
* geometry to the helper extending from the snap point origin to the cursor,
* which is the 'snap line'.
*/
void SetSnapLineOrigin( const VECTOR2I& aOrigin );
/**
* Set the end point of the snap line.
*
* Passing std::nullopt will unset the end point, but keep the origin.
*/
void SetSnapLineEnd( const OPT_VECTOR2I& aSnapPoint );
/**
* Clear the snap line origin and end points.
*/
void ClearSnapLine();
std::optional<VECTOR2I> GetSnapLineOrigin() const { return m_snapLineOrigin; }
/**
* Inform the construction manager that an anchor snap is wanted.
*
* This will also update the snap line if appropriate.
*/
void SetSnappedAnchor( const VECTOR2I& aAnchorPos );
// Get the list of additional geometry items that should be considered
std::vector<CONSTRUCTION_ITEM_BATCH> GetConstructionItems() const;
/**
* If the snap line is active, return the best snap point that is closest to the cursor
*
* If there's no active snap line, return std::nullopt.
*
* If there's a snap very near, use that otherwise, use the grid point.
* With this point, snap to it on an H/V axis.
*
* Then, if there's a grid point near, snap to it on an H/V axis
*
* @param aCursor The cursor position
* @param aNearestGrid The nearest grid point to the cursor
* @param aDistToNearest The distance to the nearest non-grid snap point, if any
* @param snapRange The snap range
*/
OPT_VECTOR2I GetNearestSnapLinePoint( const VECTOR2I& aCursor, const VECTOR2I& aNearestGrid,
std::optional<int> aDistToNearest, int snapRange ) const;
using GFX_UPDATE_CALLBACK = std::function<void( bool )>;
/**
* Set the callback to call when the construction geometry changes and a view may need updating.
*/
void SetUpdateCallback( GFX_UPDATE_CALLBACK aCallback ) { m_updateCallback = aCallback; }
private:
void updateView();
// An (external) construction helper view item, that this manager adds/removes
// construction objects to/from.
KIGFX::CONSTRUCTION_GEOM& m_constructionGeomPreview;
// Within one "operation", there is one set of construction items that are
// "persistent", and are always shown. Usually the original item and any
// extensions.
std::optional<CONSTRUCTION_ITEM_BATCH> m_persistentConstructionBatch;
// Temporary construction items are added and removed as needed
std::deque<CONSTRUCTION_ITEM_BATCH> m_temporaryConstructionBatches;
// Set of all items for which construction geometry has been added
std::set<EDA_ITEM*> m_involvedItems;
std::vector<VECTOR2I> m_referenceOnlyPoints;
// If a snap point is "active", extra construction geometry is added to the helper
// extending from the snap point to the cursor.
OPT_VECTOR2I m_snapLineOrigin;
OPT_VECTOR2I m_snapLineEnd;
GFX_UPDATE_CALLBACK m_updateCallback;
};

View File

@ -29,6 +29,8 @@
#include <geometry/point_types.h>
#include <math/vector2d.h>
#include <preview_items/snap_indicator.h>
#include <preview_items/construction_geom.h>
#include <tool/construction_manager.h>
#include <tool/tool_manager.h>
#include <tool/selection.h>
#include <origin_viewitem.h>
@ -50,7 +52,7 @@ enum GRID_HELPER_GRIDS : int
class GRID_HELPER
{
public:
GRID_HELPER( TOOL_MANAGER* aToolMgr );
GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer );
virtual ~GRID_HELPER();
VECTOR2I GetGrid() const;
@ -127,7 +129,11 @@ public:
ORIGIN = 8,
VERTICAL = 16,
HORIZONTAL = 32,
ALL = CORNER | OUTLINE | SNAPPABLE | ORIGIN | VERTICAL | HORIZONTAL
// This anchor comes from 'constructed' geometry (e.g. an intersection
// with something else), and not from some intrinsic point of an item
// (e.g. an endpoint)
CONSTRUCTED = 64,
ALL = CORNER | OUTLINE | SNAPPABLE | ORIGIN | VERTICAL | HORIZONTAL | CONSTRUCTED
};
protected:
@ -143,27 +149,40 @@ protected:
* @param aPointTypes The point types that this anchor represents in geometric terms
* @param aItem The item to which the anchor belongs
*/
ANCHOR( const VECTOR2I& aPos, int aFlags, int aPointTypes, EDA_ITEM* aItem ) :
pos( aPos ), flags( aFlags ), pointTypes( aPointTypes ), item( aItem )
ANCHOR( const VECTOR2I& aPos, int aFlags, int aPointTypes, std::vector<EDA_ITEM*> aItems ) :
pos( aPos ), flags( aFlags ), pointTypes( aPointTypes ),
items( std::move( aItems ) )
{
}
VECTOR2I pos;
int flags;
int pointTypes;
EDA_ITEM* item;
// Items that are associated with this anchor (can be more than one, e.g. for an intersection)
std::vector<EDA_ITEM*> items;
double Distance( const VECTOR2I& aP ) const
{
return VECTOR2D( (double) aP.x - pos.x, (double) aP.y - pos.y ).EuclideanNorm();
}
bool InvolvesItem( const EDA_ITEM& aItem ) const
{
return std::find( items.begin(), items.end(), &aItem ) != items.end();
}
};
void addAnchor( const VECTOR2I& aPos, int aFlags, EDA_ITEM* aItem,
int aPointTypes = POINT_TYPE::PT_NONE )
{
addAnchor( aPos, aFlags, std::vector<EDA_ITEM*>{ aItem }, aPointTypes );
}
void addAnchor( const VECTOR2I& aPos, int aFlags, std::vector<EDA_ITEM*> aItems,
int aPointTypes )
{
if( ( aFlags & m_maskTypes ) == aFlags )
m_anchors.emplace_back( ANCHOR( aPos, aFlags, aPointTypes, aItem ) );
m_anchors.emplace_back( ANCHOR( aPos, aFlags, aPointTypes, std::move( aItems ) ) );
}
void clearAnchors()
@ -181,6 +200,12 @@ protected:
const VECTOR2I& aOffset ) const;
protected:
void showConstructionGeometry( bool aShow );
CONSTRUCTION_MANAGER& getConstructionManager() { return m_constructionManager; }
void updateSnapPoint( const TYPED_POINT2I& aPoint );
std::vector<ANCHOR> m_anchors;
TOOL_MANAGER* m_toolMgr;
@ -196,8 +221,17 @@ protected:
VECTOR2I m_skipPoint; // When drawing a line, we avoid snapping to the
// source point
KIGFX::SNAP_INDICATOR m_viewSnapPoint;
KIGFX::ORIGIN_VIEWITEM m_viewSnapLine;
KIGFX::ORIGIN_VIEWITEM m_viewAxis;
private:
// Construction helper - this is what actually shows construction geometry
// (if any) on the canvas.
KIGFX::CONSTRUCTION_GEOM m_constructionGeomPreview;
// Construction manager - this is what manages the construction geometry
// and deals with updating the construction helper as well as keeping
// track of what geometry is "active" for construction purposes.
CONSTRUCTION_MANAGER m_constructionManager;
};
#endif

View File

@ -19,19 +19,23 @@ set( KIMATH_SRCS
src/geometry/convex_hull.cpp
src/geometry/direction_45.cpp
src/geometry/geometry_utils.cpp
src/geometry/half_line.cpp
src/geometry/intersection.cpp
src/geometry/line.cpp
src/geometry/nearest.cpp
src/geometry/oval.cpp
src/geometry/seg.cpp
src/geometry/shape.cpp
src/geometry/shape_arc.cpp
src/geometry/shape_collisions.cpp
src/geometry/shape_compound.cpp
src/geometry/shape_file_io.cpp
src/geometry/shape_line_chain.cpp
src/geometry/shape_poly_set.cpp
src/geometry/shape_rect.cpp
src/geometry/shape_compound.cpp
src/geometry/shape_segment.cpp
src/geometry/vector_utils.cpp
src/geometry/shape_utils.cpp
src/geometry/vertex_set.cpp

View File

@ -38,6 +38,8 @@ public:
CIRCLE( const CIRCLE& aOther );
bool operator==( const CIRCLE& aOther ) const = default;
/**
* Construct this circle such that it is tangent to the given segments and passes through the
* given point, generating the solution which can be used to fillet both segments.

View File

@ -0,0 +1,95 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <optional>
#include <geometry/seg.h>
#include <math/box2.h>
/*
* A geometric half-line of infinite length, starting at a given point and extending infinitely.
* A.k.a. a ray.
*
* In terms of geometric ops, a SEG would probably do in most cases, as it
* has the same definition, but a separate class is more explicit and also
* allows compile-time reasoning about the meaning of the object through
* the type system.
*/
class HALF_LINE
{
public:
/**
* Construct a ray from a segment - the ray will start at the segment's A point and
* extend infinitely in the direction of the segment, passing through its B point.
*/
HALF_LINE( const SEG& aSeg ) : m_seg( aSeg ) {}
HALF_LINE( const VECTOR2I& aStart, const VECTOR2I& aOtherContainedPoint ) :
m_seg( aStart, aOtherContainedPoint )
{
}
/**
* Get the start point of the ray.
*/
const VECTOR2I& GetStart() const { return m_seg.A; }
/**
* Get one (of the infinite number) of points that the ray passes through.
*/
const VECTOR2I& GetContainedPoint() const { return m_seg.B; }
bool Contains( const VECTOR2I& aPoint ) const;
OPT_VECTOR2I Intersect( const SEG& aSeg ) const;
OPT_VECTOR2I Intersect( const HALF_LINE& aOther ) const;
/**
* Get the nearest point on the ray to the given point.
*
* This will be the start point of the ray for half the 2D plane.
*/
VECTOR2I NearestPoint( const VECTOR2I& aPoint ) const;
/**
* Based on the ray being identically defined. TODO: this is not geoemetrical equality?!
*/
bool operator==( const HALF_LINE& aOther ) const { return m_seg == aOther.m_seg; }
/**
* Gets the (one of the infinite number of) segments that the ray passes through.
*
* The segment's A point is the start of the ray, and the B point is on the ray.
*/
const SEG& GetContainedSeg() const { return m_seg; }
private:
/// Internally, we can represent a just a segment that the ray passes through
SEG m_seg;
};
std::optional<SEG> ClipHalfLineToBox( const HALF_LINE& aRay, const BOX2I& aBox );

View File

@ -27,17 +27,20 @@
#include <vector>
#include <math/vector2d.h>
#include <math/box2.h>
class SEG;
class CIRCLE;
class SHAPE_ARC;
class SHAPE_RECT;
#include <geometry/circle.h>
#include <geometry/half_line.h>
#include <geometry/line.h>
#include <geometry/seg.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_rect.h>
/**
* A variant type that can hold any of the supported geometry types
* for intersection calculations.
*/
using INTERSECTABLE_GEOM = std::variant<SEG, CIRCLE, SHAPE_ARC, SHAPE_RECT>;
using INTERSECTABLE_GEOM = std::variant<LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, BOX2I>;
/**
* A visitor that visits INTERSECTABLE_GEOM variant objects with another
@ -63,9 +66,11 @@ public:
* other geometry.
*/
void operator()( const SEG& aSeg ) const;
void operator()( const LINE& aLine ) const;
void operator()( const HALF_LINE& aLine ) const;
void operator()( const CIRCLE& aCircle ) const;
void operator()( const SHAPE_ARC& aArc ) const;
void operator()( const SHAPE_RECT& aArc ) const;
void operator()( const BOX2I& aArc ) const;
private:
const INTERSECTABLE_GEOM& m_otherGeometry;

View File

@ -0,0 +1,67 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <geometry/seg.h>
/*
* A geometric line of infinite length.
*
* In terms of geometric ops, a SEG would probably do as it has the same definition,
* but a separate class is more explicit and also allows compile-time
* reasoning about the meaning of the object through the type system.
*/
class LINE
{
public:
using ecoord = VECTOR2I::extended_type;
LINE( const SEG& aSeg ) : m_seg( aSeg ) {}
LINE( const VECTOR2I& aStart, const VECTOR2I& aEnd ) : m_seg( aStart, aEnd ) {}
bool operator==( const LINE& aOther ) const { return m_seg == aOther.m_seg; }
/**
* Gets the (one of the infinite number of) segments that the line passes through.
*/
const SEG& GetContainedSeg() const { return m_seg; }
OPT_VECTOR2I Intersect( const SEG& aOther ) const;
OPT_VECTOR2I Intersect( const LINE& aOther ) const;
/**
* Gets the distance from the line to the given point.
*/
int Distance( const VECTOR2I& aPoint ) const;
/**
* Gets the nearest point on the line to the given point.
*/
VECTOR2I NearestPoint( const VECTOR2I& aPoint ) const;
private:
/// Internally, we can represent a just a segment that the line passes through
SEG m_seg;
};

View File

@ -0,0 +1,59 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <variant>
#include <vector>
#include <math/vector2d.h>
#include <math/box2.h>
#include <geometry/circle.h>
#include <geometry/half_line.h>
#include <geometry/line.h>
#include <geometry/seg.h>
#include <geometry/shape_arc.h>
/**
* A variant type that can hold any of the supported geometry types for
* nearest point calculations.
*/
using NEARABLE_GEOM = std::variant<LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, BOX2I, VECTOR2I>;
/**
* Get the nearest point on a geometry to a given point.
*/
VECTOR2I GetNearestPoint( const NEARABLE_GEOM& aGeom, const VECTOR2I& aPt );
/**
* Get the nearest point on any of a list of geometries to a given point.
*
* @param aGeoms The geometries to check.
* @param aPt The point to find the nearest point to.
*
* @return The nearest point on any of the geometries to the given point (or std::nullopt if
* no geometries were provided).
*/
OPT_VECTOR2I GetNearestPoint( const std::vector<NEARABLE_GEOM>& aGeoms, const VECTOR2I& aPt );

View File

@ -65,15 +65,21 @@ enum POINT_TYPE
* The point is an intersection of two (or more) items.
*/
PT_INTERSECTION = 1 << 5,
/**
* The point is somewhere on another element, but not some specific point.
* (you can infer this from some other point types)
*/
PT_ON_ELEMENT = 1 << 6,
};
struct TYPED_POINT2I
{
VECTOR2I m_point;
POINT_TYPE m_types;
VECTOR2I m_point;
// Bitwise OR of POINT_TYPE values
int m_types;
// Clang needs this apparently
TYPED_POINT2I( const VECTOR2I& aVec, POINT_TYPE aTypes ) : m_point( aVec ), m_types( aTypes ) {}
TYPED_POINT2I( const VECTOR2I& aVec, int aTypes ) : m_point( aVec ), m_types( aTypes ) {}
bool operator==( const TYPED_POINT2I& ) const = default;
};

View File

@ -0,0 +1,72 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
/**
* @file geometry/shape_utils.h
*
* @brief Utility functions for working with shapes.
*
* These are free functions to avoid bloating the shape classes with functions
* that only need to be used in a few places and can just use the public
* interfaces.
*/
#include <array>
#include <optional>
#include <math/vector2d.h>
#include <math/box2.h>
class HALF_LINE;
class LINE;
class SEG;
namespace KIGEOM
{
/**
* Returns a SEG such that the start point is smaller or equal
* in x and y compared to the end point.
*/
SEG NormalisedSeg( const SEG& aSeg );
/**
* Decompose a BOX2 into four segments.
*
* Segments are returned in the order: Top, Right, Bottom, Left.
*/
std::array<SEG, 4> BoxToSegs( const BOX2I& aBox );
/**
* Get the segment of a half-line that is inside a box, if any.
*/
std::optional<SEG> ClipHalfLineToBox( const HALF_LINE& aRay, const BOX2I& aBox );
/**
* Get the segment of a line that is inside a box, if any.
*/
std::optional<SEG> ClipLineToBox( const LINE& aLine, const BOX2I& aBox );
} // namespace KIGEOM

View File

@ -28,7 +28,7 @@ namespace KIGEOM
* @file vector_utils.h
*
* Supplemental functions for working with vectors and
* objects that interact with vectors.
* simple objects that interact with vectors.
*/
/*
@ -82,6 +82,7 @@ bool PointProjectsOntoSegment( const VECTOR2I& aPoint, const SEG& aSeg );
/**
* Get the ratio of the vector to a point from the segment's start,
* compared to the segment's length.
*
* /--- aPoint
* A<---+-------->B <-- Length L
* | |

View File

@ -67,6 +67,11 @@ public:
Normalize();
}
static BOX2<Vec> ByCorners( const Vec& aCorner1, const Vec& aCorner2 )
{
return BOX2( aCorner1, aCorner2 - aCorner1 );
}
void SetMaximum()
{
if constexpr( std::is_floating_point<coord_type>() )

View File

@ -214,6 +214,10 @@ public:
*/
double Distance( const VECTOR2<extended_type>& aVector ) const;
/**
* Compute the squared distance between two vectors.
*/
constexpr extended_type SquaredDistance( const VECTOR2<T>& aVector ) const;
// Operators
@ -556,6 +560,15 @@ double VECTOR2<T>::Distance( const VECTOR2<extended_type>& aVector ) const
return diff.EuclideanNorm();
}
template <class T>
constexpr typename VECTOR2<T>::extended_type
VECTOR2<T>::SquaredDistance( const VECTOR2<T>& aVector ) const
{
const extended_type dx = (extended_type) x - aVector.x;
const extended_type dy = (extended_type) y - aVector.y;
return dx * dx + dy * dy;
}
template <class T>
bool VECTOR2<T>::operator<( const VECTOR2<T>& aVector ) const

View File

@ -0,0 +1,131 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "geometry/half_line.h"
#include <math/box2.h>
#include <geometry/shape_utils.h>
/**
* Check if two vectors point into the same quadrant.
*/
bool VectorsInSameQuadrant( const VECTOR2I& aA, const VECTOR2I& aB )
{
// The sign of the x and y components of the vectors must be the same
return ( ( aA.x >= 0 ) == ( aB.x >= 0 ) ) && ( ( aA.y >= 0 ) == ( aB.y >= 0 ) );
}
bool HALF_LINE::Contains( const VECTOR2I& aPoint ) const
{
// Check that the point is on the right side of the ray from
// the start point
// This is quick, so we can do it first
if( !VectorsInSameQuadrant( m_seg.B - m_seg.A, aPoint - m_seg.A ) )
{
return false;
}
// Check that the point is within a distance of 1 from the
// infinite line of the ray
return m_seg.LineDistance( aPoint ) <= 1;
}
OPT_VECTOR2I HALF_LINE::Intersect( const SEG& aSeg ) const
{
// Intsersection of two infinite lines
const SEG seg = GetContainedSeg();
OPT_VECTOR2I intersection = aSeg.Intersect( seg, false, true );
// Reject parallel lines
if( !intersection )
{
return std::nullopt;
}
// Check that the intersection is on the right side of the
// ray's start point (i.e. equal quadrants)
if( !VectorsInSameQuadrant( m_seg.B - m_seg.A, *intersection - m_seg.A ) )
{
return std::nullopt;
}
// Check that the intersection is not somewhere past the end
// of the segment
if( !aSeg.Contains( *intersection ) )
{
return std::nullopt;
}
return intersection;
}
OPT_VECTOR2I HALF_LINE::Intersect( const HALF_LINE& aOther ) const
{
// Intsersection of two infinite lines
const SEG otherSeg = aOther.GetContainedSeg();
OPT_VECTOR2I intersection = m_seg.Intersect( otherSeg, false, true );
// Reject parallel lines
if( !intersection )
{
return std::nullopt;
}
// Check that the intersection is on the right side of both
// rays' start points (i.e. equal quadrants)
if( !VectorsInSameQuadrant( m_seg.B - m_seg.A, *intersection - m_seg.A )
|| !VectorsInSameQuadrant( aOther.m_seg.B - aOther.m_seg.A,
*intersection - aOther.m_seg.A ) )
{
return std::nullopt;
}
return intersection;
}
VECTOR2I HALF_LINE::NearestPoint( const VECTOR2I& aPoint ) const
{
// Same as the SEG implementation, but without the early return
// if the point isn't on the segment.
// Inlined for performance reasons
VECTOR2L d( m_seg.B.x - m_seg.A.x, m_seg.B.y - m_seg.A.y );
SEG::ecoord l_squared( d.x * d.x + d.y * d.y );
if( l_squared == 0 )
return m_seg.A;
SEG::ecoord t = d.Dot( aPoint - m_seg.A );
if( t < 0 )
return m_seg.A;
SEG::ecoord xp = rescale( t, (SEG::ecoord) d.x, l_squared );
SEG::ecoord yp = rescale( t, (SEG::ecoord) d.y, l_squared );
return VECTOR2<SEG::ecoord>( m_seg.A.x + xp, m_seg.A.y + yp );
}

View File

@ -25,10 +25,7 @@
#include <core/type_helpers.h>
#include <geometry/seg.h>
#include <geometry/circle.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_utils.h>
/*
* Helper functions that dispatch to the correct intersection function
@ -47,6 +44,27 @@ void findIntersections( const SEG& aSegA, const SEG& aSegB, std::vector<VECTOR2I
}
}
void findIntersections( const SEG& aSeg, const LINE& aLine, std::vector<VECTOR2I>& aIntersections )
{
OPT_VECTOR2I intersection = aLine.Intersect( aSeg );
if( intersection )
{
aIntersections.push_back( *intersection );
}
}
void findIntersections( const SEG& aSeg, const HALF_LINE& aHalfLine,
std::vector<VECTOR2I>& aIntersections )
{
OPT_VECTOR2I intersection = aHalfLine.Intersect( aSeg );
if( intersection )
{
aIntersections.push_back( *intersection );
}
}
void findIntersections( const SEG& aSeg, const CIRCLE& aCircle,
std::vector<VECTOR2I>& aIntersections )
{
@ -71,6 +89,68 @@ void findIntersections( const SEG& aSeg, const SHAPE_ARC& aArc,
}
}
void findIntersections( const LINE& aLineA, const LINE& aLineB,
std::vector<VECTOR2I>& aIntersections )
{
OPT_VECTOR2I intersection = aLineA.Intersect( aLineB );
if( intersection )
{
aIntersections.push_back( *intersection );
}
}
void findIntersections( const LINE& aLine, const HALF_LINE& aHalfLine,
std::vector<VECTOR2I>& aIntersections )
{
// Intersect as two infinite lines
OPT_VECTOR2I intersection =
aHalfLine.GetContainedSeg().Intersect( aLine.GetContainedSeg(), false, true );
// No intersection at all (parallel, or passes on the other side of the start point)
if( !intersection )
{
return;
}
if( aHalfLine.Contains( *intersection ) )
{
aIntersections.push_back( *intersection );
}
}
void findIntersections( const HALF_LINE& aHalfLineA, const HALF_LINE& aHalfLineB,
std::vector<VECTOR2I>& aIntersections )
{
OPT_VECTOR2I intersection = aHalfLineA.Intersect( aHalfLineB );
if( intersection )
{
aIntersections.push_back( *intersection );
}
}
void findIntersections( const CIRCLE& aCircle, const LINE& aLine,
std::vector<VECTOR2I>& aIntersections )
{
std::vector<VECTOR2I> intersections = aCircle.IntersectLine( aLine.GetContainedSeg() );
aIntersections.insert( aIntersections.end(), intersections.begin(), intersections.end() );
}
void findIntersections( const CIRCLE& aCircle, const HALF_LINE& aHalfLine,
std::vector<VECTOR2I>& aIntersections )
{
std::vector<VECTOR2I> intersections = aCircle.IntersectLine( aHalfLine.GetContainedSeg() );
for( const VECTOR2I& intersection : intersections )
{
if( aHalfLine.Contains( intersection ) )
{
aIntersections.push_back( intersection );
}
}
}
void findIntersections( const CIRCLE& aCircleA, const CIRCLE& aCircleB,
std::vector<VECTOR2I>& aIntersections )
@ -91,19 +171,28 @@ void findIntersections( const SHAPE_ARC& aArcA, const SHAPE_ARC& aArcB,
aArcA.Intersect( aArcB, &aIntersections );
}
std::vector<SEG> RectToSegs( const SHAPE_RECT& aRect )
void findIntersections( const SHAPE_ARC& aArc, const LINE& aLine,
std::vector<VECTOR2I>& aIntersections )
{
const VECTOR2I corner = aRect.GetPosition();
const int w = aRect.GetWidth();
const int h = aRect.GetHeight();
std::vector<VECTOR2I> intersections;
aArc.IntersectLine( aLine.GetContainedSeg(), &intersections );
return {
SEG( corner, { corner + VECTOR2I( w, 0 ) } ),
SEG( { corner + VECTOR2I( w, 0 ) }, { corner + VECTOR2I( w, h ) } ),
SEG( { corner + VECTOR2I( w, h ) }, { corner + VECTOR2I( 0, h ) } ),
SEG( { corner + VECTOR2I( 0, h ) }, corner ),
};
aIntersections.insert( aIntersections.end(), intersections.begin(), intersections.end() );
}
void findIntersections( const SHAPE_ARC& aArc, const HALF_LINE& aHalfLine,
std::vector<VECTOR2I>& aIntersections )
{
std::vector<VECTOR2I> intersections;
aArc.IntersectLine( aHalfLine.GetContainedSeg(), &intersections );
for( const VECTOR2I& intersection : intersections )
{
if( aHalfLine.Contains( intersection ) )
{
aIntersections.push_back( intersection );
}
}
}
} // namespace
@ -132,10 +221,10 @@ void INTERSECTION_VISITOR::operator()( const SEG& aSeg ) const
{
using OtherGeomType = std::decay_t<decltype( otherGeom )>;
if constexpr( std::is_same_v<OtherGeomType, SHAPE_RECT> )
if constexpr( std::is_same_v<OtherGeomType, BOX2I> )
{
// Seg-Rect via decomposition into segments
for( const SEG& aRectSeg : RectToSegs( otherGeom ) )
for( const SEG& aRectSeg : KIGEOM::BoxToSegs( otherGeom ) )
{
findIntersections( aSeg, aRectSeg, m_intersections );
}
@ -149,6 +238,82 @@ void INTERSECTION_VISITOR::operator()( const SEG& aSeg ) const
m_otherGeometry );
}
void INTERSECTION_VISITOR::operator()( const LINE& aLine ) const
{
// Dispatch to the correct function
return std::visit(
[&]( const auto& otherGeom )
{
using OtherGeomType = std::decay_t<decltype( otherGeom )>;
// Dispatch in the correct order
if constexpr( std::is_same_v<OtherGeomType, SEG>
|| std::is_same_v<OtherGeomType, LINE>
|| std::is_same_v<OtherGeomType, CIRCLE>
|| std::is_same_v<OtherGeomType, SHAPE_ARC> )
{
// Seg-Line, Line-Line, Circle-Line, Arc-Line
findIntersections( otherGeom, aLine, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, HALF_LINE> )
{
// Line-HalfLine
findIntersections( aLine, otherGeom, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, BOX2I> )
{
// Line-Rect via decomposition into segments
for( const SEG& aRectSeg : KIGEOM::BoxToSegs( otherGeom ) )
{
findIntersections( aRectSeg, aLine, m_intersections );
}
}
else
{
static_assert( always_false<OtherGeomType>::value,
"Unhandled other geometry type" );
}
},
m_otherGeometry );
};
void INTERSECTION_VISITOR::operator()( const HALF_LINE& aHalfLine ) const
{
// Dispatch to the correct function
return std::visit(
[&]( const auto& otherGeom )
{
using OtherGeomType = std::decay_t<decltype( otherGeom )>;
// Dispatch in the correct order
if constexpr( std::is_same_v<OtherGeomType, SEG>
|| std::is_same_v<OtherGeomType, HALF_LINE>
|| std::is_same_v<OtherGeomType, CIRCLE>
|| std::is_same_v<OtherGeomType, SHAPE_ARC> )
{
// Seg-HalfLine, HalfLine-HalfLine, Circle-HalfLine, Arc-HalfLine
findIntersections( otherGeom, aHalfLine, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, LINE> )
{
// Line-HalfLine
findIntersections( otherGeom, aHalfLine, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, BOX2I> )
{
// HalfLine-Rect via decomposition into segments
for( const SEG& aRectSeg : KIGEOM::BoxToSegs( otherGeom ) )
{
findIntersections( aRectSeg, aHalfLine, m_intersections );
}
}
else
{
static_assert( always_false<OtherGeomType>::value,
"Unhandled other geometry type" );
}
},
m_otherGeometry );
};
void INTERSECTION_VISITOR::operator()( const CIRCLE& aCircle ) const
{
// Dispatch to the correct function
@ -163,15 +328,17 @@ void INTERSECTION_VISITOR::operator()( const CIRCLE& aCircle ) const
// Seg-Circle, Circle-Circle
findIntersections( otherGeom, aCircle, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, SHAPE_ARC> )
else if constexpr( std::is_same_v<OtherGeomType, SHAPE_ARC>
|| std::is_same_v<OtherGeomType, LINE>
|| std::is_same_v<OtherGeomType, HALF_LINE> )
{
// Circle-Arc
// Circle-Arc, Circle-Line, Circle-HalfLine
findIntersections( aCircle, otherGeom, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, SHAPE_RECT> )
else if constexpr( std::is_same_v<OtherGeomType, BOX2I> )
{
// Circle-Rect via decomposition into segments
for( const SEG& aRectSeg : RectToSegs( otherGeom ) )
for( const SEG& aRectSeg : KIGEOM::BoxToSegs( otherGeom ) )
{
findIntersections( aRectSeg, aCircle, m_intersections );
}
@ -200,10 +367,16 @@ void INTERSECTION_VISITOR::operator()( const SHAPE_ARC& aArc ) const
// Seg-Arc, Circle-Arc, Arc-Arc
findIntersections( otherGeom, aArc, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, SHAPE_RECT> )
else if constexpr( std::is_same_v<OtherGeomType, LINE>
|| std::is_same_v<OtherGeomType, HALF_LINE> )
{
// Arc-Line, Arc-HalfLine
findIntersections( aArc, otherGeom, m_intersections );
}
else if constexpr( std::is_same_v<OtherGeomType, BOX2I> )
{
// Arc-Rect via decomposition into segments
for( const SEG& aRectSeg : RectToSegs( otherGeom ) )
for( const SEG& aRectSeg : KIGEOM::BoxToSegs( otherGeom ) )
{
findIntersections( aRectSeg, aArc, m_intersections );
}
@ -218,13 +391,13 @@ void INTERSECTION_VISITOR::operator()( const SHAPE_ARC& aArc ) const
};
void INTERSECTION_VISITOR::operator()( const SHAPE_RECT& aRect ) const
void INTERSECTION_VISITOR::operator()( const BOX2I& aRect ) const
{
// Defer to the SEG visitor repeatedly
// Note - in some cases, points can be repeated in the intersection list
// if that's an issue, both directions of the visitor can be implemented
// to take care of that.
const std::vector<SEG> segs = RectToSegs( aRect );
const std::array<SEG, 4> segs = KIGEOM::BoxToSegs( aRect );
for( const SEG& seg : segs )
{

View File

@ -0,0 +1,70 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <geometry/line.h>
OPT_VECTOR2I LINE::Intersect( const SEG& aSeg ) const
{
// intersect as two lines
OPT_VECTOR2I intersection = aSeg.Intersect( m_seg, false, true );
if( intersection )
{
// Not parallel.
// That was two lines, but we need to check if the intersection is on
// the requested segment
if( aSeg.Contains( *intersection ) )
{
return intersection;
}
}
return std::nullopt;
}
OPT_VECTOR2I LINE::Intersect( const LINE& aOther ) const
{
// Defer to the SEG implementation
return aOther.m_seg.Intersect( m_seg, false, true );
}
int LINE::Distance( const VECTOR2I& aPoint ) const
{
// Just defer to the SEG implementation
return m_seg.LineDistance( aPoint );
}
VECTOR2I LINE::NearestPoint( const VECTOR2I& aPoint ) const
{
// Same as the SEG implementation, but without the early return
// if the point isn't on the segment.
// Inlined for performance reasons
VECTOR2L d( m_seg.B.x - m_seg.A.x, m_seg.B.y - m_seg.A.y );
ecoord l_squared( d.x * d.x + d.y * d.y );
if( l_squared == 0 )
return m_seg.A;
ecoord t = d.Dot( aPoint - m_seg.A );
ecoord xp = rescale( t, (ecoord) d.x, l_squared );
ecoord yp = rescale( t, (ecoord) d.y, l_squared );
return VECTOR2<ecoord>( m_seg.A.x + xp, m_seg.A.y + yp );
}

Some files were not shown because too many files have changed in this diff Show More