source: trunk/gui/scripts/heightmapviewer.tcl @ 909

Last change on this file since 909 was 909, checked in by gah, 17 years ago

VisViewer? base class

File size: 35.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: heightmapviewer - 3D volume rendering
3#
4#  This widget performs volume rendering on 3D scalar/vector datasets.
5#  It connects to the Nanovis server running on a rendering farm,
6#  transmits data, and displays the results.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14
15package require Itk
16package require BLT
17package require Img
18
19option add *HeightmapViewer.width 4i widgetDefault
20option add *HeightmapViewer.height 4i widgetDefault
21option add *HeightmapViewer.foreground black widgetDefault
22option add *HeightmapViewer.controlBackground gray widgetDefault
23option add *HeightmapViewer.controlDarkBackground #999999 widgetDefault
24option add *HeightmapViewer.plotBackground black widgetDefault
25option add *HeightmapViewer.plotForeground white widgetDefault
26option add *HeightmapViewer.plotOutline white widgetDefault
27option add *HeightmapViewer.font \
28    -*-helvetica-medium-r-normal-*-12-* widgetDefault
29
30# must use this name -- plugs into Rappture::resources::load
31proc HeightmapViewer_init_resources {} {
32    Rappture::resources::register \
33        nanovis_server [list Rappture::VisViewer::SetServerList "nanovis"]
34}
35
36itcl::class Rappture::HeightmapViewer {
37    inherit Rappture::VisViewer
38
39    itk_option define -plotforeground plotForeground Foreground ""
40    itk_option define -plotbackground plotBackground Background ""
41    itk_option define -plotoutline plotOutline PlotOutline ""
42
43    constructor { hostlist args } {
44        Rappture::VisViewer::constructor $hostlist
45    } {
46        # defined below
47    }
48    destructor {
49        # defined below
50    }
51
52    public method isconnected {}
53    public method add {dataobj {settings ""}}
54    public method get {args}
55    public method delete {args}
56    public method scale {args}
57    public method download {option args}
58    public method parameters {title args} {
59        # do nothing
60    }
61    protected method Connect {}
62    protected method Disconnect {}
63
64    protected method _send {string}
65    protected method _send_dataobjs {}
66    protected method _receive_image {option size}
67    protected method _receive_legend {ivol vmin vmax size}
68    protected method _receive_echo {channel {data ""}}
69    protected method _receive_data {args}
70
71    protected method _rebuild {}
72    protected method _zoom {option}
73    protected method _move {option x y}
74
75    protected method _state {comp}
76    protected method _fixSettings {what {value ""}}
77    protected method _getTransfuncData {dataobj comp}
78
79
80    private variable _outbuf       ;# buffer for outgoing commands
81
82    private variable _dlist ""     ;# list of data objects
83    private variable _dims ""      ;# dimensionality of data objects
84    private variable _obj2style    ;# maps dataobj => style settings
85    private variable _obj2ovride   ;# maps dataobj => style override
86    private variable _obj2id       ;# maps dataobj => heightmap ID in server
87    private variable _id2obj       ;# maps heightmap ID => dataobj in server
88    private variable _sendobjs ""  ;# list of data objs to send to server
89    private variable _receiveids   ;# list of data responses from the server
90    private variable _click        ;# info used for _move operations
91    private variable _limits       ;# autoscale min/max for all axes
92    private variable _view         ;# view params for 3D view
93
94    private common _settings      ;# Array used for checkbuttons and radiobuttons
95                       
96}
97
98itk::usual HeightmapViewer {
99    keep -background -foreground -cursor -font
100    keep -plotbackground -plotforeground
101}
102
103# ----------------------------------------------------------------------
104# CONSTRUCTOR
105# ----------------------------------------------------------------------
106itcl::body Rappture::HeightmapViewer::constructor {hostlist args} {
107    # Draw legend event
108    $_dispatcher register !legend
109    $_dispatcher dispatch $this !legend \
110        "[itcl::code $this _fixSettings legend]; list"
111    # Send dataobjs event
112    $_dispatcher register !send_dataobjs
113    $_dispatcher dispatch $this !send_dataobjs \
114        "[itcl::code $this _send_dataobjs]; list"
115    # Rebuild event
116    $_dispatcher register !rebuild
117    $_dispatcher dispatch $this !rebuild "[itcl::code $this _rebuild]; list"
118
119    set _outbuf ""
120
121    #
122    # Populate parser with commands handle incoming requests
123    #
124    $_parser alias image [itcl::code $this _receive_image]
125    $_parser alias legend [itcl::code $this _receive_legend]
126    $_parser alias data [itcl::code $this _receive_data]
127
128    set _view(theta) 45
129    set _view(phi) 45
130    set _view(psi) 0
131    set _view(zoom) 1
132    set _view(xfocus) 0
133    set _view(yfocus) 0
134    set _view(zfocus) 0
135    set _obj2id(count) 0
136
137    itk_component add zoom {
138        frame $itk_component(controls).zoom
139    } {
140        usual
141        rename -background -controlbackground controlBackground Background
142    }
143    pack $itk_component(zoom) -side top
144
145    itk_component add reset {
146        button $itk_component(zoom).reset \
147            -borderwidth 1 -padx 1 -pady 1 \
148            -bitmap [Rappture::icon reset] \
149            -command [itcl::code $this _zoom reset]
150    } {
151        usual
152        ignore -borderwidth
153        rename -highlightbackground -controlbackground controlBackground Background
154    }
155    pack $itk_component(reset) -side left -padx {4 1} -pady 4
156    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
157
158    itk_component add zoomin {
159        button $itk_component(zoom).zin \
160            -borderwidth 1 -padx 1 -pady 1 \
161            -bitmap [Rappture::icon zoomin] \
162            -command [itcl::code $this _zoom in]
163    } {
164        usual
165        ignore -borderwidth
166        rename -highlightbackground -controlbackground controlBackground Background
167    }
168    pack $itk_component(zoomin) -side left -padx 1 -pady 4
169    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
170
171    itk_component add zoomout {
172        button $itk_component(zoom).zout \
173            -borderwidth 1 -padx 1 -pady 1 \
174            -bitmap [Rappture::icon zoomout] \
175            -command [itcl::code $this _zoom out]
176    } {
177        usual
178        ignore -borderwidth
179        rename -highlightbackground -controlbackground controlBackground Background
180    }
181    pack $itk_component(zoomout) -side left -padx {1 4} -pady 4
182    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
183
184    #
185    # Settings panel...
186    #
187    itk_component add settings {
188        button $itk_component(controls).settings -text "Settings..." \
189            -borderwidth 1 -relief flat -overrelief raised \
190            -padx 2 -pady 1 \
191            -command [list $itk_component(controls).panel activate $itk_component(controls).settings left]
192    } {
193        usual
194        ignore -borderwidth
195        rename -background -controlbackground controlBackground Background
196        rename -highlightbackground -controlbackground controlBackground Background
197    }
198    pack $itk_component(settings) -side top -pady 8
199
200    Rappture::Balloon $itk_component(controls).panel -title "Settings"
201    set inner [$itk_component(controls).panel component inner]
202   
203    frame $inner.f
204    pack $inner.f -side top -fill x
205    grid columnconfigure $inner.f 1 -weight 1
206    set fg [option get $itk_component(hull) font Font]
207   
208    set ::Rappture::HeightmapViewer::_settings($this-grid) 1
209    ::checkbutton $inner.f.grid \
210        -text "Show Grid" \
211        -variable ::Rappture::HeightmapViewer::_settings($this-grid) \
212        -command [itcl::code $this _fixSettings grid]
213    grid $inner.f.grid -row 0 -column 0 -sticky w
214
215    set ::Rappture::HeightmapViewer::_settings($this-axes) 1
216    ::checkbutton $inner.f.axes \
217        -text "Show Axes" \
218        -variable ::Rappture::HeightmapViewer::_settings($this-axes) \
219        -command [itcl::code $this _fixSettings axes]
220    grid $inner.f.axes -row 1 -column 0 -sticky w
221
222    set ::Rappture::HeightmapViewer::_settings($this-contourlines) 1
223    ::checkbutton $inner.f.contour \
224        -text "Show Contour Lines" \
225        -variable ::Rappture::HeightmapViewer::_settings($this-contourlines) \
226        -command [itcl::code $this _fixSettings contourlines]
227    grid $inner.f.contour -row 2 -column 0 -sticky w
228
229
230    # Legend
231    set _image(legend) [image create photo]
232    itk_component add legend {
233        canvas $itk_component(area).legend -height 50 -highlightthickness 0
234    } {
235        usual
236        ignore -highlightthickness
237        rename -background -plotbackground plotBackground Background
238    }
239    pack $itk_component(legend) -side bottom -fill x
240    bind $itk_component(legend) <Configure> \
241        [list $_dispatcher event -idle !legend]
242
243    # set up bindings for rotation
244    bind $itk_component(3dview) <ButtonPress> \
245        [itcl::code $this _move click %x %y]
246    bind $itk_component(3dview) <B1-Motion> \
247        [itcl::code $this _move drag %x %y]
248    bind $itk_component(3dview) <ButtonRelease> \
249        [itcl::code $this _move release %x %y]
250    bind $itk_component(3dview) <Configure> \
251        [itcl::code $this _send "screen %w %h"]
252
253    set _image(download) [image create photo]
254
255    eval itk_initialize $args
256
257    Connect
258}
259
260# ----------------------------------------------------------------------
261# DESTRUCTOR
262# ----------------------------------------------------------------------
263itcl::body Rappture::HeightmapViewer::destructor {} {
264    set _sendobjs ""  ;# stop any send in progress
265    $_dispatcher cancel !rebuild
266    $_dispatcher cancel !send_dataobjs
267    image delete $_image(plot)
268    image delete $_image(legend)
269    image delete $_image(download)
270}
271
272# ----------------------------------------------------------------------
273# USAGE: add <dataobj> ?<settings>?
274#
275# Clients use this to add a data object to the plot.  The optional
276# <settings> are used to configure the plot.  Allowed settings are
277# -color, -brightness, -width, -linestyle, and -raise.
278# ----------------------------------------------------------------------
279itcl::body Rappture::HeightmapViewer::add {dataobj {settings ""}} {
280    array set params {
281        -color auto
282        -width 1
283        -linestyle solid
284        -brightness 0
285        -raise 0
286        -description ""
287        -param ""
288    }
289    foreach {opt val} $settings {
290        if {![info exists params($opt)]} {
291            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
292        }
293        set params($opt) $val
294    }
295    if {$params(-color) == "auto" || $params(-color) == "autoreset"} {
296        # can't handle -autocolors yet
297        set params(-color) black
298    }
299
300    set pos [lsearch -exact $dataobj $_dlist]
301    if {$pos < 0} {
302        lappend _dlist $dataobj
303        set _obj2ovride($dataobj-color) $params(-color)
304        set _obj2ovride($dataobj-width) $params(-width)
305        set _obj2ovride($dataobj-raise) $params(-raise)
306        $_dispatcher event -idle !rebuild
307    }
308}
309
310# ----------------------------------------------------------------------
311# USAGE: get ?-objects?
312# USAGE: get ?-image 3dview|legend?
313#
314# Clients use this to query the list of objects being plotted, in
315# order from bottom to top of this result.  The optional "-image"
316# flag can also request the internal images being shown.
317# ----------------------------------------------------------------------
318itcl::body Rappture::HeightmapViewer::get {args} {
319    if {[llength $args] == 0} {
320        set args "-objects"
321    }
322
323    set op [lindex $args 0]
324    switch -- $op {
325      -objects {
326        # put the dataobj list in order according to -raise options
327        set dlist $_dlist
328        foreach obj $dlist {
329            if { [info exists _obj2ovride($obj-raise)] &&
330                 $_obj2ovride($obj-raise)} {
331                set i [lsearch -exact $dlist $obj]
332                if {$i >= 0} {
333                    set dlist [lreplace $dlist $i $i]
334                    lappend dlist $obj
335                }
336            }
337        }
338        return $dlist
339      }
340      -image {
341        if {[llength $args] != 2} {
342            error "wrong # args: should be \"get -image 3dview|legend\""
343        }
344        switch -- [lindex $args end] {
345            3dview {
346                return $_image(plot)
347            }
348            legend {
349                return $_image(legend)
350            }
351            default {
352                error "bad image name \"[lindex $args end]\": should be 3dview or legend"
353            }
354        }
355      }
356      default {
357        error "bad option \"$op\": should be -objects or -image"
358      }
359    }
360}
361
362# ----------------------------------------------------------------------
363# USAGE: delete ?<dataobj1> <dataobj2> ...?
364#
365# Clients use this to delete a dataobj from the plot.  If no dataobjs
366# are specified, then all dataobjs are deleted.
367# ----------------------------------------------------------------------
368itcl::body Rappture::HeightmapViewer::delete {args} {
369    if {[llength $args] == 0} {
370        set args $_dlist
371    }
372
373    # delete all specified dataobjs
374    set changed 0
375    foreach dataobj $args {
376        set pos [lsearch -exact $_dlist $dataobj]
377        if {$pos >= 0} {
378            set _dlist [lreplace $_dlist $pos $pos]
379            foreach key [array names _obj2ovride $dataobj-*] {
380                unset _obj2ovride($key)
381            }
382            set changed 1
383        }
384    }
385
386    # if anything changed, then rebuild the plot
387    if {$changed} {
388        $_dispatcher event -idle !rebuild
389    }
390}
391
392# ----------------------------------------------------------------------
393# USAGE: scale ?<data1> <data2> ...?
394#
395# Sets the default limits for the overall plot according to the
396# limits of the data for all of the given <data> objects.  This
397# accounts for all objects--even those not showing on the screen.
398# Because of this, the limits are appropriate for all objects as
399# the user scans through data in the ResultSet viewer.
400# ----------------------------------------------------------------------
401itcl::body Rappture::HeightmapViewer::scale {args} {
402    foreach val {xmin xmax ymin ymax zmin zmax vmin vmax} {
403        set _limits($val) ""
404    }
405    foreach obj $args {
406        foreach axis {x y z v} {
407            foreach {min max} [$obj limits $axis] break
408            if {"" != $min && "" != $max} {
409                if {"" == $_limits(${axis}min)} {
410                    set _limits(${axis}min) $min
411                    set _limits(${axis}max) $max
412                } else {
413                    if {$min < $_limits(${axis}min)} {
414                        set _limits(${axis}min) $min
415                    }
416                    if {$max > $_limits(${axis}max)} {
417                        set _limits(${axis}max) $max
418                    }
419                }
420            }
421        }
422    }
423}
424
425# ----------------------------------------------------------------------
426# USAGE: download coming
427# USAGE: download controls <downloadCommand>
428# USAGE: download now
429#
430# Clients use this method to create a downloadable representation
431# of the plot.  Returns a list of the form {ext string}, where
432# "ext" is the file extension (indicating the type of data) and
433# "string" is the data itself.
434# ----------------------------------------------------------------------
435itcl::body Rappture::HeightmapViewer::download {option args} {
436    switch $option {
437        coming {
438            if {[catch {
439                blt::winop snap $itk_component(area) $_image(download)
440            }]} {
441                $_image(download) configure -width 1 -height 1
442                $_image(download) put #000000
443            }
444        }
445        controls {
446            # no controls for this download yet
447            return ""
448        }
449        now {
450            #
451            # Hack alert!  Need data in binary format,
452            # so we'll save to a file and read it back.
453            #
454            set tmpfile /tmp/image[pid].jpg
455            $_image(download) write $tmpfile -format jpeg
456            set fid [open $tmpfile r]
457            fconfigure $fid -encoding binary -translation binary
458            set bytes [read $fid]
459            close $fid
460            file delete -force $tmpfile
461
462            return [list .jpg $bytes]
463        }
464        default {
465            error "bad option \"$option\": should be coming, controls, now"
466        }
467    }
468}
469
470# ----------------------------------------------------------------------
471# USAGE: Connect ?<host:port>,<host:port>...?
472#
473# Clients use this method to establish a connection to a new
474# server, or to reestablish a connection to the previous server.
475# Any existing connection is automatically closed.
476# ----------------------------------------------------------------------
477itcl::body Rappture::HeightmapViewer::Connect {} {
478    Disconnect
479    set _hosts [GetServerList "nanovis"]
480    if { "" == $_hosts } {
481        return 0
482    }
483    set result [VisViewer::Connect $_hosts]
484    return $result
485}
486
487# ----------------------------------------------------------------------
488# USAGE: Disconnect
489#
490# Clients use this method to disconnect from the current rendering
491# server.
492# ----------------------------------------------------------------------
493itcl::body Rappture::HeightmapViewer::Disconnect {} {
494    VisViewer::Disconnect
495
496    set _outbuf ""
497    # disconnected -- no more data sitting on server
498    catch {unset _obj2id}
499    array unset _id2obj
500    set _obj2id(count) 0
501    set _id2obj(cound) 0
502    set _sendobjs ""
503}
504
505# ----------------------------------------------------------------------
506# USAGE: isconnected
507#
508# Clients use this method to see if we are currently connected to
509# a server.
510# ----------------------------------------------------------------------
511itcl::body Rappture::HeightmapViewer::isconnected {} {
512    return [VisViewer::IsConnected]
513}
514
515# ----------------------------------------------------------------------
516# USAGE: _send <string>
517#
518# Used internally to send commands off to the rendering server.
519# ----------------------------------------------------------------------
520itcl::body Rappture::HeightmapViewer::_send {string} {
521    if { ![isconnected] } {
522        $_dispatcher cancel !serverDown
523        set x [expr {[winfo rootx $itk_component(area)]+10}]
524        set y [expr {[winfo rooty $itk_component(area)]+10}]
525        Rappture::Tooltip::cue @$x,$y "Connecting..."
526
527        set code [catch { Connect } ok]
528        if { $code == 0 && $ok} {
529            set w [winfo width $itk_component(3dview)]
530            set h [winfo height $itk_component(3dview)]
531
532            if { [Send "screen $w $h"] } {
533                set _view(theta) 45
534                set _view(phi) 45
535                set _view(psi) 0
536                set _view(zoom) 1.0
537                $_dispatcher event -idle !rebuild
538                Rappture::Tooltip::cue hide
539            }
540        } else {
541            Rappture::Tooltip::cue @$x,$y "Can't connect to visualization server.  This may be a network problem.  Wait a few moments and try resetting the view."
542        }
543    } else {
544        # if we're transmitting objects, then buffer this command
545        if {[llength $_sendobjs] > 0} {
546            append _outbuf $string "\n"
547        } else {
548            if { [Send $string] } {
549                foreach line [split $string \n] {
550                    SendEcho >>line $line
551                }
552            }
553        }
554    }
555}
556
557# ----------------------------------------------------------------------
558# USAGE: _send_dataobjs
559#
560# Used internally to send a series of volume objects off to the
561# server.  Sends each object, a little at a time, with updates in
562# between so the interface doesn't lock up.
563# ----------------------------------------------------------------------
564itcl::body Rappture::HeightmapViewer::_send_dataobjs {} {
565    blt::busy hold $itk_component(hull); update idletasks
566
567    foreach dataobj $_sendobjs {
568        foreach comp [$dataobj components] {
569            # send the data as one huge base64-encoded mess -- yuck!
570            set data [$dataobj blob $comp]
571
572            # tell the engine to expect some data
573            set length [string length $data]
574            set cmdstr "heightmap data follows $length"
575            if { ![Send $cmdstr] } {
576                return
577            }
578            while {[string length $data] > 0} {
579                update
580
581                set chunk [string range $data 0 8095]
582                set data [string range $data 8096 end]
583                if { ![Send $chunk -nonewline] } {
584                    return
585                }
586                Flush
587            }
588            Send ""
589
590            set id $_obj2id(count)
591            incr _obj2id(count)
592            set _id2obj($id) [list $dataobj $comp]
593            set _obj2id($dataobj-$comp) $id
594            set _receiveids($id) 1
595
596            #
597            # Determine the transfer function needed for this volume
598            # and make sure that it's defined on the server.
599            #
600            foreach {sname cmap wmap} [_getTransfuncData $dataobj $comp] break
601            set cmdstr [list "transfunc" "define" $sname $cmap $wmap]
602            if {![Send $cmdstr]} {
603                return
604            }
605            set _obj2style($dataobj-$comp) $sname
606        }
607    }
608    set _sendobjs ""
609    blt::busy release $itk_component(hull)
610
611    # activate the proper volume
612    set first [lindex [get] 0]
613    if {"" != $first} {
614        set axis [$first hints updir]
615        if {"" != $axis} {
616            _send "up $axis"
617        }
618    }
619
620    foreach key [array names _obj2id *-*] {
621        set state [string match $first-* $key]
622        _send "heightmap data visible $state $_obj2id($key)"
623        if {[info exists _obj2style($key)]} {
624            _send "heightmap transfunc $_obj2style($key) $_obj2id($key)"
625        }
626    }
627
628    # if there are any commands in the buffer, send them now that we're done
629    Send $_outbuf
630    set _outbuf ""
631
632    $_dispatcher event -idle !legend
633}
634
635# ----------------------------------------------------------------------
636# USAGE: _receive_image -bytes <size>
637#
638# Invoked automatically whenever the "image" command comes in from
639# the rendering server.  Indicates that binary image data with the
640# specified <size> will follow.
641# ----------------------------------------------------------------------
642itcl::body Rappture::HeightmapViewer::_receive_image {option size} {
643    if {[isconnected]} {
644        set bytes [Receive $size]
645        $_image(plot) configure -data $bytes
646        ReceiveEcho <<line "<read $size bytes for [image width $_image(plot)]x[image height $_image(plot)] image>"
647    }
648}
649
650# ----------------------------------------------------------------------
651# USAGE: _receive_legend <volume> <vmin> <vmax> <size>
652#
653# Invoked automatically whenever the "legend" command comes in from
654# the rendering server.  Indicates that binary image data with the
655# specified <size> will follow.
656# ----------------------------------------------------------------------
657itcl::body Rappture::HeightmapViewer::_receive_legend {ivol vmin vmax size} {
658    if { [isconnected] } {
659        set bytes [Receive $size]
660        $_image(legend) configure -data $bytes
661        ReceiveEcho <<line "<read $size bytes for [image width $_image(legend)]x[image height $_image(legend)] legend>"
662
663        set c $itk_component(legend)
664        set w [winfo width $c]
665        set h [winfo height $c]
666        if {"" == [$c find withtag transfunc]} {
667            $c create image 10 10 -anchor nw \
668                 -image $_image(legend) -tags transfunc
669
670            $c create text 10 [expr {$h-8}] -anchor sw \
671                 -fill $itk_option(-plotforeground) -tags vmin
672            $c create text [expr {$w-10}] [expr {$h-8}] -anchor se \
673                 -fill $itk_option(-plotforeground) -tags vmax
674        }
675        $c itemconfigure vmin -text $vmin
676        $c coords vmin 10 [expr {$h-8}]
677        $c itemconfigure vmax -text $vmax
678        $c coords vmax [expr {$w-10}] [expr {$h-8}]
679    }
680}
681
682# ----------------------------------------------------------------------
683# USAGE: _receive_data <id> <vmin> <vmax>
684#
685# Invoked automatically whenever the "legend" command comes in from
686# the rendering server.  Indicates that binary image data with the
687# specified <size> will follow.
688# ----------------------------------------------------------------------
689itcl::body Rappture::HeightmapViewer::_receive_data { args } {
690    if { [isconnected] } {
691        array set info $args
692        set id $info(id)
693        foreach { dataobj comp } $_id2obj($id) break
694        if { ![info exists _limits($dataobj-vmin] } {
695            set _limits($dataobj-vmin) $info(min)
696            set _limits($dataobj-vmax) $info(max)
697        } else {
698            if { $_limits($dataobj-vmin) > $info(min) } {
699                set _limits($dataobj-vmin) $info(min)
700            }
701            if { $_limits($dataobj-vmax) > $info(max) } {
702                set _limits($dataobj-vmax) $info(max)
703            }
704        }           
705        set _limits(vmin) $info(vmin)
706        set _limits(vmax) $info(vmax)
707        lappend _sendobjs2 $dataobj
708        unset _receiveids($info(id))
709        if { [array size _receiveids] == 0 } {
710            #$_dispatcher event -idle !send_transfuncs
711        }
712    }
713}
714
715# ----------------------------------------------------------------------
716# USAGE: _rebuild
717#
718# Called automatically whenever something changes that affects the
719# data in the widget.  Clears any existing data and rebuilds the
720# widget to display new data.
721# ----------------------------------------------------------------------
722itcl::body Rappture::HeightmapViewer::_rebuild {} {
723    # in the midst of sending data? then bail out
724    if {[llength $_sendobjs] > 0} {
725        return
726    }
727
728    #
729    # Find any new data that needs to be sent to the server.
730    # Queue this up on the _sendobjs list, and send it out
731    # a little at a time.  Do this first, before we rebuild
732    # the rest.
733    #
734    foreach dataobj [get] {
735        set comp [lindex [$dataobj components] 0]
736        if {![info exists _obj2id($dataobj-$comp)]} {
737            set i [lsearch -exact $_sendobjs $dataobj]
738            if {$i < 0} {
739                lappend _sendobjs $dataobj
740            }
741        }
742    }
743    if {[llength $_sendobjs] > 0} {
744        # send off new data objects
745        $_dispatcher event -idle !send_dataobjs
746    } else {
747        # nothing to send -- activate the proper volume
748        set first [lindex [get] 0]
749        if {"" != $first} {
750            set axis [$first hints updir]
751            if {"" != $axis} {
752                _send "up $axis"
753            }
754        }
755        foreach key [array names _obj2id *-*] {
756            set state [string match $first-* $key]
757            _send "heightmap data visible $state $_obj2id($key)"
758            if {[info exists _obj2style($key)]} {
759                _send "heightmap transfunc $_obj2style($key) $_obj2id($key)"
760            }
761        }
762        $_dispatcher event -idle !legend
763    }
764
765    #
766    # Reset the camera and other view parameters
767    #
768    _send "camera angle [Euler2XYZ $_view(theta) $_view(phi) $_view(psi)]"
769    _send "camera zoom $_view(zoom)"
770
771     if {"" == $itk_option(-plotoutline)} {
772         _send "grid linecolor [Color2RGB $itk_option(-plotoutline)]"
773     }
774    _fixSettings grid
775    _fixSettings axes
776    _fixSettings contourlines
777}
778
779# ----------------------------------------------------------------------
780# USAGE: _zoom in
781# USAGE: _zoom out
782# USAGE: _zoom reset
783#
784# Called automatically when the user clicks on one of the zoom
785# controls for this widget.  Changes the zoom for the current view.
786# ----------------------------------------------------------------------
787itcl::body Rappture::HeightmapViewer::_zoom {option} {
788    switch -- $option {
789        in {
790            set _view(zoom) [expr {$_view(zoom)*1.25}]
791            _send "camera zoom $_view(zoom)"
792        }
793        out {
794            set _view(zoom) [expr {$_view(zoom)*0.8}]
795            _send "camera zoom $_view(zoom)"
796        }
797        reset {
798            set _view(theta) 45
799            set _view(phi) 45
800            set _view(psi) 0
801            set _view(zoom) 1.0
802            set xyz [Euler2XYZ $_view(theta) $_view(phi) $_view(psi)]
803            _send "camera angle $xyz"
804            _send "camera zoom $_view(zoom)"
805        }
806    }
807}
808
809# ----------------------------------------------------------------------
810# USAGE: _move click <x> <y>
811# USAGE: _move drag <x> <y>
812# USAGE: _move release <x> <y>
813#
814# Called automatically when the user clicks/drags/releases in the
815# plot area.  Moves the plot according to the user's actions.
816# ----------------------------------------------------------------------
817itcl::body Rappture::HeightmapViewer::_move {option x y} {
818    switch -- $option {
819        click {
820            $itk_component(3dview) configure -cursor fleur
821            set _click(x) $x
822            set _click(y) $y
823            set _click(theta) $_view(theta)
824            set _click(phi) $_view(phi)
825        }
826        drag {
827            if {[array size _click] == 0} {
828                _move click $x $y
829            } else {
830                set w [winfo width $itk_component(3dview)]
831                set h [winfo height $itk_component(3dview)]
832                if {$w <= 0 || $h <= 0} {
833                    return
834                }
835
836                if {[catch {
837                    # this fails sometimes for no apparent reason
838                    set dx [expr {double($x-$_click(x))/$w}]
839                    set dy [expr {double($y-$_click(y))/$h}]
840                }]} {
841                    return
842                }
843
844                #
845                # Rotate the camera in 3D
846                #
847                if {$_view(psi) > 90 || $_view(psi) < -90} {
848                    # when psi is flipped around, theta moves backwards
849                    set dy [expr {-$dy}]
850                }
851                set theta [expr {$_view(theta) - $dy*180}]
852                while {$theta < 0} { set theta [expr {$theta+180}] }
853                while {$theta > 180} { set theta [expr {$theta-180}] }
854
855                if {abs($theta) >= 30 && abs($theta) <= 160} {
856                    set phi [expr {$_view(phi) - $dx*360}]
857                    while {$phi < 0} { set phi [expr {$phi+360}] }
858                    while {$phi > 360} { set phi [expr {$phi-360}] }
859                    set psi $_view(psi)
860                } else {
861                    set phi $_view(phi)
862                    set psi [expr {$_view(psi) - $dx*360}]
863                    while {$psi < -180} { set psi [expr {$psi+360}] }
864                    while {$psi > 180} { set psi [expr {$psi-360}] }
865                }
866
867                set _view(theta) $theta
868                set _view(phi) $phi
869                set _view(psi) $psi
870                set xyz [Euler2XYZ $_view(theta) $_view(phi) $_view(psi)]
871                _send "camera angle $xyz"
872                set _click(x) $x
873                set _click(y) $y
874            }
875        }
876        release {
877            _move drag $x $y
878            $itk_component(3dview) configure -cursor ""
879            catch {unset _click}
880        }
881        default {
882            error "bad option \"$option\": should be click, drag, release"
883        }
884    }
885}
886
887# ----------------------------------------------------------------------
888# USAGE: _state <component>
889#
890# Used internally to determine the state of a toggle button.
891# The <component> is the itk component name of the button.
892# Returns on/off for the state of the button.
893# ----------------------------------------------------------------------
894itcl::body Rappture::HeightmapViewer::_state {comp} {
895    if {[$itk_component($comp) cget -relief] == "sunken"} {
896        return "on"
897    }
898    return "off"
899}
900
901# ----------------------------------------------------------------------
902# USAGE: _fixSettings <what> ?<value>?
903#
904# Used internally to update rendering settings whenever parameters
905# change in the popup settings panel.  Sends the new settings off
906# to the back end.
907# ----------------------------------------------------------------------
908itcl::body Rappture::HeightmapViewer::_fixSettings { what {value ""} } {
909    switch -- $what {
910        "legend" {
911            set lineht [font metrics $itk_option(-font) -linespace]
912            set w [expr {[winfo width $itk_component(legend)]-20}]
913            set h [expr {[winfo height $itk_component(legend)]-20-$lineht}]
914            set imap ""
915           
916            set dataobj [lindex [get] 0]
917            if {"" != $dataobj} {
918                set comp [lindex [$dataobj components] 0]
919                if {[info exists _obj2id($dataobj-$comp)]} {
920                    set imap $_obj2id($dataobj-$comp)
921                }
922            }
923            if {$w > 0 && $h > 0 && "" != $imap} {
924                _send "heightmap legend $imap $w $h"
925            } else {
926                $itk_component(legend) delete all
927            }
928        }
929        "grid" {
930            if { [isconnected] } {
931                _send "grid visible $_settings($this-grid)"
932            }
933        }
934        "axes" {
935            if { [isconnected] } {
936                _send "axis visible $_settings($this-axes)"
937            }
938        }
939        "contourlines" {
940            if {[isconnected]} {
941                set dataobj [lindex [get] 0]
942                if {"" != $dataobj} {
943                    set comp [lindex [$dataobj components] 0]
944                    if {[info exists _obj2id($dataobj-$comp)]} {
945                        set i $_obj2id($dataobj-$comp)
946                        set bool $_settings($this-contourlines)
947                        _send "heightmap linecontour visible $bool $i"
948                    }
949                }
950            }
951        }
952        default {
953            error "don't know how to fix $what: should be grid, axes, contourlines, or legend"
954        }
955    }
956}
957
958# ----------------------------------------------------------------------
959# USAGE: _getTransfuncData <dataobj> <comp>
960#
961# Used internally to compute the colormap and alpha map used to define
962# a transfer function for the specified component in a data object.
963# Returns: name {v r g b ...} {v w ...}
964# ----------------------------------------------------------------------
965itcl::body Rappture::HeightmapViewer::_getTransfuncData {dataobj comp} {
966    array set style {
967        -color rainbow
968        -levels 6
969        -opacity 0.5
970    }
971    array set style [lindex [$dataobj components -style $comp] 0]
972    set sname "$style(-color):$style(-levels):$style(-opacity)"
973
974    if {$style(-color) == "rainbow"} {
975        set style(-color) "white:yellow:green:cyan:blue:magenta"
976    }
977    set clist [split $style(-color) :]
978    set cmap "0.0 [Color2RGB white] "
979    for {set i 0} {$i < [llength $clist]} {incr i} {
980        set xval [expr {double($i+1)/([llength $clist]+1)}]
981        set color [lindex $clist $i]
982        append cmap "$xval [Color2RGB $color] "
983    }
984    append cmap "1.0 [Color2RGB $color]"
985
986    set max $style(-opacity)
987    set levels $style(-levels)
988    if {[string is int $levels]} {
989        set wmap "0.0 0.0 "
990        set delta [expr {0.125/($levels+1)}]
991        for {set i 1} {$i <= $levels} {incr i} {
992            # add spikes in the middle
993            set xval [expr {double($i)/($levels+1)}]
994            append wmap "[expr {$xval-$delta-0.01}] 0.0  [expr {$xval-$delta}] $max [expr {$xval+$delta}] $max  [expr {$xval+$delta+0.01}] 0.0 "
995        }
996        append wmap "1.0 0.0 "
997    } else {
998        set wmap "0.0 0.0 "
999        set delta 0.05
1000        foreach xval [split $levels ,] {
1001            append wmap "[expr {$xval-$delta}] 0.0  $xval $max [expr {$xval+$delta}] 0.0 "
1002        }
1003        append wmap "1.0 0.0 "
1004    }
1005
1006    return [list $sname $cmap $wmap]
1007}
1008
1009# ----------------------------------------------------------------------
1010# CONFIGURATION OPTION: -plotbackground
1011# ----------------------------------------------------------------------
1012itcl::configbody Rappture::HeightmapViewer::plotbackground {
1013    foreach {r g b} [Color2RGB $itk_option(-plotbackground)] break
1014    #fix this!
1015    #_send "color background $r $g $b"
1016}
1017
1018# ----------------------------------------------------------------------
1019# CONFIGURATION OPTION: -plotforeground
1020# ----------------------------------------------------------------------
1021itcl::configbody Rappture::HeightmapViewer::plotforeground {
1022    foreach {r g b} [Color2RGB $itk_option(-plotforeground)] break
1023    #fix this!
1024    #_send "color background $r $g $b"
1025}
1026
1027# ----------------------------------------------------------------------
1028# CONFIGURATION OPTION: -plotoutline
1029# ----------------------------------------------------------------------
1030itcl::configbody Rappture::HeightmapViewer::plotoutline {
1031    if {[isconnected]} {
1032        _send "grid linecolor [Color2RGB $itk_option(-plotoutline)]"
1033    }
1034}
Note: See TracBrowser for help on using the repository browser.