Browse Source
Snapping: Add construction geometry snapping
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.jobs
35 changed files with 2797 additions and 257 deletions
-
5common/CMakeLists.txt
-
161common/preview_items/construction_geom.cpp
-
76common/preview_items/item_drawing_utils.cpp
-
32common/preview_items/snap_indicator.cpp
-
300common/tool/construction_manager.cpp
-
54common/tool/grid_helper.cpp
-
65eeschema/tools/ee_grid_helper.cpp
-
84include/preview_items/construction_geom.h
-
57include/preview_items/item_drawing_utils.h
-
179include/tool/construction_manager.h
-
48include/tool/grid_helper.h
-
6libs/kimath/CMakeLists.txt
-
2libs/kimath/include/geometry/circle.h
-
95libs/kimath/include/geometry/half_line.h
-
17libs/kimath/include/geometry/intersection.h
-
67libs/kimath/include/geometry/line.h
-
59libs/kimath/include/geometry/nearest.h
-
12libs/kimath/include/geometry/point_types.h
-
72libs/kimath/include/geometry/shape_utils.h
-
3libs/kimath/include/geometry/vector_utils.h
-
5libs/kimath/include/math/box2.h
-
13libs/kimath/include/math/vector2d.h
-
131libs/kimath/src/geometry/half_line.cpp
-
221libs/kimath/src/geometry/intersection.cpp
-
70libs/kimath/src/geometry/line.cpp
-
114libs/kimath/src/geometry/nearest.cpp
-
141libs/kimath/src/geometry/shape_utils.cpp
-
677pcbnew/tools/pcb_grid_helper.cpp
-
40pcbnew/tools/pcb_grid_helper.h
-
11pcbnew/tools/pcb_point_editor.cpp
-
3qa/tests/libs/kimath/CMakeLists.txt
-
8qa/tests/libs/kimath/geometry/geom_test_utils.cpp
-
1qa/tests/libs/kimath/geometry/geom_test_utils.h
-
193qa/tests/libs/kimath/geometry/test_half_line.cpp
-
32qa/tests/libs/kimath/math/test_box2.cpp
@ -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; |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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 |
@ -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 |
@ -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; |
|||
}; |
@ -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 ); |
@ -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; |
|||
}; |
@ -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 ); |
@ -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 |
@ -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 ); |
|||
} |
@ -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 ); |
|||
} |
@ -0,0 +1,114 @@ |
|||
/*
|
|||
* 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/nearest.h"
|
|||
|
|||
#include <wx/debug.h>
|
|||
|
|||
#include <core/type_helpers.h>
|
|||
|
|||
#include <geometry/shape_utils.h>
|
|||
|
|||
|
|||
namespace |
|||
{ |
|||
|
|||
VECTOR2I NearestPoint( const BOX2I& aBox, const VECTOR2I& aPt ) |
|||
{ |
|||
VECTOR2I nearest; |
|||
int bestDistance = std::numeric_limits<int>::max(); |
|||
|
|||
for( const SEG& seg : KIGEOM::BoxToSegs( aBox ) ) |
|||
{ |
|||
const VECTOR2I nearestSegPt = seg.NearestPoint( aPt ); |
|||
const int thisDistance = nearestSegPt.Distance( aPt ); |
|||
|
|||
if( thisDistance <= bestDistance ) |
|||
{ |
|||
nearest = nearestSegPt; |
|||
bestDistance = thisDistance; |
|||
} |
|||
} |
|||
return nearest; |
|||
}; |
|||
|
|||
} // namespace
|
|||
|
|||
|
|||
VECTOR2I GetNearestPoint( const NEARABLE_GEOM& aGeom, const VECTOR2I& aPt ) |
|||
{ |
|||
VECTOR2I nearest; |
|||
|
|||
std::visit( |
|||
[&]( const auto& geom ) |
|||
{ |
|||
using GeomType = std::decay_t<decltype( geom )>; |
|||
|
|||
if constexpr( std::is_same_v<GeomType, LINE> |
|||
|| std::is_same_v<GeomType, HALF_LINE> |
|||
|| std::is_same_v<GeomType, SEG> |
|||
|| std::is_same_v<GeomType, CIRCLE> |
|||
|| std::is_same_v<GeomType, SHAPE_ARC> ) |
|||
{ |
|||
// Same signatures for all these types
|
|||
// But they're not in the same polymorphic hierarchy
|
|||
nearest = geom.NearestPoint( aPt ); |
|||
} |
|||
else if constexpr( std::is_same_v<GeomType, BOX2I> ) |
|||
{ |
|||
// Defer to the utils function
|
|||
nearest = NearestPoint( geom, aPt ); |
|||
} |
|||
else if constexpr( std::is_same_v<GeomType, VECTOR2I> ) |
|||
{ |
|||
nearest = geom; |
|||
} |
|||
else |
|||
{ |
|||
static_assert( always_false<GeomType>::value, "non-exhaustive visitor" ); |
|||
} |
|||
}, |
|||
aGeom ); |
|||
|
|||
return nearest; |
|||
} |
|||
|
|||
OPT_VECTOR2I GetNearestPoint( const std::vector<NEARABLE_GEOM>& aGeoms, const VECTOR2I& aPt ) |
|||
{ |
|||
OPT_VECTOR2I nearestPointOnAny; |
|||
int bestDistance = std::numeric_limits<int>::max(); |
|||
|
|||
for( const NEARABLE_GEOM& geom : aGeoms ) |
|||
{ |
|||
const VECTOR2I thisNearest = GetNearestPoint( geom, aPt ); |
|||
const int thisDistance = thisNearest.Distance( aPt ); |
|||
|
|||
if( !nearestPointOnAny || thisDistance < bestDistance ) |
|||
{ |
|||
nearestPointOnAny = thisNearest; |
|||
bestDistance = thisDistance; |
|||
} |
|||
} |
|||
|
|||
return nearestPointOnAny; |
|||
} |
@ -0,0 +1,141 @@ |
|||
/*
|
|||
* 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/shape_utils.h"
|
|||
|
|||
#include <geometry/seg.h>
|
|||
#include <geometry/half_line.h>
|
|||
#include <geometry/line.h>
|
|||
|
|||
|
|||
SEG KIGEOM::NormalisedSeg( const SEG& aSeg ) |
|||
{ |
|||
if( LexicographicalCompare( aSeg.A, aSeg.B ) <= 0 ) |
|||
{ |
|||
return aSeg; |
|||
} |
|||
return aSeg.Reversed(); |
|||
} |
|||
|
|||
|
|||
std::array<SEG, 4> KIGEOM::BoxToSegs( const BOX2I& aBox ) |
|||
{ |
|||
const std::array<VECTOR2I, 4> corners = { |
|||
VECTOR2I{ aBox.GetLeft(), aBox.GetTop() }, |
|||
VECTOR2I{ aBox.GetRight(), aBox.GetTop() }, |
|||
VECTOR2I{ aBox.GetRight(), aBox.GetBottom() }, |
|||
VECTOR2I{ aBox.GetLeft(), aBox.GetBottom() }, |
|||
}; |
|||
|
|||
return { |
|||
SEG{ corners[0], corners[1] }, |
|||
SEG{ corners[1], corners[2] }, |
|||
SEG{ corners[2], corners[3] }, |
|||
SEG{ corners[3], corners[0] }, |
|||
}; |
|||
} |
|||
|
|||
|
|||
std::optional<SEG> KIGEOM::ClipHalfLineToBox( const HALF_LINE& aRay, const BOX2I& aBox ) |
|||
{ |
|||
// Do the naive implementation - if this really is done in a tight loop,
|
|||
// the Cohen-Sutherland implementation in ClipLine could be faster, but
|
|||
// needs to be adapted to work with half-lines.
|
|||
const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox ); |
|||
|
|||
std::optional<VECTOR2I> ptA, ptB; |
|||
|
|||
for( const SEG& boxSeg : boxSegs ) |
|||
{ |
|||
OPT_VECTOR2I intersection = aRay.Intersect( boxSeg ); |
|||
|
|||
if( !intersection ) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Init the first point or eat it if it's the same
|
|||
if( !ptA || *intersection == *ptA ) |
|||
{ |
|||
ptA = *intersection; |
|||
} |
|||
else |
|||
{ |
|||
ptB = *intersection; |
|||
} |
|||
} |
|||
|
|||
// If we have exactly two intersections, the ray crossed twice
|
|||
// so take the segment between the two points
|
|||
if( ptA && ptB ) |
|||
{ |
|||
return SEG( *ptA, *ptB ); |
|||
} |
|||
|
|||
// It only crosses once, so the start is in the box. Take the segment from
|
|||
// the start point to the intersection
|
|||
if( ptA && *ptA != aRay.GetStart() ) |
|||
{ |
|||
return SEG( aRay.GetStart(), *ptA ); |
|||
} |
|||
|
|||
// It didn't cross at all
|
|||
return std::nullopt; |
|||
} |
|||
|
|||
|
|||
std::optional<SEG> KIGEOM::ClipLineToBox( const LINE& aLine, const BOX2I& aBox ) |
|||
{ |
|||
// As above, maybe can be optimised?
|
|||
const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox ); |
|||
|
|||
std::optional<VECTOR2I> ptA, ptB; |
|||
|
|||
for( const SEG& boxSeg : boxSegs ) |
|||
{ |
|||
OPT_VECTOR2I intersection = aLine.Intersect( boxSeg ); |
|||
|
|||
// Reject intersections that are not on the actual box boundary
|
|||
if( intersection && boxSeg.Contains( *intersection ) ) |
|||
{ |
|||
// Init the first point or eat it if it's the same
|
|||
if( !ptA || *intersection == *ptA ) |
|||
{ |
|||
ptA = *intersection; |
|||
} |
|||
else |
|||
{ |
|||
ptB = *intersection; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If we have exactly two intersections, we have a segment
|
|||
// (zero is no intersection, and one is a just crossing a corner exactly)
|
|||
if( ptA && ptB ) |
|||
{ |
|||
return SEG( *ptA, *ptB ); |
|||
} |
|||
|
|||
return std::nullopt; |
|||
} |
@ -0,0 +1,193 @@ |
|||
/*
|
|||
* This program source code file is part of KiCad, a free EDA CAD application. |
|||
* |
|||
* Copyright (C) 2023 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 <qa_utils/wx_utils/unit_test_utils.h>
|
|||
|
|||
#include <geometry/half_line.h>
|
|||
#include <geometry/shape_utils.h>
|
|||
|
|||
#include "geom_test_utils.h"
|
|||
|
|||
|
|||
struct HalfLineFixture |
|||
{ |
|||
}; |
|||
|
|||
struct HalfLineBoxClipCase |
|||
{ |
|||
std::string Description; |
|||
HALF_LINE Hl; |
|||
BOX2I Box; |
|||
std::optional<SEG> ExpectedClippedSeg; |
|||
}; |
|||
|
|||
struct HalfLineHalfLineIntersectionCase |
|||
{ |
|||
std::string Description; |
|||
HALF_LINE HlA; |
|||
HALF_LINE HlB; |
|||
std::optional<VECTOR2I> ExpectedIntersection; |
|||
}; |
|||
|
|||
struct HalfLineContainsPointCase |
|||
{ |
|||
std::string Description; |
|||
HALF_LINE Hl; |
|||
VECTOR2I Point; |
|||
bool ExpectedContains; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE( HalfLine, HalfLineFixture ) |
|||
|
|||
|
|||
BOOST_AUTO_TEST_CASE( Contains ) |
|||
{ |
|||
const std::vector<HalfLineContainsPointCase> cases{ |
|||
{ |
|||
"Point on the ray", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( 50, 50 ), |
|||
true, |
|||
}, |
|||
{ |
|||
"Point on the ray start", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( 0, 0 ), |
|||
true, |
|||
}, |
|||
{ |
|||
"Point on the ray end", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( 100, 100 ), |
|||
true, |
|||
}, |
|||
{ |
|||
"Point on the ray, past the end", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( 150, 150 ), |
|||
true, |
|||
}, |
|||
{ |
|||
"Point on the infinite line, but on the wrong side", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( -50, -50 ), |
|||
false, |
|||
}, |
|||
{ |
|||
"Point to one side", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
VECTOR2I( 50, 0 ), |
|||
false, |
|||
} |
|||
}; |
|||
|
|||
for( const auto& c : cases ) |
|||
{ |
|||
BOOST_TEST_INFO( c.Description ); |
|||
|
|||
const bool contains = c.Hl.Contains( c.Point ); |
|||
|
|||
BOOST_TEST( contains == c.ExpectedContains ); |
|||
} |
|||
} |
|||
|
|||
|
|||
BOOST_AUTO_TEST_CASE( Intersect ) |
|||
{ |
|||
const std::vector<HalfLineHalfLineIntersectionCase> cases{ |
|||
{ |
|||
"Simple cross", |
|||
HALF_LINE( SEG( VECTOR2I( -100, -100 ), VECTOR2I( 0, 0 ) ) ), |
|||
HALF_LINE( SEG( VECTOR2I( 100, -100 ), VECTOR2I( 0, 0 ) ) ), |
|||
VECTOR2I( 0, 0 ), |
|||
}, |
|||
{ |
|||
"Parallel, no intersection", |
|||
HALF_LINE( SEG( VECTOR2I( -100, 0 ), VECTOR2I( -100, 100 ) ) ), |
|||
HALF_LINE( SEG( VECTOR2I( 100, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
std::nullopt, |
|||
} |
|||
}; |
|||
|
|||
for( const auto& c : cases ) |
|||
{ |
|||
BOOST_TEST_INFO( c.Description ); |
|||
|
|||
std::optional<VECTOR2I> intersection = c.HlA.Intersect( c.HlB ); |
|||
|
|||
BOOST_REQUIRE( intersection.has_value() == c.ExpectedIntersection.has_value() ); |
|||
|
|||
if( !intersection ) |
|||
continue; |
|||
|
|||
BOOST_TEST( *intersection == *c.ExpectedIntersection ); |
|||
} |
|||
} |
|||
|
|||
|
|||
BOOST_AUTO_TEST_CASE( ClipToBox ) |
|||
{ |
|||
const std::vector<HalfLineBoxClipCase> cases{ |
|||
{ |
|||
"Center to right edge", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 0 ) ) ), |
|||
BOX2I{ VECTOR2{ -1000, -1000 }, VECTOR2{ 2000, 2000 } }, |
|||
SEG( VECTOR2I( 0, 0 ), VECTOR2I( 1000, 0 ) ), |
|||
}, |
|||
{ |
|||
"Centre to corner", |
|||
HALF_LINE( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 100, 100 ) ) ), |
|||
BOX2I{ VECTOR2{ -1000, -1000 }, VECTOR2{ 2000, 2000 } }, |
|||
SEG( VECTOR2I( 0, 0 ), VECTOR2I( 1000, 1000 ) ), |
|||
}, |
|||
{ |
|||
"Ray not in the box", |
|||
HALF_LINE( SEG( VECTOR2I( 1500, 0 ), VECTOR2I( 1600, 0 ) ) ), |
|||
BOX2I{ VECTOR2{ -1000, -1000 }, VECTOR2{ 2000, 2000 } }, |
|||
std::nullopt, |
|||
}, |
|||
{ |
|||
"Ray starts outside but crosses box", |
|||
HALF_LINE( SEG( VECTOR2I( -1500, 0 ), VECTOR2I( 0, 0 ) ) ), |
|||
BOX2I{ VECTOR2{ -1000, -1000 }, VECTOR2{ 2000, 2000 } }, |
|||
SEG( VECTOR2I( -1000, 0 ), VECTOR2I( 1000, 0 ) ), |
|||
}, |
|||
}; |
|||
|
|||
for( const auto& c : cases ) |
|||
{ |
|||
BOOST_TEST_INFO( c.Description ); |
|||
|
|||
std::optional<SEG> clipped = KIGEOM::ClipHalfLineToBox( c.Hl, c.Box ); |
|||
|
|||
BOOST_REQUIRE( clipped.has_value() == c.ExpectedClippedSeg.has_value() ); |
|||
|
|||
if( !clipped ) |
|||
continue; |
|||
|
|||
BOOST_CHECK_PREDICATE( GEOM_TEST::SegmentsHaveSameEndPoints, |
|||
( *clipped )( *c.ExpectedClippedSeg ) ); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END(); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue