Package Bezier provides calculation of crossings fraction of bezier curves.
# file: Bezier-0.1.tm package provide Bezier 0.1 namespace eval bezier namespace import\ ::tcl::mathfunc::*\ ::tcl::mathop::* proc bezier::Extrema {a b c d} { # taken a, b, c, d as x-coords of bezier dots, # return list of fractions where the appropriate function has extrema set factor2 [expr {3.0 * (-$a + (3.0 * $b) + (-3.0 * $c) + $d)}] set factor1 [expr {2.0 * ((3.0 * $a) + (-6.0 * $b) + (3.0 * $c))}] set factor0 [expr {(-3.0 * $a) + (3.0 * $b)}] if {$factor2 != 0} then { set p [expr { $factor1 / $factor2 / 2 }] set q [expr { $factor0 / $factor2 }] set p2q [expr { ($p * $p) - $q }] if {$p2q > 0} then { set sqrtp2q [expr {sqrt($p2q)}] list [expr { -$p - $sqrtp2q }] [expr { -$p + $sqrtp2q }] } elseif {$p2q == 0} then { expr {-$p} } } elseif { $factor1 != 0 } then { expr { -$factor0 / $factor1 } } } proc bezier::bbox coords { lassign $coords ax ay bx by cx cy dx dy lappend allX $ax $dx lappend allY $ay $dy foreach frac [Extrema $ax $bx $cx $dx] { if {$frac > 0.0 && $frac < 1.0} then { foreach {x y} [At $coords $frac] { lappend allX $x lappend allY $y } } } foreach frac [Extrema $ay $by $cy $dy] { if {$frac > 0.0 && $frac < 1.0} then { foreach {x y} [At $coords $frac] { lappend allX $x lappend allY $y } } } list [min {*}$allX] [min {*}$allY] [max {*}$allX] [max {*}$allY] } proc bezier::Rotate {x y phi {cX 0.0} {cY 0.0}} { # coords x, y rotated by phi around cX, cY set c [cos $phi] set s [sin $phi] list\ [expr {($x - $cX) * $c - ($y - $cY) * $s + $cX}]\ [expr {($y - $cY) * $c + ($x - $cX) * $s + $cY}] } proc bezier::RotateCoords {coords phi {cX 200.0} {cY 150.0}} { concat {*}[lmap {x y} $coords { Rotate $x $y $phi $cX $cY }] } proc bezier::ScaleCoords {coords fx fy {cx 200} {cy 150}} { concat {*}[lmap {x y} $coords { list\ [expr {($x - $cx) * $fx + $cx}]\ [expr {($y - $cy) * $fy + $cy}] }] } proc bezier::TypeOf curve { lassign $curve x0 y0 - - - - x1 y1 set phi [atan2 [- $y1 $y0] [- $x1 $x0]] set curve [RotateCoords $curve [- $phi]] lassign $curve ax ay bx by cx cy dx dy if {$by + $cy - 2 * $ay < 0} then { return arc } else { return bowl } } proc bezier::CurrentAngle {left right} { # calc angle of upper left corners ob bboxes set bbox1 [bbox $left] set bbox2 [bbox $right] if {[TypeOf $left] eq "arc"} then { # n-formed like an arc lassign $bbox1 x0 y0 lassign $bbox2 x1 y1 } else { # u-formed like a bowl lassign $bbox1 x0 - - y0 lassign $bbox2 x1 - - y1 } atan2 [- $y1 $y0] [- $x1 $x0] } proc bezier::AdjustedCoords {left right {rounds 100}} { for {set i 0} {$i < $rounds} {incr i} { set phi [CurrentAngle $left $right] set left [RotateCoords $left [- $phi]] set right [RotateCoords $right [- $phi]] if {abs($phi) < 1e-16} break } list $left $right } proc bezier::UppermostFraction coords { lassign $coords x0 y0 x1 y1 x2 y2 x3 y3 set xExtr [Extrema $x0 $x1 $x2 $x3] set yExtr [Extrema $y0 $y1 $y2 $y3] set elements {} foreach f [concat $xExtr $yExtr] { if {$f >= 0 && $f <= 1} then { lassign [At $coords $f] x y lappend elements [list $f $x $y] } } if {[TypeOf $coords] eq "arc"} then { lindex [lsort -real -index end $elements] 0 0 } else { lindex [lsort -real -index end $elements] end 0 } } proc bezier::tangens2 {left right} { lassign $left x0 y0 - - - - x1 y1 set phi [atan2 [- $y1 $y0] [- $x1 $x0]] set left [RotateCoords $left [- $phi]] set right [RotateCoords $right [- $phi]] # if {[TypeOf $left] ne "arc"} then { set left [ScaleCoords $left 1 -1] set right [ScaleCoords $right 1 -1] } if {[lindex [bbox $left] 0] > [lindex [bbox $right] 0]} then { set reverse yes lassign [list $left $right] right left } else { set reverse no } set result [lmap coords [AdjustedCoords $left $right] { UppermostFraction $coords }] if {$reverse} then { lreverse $result } else { set result } } proc bezier::Part {coords frac} { lassign $coords ax ay bx by cx cy dx dy # set abx [expr {$ax + ($bx - $ax) * $frac}] set bcx [expr {$bx + ($cx - $bx) * $frac}] set cdx [expr {$cx + ($dx - $cx) * $frac}] set abcx [expr {$abx + ($bcx - $abx) * $frac}] set bcdx [expr {$bcx + ($cdx - $bcx) * $frac}] set abcdx [expr {$abcx + ($bcdx - $abcx) * $frac}] # set aby [expr {$ay + ($by - $ay) * $frac}] set bcy [expr {$by + ($cy - $by) * $frac}] set cdy [expr {$cy + ($dy - $cy) * $frac}] set abcy [expr {$aby + ($bcy - $aby) * $frac}] set bcdy [expr {$bcy + ($cdy - $bcy) * $frac}] set abcdy [expr {$abcy + ($bcdy - $abcy) * $frac}] # list $ax $ay $abx $aby $abcx $abcy $abcdx $abcdy } proc bezier::segment {coords from to} { set frac0 $to set coords1 [Part $coords $frac0] set frac1 [expr {($to - $from) / $to}] lreverse [Part [lreverse $coords1] $frac1] } proc bezier::At {coords frac} { lrange [Part $coords $frac] end-1 end } proc bezier::Det2val det { # return numeric value of 2x2 determinante $det lassign $det l1 l2 lassign $l1 a1 a2 lassign $l2 b1 b2 # - [* $a1 $b2] [* $a2 $b1] expr {double($a1 * $b2 - $a2 * $b1)} } proc bezier::lineCutLine {l0 l1 args} { # return list of fracs where lines $l0, $l1 cross each other # --- # 2 lines l0=P-Q, l1R-S # P + u( Q - P ) = R + v( S - R ) # --- # px + u(qx - px) = rx + v(sx - rx) # py + u(qy - py) = ry + v(sy - ry) # --- # solve equation by u, v lassign $l0 px py qx qy lassign $l1 rx ry sx sy set x1 [expr {$qx - $px}] set x2 [expr {$rx - $sx}] set xr [expr {$rx - $px}] set y1 [expr {$qy - $py}] set y2 [expr {$ry - $sy}] set yr [expr {$ry - $py}] set det [list [list $x1 $x2] [list $y1 $y2]] set divisor [Det2val $det] if {$divisor != 0} then { set uDet [list [list $xr $x2] [list $yr $y2]] set uDetVal [Det2val $uDet] set u [expr {$uDetVal / $divisor}] set vDet [list [list $x1 $xr] [list $y1 $yr]] set vDetVal [Det2val $vDet] set v [expr {$vDetVal / $divisor}] if {0 < $u && $u < 1 && 0 < $v && $v < 1} then { list $u $v } } } proc bezier::Cross1 {bez1 bez2 {limit 1e-5}} { lassign $bez1 ax0 ay0 ax1 ay1 ax2 ay2 ax3 ay3 lassign $bez2 bx0 by0 bx1 by1 bx2 by2 bx3 by3 set ax "$ax0 $ax1 $ax2 $ax3" set ay "$ay0 $ay1 $ay2 $ay3" set bx "$bx0 $bx1 $bx2 $bx3" set by "$by0 $by1 $by2 $by3" if { [min {*}$ax] < [max {*}$bx] && [min {*}$ay] < [max {*}$by] && [max {*}$ax] > [min {*}$bx] && [max {*}$ay] > [min {*}$by] } then { set width [- [max {*}$ax {*}$bx] [min {*}$ax {*}$bx]] set height [- [max {*}$ay {*}$by] [min {*}$ay {*}$by]] if {max($width, $height) < $limit} then { # only true crossing, no touching! lineCutLine [lreplace $bez1 2 end-2] [lreplace $bez2 2 end-2] } else { set bez1a [segment $bez1 0 0.5] set bez2a [segment $bez2 0 0.5] set bez1b [segment $bez1 0.5 1] set bez2b [segment $bez2 0.5 1] lassign [Cross1 $bez1a $bez2a $limit] frac1 frac2 if {$frac1 ne ""} then { list [expr {$frac1 / 2.0}] [expr {$frac2 / 2.0}] } else { lassign [Cross1 $bez1a $bez2b $limit] frac1 frac2 if {$frac1 ne ""} then { list [expr {$frac1 / 2.0}] [expr {$frac2 / 2.0 + 0.5}] } else { lassign [Cross1 $bez1b $bez2a $limit] frac1 frac2 if {$frac1 ne ""} then { list [expr {$frac1 / 2.0 + 0.5}] [expr {$frac2 / 2.0}] } else { lassign [Cross1 $bez1b $bez2b $limit] frac1 frac2 if {$frac1 ne ""} then { list [expr {$frac1 / 2.0 + 0.5}] [expr {$frac2 / 2.0 + 0.5}] } } } } } } } proc bezier::Nearby {x0 y0 x1 y1 {limit 1e-5}} { expr { abs($x1 - $x0) <= $limit && abs($y1 - $y0) <= $limit } } proc bezier::BezierPosNearby {coords pos1 pos2 {limit 1e-5}} { Nearby {*}[At $coords $pos1] {*}[At $coords $pos2] $limit } proc bezier::CoordsReverse coords { concat {*}[lreverse [lmap {x y} $coords {list $x $y}]] } proc bezier::cut {bez1 bez2 {limit 1e-5} {tolerant no}} { if { !$tolerant && ( [Nearby {*}[At $bez1 0.0] {*}[At $bez2 0.0] $limit] || [Nearby {*}[At $bez1 1.0] {*}[At $bez2 1.0] $limit] ) } then { lassign [cut [CoordsReverse $bez1] $bez2 $limit yes] frac1 frac2 if {$frac2 ne {}} then { list [expr {1.0 - $frac1}] $frac2 } } else { lassign [Cross1 $bez1 $bez2 $limit] frac1 frac2 if {$frac2 ne {}} then { if {[BezierPosNearby $bez1 $frac1 0]} then { set frac1 0.0 } elseif {[BezierPosNearby $bez1 $frac1 1.0]} then { set frac1 1.0 } if {[BezierPosNearby $bez2 $frac2 0]} then { set frac2 0.0 } elseif {[BezierPosNearby $bez2 $frac2 1.0]} then { set frac2 1.0 } if {(0 < $frac1 && $frac1 < 1) || (0 < $frac2 && $frac2 < 1)} then { list $frac1 $frac2 } } } } proc bezier::cuts {bez1 bez2 {limit 1e-5}} { lassign [cut $bez1 $bez2 $limit] frac1 frac2 if {$frac2 ne {}} then { lappend result $frac1 $frac2 # set seg1a [segment $bez1 0 $frac1] set seg2a [segment $bez2 0 $frac2] set seg1b [segment $bez1 $frac1 1.0] set seg2b [segment $bez2 $frac2 1.0] # foreach {f1 f2} [cuts $seg1a $seg2a $limit] { lappend result\ [expr {$f1 * $frac1}]\ [expr {$f2 * $frac2}] } foreach {f1 f2} [cuts $seg1a $seg2b $limit] { lappend result\ [expr {$f1 * $frac1}]\ [expr {$frac2 + $f2 * (1-$frac2)}] } foreach {f1 f2} [cuts $seg1b $seg2a $limit] { lappend result\ [expr {$frac1 + $f1 * (1-$frac1)}]\ [expr {$f2 * $frac2}] } foreach {f1 f2} [cuts $seg1b $seg2b $limit] { lappend result\ [expr {$frac1 + $f1 * (1-$frac1)}]\ [expr {$frac2 + $f2 * (1-$frac2)}] } set result } } proc bezier::help {} { foreach proc [namespace export] { set line "[namespace tail [namespace current]] $proc" foreach arg [info args $proc] { if {[info default $proc $arg def]} then { lappend line [list $arg $def] } else { lappend line $arg } } lappend result $line } join $result \n } namespace eval bezier { namespace export bbox tangens2 segment cut cuts help lineCutLine namespace ensemble create }
© Wolf-Dieter Busch | Home | Sitemap | Urheber | A-Z