KiCad PCB EDA Suite
Loading...
Searching...
No Matches
trigo.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
25
26#include <algorithm> // for std::clamp
27#include <limits> // for numeric_limits
28#include <cmath>
29#include <cstdlib> // for abs
30#include <type_traits> // for swap
31
32#include <geometry/seg.h>
33#include <math/util.h>
34#include <math/wide_int.h>
35#include <math/vector2d.h> // for VECTOR2I
36#include <trigo.h>
37
38
39/*
40CircleCenterFrom3Points calculate the center of a circle defined by 3 points
41It is similar to CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
42but it was needed to debug CalcArcCenter, so I keep it available for other issues in CalcArcCenter
43
44The perpendicular bisector of the segment between two points is the
45set of all points equidistant from both. So if you take the
46perpendicular bisector of (x1,y1) and (x2,y2) and the perpendicular
47bisector of the segment from (x2,y2) to (x3,y3) and find the
48intersection of those lines, that point will be the center.
49
50To find the equation of the perpendicular bisector of (x1,y1) to (x2,y2),
51you know that it passes through the midpoint of the segment:
52((x1+x2)/2,(y1+y2)/2), and if the slope of the line
53connecting (x1,y1) to (x2,y2) is m, the slope of the perpendicular
54bisector is -1/m. Work out the equations for the two lines, find
55their intersection, and bingo! You've got the coordinates of the center.
56
57An error should occur if the three points lie on a line, and you'll
58need special code to check for the case where one of the slopes is zero.
59
60see https://web.archive.org/web/20171223103555/http://mathforum.org/library/drmath/view/54323.html
61*/
62
63//#define USE_ALTERNATE_CENTER_ALGO
64
65#ifdef USE_ALTERNATE_CENTER_ALGO
66bool CircleCenterFrom3Points( const VECTOR2D& p1, const VECTOR2D& p2, const VECTOR2D& p3, VECTOR2D* aCenter )
67{
68 // Move coordinate origin to p2, to simplify calculations
69 VECTOR2D b = p1 - p2;
70 VECTOR2D d = p3 - p2;
71 double bc = ( b.x*b.x + b.y*b.y ) / 2.0;
72 double cd = ( -d.x*d.x - d.y*d.y ) / 2.0;
73 double det = -b.x*d.y + d.x*b.y;
74
75 if( fabs(det) < 1.0e-6 ) // arbitrary limit to avoid divide by 0
76 return false;
77
78 det = 1/det;
79 aCenter->x = ( -bc*d.y - cd*b.y ) * det;
80 aCenter->y = ( b.x*cd + d.x*bc ) * det;
81 *aCenter += p2;
82
83 return true;
84}
85#endif
86
87bool IsPointOnSegment( const VECTOR2I& aSegStart, const VECTOR2I& aSegEnd,
88 const VECTOR2I& aTestPoint )
89{
90 VECTOR2I vectSeg = aSegEnd - aSegStart; // Vector from S1 to S2
91 VECTOR2I vectPoint = aTestPoint - aSegStart; // Vector from S1 to P
92
93 // Use long long here to avoid overflow in calculations
94 if( (long long) vectSeg.x * vectPoint.y - (long long) vectSeg.y * vectPoint.x )
95 return false; /* Cross product non-zero, vectors not parallel */
96
97 if( ( (long long) vectSeg.x * vectPoint.x + (long long) vectSeg.y * vectPoint.y ) <
98 ( (long long) vectPoint.x * vectPoint.x + (long long) vectPoint.y * vectPoint.y ) )
99 return false; /* Point not on segment */
100
101 return true;
102}
103
104
105bool SegmentIntersectsSegment( const VECTOR2I& a_p1_l1, const VECTOR2I& a_p2_l1,
106 const VECTOR2I& a_p1_l2, const VECTOR2I& a_p2_l2,
107 VECTOR2I* aIntersectionPoint )
108{
109
110 // We are forced to use 64bit ints because the internal units can overflow 32bit ints when
111 // multiplied with each other, the alternative would be to scale the units down (i.e. divide
112 // by a fixed number).
113 int64_t dX_a, dY_a, dX_b, dY_b, dX_ab, dY_ab;
114 int64_t num_a, num_b, den;
115
116 // Test for intersection within the bounds of both line segments using line equations of the
117 // form:
118 // x_k(u_k) = u_k * dX_k + x_k(0)
119 // y_k(u_k) = u_k * dY_k + y_k(0)
120 // with 0 <= u_k <= 1 and k = [ a, b ]
121
122 dX_a = int64_t{ a_p2_l1.x } - a_p1_l1.x;
123 dY_a = int64_t{ a_p2_l1.y } - a_p1_l1.y;
124 dX_b = int64_t{ a_p2_l2.x } - a_p1_l2.x;
125 dY_b = int64_t{ a_p2_l2.y } - a_p1_l2.y;
126 dX_ab = int64_t{ a_p1_l2.x } - a_p1_l1.x;
127 dY_ab = int64_t{ a_p1_l2.y } - a_p1_l1.y;
128
129 den = dY_a * dX_b - dY_b * dX_a ;
130
131 // Check if lines are parallel.
132 if( den == 0 )
133 return false;
134
135 num_a = dY_ab * dX_b - dY_b * dX_ab;
136 num_b = dY_ab * dX_a - dY_a * dX_ab;
137
138 // Only compute the intersection point if requested.
139 if( aIntersectionPoint )
140 {
141 *aIntersectionPoint = a_p1_l1;
142 aIntersectionPoint->x += KiROUND( dX_a * ( double )num_a / ( double )den );
143 aIntersectionPoint->y += KiROUND( dY_a * ( double )num_b / ( double )den );
144 }
145
146 if( den < 0 )
147 {
148 den = -den;
149 num_a = -num_a;
150 num_b = -num_b;
151 }
152
153 // Test sign( u_a ) and return false if negative.
154 if( num_a < 0 )
155 return false;
156
157 // Test sign( u_b ) and return false if negative.
158 if( num_b < 0 )
159 return false;
160
161 // Test to ensure (u_a <= 1).
162 if( num_a > den )
163 return false;
164
165 // Test to ensure (u_b <= 1).
166 if( num_b > den )
167 return false;
168
169 return true;
170}
171
172
173bool TestSegmentHit( const VECTOR2I& aRefPoint, const VECTOR2I& aStart, const VECTOR2I& aEnd,
174 int aDist )
175{
176 int xmin = aStart.x;
177 int xmax = aEnd.x;
178 int ymin = aStart.y;
179 int ymax = aEnd.y;
180 VECTOR2I delta = aStart - aRefPoint;
181
182 if( xmax < xmin )
183 std::swap( xmax, xmin );
184
185 if( ymax < ymin )
186 std::swap( ymax, ymin );
187
188 // Check if we are outside of the bounding box.
189 if( ( ymin - aRefPoint.y > aDist ) || ( aRefPoint.y - ymax > aDist ) )
190 return false;
191
192 if( ( xmin - aRefPoint.x > aDist ) || ( aRefPoint.x - xmax > aDist ) )
193 return false;
194
195 // Eliminate easy cases.
196 if( aStart.x == aEnd.x && aRefPoint.y > ymin && aRefPoint.y < ymax )
197 return std::abs( delta.x ) <= aDist;
198
199 if( aStart.y == aEnd.y && aRefPoint.x > xmin && aRefPoint.x < xmax )
200 return std::abs( delta.y ) <= aDist;
201
202 SEG segment( aStart, aEnd );
203 return segment.SquaredDistance( aRefPoint ) < SEG::Square( aDist + 1 );
204}
205
206
207const VECTOR2I CalcArcMid( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter,
208 bool aMinArcAngle )
209{
210 VECTOR2I startVector = aStart - aCenter;
211 VECTOR2I endVector = aEnd - aCenter;
212
213 EDA_ANGLE startAngle( startVector );
214 EDA_ANGLE endAngle( endVector );
215 EDA_ANGLE midPointRotAngle = ( startAngle - endAngle ).Normalize180() / 2;
216
217 if( !aMinArcAngle )
218 midPointRotAngle += ANGLE_180;
219
220 VECTOR2I newMid = aStart;
221 RotatePoint( newMid, aCenter, midPointRotAngle );
222
223 return newMid;
224}
225
226
227void RotatePoint( int* pX, int* pY, const EDA_ANGLE& aAngle )
228{
229 VECTOR2I pt;
230 EDA_ANGLE angle = aAngle;
231
232 angle.Normalize();
233
234 // Cheap and dirty optimizations for 0, 90, 180, and 270 degrees.
235 if( angle == ANGLE_0 )
236 {
237 pt = VECTOR2I( *pX, *pY );
238 }
239 else if( angle == ANGLE_90 ) /* sin = 1, cos = 0 */
240 {
241 pt = VECTOR2I( *pY, -*pX );
242 }
243 else if( angle == ANGLE_180 ) /* sin = 0, cos = -1 */
244 {
245 pt = VECTOR2I( -*pX, -*pY );
246 }
247 else if( angle == ANGLE_270 ) /* sin = -1, cos = 0 */
248 {
249 pt = VECTOR2I( -*pY, *pX );
250 }
251 else
252 {
253 double sinus = angle.Sin();
254 double cosinus = angle.Cos();
255
256 pt.x = KiROUND( ( *pY * sinus ) + ( *pX * cosinus ) );
257 pt.y = KiROUND( ( *pY * cosinus ) - ( *pX * sinus ) );
258 }
259
260 *pX = pt.x;
261 *pY = pt.y;
262}
263
264
265void RotatePoint( int* pX, int* pY, int cx, int cy, const EDA_ANGLE& angle )
266{
267 int ox, oy;
268
269 ox = *pX - cx;
270 oy = *pY - cy;
271
272 RotatePoint( &ox, &oy, angle );
273
274 *pX = ox + cx;
275 *pY = oy + cy;
276}
277
278
279void RotatePoint( double* pX, double* pY, double cx, double cy, const EDA_ANGLE& angle )
280{
281 double ox, oy;
282
283 ox = *pX - cx;
284 oy = *pY - cy;
285
286 RotatePoint( &ox, &oy, angle );
287
288 *pX = ox + cx;
289 *pY = oy + cy;
290}
291
292
293void RotatePoint( double* pX, double* pY, const EDA_ANGLE& aAngle )
294{
295 EDA_ANGLE angle = aAngle;
296 VECTOR2D pt;
297
298 angle.Normalize();
299
300 // Cheap and dirty optimizations for 0, 90, 180, and 270 degrees.
301 if( angle == ANGLE_0 )
302 {
303 pt = VECTOR2D( *pX, *pY );
304 }
305 else if( angle == ANGLE_90 ) /* sin = 1, cos = 0 */
306 {
307 pt = VECTOR2D( *pY, -*pX );
308 }
309 else if( angle == ANGLE_180 ) /* sin = 0, cos = -1 */
310 {
311 pt = VECTOR2D( -*pX, -*pY );
312 }
313 else if( angle == ANGLE_270 ) /* sin = -1, cos = 0 */
314 {
315 pt = VECTOR2D( -*pY, *pX );
316 }
317 else
318 {
319 double sinus = angle.Sin();
320 double cosinus = angle.Cos();
321
322 pt.x = ( *pY * sinus ) + ( *pX * cosinus );
323 pt.y = ( *pY * cosinus ) - ( *pX * sinus );
324 }
325
326 *pX = pt.x;
327 *pY = pt.y;
328}
329
330
331const VECTOR2D CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aEnd,
332 const EDA_ANGLE& aAngle )
333{
334 EDA_ANGLE angle( aAngle );
335 VECTOR2D start = aStart;
336 VECTOR2D end = aEnd;
337
338 if( angle < ANGLE_0 )
339 {
340 std::swap( start, end );
341 angle = -angle;
342 }
343
344 if( angle > ANGLE_180 )
345 {
346 std::swap( start, end );
347 angle = ANGLE_360 - angle;
348 }
349
350 double chord = ( start - end ).EuclideanNorm();
351 double sinHalfAngle = ( angle / 2.0 ).Sin();
352
353 // A zero arc angle has no defined center, so fall back to the chord midpoint
354 if( sinHalfAngle == 0.0 )
355 return VECTOR2D( ( start + end ) / 2.0 );
356
357 // The center sits (chord/2) * cot(angle/2) off the chord; sqrt(r^2 - chord^2/4) cancels near 180 degrees
358 double d = ( chord / 2.0 ) * ( angle / 2.0 ).Cos() / sinHalfAngle;
359
360 if( !std::isfinite( d ) )
361 d = 0.0;
362
363 VECTOR2D vec2 = VECTOR2D(end - start).Resize( d );
364 VECTOR2D vc = VECTOR2D(end - start).Resize( chord / 2 );
365
366 RotatePoint( vec2, -ANGLE_90 );
367
368 return VECTOR2D( start + vc + vec2 );
369}
370
371
372namespace
373{
374// No unique circumcircle exists if any two of the three points coincide
375// Bbox below catches all three; pairwise checks below catch just one pair
376constexpr double kClusterExtent = 5.0;
377
378// A pair separated by more than integer rounding is a real, if small, arc. This is not the
379// cluster extent above: three points inside a 5 IU box are all noise, but two points 4 IU
380// apart with a distant third still span a healthy triangle
381constexpr double kCoincidentRadius = 2.0;
382constexpr double kCoincidentRadiusSquared = kCoincidentRadius * kCoincidentRadius;
383
384// Radius agreement required of a snapped center, in IU
385constexpr double kSnapRadiusTolerance = 1.0;
386
387// Stand-in radius for collinear points, whose true center is at infinity
388constexpr double kCollinearRadius = 1e17;
389
390// Largest magnitude at which every integer is still a double
391constexpr double kMaxExactInteger = 9007199254740992.0;
392
393
394bool degenerateArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd,
395 VECTOR2D& aCenter )
396{
397 auto [minX, maxX] = std::minmax( { aStart.x, aMid.x, aEnd.x } );
398 auto [minY, maxY] = std::minmax( { aStart.y, aMid.y, aEnd.y } );
399
400 if( maxX - minX < kClusterExtent && maxY - minY < kClusterExtent )
401 {
402 aCenter = VECTOR2D( ( aStart.x + aMid.x + aEnd.x ) / 3.0, ( aStart.y + aMid.y + aEnd.y ) / 3.0 );
403 return true;
404 }
405
406 auto coincident = []( const VECTOR2D& a, const VECTOR2D& b )
407 {
408 return ( a - b ).SquaredEuclideanNorm() < kCoincidentRadiusSquared;
409 };
410
411 // Two distinct points fall back to the chord midpoint, same as the diameter-arc paths below
412 if( coincident( aStart, aMid ) || coincident( aMid, aEnd ) )
413 {
414 aCenter = VECTOR2D( ( aStart.x + aEnd.x ) / 2.0, ( aStart.y + aEnd.y ) / 2.0 );
415 return true;
416 }
417
418 if( coincident( aStart, aEnd ) )
419 {
420 aCenter = VECTOR2D( ( aStart.x + aMid.x ) / 2.0, ( aStart.y + aMid.y ) / 2.0 );
421 return true;
422 }
423
424 return false;
425}
426
427
428VECTOR2D collinearArcCenter( const VECTOR2D& aStart, const VECTOR2D& aEnd )
429{
430 VECTOR2D chord = aEnd - aStart;
431 VECTOR2D mid( ( aStart.x + aEnd.x ) / 2.0, ( aStart.y + aEnd.y ) / 2.0 );
432
433 return mid + VECTOR2D( chord.y, -chord.x ).Resize( kCollinearRadius );
434}
435
436
437// Kahan's a*d - b*c, accurate to about one ulp despite the cancellation
438double det2( double a, double b, double c, double d )
439{
440 double w = b * c;
441 double e = std::fma( -b, c, w );
442 double f = std::fma( a, d, -w );
443
444 return f + e;
445}
446
447
448// Prefer a nice 100 nm or 10 nm center when it still lies on the circle to within a nanometer
449VECTOR2D snapArcCenter( const VECTOR2D& aCenter, const VECTOR2D& aStart, const VECTOR2D& aMid,
450 const VECTOR2D& aEnd )
451{
452 if( !( std::abs( aCenter.x ) < kMaxExactInteger ) || !( std::abs( aCenter.y ) < kMaxExactInteger ) )
453 return aCenter;
454
455 auto radiiAgree = [&]( const VECTOR2D& aCandidate )
456 {
457 double rs = ( aCandidate - aStart ).EuclideanNorm();
458 double rm = ( aCandidate - aMid ).EuclideanNorm();
459 double re = ( aCandidate - aEnd ).EuclideanNorm();
460 auto [minR, maxR] = std::minmax( { rs, rm, re } );
461
462 return maxR - minR <= kSnapRadiusTolerance;
463 };
464
465 for( double grid : { 100.0, 10.0 } )
466 {
467 VECTOR2D candidate( std::floor( aCenter.x / grid + 0.5 ) * grid,
468 std::floor( aCenter.y / grid + 0.5 ) * grid );
469
470 if( radiiAgree( candidate ) )
471 return candidate;
472 }
473
474 return aCenter;
475}
476} // namespace
477
478
479const VECTOR2D CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
480{
482
483 if( degenerateArcCenter( aStart, aMid, aEnd, center ) )
484 return center;
485
486 // Work relative to start so the result cannot depend on where the arc sits
487 double bx = aMid.x - aStart.x;
488 double by = aMid.y - aStart.y;
489 double cx = aEnd.x - aStart.x;
490 double cy = aEnd.y - aStart.y;
491
492 double b2 = std::fma( bx, bx, by * by );
493 double c2 = std::fma( cx, cx, cy * cy );
494 double d = 2.0 * det2( bx, by, cx, cy );
495
496 if( d == 0.0 )
497 return collinearArcCenter( aStart, aEnd );
498
499 double ux = det2( b2, c2, by, cy ) / d;
500 double uy = det2( c2, b2, cx, bx ) / d;
501
502 return snapArcCenter( VECTOR2D( aStart.x + ux, aStart.y + uy ), aStart, aMid, aEnd );
503}
504
505
506const VECTOR2I CalcArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
507{
508 VECTOR2D dStart( static_cast<double>( aStart.x ), static_cast<double>( aStart.y ) );
509 VECTOR2D dMid( static_cast<double>( aMid.x ), static_cast<double>( aMid.y ) );
510 VECTOR2D dEnd( static_cast<double>( aEnd.x ), static_cast<double>( aEnd.y ) );
511 VECTOR2D dCenter;
512
513 if( !degenerateArcCenter( dStart, dMid, dEnd, dCenter ) )
514 {
515 // Deltas are exact in 64 bits and the numerators in 128, so only the final divisions round
516 VECTOR2L b( int64_t( aMid.x ) - aStart.x, int64_t( aMid.y ) - aStart.y );
517 VECTOR2L c( int64_t( aEnd.x ) - aStart.x, int64_t( aEnd.y ) - aStart.y );
518
519 KI_INT128 b2 = KI_INT128( b.x ) * KI_INT128( b.x ) + KI_INT128( b.y ) * KI_INT128( b.y );
520 KI_INT128 c2 = KI_INT128( c.x ) * KI_INT128( c.x ) + KI_INT128( c.y ) * KI_INT128( c.y );
521 KI_INT128 d = CrossWide( b, c ) * KI_INT128( 2 );
522
523 if( d == KI_INT128( 0 ) )
524 {
525 dCenter = collinearArcCenter( dStart, dEnd );
526 }
527 else
528 {
529 // Fold start into the numerator so the sum is rounded once
530 double dd = ToDouble( d );
531 double cx = ToDouble( KI_INT128( aStart.x ) * d + b2 * KI_INT128( c.y ) - c2 * KI_INT128( b.y ) ) / dd;
532 double cy = ToDouble( KI_INT128( aStart.y ) * d + c2 * KI_INT128( b.x ) - b2 * KI_INT128( c.x ) ) / dd;
533
534 dCenter = snapArcCenter( VECTOR2D( cx, cy ), dStart, dMid, dEnd );
535 }
536 }
537
538 VECTOR2I iCenter;
539
540 iCenter.x = KiROUND( std::clamp( dCenter.x,
541 double( std::numeric_limits<int>::min() + 100 ),
542 double( std::numeric_limits<int>::max() - 100 ) ) );
543
544 iCenter.y = KiROUND( std::clamp( dCenter.y,
545 double( std::numeric_limits<int>::min() + 100 ),
546 double( std::numeric_limits<int>::max() - 100 ) ) );
547
548 return iCenter;
549}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
EDA_ANGLE Normalize()
Definition eda_angle.h:228
double Sin() const
Definition eda_angle.h:177
double Cos() const
Definition eda_angle.h:196
Definition seg.h:38
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:35
static SEG::ecoord Square(int a)
Definition seg.h:119
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:453
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static VECTOR2D CircleCenterFrom3Points(const VECTOR2D &p1, const VECTOR2D &p2, const VECTOR2D &p3)
VECTOR2I center
VECTOR2I end
int delta
const VECTOR2I CalcArcMid(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aMinArcAngle)
Return the middle point of an arc, half-way between aStart and aEnd.
Definition trigo.cpp:207
bool TestSegmentHit(const VECTOR2I &aRefPoint, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aDist)
Test if aRefPoint is with aDistance on the line defined by aStart and aEnd.
Definition trigo.cpp:173
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
bool SegmentIntersectsSegment(const VECTOR2I &a_p1_l1, const VECTOR2I &a_p2_l1, const VECTOR2I &a_p1_l2, const VECTOR2I &a_p2_l2, VECTOR2I *aIntersectionPoint)
Test if two lines intersect.
Definition trigo.cpp:105
bool IsPointOnSegment(const VECTOR2I &aSegStart, const VECTOR2I &aSegEnd, const VECTOR2I &aTestPoint)
Test if aTestPoint is on line defined by aSegStart and aSegEnd.
Definition trigo.cpp:87
const VECTOR2D CalcArcCenter(const VECTOR2D &aStart, const VECTOR2D &aEnd, const EDA_ANGLE &aAngle)
Definition trigo.cpp:331
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
VECTOR2< int64_t > VECTOR2L
Definition vector2d.h:709
128-bit integers for exact products of 64-bit coordinate deltas.
double ToDouble(KI_INT128 aValue)
Definition wide_int.h:94
constexpr KI_INT128 CrossWide(const VECTOR2L &aA, const VECTOR2L &aB)
Exact aA.x * aB.y - aA.y * aB.x.
Definition wide_int.h:104
__int128 KI_INT128
Definition wide_int.h:91