geometry.tcl

Go to the documentation of this file.
00001 /*  geometry.tcl --*/
00002 /* */
00003 /*  Collection of geometry functions.*/
00004 /* */
00005 /*  Copyright (c) 2001 by Ideogramic ApS and other parties.*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /* */
00010 /*  RCS: @(#) $Id: geometry.tcl,v 1.8 2005/10/04 17:31:23 andreas_kupries Exp $*/
00011 
00012 namespace ::math::geometry {
00013 }
00014 
00015 package require math
00016 
00017 /* */
00018 /* */
00019 /*  POINTS*/
00020 /* */
00021 /*     A point P consists of an x-coordinate, Px, and a y-coordinate, Py,*/
00022 /*     and both coordinates are floating point values.*/
00023 /* */
00024 /*     Points are usually denoted by A, B, C, P, or Q.*/
00025 /* */
00026 /* */
00027 /* */
00028 /*  LINES*/
00029 /* */
00030 /*     There are basically three types of lines:*/
00031 /*          line           A line is defined by two points A and B as the*/
00032 /*                         _infinite_ line going through these two points.*/
00033 /*                         Often a line is given as a list of 4 coordinates*/
00034 /*                         instead of 2 points.*/
00035 /*          line segment   A line segment is defined by two points A and B*/
00036 /*                         as the _finite_ that starts in A and ends in B.*/
00037 /*                         Often a line segment is given as a list of 4*/
00038 /*                         coordinates instead of 2 points.*/
00039 /*          polyline       A polyline is a sequence of connected line segments.*/
00040 /* */
00041 /*     Please note that given a point P, the closest point on a line is given*/
00042 /*     by the projection of P onto the line. The closest point on a line segment*/
00043 /*     may be the projection, but it may also be one of the end points of the*/
00044 /*     line segment.*/
00045 /* */
00046 /* */
00047 /* */
00048 /*  DISTANCES*/
00049 /* */
00050 /*     The distances in this package are all floating point values.*/
00051 /* */
00052 /* */
00053 
00054 
00055 
00056 /*  ::math::geometry::calculateDistanceToLine*/
00057 /* */
00058 /*        Calculate the distance between a point and a line.*/
00059 /* */
00060 /*  Arguments:*/
00061 /*        P             a point*/
00062 /*        line          a line*/
00063 /* */
00064 /*  Results:*/
00065 /*        dist          the smallest distance between P and the line*/
00066 /* */
00067 /*  Examples:*/
00068 /*      - calculateDistanceToLine {5 10} {0 0 10 10}*/
00069 /*        Result: 3.53553390593*/
00070 /*      - calculateDistanceToLine {-10 0} {0 0 10 10}*/
00071 /*        Result: 7.07106781187*/
00072 /* */
00073 ret  ::math::geometry::calculateDistanceToLine (type P , type line) {
00074     # solution based on FAQ 1.02 on comp.graphics.algorithms
00075     # L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )
00076     #     (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
00077     # s = -----------------------------
00078     #                 L^2
00079     # dist = |s|*L
00080     #
00081     # =>
00082     #
00083     #        | (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) |
00084     # dist = ---------------------------------
00085     #                       L
00086     set Ax [lindex $line 0]
00087     set Ay [lindex $line 1]
00088     set Bx [lindex $line 2]
00089     set By [lindex $line 3]
00090     set Cx [lindex $P 0]
00091     set Cy [lindex $P 1]
00092     if {$Ax==$Bx && $Ay==$By} {
00093     return [lengthOfPolyline [concat $P [lrange $line 0 1]]]
00094     } else {
00095     set L [expr {sqrt(pow($Bx-$Ax,2) + pow($By-$Ay,2))}]
00096     return [expr {abs(($Ay-$Cy)*($Bx-$Ax)-($Ax-$Cx)*($By-$Ay)) / $L}]
00097     }
00098 }
00099 
00100 /*  ::math::geometry::findClosestPointOnLine*/
00101 /* */
00102 /*        Return the point on a line which is closest to a given point.*/
00103 /* */
00104 /*  Arguments:*/
00105 /*        P             a point*/
00106 /*        line          a line*/
00107 /* */
00108 /*  Results:*/
00109 /*        Q             the point on the line that has the smallest*/
00110 /*                      distance to P*/
00111 /* */
00112 /*  Examples:*/
00113 /*      - findClosestPointOnLine {5 10} {0 0 10 10}*/
00114 /*        Result: 7.5 7.5*/
00115 /*      - findClosestPointOnLine {-10 0} {0 0 10 10}*/
00116 /*        Result: -5.0 -5.0*/
00117 /* */
00118 ret  ::math::geometry::findClosestPointOnLine (type P , type line) {
00119     return [lindex [findClosestPointOnLineImpl $P $line] 0]
00120 }
00121 
00122 /*  ::math::geometry::findClosestPointOnLineImpl*/
00123 /* */
00124 /*        PRIVATE FUNCTION USED BY OTHER FUNCTIONS.*/
00125 /*        Find the point on a line that is closest to a given point.*/
00126 /* */
00127 /*  Arguments:*/
00128 /*        P             a point*/
00129 /*        line          a line defined by points A and B*/
00130 /* */
00131 /*  Results:*/
00132 /*        Q             the point on the line that has the smallest*/
00133 /*                      distance to P*/
00134 /*        r             r has the following meaning:*/
00135 /*                         r=0      P = A*/
00136 /*                         r=1      P = B*/
00137 /*                         r<0      P is on the backward extension of AB*/
00138 /*                         r>1      P is on the forward extension of AB*/
00139 /*                         0<r<1    P is interior to AB*/
00140 /* */
00141 ret  ::math::geometry::findClosestPointOnLineImpl (type P , type line) {
00142     # solution based on FAQ 1.02 on comp.graphics.algorithms
00143     #   L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )
00144     #        (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
00145     #   r = -------------------------------
00146     #                     L^2
00147     #   Px = Ax + r(Bx-Ax)
00148     #   Py = Ay + r(By-Ay)
00149     set Ax [lindex $line 0]
00150     set Ay [lindex $line 1]
00151     set Bx [lindex $line 2]
00152     set By [lindex $line 3]
00153     set Cx [lindex $P 0]
00154     set Cy [lindex $P 1]
00155     if {$Ax==$Bx && $Ay==$By} {
00156     return [list [list $Ax $Ay] 0]
00157     } else {
00158     set L [expr {sqrt(pow($Bx-$Ax,2) + pow($By-$Ay,2))}]
00159     set r [expr {(($Cx-$Ax)*($Bx-$Ax) + ($Cy-$Ay)*($By-$Ay))/pow($L,2)}]
00160     set Px [expr {$Ax + $r*($Bx-$Ax)}]
00161     set Py [expr {$Ay + $r*($By-$Ay)}]
00162     return [list [list $Px $Py] $r]
00163     }
00164 }
00165 
00166 /*  ::math::geometry::calculateDistanceToLineSegment*/
00167 /* */
00168 /*        Calculate the distance between a point and a line segment.*/
00169 /* */
00170 /*  Arguments:*/
00171 /*        P             a point*/
00172 /*        linesegment   a line segment*/
00173 /* */
00174 /*  Results:*/
00175 /*        dist          the smallest distance between P and any point*/
00176 /*                      on the line segment*/
00177 /* */
00178 /*  Examples:*/
00179 /*      - calculateDistanceToLineSegment {5 10} {0 0 10 10}*/
00180 /*        Result: 3.53553390593*/
00181 /*      - calculateDistanceToLineSegment {-10 0} {0 0 10 10}*/
00182 /*        Result: 10.0*/
00183 /* */
00184 ret  ::math::geometry::calculateDistanceToLineSegment (type P , type linesegment) {
00185     set result [calculateDistanceToLineSegmentImpl $P $linesegment]
00186     set distToLine [lindex $result 0]
00187     set r [lindex $result 1]
00188     if {$r<0} {
00189     return [lengthOfPolyline [concat $P [lrange $linesegment 0 1]]]
00190     } elseif {$r>1} {
00191     return [lengthOfPolyline [concat $P [lrange $linesegment 2 3]]]
00192     } else {
00193     return $distToLine
00194     }
00195 }
00196 
00197 /*  ::math::geometry::calculateDistanceToLineSegmentImpl*/
00198 /* */
00199 /*        PRIVATE FUNCTION USED BY OTHER FUNCTIONS.*/
00200 /*        Find the distance between a point and a line.*/
00201 /* */
00202 /*  Arguments:*/
00203 /*        P             a point*/
00204 /*        linesegment   a line segment A->B*/
00205 /* */
00206 /*  Results:*/
00207 /*        dist          the smallest distance between P and the line*/
00208 /*        r             r has the following meaning:*/
00209 /*                         r=0      P = A*/
00210 /*                         r=1      P = B*/
00211 /*                         r<0      P is on the backward extension of AB*/
00212 /*                         r>1      P is on the forward extension of AB*/
00213 /*                         0<r<1    P is interior to AB*/
00214 /* */
00215 ret  ::math::geometry::calculateDistanceToLineSegmentImpl (type P , type linesegment) {
00216     # solution based on FAQ 1.02 on comp.graphics.algorithms
00217     # L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )
00218     #     (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
00219     # s = -----------------------------
00220     #                 L^2
00221     #      (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
00222     # r = -------------------------------
00223     #                   L^2
00224     # dist = |s|*L
00225     #
00226     # =>
00227     #
00228     #        | (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) |
00229     # dist = ---------------------------------
00230     #                       L
00231     set Ax [lindex $linesegment 0]
00232     set Ay [lindex $linesegment 1]
00233     set Bx [lindex $linesegment 2]
00234     set By [lindex $linesegment 3]
00235     set Cx [lindex $P 0]
00236     set Cy [lindex $P 1]
00237     if {$Ax==$Bx && $Ay==$By} {
00238     return [list [lengthOfPolyline [concat $P [lrange $linesegment 0 1]]] 0]
00239     } else {
00240     set L [expr {sqrt(pow($Bx-$Ax,2) + pow($By-$Ay,2))}]
00241     set r [expr {(($Cx-$Ax)*($Bx-$Ax) + ($Cy-$Ay)*($By-$Ay))/pow($L,2)}]
00242     return [list [expr {abs(($Ay-$Cy)*($Bx-$Ax)-($Ax-$Cx)*($By-$Ay)) / $L}] $r]
00243     }
00244 }
00245 
00246 /*  ::math::geometry::findClosestPointOnLineSegment*/
00247 /* */
00248 /*        Return the point on a line segment which is closest to a given point.*/
00249 /* */
00250 /*  Arguments:*/
00251 /*        P             a point*/
00252 /*        linesegment   a line segment*/
00253 /* */
00254 /*  Results:*/
00255 /*        Q             the point on the line segment that has the*/
00256 /*                      smallest distance to P*/
00257 /* */
00258 /*  Examples:*/
00259 /*      - findClosestPointOnLineSegment {5 10} {0 0 10 10}*/
00260 /*        Result: 7.5 7.5*/
00261 /*      - findClosestPointOnLineSegment {-10 0} {0 0 10 10}*/
00262 /*        Result: 0 0*/
00263 /* */
00264 ret  ::math::geometry::findClosestPointOnLineSegment (type P , type linesegment) {
00265     set result [findClosestPointOnLineImpl $P $linesegment]
00266     set Q [lindex $result 0]
00267     set r [lindex $result 1]
00268     if {$r<0} {
00269     return [lrange $linesegment 0 1]
00270     } elseif {$r>1} {
00271     return [lrange $linesegment 2 3]
00272     } else {
00273     return $Q
00274     }
00275 
00276 }
00277 
00278 /*  ::math::geometry::calculateDistanceToPolyline*/
00279 /* */
00280 /*        Calculate the distance between a point and a polyline.*/
00281 /* */
00282 /*  Arguments:*/
00283 /*        P           a point*/
00284 /*        polyline    a polyline*/
00285 /* */
00286 /*  Results:*/
00287 /*        dist        the smallest distance between P and any point*/
00288 /*                    on the polyline*/
00289 /* */
00290 /*  Examples:*/
00291 /*      - calculateDistanceToPolyline {10 10} {0 0 10 5 20 0}*/
00292 /*        Result: 5.0*/
00293 /*      - calculateDistanceToPolyline {5 10} {0 0 10 5 20 0}*/
00294 /*        Result: 6.7082039325*/
00295 /* */
00296 ret  ::math::geometry::calculateDistanceToPolyline (type P , type polyline) {
00297     set minDist "none"
00298     foreach {Ax Ay} [lrange $polyline 0 end-2] {Bx By} [lrange $polyline 2 end] {
00299     set dist [calculateDistanceToLineSegment $P [list $Ax $Ay $Bx $By]]
00300     if {$minDist=="none" || $dist < $minDist} {
00301         set minDist $dist
00302     }
00303     }
00304     return $minDist
00305 }
00306 
00307 /*  ::math::geometry::findClosestPointOnPolyline*/
00308 /* */
00309 /*        Return the point on a polyline which is closest to a given point.*/
00310 /* */
00311 /*  Arguments:*/
00312 /*        P           a point*/
00313 /*        polyline    a polyline*/
00314 /* */
00315 /*  Results:*/
00316 /*        Q           the point on the polyline that has the smallest*/
00317 /*                    distance to P*/
00318 /* */
00319 /*  Examples:*/
00320 /*      - findClosestPointOnPolyline {10 10} {0 0 10 5 20 0}*/
00321 /*        Result: 10 5*/
00322 /*      - findClosestPointOnPolyline {5 10} {0 0 10 5 20 0}*/
00323 /*        Result: 8.0 4.0*/
00324 /* */
00325 ret  ::math::geometry::findClosestPointOnPolyline (type P , type polyline) {
00326     set closestPoint "none"
00327     foreach {Ax Ay} [lrange $polyline 0 end-2] {Bx By} [lrange $polyline 2 end] {
00328     set Q [findClosestPointOnLineSegment $P [list $Ax $Ay $Bx $By]]
00329     set dist [lengthOfPolyline [concat $P $Q]]
00330     if {$closestPoint=="none" || $dist<$closestDistance} {
00331         set closestPoint $Q
00332         set closestDistance $dist
00333     }
00334     }
00335     return $closestPoint
00336 }
00337 
00338 
00339 
00340 
00341 
00342 
00343 /*  ::math::geometry::lengthOfPolyline*/
00344 /* */
00345 /*        Find the length of a polyline, i.e., the sum of the*/
00346 /*        lengths of the individual line segments.*/
00347 /* */
00348 /*  Arguments:*/
00349 /*        polyline      a polyline*/
00350 /* */
00351 /*  Results:*/
00352 /*        length        the length of the polyline*/
00353 /* */
00354 /*  Examples:*/
00355 /*      - lengthOfPolyline {0 0 5 0 5 10}*/
00356 /*        Result: 15.0*/
00357 /* */
00358 ret  ::math::geometry::lengthOfPolyline (type polyline) {
00359     set length 0
00360     foreach {x1 y1} [lrange $polyline 0 end-2] {x2 y2} [lrange $polyline 2 end] {
00361     set length [expr {$length + sqrt(pow($x1-$x2,2) + pow($y1-$y2,2))}]
00362     #set length [expr {$length + sqrt(($x1-$x2)*($x1-$x2) + ($y1-$y2)*($y1-$y2))}]
00363     }
00364     return $length
00365 }
00366 
00367 
00368 
00369 
00370 /*  ::math::geometry::movePointInDirection*/
00371 /* */
00372 /*        Move a point in a given direction.*/
00373 /* */
00374 /*  Arguments:*/
00375 /*        P             the starting point*/
00376 /*        direction     the direction from P*/
00377 /*                      The direction is in 360-degrees going counter-clockwise,*/
00378 /*                      with "straight right" being 0 degrees*/
00379 /*        dist          the distance from P*/
00380 /* */
00381 /*  Results:*/
00382 /*        Q             the point which is found by starting in P and going*/
00383 /*                      in the given direction, until the distance between*/
00384 /*                      P and Q is dist*/
00385 /* */
00386 /*  Examples:*/
00387 /*      - movePointInDirection {0 0} 45.0 10*/
00388 /*        Result: 7.07106781187 7.07106781187*/
00389 /* */
00390 ret  ::math::geometry::movePointInDirection (type P , type direction , type dist) {
00391     set x [lindex $P 0]
00392     set y [lindex $P 1]
00393     set pi [expr {4*atan(1)}]
00394     set xt [expr {$x + $dist*cos(($direction*$pi)/180)}]
00395     set yt [expr {$y + $dist*sin(($direction*$pi)/180)}]
00396     return [list $xt $yt]
00397 }
00398 
00399 
00400 /*  ::math::geometry::angle*/
00401 /* */
00402 /*        Calculates angle from the horizon (0,0)->(1,0) to a line.*/
00403 /* */
00404 /*  Arguments:*/
00405 /*        line          a line defined by two points A and B*/
00406 /* */
00407 /*  Results:*/
00408 /*        angle         the angle between the line (0,0)->(1,0) and (Ax,Ay)->(Bx,By).*/
00409 /*                      Angle is in 360-degrees going counter-clockwise*/
00410 /* */
00411 /*  Examples:*/
00412 /*      - angle {10 10 15 13}*/
00413 /*        Result: 30.9637565321*/
00414 /* */
00415 ret  ::math::geometry::angle (type line) {
00416     set x1 [lindex $line 0]
00417     set y1 [lindex $line 1]
00418     set x2 [lindex $line 2]
00419     set y2 [lindex $line 3]
00420     # - handle vertical lines
00421     if {$x1==$x2} {if {$y1<$y2} {return 90} else {return 270}}
00422     # - handle other lines
00423     set a [expr {atan(abs((1.0*$y1-$y2)/(1.0*$x1-$x2)))}] ; # a is between 0 and pi/2
00424     set pi [expr {4*atan(1)}]
00425     if {$y1<=$y2} {
00426     # line is going upwards
00427     if {$x1<$x2} {set b $a} else {set b [expr {$pi-$a}]}
00428     } else {
00429     # line is going downwards
00430     if {$x1<$x2} {set b [expr {2*$pi-$a}]} else {set b [expr {$pi+$a}]}
00431     }
00432     return [expr {$b/$pi*180}] ; # convert b to degrees
00433 }
00434 
00435 
00436 
00437 
00438 /* */
00439 /* */
00440 /*  Intersection procedures*/
00441 /* */
00442 /* */
00443 
00444 /*  ::math::geometry::lineSegmentsIntersect*/
00445 /* */
00446 /*        Checks whether two line segments intersect.*/
00447 /* */
00448 /*  Arguments:*/
00449 /*        linesegment1  the first line segment*/
00450 /*        linesegment2  the second line segment*/
00451 /* */
00452 /*  Results:*/
00453 /*        dointersect   a boolean saying whether the line segments intersect*/
00454 /*                      (i.e., have any points in common)*/
00455 /* */
00456 /*  Examples:*/
00457 /*      - lineSegmentsIntersect {0 0 10 10} {0 10 10 0}*/
00458 /*        Result: 1*/
00459 /*      - lineSegmentsIntersect {0 0 10 10} {20 20 20 30}*/
00460 /*        Result: 0*/
00461 /*      - lineSegmentsIntersect {0 0 10 10} {10 10 15 15}*/
00462 /*        Result: 1*/
00463 /* */
00464 ret  ::math::geometry::lineSegmentsIntersect (type linesegment1 , type linesegment2) {
00465     # Algorithm based on Sedgewick.
00466     set l1x1 [lindex $linesegment1 0]
00467     set l1y1 [lindex $linesegment1 1]
00468     set l1x2 [lindex $linesegment1 2]
00469     set l1y2 [lindex $linesegment1 3]
00470     set l2x1 [lindex $linesegment2 0]
00471     set l2y1 [lindex $linesegment2 1]
00472     set l2x2 [lindex $linesegment2 2]
00473     set l2y2 [lindex $linesegment2 3]
00474     return [expr {([ccw [list $l1x1 $l1y1] [list $l1x2 $l1y2] [list $l2x1 $l2y1]]\
00475         *[ccw [list $l1x1 $l1y1] [list $l1x2 $l1y2] [list $l2x2 $l2y2]] <= 0) \
00476         && ([ccw [list $l2x1 $l2y1] [list $l2x2 $l2y2] [list $l1x1 $l1y1]]\
00477         *[ccw [list $l2x1 $l2y1] [list $l2x2 $l2y2] [list $l1x2 $l1y2]] <= 0)}]
00478 }
00479 
00480 /*  ::math::geometry::findLineSegmentIntersection*/
00481 /* */
00482 /*        Returns the intersection point of two line segments.*/
00483 /*        Note: may also return "coincident" and "none".*/
00484 /* */
00485 /*  Arguments:*/
00486 /*        linesegment1  the first line segment*/
00487 /*        linesegment2  the second line segment*/
00488 /* */
00489 /*  Results:*/
00490 /*        P             the intersection point of linesegment1 and linesegment2.*/
00491 /*                      If linesegment1 and linesegment2 have an infinite number*/
00492 /*                      of points in common, the procedure returns "coincident".*/
00493 /*                      If there are no intersection points, the procedure*/
00494 /*                      returns "none".*/
00495 /* */
00496 /*  Examples:*/
00497 /*      - findLineSegmentIntersection {0 0 10 10} {0 10 10 0}*/
00498 /*        Result: 5.0 5.0*/
00499 /*      - findLineSegmentIntersection {0 0 10 10} {20 20 20 30}*/
00500 /*        Result: none*/
00501 /*      - findLineSegmentIntersection {0 0 10 10} {10 10 15 15}*/
00502 /*        Result: 10.0 10.0*/
00503 /*      - findLineSegmentIntersection {0 0 10 10} {5 5 15 15}*/
00504 /*        Result: coincident*/
00505 /* */
00506 ret  ::math::geometry::findLineSegmentIntersection (type linesegment1 , type linesegment2) {
00507     if {[lineSegmentsIntersect $linesegment1 $linesegment2]} {
00508     set lineintersect [findLineIntersection $linesegment1 $linesegment2]
00509     switch -- $lineintersect {
00510 
00511         "coincident" {
00512         # lines are coincident
00513         set l1x1 [lindex $linesegment1 0]
00514         set l1y1 [lindex $linesegment1 1]
00515         set l1x2 [lindex $linesegment1 2]
00516         set l1y2 [lindex $linesegment1 3]
00517         set l2x1 [lindex $linesegment2 0]
00518         set l2y1 [lindex $linesegment2 1]
00519         set l2x2 [lindex $linesegment2 2]
00520         set l2y2 [lindex $linesegment2 3]
00521         # check if the line SEGMENTS overlap
00522         # (NOT enough to check if the x-intervals overlap (vertical lines!))
00523         set overlapx [intervalsOverlap $l1x1 $l1x2 $l2x1 $l2x2 0]
00524         set overlapy [intervalsOverlap $l1y1 $l1y2 $l2y1 $l2y2 0]
00525         if {$overlapx && $overlapy} {
00526             return "coincident"
00527         } else {
00528             return "none"
00529         }
00530         }
00531 
00532         "none" {
00533         # should never happen, because we call "lineSegmentsIntersect" first
00534         puts stderr "::math::geometry::findLineSegmentIntersection: suddenly no intersection?"
00535         return "none"
00536         }
00537 
00538         default {
00539         # lineintersect = the intersection point
00540         return $lineintersect
00541         }
00542     }
00543     } else {
00544     return "none"
00545     }
00546 }
00547 
00548 /*  ::math::geometry::findLineIntersection {line1 line2}*/
00549 /* */
00550 /*        Returns the intersection point of two lines.*/
00551 /*        Note: may also return "coincident" and "none".*/
00552 /* */
00553 /*  Arguments:*/
00554 /*        line1         the first line*/
00555 /*        line2         the second line*/
00556 /* */
00557 /*  Results:*/
00558 /*        P             the intersection point of line1 and line2.*/
00559 /*                      If line1 and line2 have an infinite number of points*/
00560 /*                      in common, the procedure returns "coincident".*/
00561 /*                      If there are no intersection points, the procedure*/
00562 /*                      returns "none".*/
00563 /* */
00564 /*  Examples:*/
00565 /*      - findLineIntersection {0 0 10 10} {0 10 10 0}*/
00566 /*        Result: 5.0 5.0*/
00567 /*      - findLineIntersection {0 0 10 10} {20 20 20 30}*/
00568 /*        Result: 20.0 20.0*/
00569 /*      - findLineIntersection {0 0 10 10} {10 10 15 15}*/
00570 /*        Result: coincident*/
00571 /*      - findLineIntersection {0 0 10 10} {5 5 15 15}*/
00572 /*        Result: coincident*/
00573 /*      - findLineIntersection {0 0 10 10} {0 1 10 11}*/
00574 /*        Result: none*/
00575 /* */
00576 ret  ::math::geometry::findLineIntersection (type line1 , type line2) {
00577     set l1x1 [lindex $line1 0]
00578     set l1y1 [lindex $line1 1]
00579     set l1x2 [lindex $line1 2]
00580     set l1y2 [lindex $line1 3]
00581     set l2x1 [lindex $line2 0]
00582     set l2y1 [lindex $line2 1]
00583     set l2x2 [lindex $line2 2]
00584     set l2y2 [lindex $line2 3]
00585 
00586     # Is one of the lines vertical?
00587     if {$l1x1==$l1x2 || $l2x1==$l2x2} {
00588     # One of the lines is vertical
00589     if {$l1x1==$l1x2 && $l2x1==$l2x2} {
00590         # both lines are vertical
00591         if {$l1x1==$l2x1} {
00592         return "coincident"
00593         } else {
00594         return "none"
00595         }
00596     }
00597 
00598     # make sure line1 is a vertical line
00599     if {$l1x1!=$l1x2} {
00600         # interchange line 1 and 2
00601         set l1x1 [lindex $line2 0]
00602         set l1y1 [lindex $line2 1]
00603         set l1x2 [lindex $line2 2]
00604         set l1y2 [lindex $line2 3]
00605         set l2x1 [lindex $line1 0]
00606         set l2y1 [lindex $line1 1]
00607         set l2x2 [lindex $line1 2]
00608         set l2y2 [lindex $line1 3]
00609     }
00610 
00611     # get equation of line 2 (y=a*x+b)
00612     set a [expr {1.0*($l2y2-$l2y1)/($l2x2-$l2x1)}]
00613     set b [expr {$l2y1-$a*$l2x1}]
00614 
00615     # Calculate intersection
00616     set y [expr {$a*$l1x1+$b}]
00617     return [list $l1x1 $y]
00618     } else {
00619     # None of the lines are vertical
00620     # - get equation of line 1 (y=a1*x+b1)
00621     set a1 [expr {(1.0*$l1y2-$l1y1)/($l1x2-$l1x1)}]
00622     set b1 [expr {$l1y1-$a1*$l1x1}]
00623     # - get equation of line 2 (y=a2*x+b2)
00624     set a2 [expr {(1.0*$l2y2-$l2y1)/($l2x2-$l2x1)}]
00625     set b2 [expr {$l2y1-$a2*$l2x1}]
00626     
00627     if {abs($a2-$a1) > 0.0001} {
00628         # the lines are not parallel
00629         set x [expr {($b2-$b1)/($a1-$a2)}]
00630         set y [expr {$a1*$x+$b1}]
00631         return [list $x $y]
00632     } else {
00633         # the lines are parallel
00634         if {abs($b1-$b2) < 0.00001} {
00635         return "coincident"
00636         } else {
00637         return "none"
00638         }
00639     }
00640     }
00641 }
00642 
00643 
00644 /*  ::math::geometry::polylinesIntersect*/
00645 /* */
00646 /*        Checks whether two polylines intersect.*/
00647 /* */
00648 /*  Arguments;*/
00649 /*        polyline1     the first polyline*/
00650 /*        polyline2     the second polyline*/
00651 /* */
00652 /*  Results:*/
00653 /*        dointersect   a boolean saying whether the polylines intersect*/
00654 /* */
00655 /*  Examples:*/
00656 /*      - polylinesIntersect {0 0 10 10 10 20} {0 10 10 0}*/
00657 /*        Result: 1*/
00658 /*      - polylinesIntersect {0 0 10 10 10 20} {5 4 10 4}*/
00659 /*        Result: 0*/
00660 /* */
00661 ret  ::math::geometry::polylinesIntersect (type polyline1 , type polyline2) {
00662     return [polylinesBoundingIntersect $polyline1 $polyline2 0]
00663 }
00664 
00665 /*  ::math::geometry::polylinesBoundingIntersect*/
00666 /* */
00667 /*        Check whether two polylines intersect, but reduce*/
00668 /*        the correctness of the result to the given granularity.*/
00669 /*        Use this for faster, but weaker, intersection checking.*/
00670 /* */
00671 /*        How it works:*/
00672 /*           Each polyline is split into a number of smaller polylines,*/
00673 /*           consisting of granularity points each. If a pair of those smaller*/
00674 /*           lines' bounding boxes intersect, then this procedure returns 1,*/
00675 /*           otherwise it returns 0.*/
00676 /* */
00677 /*  Arguments:*/
00678 /*        polyline1     the first polyline*/
00679 /*        polyline2     the second polyline*/
00680 /*        granularity   the number of points in each part-polyline*/
00681 /*                      granularity<=1 means full correctness*/
00682 /* */
00683 /*  Results:*/
00684 /*        dointersect   a boolean saying whether the polylines intersect*/
00685 /* */
00686 /*  Examples:*/
00687 /*      - polylinesBoundingIntersect {0 0 10 10 10 20} {0 10 10 0} 2*/
00688 /*        Result: 1*/
00689 /*      - polylinesBoundingIntersect {0 0 10 10 10 20} {5 4 10 4} 2*/
00690 /*        Result: 1*/
00691 /* */
00692 ret  ::math::geometry::polylinesBoundingIntersect (type polyline1 , type polyline2 , type granularity) {
00693     if {$granularity<=1} {
00694     # Use perfect intersect
00695     # => first pin down where an intersection point may be, and then
00696     #    call MultilinesIntersectPerfect on those parts
00697     set granularity 10 ; # optimal search granularity?
00698     set perfectmatch 1
00699     } else {
00700     set perfectmatch 0
00701     }
00702 
00703     # split the lines into parts consisting of $granularity points
00704     set polyline1parts {}
00705     for {set i 0} {$i<[llength $polyline1]} {incr i [expr {2*$granularity-2}]} {
00706     lappend polyline1parts [lrange $polyline1 $i [expr {$i+2*$granularity-1}]]
00707     }
00708     set polyline2parts {}
00709     for {set i 0} {$i<[llength $polyline2]} {incr i [expr {2*$granularity-2}]} {
00710     lappend polyline2parts [lrange $polyline2 $i [expr {$i+2*$granularity-1}]]
00711     }
00712 
00713     # do any of the parts overlap?
00714     foreach part1 $polyline1parts {
00715     foreach part2 $polyline2parts {
00716         set part1bbox [bbox $part1]
00717         set part2bbox [bbox $part2]
00718         if {[rectanglesOverlap [lrange $part1bbox 0 1] [lrange $part1bbox 2 3] \
00719             [lrange $part2bbox 0 1] [lrange $part2bbox 2 3] 0]} {
00720         # the lines' bounding boxes intersect
00721         if {$perfectmatch} {
00722             foreach {l1x1 l1y1} [lrange $part1 0 end-2] {l1x2 l1y2} [lrange $part1 2 end] {
00723             foreach {l2x1 l2y1} [lrange $part2 0 end-2] {l2x2 l2y2} [lrange $part2 2 end] {
00724                 if {[lineSegmentsIntersect [list $l1x1 $l1y1 $l1x2 $l1y2] \
00725                     [list $l2x1 $l2y1 $l2x2 $l2y2]]} {
00726                 # two line segments overlap
00727                 return 1
00728                 }
00729             }
00730             }
00731             return 0
00732         } else {
00733             return 1
00734         }
00735         }
00736     }
00737     }
00738     return 0
00739 }
00740 
00741 /*  ::math::geometry::ccw*/
00742 /* */
00743 /*        PRIVATE FUNCTION USED BY OTHER FUNCTIONS.*/
00744 /*        Returns whether traversing from A to B to C is CounterClockWise*/
00745 /*        Algorithm by Sedgewick.*/
00746 /* */
00747 /*  Arguments:*/
00748 /*        A             first point*/
00749 /*        B             second point*/
00750 /*        C             third point*/
00751 /* */
00752 /*  Reeults:*/
00753 /*        ccw           a boolean saying whether traversing from A to B to C*/
00754 /*                      is CounterClockWise*/
00755 /* */
00756 ret  ::math::geometry::ccw (type A , type B , type C) {
00757     set Ax [lindex $A 0]
00758     set Ay [lindex $A 1]
00759     set Bx [lindex $B 0]
00760     set By [lindex $B 1]
00761     set Cx [lindex $C 0]
00762     set Cy [lindex $C 1]
00763     set dx1 [expr {$Bx - $Ax}]
00764     set dy1 [expr {$By - $Ay}]
00765     set dx2 [expr {$Cx - $Ax}]
00766     set dy2 [expr {$Cy - $Ay}]
00767     if {$dx1*$dy2 > $dy1*$dx2} {return 1}
00768     if {$dx1*$dy2 < $dy1*$dx2} {return -1}
00769     if {($dx1*$dx2 < 0) || ($dy1*$dy2 < 0)} {return -1}
00770     if {($dx1*$dx1 + $dy1*$dy1) < ($dx2*$dx2+$dy2*$dy2)} {return 1}
00771     return 0
00772 }
00773 
00774 
00775 
00776 
00777 
00778 
00779 
00780 /* */
00781 /* */
00782 /*  Overlap procedures*/
00783 /* */
00784 /* */
00785 
00786 /*  ::math::geometry::intervalsOverlap*/
00787 /* */
00788 /*        Check whether two intervals overlap.*/
00789 /*        Examples:*/
00790 /*          - (2,4) and (5,3) overlap with strict=0 and strict=1*/
00791 /*          - (2,4) and (1,2) overlap with strict=0 but not with strict=1*/
00792 /* */
00793 /*  Arguments:*/
00794 /*        y1,y2         the first interval*/
00795 /*        y3,y4         the second interval*/
00796 /*        strict        choosing strict or non-strict interpretation*/
00797 /* */
00798 /*  Results:*/
00799 /*        dooverlap     a boolean saying whether the intervals overlap*/
00800 /* */
00801 /*  Examples:*/
00802 /*      - intervalsOverlap 2 4 4 6 1*/
00803 /*        Result: 0*/
00804 /*      - intervalsOverlap 2 4 4 6 0*/
00805 /*        Result: 1*/
00806 /*      - intervalsOverlap 4 2 3 5 0*/
00807 /*        Result: 1*/
00808 /* */
00809 ret  ::math::geometry::intervalsOverlap (type y1 , type y2 , type y3 , type y4 , type strict) {
00810     if {$y1>$y2} {
00811     set temp $y1
00812     set y1 $y2
00813     set y2 $temp
00814     }
00815     if {$y3>$y4} {
00816     set temp $y3
00817     set y3 $y4
00818     set y4 $temp
00819     }
00820     if {$strict} {
00821     return [expr {$y2>$y3 && $y4>$y1}]
00822     } else {
00823     return [expr {$y2>=$y3 && $y4>=$y1}]
00824     }
00825 }
00826 
00827 /*  ::math::geometry::rectanglesOverlap*/
00828 /* */
00829 /*        Check whether two rectangles overlap (see also intervalsOverlap).*/
00830 /* */
00831 /*  Arguments:*/
00832 /*        P1            upper-left corner of the first rectangle*/
00833 /*        P2            lower-right corner of the first rectangle*/
00834 /*        Q1            upper-left corner of the second rectangle*/
00835 /*        Q2            lower-right corner of the second rectangle*/
00836 /*        strict        choosing strict or non-strict interpretation*/
00837 /* */
00838 /*  Results:*/
00839 /*        dooverlap     a boolean saying whether the rectangles overlap*/
00840 /* */
00841 /*  Examples:*/
00842 /*      - rectanglesOverlap {0 10} {10 0} {10 10} {20 0} 1*/
00843 /*        Result: 0*/
00844 /*      - rectanglesOverlap {0 10} {10 0} {10 10} {20 0} 0*/
00845 /*        Result: 1*/
00846 /* */
00847 ret  ::math::geometry::rectanglesOverlap (type P1 , type P2 , type Q1 , type Q2 , type strict) {
00848     set b1x1 [lindex $P1 0]
00849     set b1y1 [lindex $P1 1]
00850     set b1x2 [lindex $P2 0]
00851     set b1y2 [lindex $P2 1]
00852     set b2x1 [lindex $Q1 0]
00853     set b2y1 [lindex $Q1 1]
00854     set b2x2 [lindex $Q2 0]
00855     set b2y2 [lindex $Q2 1]
00856     # ensure b1x1<=b1x2 etc.
00857     if {$b1x1 > $b1x2} {
00858     set temp $b1x1
00859     set b1x1 $b1x2
00860     set b1x2 $temp
00861     }
00862     if {$b1y1 > $b1y2} {
00863     set temp $b1y1
00864     set b1y1 $b1y2
00865     set b1y2 $temp
00866     }
00867     if {$b2x1 > $b2x2} {
00868     set temp $b2x1
00869     set b2x1 $b2x2
00870     set b2x2 $temp
00871     }
00872     if {$b2y1 > $b2y2} {
00873     set temp $b2y1
00874     set b2y1 $b2y2
00875     set b2y2 $temp
00876     }
00877     # Check if the boxes intersect
00878     # (From: Cormen, Leiserson, and Rivests' "Algorithms", page 889)
00879     if {$strict} {
00880     return [expr {($b1x2>$b2x1) && ($b2x2>$b1x1) \
00881         && ($b1y2>$b2y1) && ($b2y2>$b1y1)}]
00882     } else {
00883     return [expr {($b1x2>=$b2x1) && ($b2x2>=$b1x1) \
00884         && ($b1y2>=$b2y1) && ($b2y2>=$b1y1)}]
00885     }
00886 }
00887 
00888 
00889 
00890 /*  ::math::geometry::bbox*/
00891 /* */
00892 /*        Calculate the bounding box of a polyline.*/
00893 /* */
00894 /*  Arguments:*/
00895 /*        polyline      a polyline*/
00896 /* */
00897 /*  Results:*/
00898 /*        x1,y1,x2,y2   four coordinates where (x1,y1) is the upper-left corner*/
00899 /*                      of the bounding box, and (x2,y2) is the lower-right corner*/
00900 /* */
00901 /*  Examples:*/
00902 /*      - bbox {0 10 4 1 6 23 -12 5}*/
00903 /*        Result: -12 1 6 23*/
00904 /* */
00905 ret  ::math::geometry::bbox (type polyline) {
00906     set minX [lindex $polyline 0]
00907     set maxX $minX
00908     set minY [lindex $polyline 1]
00909     set maxY $minY
00910     foreach {x y} $polyline {
00911     if {$x < $minX} {set minX $x}
00912     if {$x > $maxX} {set maxX $x}
00913     if {$y < $minY} {set minY $y}
00914     if {$y > $maxY} {set maxY $y}
00915     }
00916     return [list $minX $minY $maxX $maxY]
00917 }
00918 
00919 
00920 
00921 
00922 
00923 
00924 /*  ::math::geometry::pointInsidePolygon*/
00925 /* */
00926 /*        Determine if a point is completely inside a polygon. If the point*/
00927 /*        touches the polygon, then the point is not complete inside the*/
00928 /*        polygon.*/
00929 /* */
00930 /*  Arguments:*/
00931 /*        P             a point*/
00932 /*        polygon       a polygon*/
00933 /* */
00934 /*  Results:*/
00935 /*        isinside      a boolean saying whether the point is*/
00936 /*                      completely inside the polygon or not*/
00937 /* */
00938 /*  Examples:*/
00939 /*      - pointInsidePolygon {5 5} {4 4 4 6 6 6 6 4}*/
00940 /*        Result: 1*/
00941 /*      - pointInsidePolygon {5 5} {6 6 6 7 7 7}*/
00942 /*        Result: 0*/
00943 /* */
00944 ret  ::math::geometry::pointInsidePolygon (type P , type polygon) {
00945     # check if P is on one of the polygon's sides (if so, P is not
00946     # inside the polygon)
00947     set closedPolygon [concat $polygon [lrange $polygon 0 1]]
00948     foreach {x1 y1} [lrange $closedPolygon 0 end-2] {x2 y2} [lrange $closedPolygon 2 end] {
00949     if {[calculateDistanceToLineSegment $P [list $x1 $y1 $x2 $y2]]<0.0000001} {
00950         return 0
00951     }
00952     }
00953 
00954     # Algorithm
00955     #
00956     # Consider a straight line going from P to a point far away from both
00957     # P and the polygon (in particular outside the polygon).
00958     #   - If the line intersects with 0 of the polygon's sides, then
00959     #     P must be outside the polygon.
00960     #   - If the line intersects with 1 of the polygon's sides, then
00961     #     P must be inside the polygon (since the other end of the line
00962     #     is outside the polygon).
00963     #   - If the line intersects with 2 of the polygon's sides, then
00964     #     the line must pass into one polygon area and out of it again,
00965     #     and hence P is outside the polygon.
00966     #   - In general: if the line intersects with the polygon's sides an odd
00967     #     number of times, then P is inside the polygon. Note: we also have
00968     #     to check whether the line crosses one of the polygon's
00969     #     bend points for the same reason.
00970 
00971     # get point far away and define the line
00972     set polygonBbox [bbox $polygon]
00973     set pointFarAway [list [expr {[lindex $polygonBbox 0]-1}] [expr {[lindex $polygonBbox 1]-1}]]
00974     set infinityLine [concat $pointFarAway $P]
00975     # calculate number of intersections
00976     set noOfIntersections 0
00977     #   1. count intersections between the line and the polygon's sides
00978     set closedPolygon [concat $polygon [lrange $polygon 0 1]]
00979     foreach {x1 y1} [lrange $closedPolygon 0 end-2] {x2 y2} [lrange $closedPolygon 2 end] {
00980     if {[lineSegmentsIntersect $infinityLine [list $x1 $y1 $x2 $y2]]} {
00981         incr noOfIntersections
00982     }
00983     }
00984     #   2. count intersections between the line and the polygon's points
00985     foreach {x1 y1} $polygon {
00986     if {[calculateDistanceToLineSegment [list $x1 $y1] $infinityLine]<0.0000001} {
00987         incr noOfIntersections
00988     }
00989     }
00990     return [expr {$noOfIntersections % 2}]
00991 }
00992 
00993 
00994 /*  ::math::geometry::rectangleInsidePolygon*/
00995 /* */
00996 /*        Determine if a rectangle is completely inside a polygon. If polygon*/
00997 /*        touches the rectangle, then the rectangle is not complete inside the*/
00998 /*        polygon.*/
00999 /* */
01000 /*  Arguments:*/
01001 /*        P1            upper-left corner of the rectangle*/
01002 /*        P2            lower-right corner of the rectangle*/
01003 /*        polygon       a polygon*/
01004 /* */
01005 /*  Results:*/
01006 /*        isinside      a boolean saying whether the rectangle is*/
01007 /*                      completely inside the polygon or not*/
01008 /* */
01009 /*  Examples:*/
01010 /*      - rectangleInsidePolygon {0 10} {10 0} {-10 -10 0 11 11 11 11 0}*/
01011 /*        Result: 1*/
01012 /*      - rectangleInsidePolygon {0 0} {0 0} {-16 14 5 -16 -16 -25 -21 16 -19 24}*/
01013 /*        Result: 1*/
01014 /*      - rectangleInsidePolygon {0 0} {0 0} {2 2 2 4 4 4 4 2}*/
01015 /*        Result: 0*/
01016 /* */
01017 ret  ::math::geometry::rectangleInsidePolygon (type P1 , type P2 , type polygon) {
01018     # get coordinates of rectangle
01019     set bx1 [lindex $P1 0]
01020     set by1 [lindex $P1 1]
01021     set bx2 [lindex $P2 0]
01022     set by2 [lindex $P2 1]
01023 
01024     # if rectangle does not overlap with the bbox of polygon, then the
01025     # rectangle cannot be inside the polygon (this is a quick way to
01026     # get an answer in many cases)
01027     set polygonBbox [bbox $polygon]
01028     set polygonP1x [lindex $polygonBbox 0]
01029     set polygonP1y [lindex $polygonBbox 1]
01030     set polygonP2x [lindex $polygonBbox 2]
01031     set polygonP2y [lindex $polygonBbox 3]
01032     if {![rectanglesOverlap [list $bx1 $by1] [list $bx2 $by2] \
01033         [list $polygonP1x $polygonP1y] [list $polygonP2x $polygonP2y] 0]} {
01034     return 0
01035     }
01036 
01037     # 1. if one of the points of the polygon is inside the rectangle,
01038     # then the rectangle cannot be inside the polygon
01039     foreach {x y} $polygon {
01040     if {$bx1<$x && $x<$bx2 && $by1<$y && $y<$by2} {
01041         return 0
01042     }
01043     }
01044 
01045     # 2. if one of the line segments of the polygon intersect with the
01046     # rectangle, then the rectangle cannot be inside the polygon
01047     set rectanglePolyline [list $bx1 $by1 $bx2 $by1 $bx2 $by2 $bx1 $by2 $bx1 $by1]
01048     set closedPolygon [concat $polygon [lrange $polygon 0 1]]
01049     if {[polylinesIntersect $closedPolygon $rectanglePolyline]} {
01050     return 0
01051     }
01052 
01053     # at this point we know that:
01054     #  1. the polygon has no points inside the rectangle
01055     #  2. the polygon's sides don't intersect with the rectangle
01056     # therefore:
01057     #  either the rectangle is (completely) inside the polygon, or
01058     #  the rectangle is (completely) outside the polygon
01059 
01060     # final test: if one of the points on the rectangle is inside the
01061     # polygon, then the whole rectangle must be inside the rectangle
01062     return [pointInsidePolygon [list $bx1 $by1] $polygon]
01063 }
01064 
01065 
01066 /*  ::math::geometry::areaPolygon*/
01067 /* */
01068 /*        Determine the area enclosed by a (non-complex) polygon*/
01069 /* */
01070 /*  Arguments:*/
01071 /*        polygon       a polygon*/
01072 /* */
01073 /*  Results:*/
01074 /*        area          the area enclosed by the polygon*/
01075 /* */
01076 /*  Examples:*/
01077 /*      - areaPolygon {-10 -10 10 -10 10 10 -10 10}*/
01078 /*        Result: 400*/
01079 /* */
01080 ret  ::math::geometry::areaPolygon (type polygon) {
01081 
01082     foreach {a1 a2 b1 b2} $polygon {break}
01083 
01084     set area 0.0
01085     foreach {c1 c2} [lrange $polygon 4 end] {
01086         set area [expr {$area + $b1*$c2 - $b2*$c1}]
01087         set b1   $c1
01088         set b2   $c2
01089     }
01090     expr {0.5*abs($area)}
01091 }
01092 
01093 package provide math::geometry 1.0.3
01094 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1