OUTPUT BUFFER:
proc Track_Down {x y w} { } proc Track_Init {} { global kTol kDeg2Rad kRad2Deg global gXCenterTrackball gYCenterTrackball set kTol 0.001 set kRad2Deg [expr {180. / 3.1415927}] set kDeg2Rad [expr {3.1415927 / 180.}] set gXCenterTrackball 0 set gYCenterTrackball 0 set originX 0 set originY 0 return } proc startTrackball {x y} { global pgl gStartPtTrackball global originX originY set nx $pgl(width) set ny $pgl(height) if {$nx > $ny} { set gRadiusTrackball [expr {$ny * 0.5}] } else { set gRadiusTrackball [expr {$nx * 0.5}] } # Figure the center of the view. set gXCenterTrackball [expr {$originX+$pgl(width)/2}] set gYCenterTrackball [expr {$originY+$pgl(height) / 2}] # Compute the starting vector from the surface of the ball to its center. set gStartPtTrackballX [expr {$x - $gXCenterTrackball}] set gStartPtTrackballY [expr {$y - $gYCenterTrackball}] set xxyy [expr {$gStartPtTrackballX * $gStartPtTrackballX + $gStartPtTrackballY * $gStartPtTrackballY}] if {$xxyy > $gRadiusTrackball * $gRadiusTrackball} { # Outside the sphere. set gStartPtTrackballZ 0. } else { set gStartPtTrackballZ [expr {sqrt($gRadiusTrackball * $gRadiusTrackball - $xxyy)}] } set gStartPtTrackball [list $gStartPtTrackballX $gStartPtTrackballY $gStartPtTrackballZ 0.0] return } # update to new mouse position, output rotation angle proc rollToTrackball {x y rot} { global gStartTrackball gRadiusTrackball rot lassign $gStartTrackball gStartTrackballX gStartTrackballY gStartTrackballZ gStartTrackballW set gEndPtTrackballX [expr {$x - $gXCenterTrackball}] set gEndPtTrackballY [expr {$y - $gYCenterTrackball}] if {abs($gEndPtTrackballX - $gStartPtTrackballX) < $kTol && abs($gEndPtTrackballY - $gStartPtTrackballY) < $kTol} {return} # Compute the ending vector from the surface of the ball to its center. set xxyy [expr {$gEndPtTrackballX * $gEndPtTrackballX + $gEndPtTrackballY * $gEndPtTrackballY if {$xxyy > $gRadiusTrackball * $gRadiusTrackball} { # Outside the sphere. set gEndPtTrackballZ 0. } else { set gEndPtTrackballZ [expr {sqrt($gRadiusTrackball * $gRadiusTrackball - $xxyy)}] } # Take the cross product of the two vectors. r = s X e set rot1 [expr {$gStartPtTrackballY * $gEndPtTrackballZ - $gStartPtTrackballZ * $gEndPtTrackballY}] set rot2 [expr {-1.0*$gStartPtTrackballX * $gEndPtTrackballZ + $gStartPtTrackballZ * $gEndPtTrackballX}] set rot3 [expr {$gStartPtTrackballX * $gEndPtTrackballY - $gStartPtTrackballY * $gEndPtTrackballX}] # Use atan for a better angle. If you use only cos or sin, you only get # half the possible angles, and you can end up with rotations that flip around near # the poles. # cos(a) = (s . e) / (||s|| ||e||) set cosAng [expr {$gStartPtTrackballX * $gEndPtTrackballX + $gStartPtTrackballY * $gEndPtTrackballY + $gStartPtTrackballZ * $gEndPtTrackballZ}] # (s . e) set ls [expr {sqrt($gStartPtTrackballX * $gStartPtTrackballX + $gStartPtTrackballY * $gStartPtTrackballY + $gStartPtTrackballZ * $gStartPtTrackballZ)}] set ls [expr {1. / $ls}] # 1 / ||s|| set le [expr {sqrt($gEndPtTrackballX * $gEndPtTrackballX + $gEndPtTrackballY * $gEndPtTrackballY + $gEndPtTrackballZ * $gEndPtTrackballZ)}] set le [expr {1. / $le}] set cosAng [expr {$cosAng * $ls * $le}] # sin(a) = ||(s X e)|| / (||s|| ||e||) set sinAng [expr {sqrt($rot1 * $rot1 + $rot2 * $rot2 + $rot3 * $rot3)}] set lr $sinAng # keep this length in lr for normalizing the rotation vector later. set sinAng [expr {$sinAng * $ls * $le}] set rot0 [expr {atan2($sinAng, $cosAng) * $kRad2Deg}] # Normalize the rotation axis. set lr [expr {1. / $lr}] set rot1 [expr {$rot1* $lr}] set rot2 [expr {$rot2* $lr}] set rot3 [expr {$rot3* $lr}] set rot [list $rot0 $rot1 $rot2 $rot3] # returns rotate return } proc rotation2Quat {A aq} { upvar $aq q global kDeg2Rad # Convert a GL-style rotation to a quaternion. The GL rotation looks like this: #/ {angle, x, y, z}, the corresponding quaternion looks like this: # {{v}, cos(angle/2)}, where {v} is {x, y, z} / sin(angle/2). lassign $A A0 A1 A2 A3 lassign $q q0 q1 q2 q3 set ang2 [expr {$A0 * $kDeg2Rad * 0.5}] set sinAng2 [expr {sin($ang2)}] set q0 [expr {$A1 * $sinAng2}] set q1 [expr {$A2 * $sinAng2}] set q2 [expr {$A3 * $sinAng2}] set q3 [expr {cos($ang2)}] set q [list $q0 $q1 $q2 $q3] return } proc addToRotationTrackball {dA A} { # Figure out A' = A . dA # In quaternions: let q0 <- A, and q1 <- dA. # Figure out q2 = q1 + q0 (note the order reversal!). # A' <- q3. rotation2Quat $A q0 rotation2Quat $dA q1 lassign $q0 q00 q01 q02 q03 lassign $q1 q10 q11 q12 q13 # q2 = q1 + q0; set q20 [expr {$q11*$q02 - $q12*$q01 + $q13*$q00 + $q10*$q03}] set q21 [expr {$q12*$q00 - $q10*$q02 + $q13*$q01 + $q11*$q03}] set q22 [expr {$q10*$q01 - $q11*$q00 + $q13*$q02 + $q12*$q03}] set q23 [expr {$q13*$q03 - $q10*$q00 - $q11*$q01 - $q12*$q02}] # Here's an excersize for the reader: it's a good idea to re-normalize your quaternions # every so often. Experiment with different frequencies. # An identity rotation is expressed as rotation by 0 about any axis. # The "angle" term in a quaternion is really the cosine of the half-angle. # So, if the cosine of the half-angle is one (or, 1.0 within our tolerance), # then you have an identity rotation. if {abs(abs($q23 - 1.)) < 1.0e-7} { # Identity rotation. set A0 0. set A1 1. set A2 0. set A3 0. return } # If you get here, then you have a non-identity rotation. In non-identity rotations, # the cosine of the half-angle is non-0, which means the sine of the angle is also # non-0. So we can safely divide by sin(theta2). # Turn the quaternion back into an {angle, {axis}} rotation. set theta2 [expr {acos($q23)}] set sinTheta2 [expr {1./sin($theta2)}] set A0 [expr {$theta2 * 2. * $kRad2Deg}] set A1 [expr {$q20 * $sinTheta2}] set A2 [expr {$q21 * $sinTheta2}] set A3 [expr {$q22 * $sinTheta2}] set A [list $A0 $A1 $A2 $A3] return }