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