OUTPUT BUFFER:
# QJuliaGPU -- Keenan Crane (kcrane@uiuc.edu) # 4/17/2004 # # This program ray traces the quaternion Julia set in a fragment shader # using the sphere tracing method. The program draws a fullscreen quad # where each fragment of the quad specifies a different ray. These rays # are passed to the fragment shader which iteratively takes conservative # steps along a ray as determined by a distance estimator for the set. The # rays will either stop when close to an isosurface of the distance # function (considered a hit), or leave the bounding sphere of the Julia # set. If the ray is a hit, shading is performed by approximating the # gradient of the distance function and using this as a surface normal. # # A more complete description of the sphere tracing method can be found in # John Hart's paper, "Ray Tracing Deterministic 3-D Fractals" # (http://graphics.cs.uiuc.edu/~jch/papers/rtqjs.pdf). # # Controls: # # left mouse button: rotate view # middle mouse button: zoom in/out # right mouse button: translate view # m: toggle morph animation # s: toggle shadows on/off # r: reload shaders from disk # i/I: increment/decrement 1st imaginary component of Julia set constant # j/J: increment/decrement 2nd imaginary component of Julia set constant # k/K: increment/decrement 3rd imaginary component of Julia set constant # l/L: increment/decrement real component of Julia set constant # -/+: change number of iterations used to test convergence of a point # b/n: change precision of rendering # # By default the program will shift through a random constants for the # Julia set within the cube [-1,1]^4. Increasing the number of iterations # or the precision will increase the amount of detail seen in the # rendering. The former more accurately determines whether a point is # included in the set, whereas the latter intersects an isosurface of the # distance function closer to the actual set. Both of these parameters run # into precision or computation limits when set too high. # # # Original C++ and Cg code by Keenan Crane (kcrane@uiuc.edu) # See http://www.cs.caltech.edu/~keenan/project_qjulia.html for the original files. # # Modified for Tcl3D by Paul Obermeier 2009/08/29 # See www.tcl3d.org for the Tcl3D extension. package require Tk package require tcl3d 0.5.0 if { [info procs tcl3dHaveCg] ne "" } { if { ![tcl3dHaveCg] } { tk_messageBox -icon error -type ok -title "Error" \ -message "You do not have Cg installed." exit } } # Font to be used in the Tk listbox. set gDemo(listFont) {-family {Courier} -size 10} # Determine the directory of this script. set gDemo(scriptDir) [file dirname [info script]] # Julia set parameters set gDemo(maxIterations) 8 ; # maximum number of iterations used to determine convergence set gDemo(epsilon) 0.003 ; # precision of ray tracing set gDemo(morphTimer) 0.0 ; # morph interpolant parameter # camera parameters set gDemo(fov) 60.0 set gGui(defaultWindow,width) 384 set gGui(defaultWindow,height) 384 set gGui(shadowsOn) 0 ; # whether self-shadowing is being rendered set gGui(animateOn) 1 ; # whether we're randomly morphing among sets set gGui(spinOn) false set gGui(lastRotation) [tcl3dVector GLdouble 16] set gGui(curRotation) [tcl3dVector GLdouble 16] set gGui(stepSize) 0.05 set gGui(translateRate) 0.005 set gGui(lastZoom) 0.0 set gGui(zoom) 3.0 set gGui(zoomRate) 0.01 set gGui(spinRate) 0.2 set gGui(spinAngle) 0.0 set gGui(lastTranslate) {0.0 0.0} set gGui(translate) {0.0 0.0} set gDemo(light) [list 0.0 0.0 0.0 0.0] set gDemo(rotationAxis) [list 1.0 0.0 0.0] set gDemo(mup,0) 0.0 set gDemo(mup,1) 0.0 set gDemo(mup,2) 0.0 set gDemo(mup,3) 0.0 set gDemo(stopWatch) [tcl3dNewSwatch] tcl3dResetSwatch $gDemo(stopWatch) tcl3dStartSwatch $gDemo(stopWatch) set gDemo(frameCount) 0 set gDemo(lastTime) [tcl3dLookupSwatch $gDemo(stopWatch)] set gDemo(maxSpinDelay) 0.25 set gDemo(haveNeededProfile) true # Show errors occuring in the Togl callbacks. proc bgerror { msg } { tk_messageBox -icon error -type ok -message "Error: $msg\n\n$::errorInfo" exit } # Print info message into widget a the bottom of the window. proc PrintInfo { msg } { if { [winfo exists .fr.info] } { .fr.info configure -text $msg } } proc CopyVector { from to } { for { set i 0 } { $i < 16 } { incr i } { $to set $i [$from get $i] } } # Map a point in the window to a point on the unit sphere (used for rotation). proc GetSpherePoint { x y } { set viewport [tcl3dVector GLint 4] glGetIntegerv GL_VIEWPORT $viewport set mx [expr { 2.0 * $x / double ([$viewport get 2]) - 1.0}] set my [expr { 2.0 * $y / double ([$viewport get 3]) - 1.0}] set my [expr {-1.0 * $my}] set r [expr {sqrt ($mx*$mx + $my*$my)}] if { $r > 1 } { set r [expr {1.0 / $r}] set mx [expr {$mx * $r}] set my [expr {$my * $r}] set mz 0.0 } else { set mz [expr {sqrt (1.0 - $r)}] } set r [expr {1.0 / sqrt ($mx*$mx + $my*$my + $mz*$mz)}] set mx [expr {$mx * $r}] set my [expr {$my * $r}] set mz [expr {$mz * $r}] $viewport delete return [list $mx $my $mz] } # Handle mouse clicks (changes the view). proc ClickButtonPressLeft { x y } { global gDemo gGui set tmp $x set x $y set y $tmp set gDemo(startPoint) [GetSpherePoint $x $y] set gGui(spinOn) false set gGui(firstDrag) [tcl3dLookupSwatch $gDemo(stopWatch)] set gGui(lastX) -1 set gGui(lastY) -1 set gGui(lastZoom) $gGui(zoom) } proc ClickButtonPressMiddle { x y } { global gDemo gGui set tmp $x set x $y set y $tmp set gGui(lastX) $x set gGui(lastY) $y set gGui(lastZoom) $gGui(zoom) } proc ClickButtonPressRight { x y } { global gDemo gGui set tmp $x set x $y set y $tmp set gGui(lastX) $x set gGui(lastY) $y set gGui(lastTranslate) $gGui(translate) set gGui(lastZoom) $gGui(zoom) } proc ClickButtonRelease { x y } { global gDemo gGui set tmp $x set x $y set y $tmp CopyVector $gGui(curRotation) $gGui(lastRotation) set gGui(spinOn) true if { $gGui(lastX) == -1 || $gGui(lastY) == -1 } { set gGui(spinAngle) 0.0 } } # Rotate the modelview by the specified angle around the specified axis. proc Spin { theta v } { global gGui glMatrixMode GL_MODELVIEW glPushMatrix glLoadIdentity glRotatef $theta [lindex $v 0] [lindex $v 1] [lindex $v 2] glMultMatrixd [tcl3dVectorToList $gGui(lastRotation) 16] glGetDoublev GL_MODELVIEW_MATRIX $gGui(curRotation) glPopMatrix } # Handle mouse movement while a button is pressed. (Handles camera movement) proc DragLeft { x y } { global gDemo gGui set tmp $x set x $y set y $tmp foreach {end0 end1 end2} [GetSpherePoint $x $y] { break } foreach {start0 start1 start2} $gDemo(startPoint) { break } lset gDemo(rotationAxis) 0 [expr {$start1 * $end2 - $start2 * $end1}] lset gDemo(rotationAxis) 1 [expr {$start2 * $end0 - $start0 * $end2}] lset gDemo(rotationAxis) 2 [expr {$start0 * $end1 - $start1 * $end0}] set gDemo(rotationAngle) [expr {acos ( \ $start0*$end0 + $start1*$end1 + $start2*$end2)}] if { $gDemo(rotationAngle) < 0.001 } { CopyVector $gGui(lastRotation) $gGui(curRotation) return } set gDemo(rotationAngle) [tcl3dRadToDeg $gDemo(rotationAngle)] Spin [expr {-1.0*$gDemo(rotationAngle)}] $gDemo(rotationAxis) set dx [expr {double($gGui(lastX)) - $x}] set dy [expr {double($gGui(lastY)) - $y}] if { [tcl3dLookupSwatch $gDemo(stopWatch)] - $gGui(firstDrag) < $gDemo(maxSpinDelay) } { set gGui(spinAngle) [expr {sqrt ($dx*$dx + $dy*$dy) * $gGui(spinRate)}] } else { set gGui(spinAngle) 0.0 } set gGui(lastX) $x set gGui(lastY) $y } proc DragMiddle { x y } { global gDemo gGui set tmp $x set x $y set y $tmp set gGui(zoom) [expr {$gGui(lastZoom) + ($gGui(lastX) - $x) * $gGui(zoomRate)}] if { $gGui(zoom) < 0.0 } { set gGui(zoom) 0.0 } } proc DragRight { x y } { global gDemo gGui set tmp $x set x $y set y $tmp lset gGui(translate) 0 [expr {[lindex $gGui(lastTranslate) 0] + \ ($x - $gGui(lastX) )*$gGui(translateRate)}] lset gGui(translate) 1 [expr {[lindex $gGui(lastTranslate) 1] + \ ($gGui(lastY) - $y )*$gGui(translateRate)}] } # Convert transformation matrix into a basis for the space of the image plane. # This basis is used to generate the rays which are intersected with the Julia set. proc CalculateView {} { global gDemo gGui # First apply the view transformations to the initial eye, look at, and # up. These will be used later to determine the basis. set eyeStart { 0.0 0.0 1.0 1.0 } ; # eye starts on the unit sphere set lookatStart { 0.0 0.0 0.0 1.0 } ; # initially look at the origin set upStart { 0.0 1.0 0.0 0.0 } ; # up is initially along the y-axis # translate the eye and look at points lset eyeStart 0 [expr {[lindex $eyeStart 0] + [lindex $gGui(translate) 0]}] lset eyeStart 1 [expr {[lindex $eyeStart 1] + [lindex $gGui(translate) 1]}] lset eyeStart 2 [expr {[lindex $eyeStart 2] + $gGui(zoom)}] lset lookatStart 0 [expr {[lindex $lookatStart 0] + [lindex $gGui(translate) 0]}] lset lookatStart 1 [expr {[lindex $lookatStart 1] + [lindex $gGui(translate) 1]}] lset lookatStart 2 [expr {[lindex $lookatStart 2] + $gGui(zoom)}] # rotate eye, lookat, and up by multiplying them with the current rotation matrix for { set i 0 } { $i < 4 } { incr i } { set gDemo(eye,$i) 0.0 set gDemo(lookAt,$i) 0.0 set gDemo(up,$i) 0.0 for { set j 0 } { $j < 4 } { incr j } { set ind [expr {$i*4 + $j}] set matElem [$gGui(curRotation) get $ind] set gDemo(eye,$i) [expr {$gDemo(eye,$i) + $matElem*[lindex $eyeStart $j]}] set gDemo(lookAt,$i) [expr {$gDemo(lookAt,$i) + $matElem*[lindex $lookatStart $j]}] set gDemo(up,$i) [expr {$gDemo(up,$i) + $matElem*[lindex $upStart $j]}] } } # Now we construct the basis: # # N = (look at) - (eye) # T = up # B = N x T # find and normalize N = (lookat - eye) for { set i 0 } { $i<3 } {incr i} { set gDemo(N,$i) [expr {$gDemo(lookAt,$i) - $gDemo(eye,$i)}] } set mag [expr {1.0 / sqrt($gDemo(N,0)*$gDemo(N,0) + \ $gDemo(N,1)*$gDemo(N,1) + \ $gDemo(N,2)*$gDemo(N,2))}] for { set i 0 } { $i<3 } {incr i} { set gDemo(N,$i) [expr {$gDemo(N,$i) * $mag}] } # find and normalize T = up for { set i 0 } { $i<3 } {incr i} { set gDemo(T,$i) $gDemo(up,$i) } set mag [expr {1.0 / sqrt($gDemo(T,0)*$gDemo(T,0) + \ $gDemo(T,1)*$gDemo(T,1) + \ $gDemo(T,2)*$gDemo(T,2))}] for { set i 0 } { $i<3 } {incr i} { set gDemo(T,$i) [expr {$gDemo(T,$i) * $mag}] } # find B = N x T (already unit length) set gDemo(B,0) [expr {$gDemo(N,1)*$gDemo(T,2) - $gDemo(N,2)*$gDemo(T,1)}] set gDemo(B,1) [expr {$gDemo(N,2)*$gDemo(T,0) - $gDemo(N,0)*$gDemo(T,2)}] set gDemo(B,2) [expr {$gDemo(N,0)*$gDemo(T,1) - $gDemo(N,1)*$gDemo(T,0)}] # we also use this basis to determine the light position # (move the light a little bit up and to the right of the eye). for { set i 0 } { $i<3 } {incr i} { lset gDemo(light) $i [expr {$gDemo(eye,$i) + $gDemo(B,$i) * 0.5}] lset gDemo(light) $i [expr {$gDemo(eye,$i) + $gDemo(T,$i) * 0.5}] } } # Setup state required to run Cg shaders. Namely, a context # and profiles for fragment and vertex programs. proc InitializeCg {} { global gDemo gGui set retVal true tcl3dCgResetError set gDemo(context) [cgCreateContext] if { $gDemo(context) eq "NULL" } { set gDemo(errMsg) "Could not create Cg context" set retVal false } set gDemo(fragmentProfile) $::CG_PROFILE_FP40 if { $retVal && [tcl3dCgFindProfile $gDemo(fragmentProfile)] eq "" } { set gDemo(errMsg) "Program needs at least CG_PROFILE_FP40" set retVal false } set gDemo(vertexProfile) $::CG_PROFILE_VP40 if { $retVal && [tcl3dCgFindProfile $gDemo(vertexProfile)] eq "" } { set gDemo(errMsg) "Program needs at least CG_PROFILE_VP40" set retVal false } cgGLSetOptimalOptions $gDemo(fragmentProfile) cgGLSetOptimalOptions $gDemo(vertexProfile) return $retVal } # Reload the vertex and fragment shader from disk. If the # shaders don't compile, print a warning and use the previously # compiled shader. This function lets us modify the shaders # while the program is running, and behaves nicely if we make a typo. proc ReloadShaders {} { global gDemo gGui gCg set args { "-unroll" "none" 0 } set fragmentFn [tcl3dGetExtFile [file join $gDemo(scriptDir) "QJuliaFragment.cg"]] set vertexFn [tcl3dGetExtFile [file join $gDemo(scriptDir) "QJuliaVertex.cg"]] set newProgram [cgCreateProgramFromFile $gDemo(context) CG_SOURCE $fragmentFn \ $gDemo(fragmentProfile) "main" $args] set retVal [tcl3dCgGetError $gDemo(context)] if { $retVal ne "" } { error "cgCreateProgramFromFile $fragmentFn : $retVal" } else { if { [info exists gDemo(fragmentProgram)] } { cgDestroyProgram $gDemo(fragmentProgram) } set gDemo(fragmentProgram) $newProgram cgGLLoadProgram $gDemo(fragmentProgram) } set newProgram [cgCreateProgramFromFile $gDemo(context) CG_SOURCE $vertexFn \ $gDemo(vertexProfile) "main" $args] set retVal [tcl3dCgGetError $gDemo(context)] if { $retVal ne "" } { error "cgCreateProgramFromFile $vertexFn : $retVal" } else { if { [info exists gDemo(vertexProgram)] } { cgDestroyProgram $gDemo(vertexProgram) } set gDemo(vertexProgram) $newProgram cgGLLoadProgram $gDemo(vertexProgram) } # shader parameters -- these are handles to uniform parameters in the shader # gCg(constant) -- constant specifying the Julia set # gCg(eyeP) -- eye location # gCg(lightP) -- light location # gCg(shadowP) -- flag for drawing shadows # gCg(iterationsP) -- maximum number of iterations used to determine convergence # gCg(epsilonP) -- precision of ray tracing # get a handle to some fragment shader parameters set gCg(constant) [cgGetNamedParameter $gDemo(fragmentProgram) "mu" ] set gCg(eyeP) [cgGetNamedParameter $gDemo(fragmentProgram) "eye" ] set gCg(lightP) [cgGetNamedParameter $gDemo(fragmentProgram) "light" ] set gCg(shadowP) [cgGetNamedParameter $gDemo(fragmentProgram) "renderShadows"] set gCg(iterationsP) [cgGetNamedParameter $gDemo(fragmentProgram) "maxIterations"] set gCg(epsilonP) [cgGetNamedParameter $gDemo(fragmentProgram) "epsilon" ] } proc CreateCallback { toglwin } { global gDemo gGui glClearColor 0.2 0.2 0.3 0.0 ; # background color set gDemo(randGen) [tcl3dNewRandomGen 0] ; # seed the morph sequence # Setup Cg and load the shaders used for ray tracing. if { ![InitializeCg] } { set gDemo(haveNeededProfile) false } else { ReloadShaders } # set the initial constant which specifies the Julia set set gDemo(mu,0) 0.0 set gDemo(mu,1) 0.0 set gDemo(mu,2) 0.0 set gDemo(mu,3) 0.0 # set the morph timer to 100% so on the next frame we generate a new # constant for the target Julia set and start morphing to it. set gDemo(morphTimer) 1.0 # start the view rotation matrices with the identity matrix glMatrixMode GL_MODELVIEW glLoadIdentity glGetDoublev GL_MODELVIEW_MATRIX $gGui(lastRotation) glGetDoublev GL_MODELVIEW_MATRIX $gGui(curRotation) } # Get the interpolated constant for the current time (used for mophing # between two Julia sets). proc GetCurMu { t } { global gDemo for { set i 0 } { $i < 4 } { incr i } { lappend cur [expr {(1.0 - $t) * $gDemo(mu,$i) + $t * $gDemo(mup,$i)}] } return $cur } # This draw function is called every frame and is ultimately the thing that generates the # ray traced image. It generates rays by drawing a fullscreen quad where # ray attributes are specified on the vertices. These attributes are # interpolated across the quad, generating a unique ray for every fragment on # the screen. These rays are then sent to the fragment processor which # runs the ray-Julia intersection shader (found in QJuliaFragment.cg). proc DisplayCallback { toglwin } { global gDemo gGui gCg glClear GL_COLOR_BUFFER_BIT glMatrixMode GL_MODELVIEW glLoadIdentity if { ! $gDemo(haveNeededProfile) } { $toglwin swapbuffer return } # determine the basis for the image plane -- used to generate rays CalculateView # enable the vertex and fragment programs cgGLEnableProfile $gDemo(vertexProfile) cgGLBindProgram $gDemo(vertexProgram) cgGLEnableProfile $gDemo(fragmentProfile) cgGLBindProgram $gDemo(fragmentProgram) # determine the constant of the current Julia set being rendered and # pass it to the fragment program set curMu [GetCurMu $gDemo(morphTimer)] cgGLSetParameter4fv $gCg(constant) $curMu # send some other flags / parameters to the fragment program cgGLSetParameter1f $gCg(shadowP) $gGui(shadowsOn) cgGLSetParameter1f $gCg(iterationsP) $gDemo(maxIterations) cgGLSetParameter1f $gCg(epsilonP) $gDemo(epsilon) cgGLSetParameter3f $gCg(eyeP) $gDemo(eye,0) $gDemo(eye,1) $gDemo(eye,2) cgGLSetParameter3f $gCg(lightP) [lindex $gDemo(light) 0] \ [lindex $gDemo(light) 1] \ [lindex $gDemo(light) 2] # draw a fullscreen quad using the ray tracing fragment shader glBegin GL_QUADS # Rays are specified by determining the ray from the eye to each of the # corners of the image plane (fullscreen quad). The image plane is # unit distance in front of the eye along the look at direction. The # size of the image plane is determined by the field of view (fov) and # the aspect ratio of the current window. To find the rays through a # corner, we calculate the world space position of the corner and # subtract the eye. The position of a corner is found by adding # 1 times the look at direction, width times the tangent direction, and # height times the bitangent direction to the eye location. set beta [expr {tan([tcl3dDegToRad $gDemo(fov)]/2.0)}] ; # find height set alpha [expr {$beta * $gDemo(aspect)}] ; # find width # specify the ray origins with texture coordinates - in our case this # is always the same as the eye location glTexCoord4f $gDemo(eye,0) $gDemo(eye,1) $gDemo(eye,2) 0 # specify ray directions with colors # upper left corner glColor4f [expr {-1.0*$alpha*$gDemo(T,0) - $beta*$gDemo(B,0) + $gDemo(N,0)}] \ [expr {-1.0*$alpha*$gDemo(T,1) - $beta*$gDemo(B,1) + $gDemo(N,1)}] \ [expr {-1.0*$alpha*$gDemo(T,2) - $beta*$gDemo(B,2) + $gDemo(N,2)}] 0 glVertex2f -1 -1 # upper right corner glColor4f [expr {$alpha*$gDemo(T,0) - $beta*$gDemo(B,0) + $gDemo(N,0)}] \ [expr {$alpha*$gDemo(T,1) - $beta*$gDemo(B,1) + $gDemo(N,1)}] \ [expr {$alpha*$gDemo(T,2) - $beta*$gDemo(B,2) + $gDemo(N,2)}] 0 glVertex2f 1 -1 # lower right corner glColor4f [expr {$alpha*$gDemo(T,0) + $beta*$gDemo(B,0) + $gDemo(N,0)}] \ [expr {$alpha*$gDemo(T,1) + $beta*$gDemo(B,1) + $gDemo(N,1)}] \ [expr {$alpha*$gDemo(T,2) + $beta*$gDemo(B,2) + $gDemo(N,2)}] 0 glVertex2f 1 1 # lower left corner glColor4f [expr {-1.0*$alpha*$gDemo(T,0) + $beta*$gDemo(B,0) + $gDemo(N,0)}] \ [expr {-1.0*$alpha*$gDemo(T,1) + $beta*$gDemo(B,1) + $gDemo(N,1)}] \ [expr {-1.0*$alpha*$gDemo(T,2) + $beta*$gDemo(B,2) + $gDemo(N,2)}] 0 glVertex2f -1 1 glEnd # turn off the shaders cgGLDisableProfile $gDemo(fragmentProfile) cgGLDisableProfile $gDemo(vertexProfile) DisplayFPS $toglwin swapbuffer } # toggle morphing on / off (Key-m) proc ToggleMorph { toglwin } { global gDemo gGui if { $gGui(animateOn) } { set curMu [GetCurMu $gDemo(morphTimer)] for { set i 0 } { $i < 4 } { incr i } { set gDemo(mu,$i) [format "%.8f" [lindex $curMu $i]] } set gDemo(morphTimer) 0.0 } set gDemo(mup,0) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,1) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,2) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,3) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gGui(animateOn) [expr {! $gGui(animateOn)}] $toglwin postredisplay } proc ToggleMorphByBtn { toglwin } { global gDemo gGui set gGui(animateOn) [expr {! $gGui(animateOn)}] ToggleMorph $toglwin } # increase the maximum number of iterations used to determine convergence # (increases the detail of the rendering) (Key-+) proc IncrIterations { toglwin } { global gDemo gGui incr gDemo(maxIterations) if { $gDemo(maxIterations) > 20 } { set gDemo(maxIterations) 20 } $toglwin postredisplay } # decrease the maximum number of iterations used to determine convergence # (decreases the detail of the rendering) (Key--) proc DecrIterations { toglwin } { global gDemo gGui incr gDemo(maxIterations) -1 if { $gDemo(maxIterations) < 1 } { set gDemo(maxIterations) 1 } $toglwin postredisplay } # increase the rendering precision (Key-[) proc IncrPrecision { toglwin } { global gDemo gGui set gDemo(epsilon) [expr {$gDemo(epsilon) + 0.0001}] $toglwin postredisplay } # decrease the rendering precision (Key-]) proc DecrPrecision { toglwin } { global gDemo gGui set gDemo(epsilon) [expr {$gDemo(epsilon) - 0.0001}] if { $gDemo(epsilon) < 0.0001 } { set gDemo(epsilon) 0.0001 } $toglwin postredisplay } # toggle self-shadowing on / off (Key-s) proc ToggleShadowing { toglwin } { global gDemo gGui set gGui(shadowsOn) [expr {!$gGui(shadowsOn)}] $toglwin postredisplay } # i/I, j/J, k/K, and l/L explicitly change the components # of the quaternion Julia constant proc ChangeComponent { toglwin comp dir } { global gDemo gGui set gDemo(mu,$comp) [expr {$gDemo(mu,$comp) + $dir * $gGui(stepSize)}] $toglwin postredisplay } # reload shaders from disk (Key-r) proc ReloadShader { toglwin } { ReloadShaders $toglwin postredisplay } proc GetFPS { { elapsedFrames 1 } } { global gDemo set currentTime [tcl3dLookupSwatch $gDemo(stopWatch)] set fps [expr $elapsedFrames / ($currentTime - $gDemo(lastTime))] set ::gDemo(lastTime) $currentTime return $fps } proc DisplayFPS {} { global gDemo incr gDemo(frameCount) if { $gDemo(frameCount) == 50 } { set msg [format "Using Cg version %s (%.0f fps)" \ [tcl3dCgGetVersion] [GetFPS $gDemo(frameCount)]] .fr.usage configure -state normal .fr.usage delete 0 end .fr.usage insert end $msg .fr.usage configure -state disabled set gDemo(frameCount) 0 } } # Handle animation or other tasks performed every frame. proc Animate {} { global gDemo gGui # handle spinning the camera if the mouse was released quickly if { $gGui(spinOn) } { Spin [expr {-1.0*$gGui(spinAngle)}] $gDemo(rotationAxis) CopyVector $gGui(curRotation) $gGui(lastRotation) } # handle automatic morphing from one Julia set to another if { $gGui(animateOn) } { set gDemo(morphTimer) [expr {$gDemo(morphTimer) + 0.01}] if { $gDemo(morphTimer) >= 1.0 } { set gDemo(morphTimer) 0.0 set gDemo(mu,0) [format "%.8f" $gDemo(mup,0)] set gDemo(mu,1) [format "%.8f" $gDemo(mup,1)] set gDemo(mu,2) [format "%.8f" $gDemo(mup,2)] set gDemo(mu,3) [format "%.8f" $gDemo(mup,3)] set gDemo(mup,0) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,1) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,2) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] set gDemo(mup,3) [expr {2.0 * [tcl3dGetRandomFloat $gDemo(randGen) 0 1] - 1.0}] } } .fr.toglwin postredisplay set ::animateId [tcl3dAfterIdle Animate] } proc StartAnimation {} { if { ! [info exists ::animateId] } { Animate } } proc StopAnimation {} { if { [info exists ::animateId] } { after cancel $::animateId unset ::animateId } } proc ReshapeCallback { toglwin { w -1 } { h -1 } } { global gDemo set w [$toglwin width] set h [$toglwin height] glViewport 0 0 $w $h set gDemo(aspect) [expr {double($w)/double($h)}] glMatrixMode GL_PROJECTION glLoadIdentity glOrtho -1 1 -1 1 0 100 glMatrixMode GL_MODELVIEW glLoadIdentity } proc Cleanup {} { global gDemo gGui $gGui(lastRotation) delete $gGui(curRotation) delete tcl3dDeleteSwatch $gDemo(stopWatch) cgDestroyContext $gDemo(context) catch { unset ::animateId } unset gDemo unset gGui } # Put all exit related code here. proc ExitProg {} { exit } # Create the OpenGL window and some Tk helper widgets. proc CreateWindow {} { global gDemo gGui frame .fr pack .fr -expand 1 -fill both togl .fr.toglwin -width $gGui(defaultWindow,width) -height $gGui(defaultWindow,height) \ -double true -depth true \ -createproc CreateCallback \ -reshapeproc ReshapeCallback \ -displayproc DisplayCallback frame .fr.btns listbox .fr.usage -font $gDemo(listFont) -height 1 label .fr.info grid .fr.toglwin -row 0 -column 0 -sticky news grid .fr.btns -row 0 -column 1 -sticky news -rowspan 2 grid .fr.usage -row 1 -column 0 -sticky news grid .fr.info -row 2 -column 0 -sticky news -columnspan 2 grid rowconfigure .fr 0 -weight 1 grid columnconfigure .fr 0 -weight 1 labelframe .fr.btns.frCmds -text "Commands" labelframe .fr.btns.frJul -text "Julia settings" pack .fr.btns.frCmds .fr.btns.frJul -side top -fill x -padx 2 checkbutton .fr.btns.frCmds.cbMorph -text "Morphing (m)" \ -variable gGui(animateOn) -command "ToggleMorphByBtn .fr.toglwin" \ -anchor w pack .fr.btns.frCmds.cbMorph -side top -expand 1 -fill x checkbutton .fr.btns.frCmds.cbShadow -text "Shadows (s)" \ -variable gGui(shadowsOn) -command ".fr.toglwin postredisplay" \ -anchor w pack .fr.btns.frCmds.cbShadow -side top -expand 1 -fill x label .fr.btns.frJul.l_iter -text "Iterations (-/+):" -anchor w spinbox .fr.btns.frJul.e_iter -textvariable gDemo(maxIterations) \ -command ".fr.toglwin postredisplay" -width 10 \ -from 1 -to 20 -increment 1 grid .fr.btns.frJul.l_iter -row 0 -column 0 -sticky news grid .fr.btns.frJul.e_iter -row 0 -column 1 -sticky news label .fr.btns.frJul.l_eps -text "Precision (b/n):" -anchor w spinbox .fr.btns.frJul.e_eps -textvariable gDemo(epsilon) \ -command ".fr.toglwin postredisplay" -width 10 \ -from 0.0001 -to 0.1 -increment 0.0001 grid .fr.btns.frJul.l_eps -row 1 -column 0 -sticky news grid .fr.btns.frJul.e_eps -row 1 -column 1 -sticky news label .fr.btns.frJul.l_re -text "Real (l/L):" -anchor w spinbox .fr.btns.frJul.e_re -textvariable gDemo(mu,0) \ -command ".fr.toglwin postredisplay" -width 10 \ -from -2.0 -to 2.0 -increment $gGui(stepSize) grid .fr.btns.frJul.l_re -row 2 -column 0 -sticky news grid .fr.btns.frJul.e_re -row 2 -column 1 -sticky news label .fr.btns.frJul.l_im1 -text "Im 1 (i/I):" -anchor w spinbox .fr.btns.frJul.e_im1 -textvariable gDemo(mu,1) \ -command ".fr.toglwin postredisplay" -width 10 \ -from -2.0 -to 2.0 -increment $gGui(stepSize) grid .fr.btns.frJul.l_im1 -row 3 -column 0 -sticky news grid .fr.btns.frJul.e_im1 -row 3 -column 1 -sticky news label .fr.btns.frJul.l_im2 -text "Im 2 (j/J):" -anchor w spinbox .fr.btns.frJul.e_im2 -textvariable gDemo(mu,2) \ -command ".fr.toglwin postredisplay" -width 10 \ -from -2.0 -to 2.0 -increment $gGui(stepSize) grid .fr.btns.frJul.l_im2 -row 4 -column 0 -sticky news grid .fr.btns.frJul.e_im2 -row 4 -column 1 -sticky news label .fr.btns.frJul.l_im3 -text "Im 3 (k/K):" -anchor w spinbox .fr.btns.frJul.e_im3 -textvariable gDemo(mu,3) \ -command ".fr.toglwin postredisplay" -width 10 \ -from -2.0 -to 2.0 -increment $gGui(stepSize) grid .fr.btns.frJul.l_im3 -row 5 -column 0 -sticky news grid .fr.btns.frJul.e_im3 -row 5 -column 1 -sticky news wm title . "Tcl3D demo: Keenan Crane's Quaternion Julia Sets" wm protocol . WM_DELETE_WINDOW "ExitProg" bind .