proc ddhfur_spinbox { framename labeltext bmin bmax binc var sbwidth balloon } {

   frame $framename
   pack $framename -side top -fill x

   label $framename.lbl -text $labeltext
   pack $framename.lbl -side left -fill x

   spinbox $framename.sbx -from $bmin -increment $binc -to $bmax -textvariable $var -width $sbwidth -command {
      if { $ddhfur_realtime != "0" } {
         eval ac3d ddhfur_go $ddhfur_spu $ddhfur_randplace $ddhfur_sps $ddhfur_slen $ddhfur_srandlen $ddhfur_sthick $ddhfur_srandthick $ddhfur_ssides $ddhfur_srot $ddhfur_dranddir $ddhfur_gravx $ddhfur_gravy $ddhfur_gravz $ddhfur_smax $ddhfur_srootdepth $ddhfur_realtime 1 $ddhfur_randseed $ddhfur_remdup
      }
   }
   pack $framename.sbx -side right -fill x

   add_balloon $framename.lbl $balloon

}

proc ddhfur_makeframe {} {

   toplevel .ddhfur
   wm title .ddhfur "Fur"
   wm transient .ddhfur [winfo toplevel [winfo parent .ddhfur]]

   set width 6
   set ypad 3

   ###########################################################################
   # Coverage
   frame .ddhfur.frmcover1
   pack .ddhfur.frmcover1 -side top -fill x

   label .ddhfur.frmcover1.lbl -text "==== COVERAGE ===="
   pack .ddhfur.frmcover1.lbl -side top -fill x

   # Strands/unit
   ddhfur_spinbox .ddhfur.frmcoverspu "Strands per unit" 1 400000 10 ddhfur_spu $width "Number of strands of fur that will fit into a 1x1 unit"

   # Randomly offste roots by amount
   ddhfur_spinbox .ddhfur.frmcoverrandplace "% random placement" 0 10000 5 ddhfur_randplace $width "Randomize the placement of hairs by the specified percentage.\n100% displacemeant generally results in the most natural/random placement,\ngood for objects like grass, tangled hair, etc."

   ###########################################################################
   # Strands
   frame .ddhfur.frmstrands1
   pack .ddhfur.frmstrands1 -side top -fill x

   label .ddhfur.frmstrands1.lbl -text "==== STRANDS ===="
   pack .ddhfur.frmstrands1.lbl -side top -fill x

   # Segments/strand
   ddhfur_spinbox .ddhfur.frmstrandsps "Segments per strand" 1 32 1 ddhfur_sps $width "Number of segments per strand of fur.\nThe number of 'bends' in a strand."

   # Strand length
   ddhfur_spinbox .ddhfur.frmstrandslen "Strand length" 0.01 999 0.01 ddhfur_slen $width "Length of each strand."

   # Strand length randomize
   ddhfur_spinbox .ddhfur.frmstrandrandlen "% random length" 0 999 1 ddhfur_srandlen $width "Randomize the length of hairs by a percentage."

   # Root depth
   ddhfur_spinbox .ddhfur.frmstrandrootdepth "Root depth" 0 99 0.01 ddhfur_srootdepth $width ".Depth of strand below the surface.\nMake negative for strands to hover above surface,"

   # Maximum strands
   ddhfur_spinbox .ddhfur.frmstrandmax "Maximum strands" 0 10000000 1000 ddhfur_smax $width "Maximum number of strands to create"

   # Rmove duplicates
   frame .ddhfur.frmremdupes
   pack .ddhfur.frmremdupes -side top -fill x
   checkbutton .ddhfur.frmremdupes.ddhfur_remdup -text "Remove duplicates (very slow)" -variable ddhfur_remdup -command {
      if { $ddhfur_realtime != "0" } {
         eval ac3d ddhfur_go $ddhfur_spu $ddhfur_randplace $ddhfur_sps $ddhfur_slen $ddhfur_srandlen $ddhfur_sthick $ddhfur_srandthick $ddhfur_ssides $ddhfur_srot $ddhfur_dranddir $ddhfur_gravx $ddhfur_gravy $ddhfur_gravz $ddhfur_smax $ddhfur_srootdepth $ddhfur_realtime 1 $ddhfur_randseed $ddhfur_remdup
      }
   }
   pack .ddhfur.frmremdupes.ddhfur_remdup -side top -fill x -expand 1
   add_balloon .ddhfur.frmremdupes.ddhfur_remdup "Removes extra strands that may appear between surfaces\nWARNING: This function may cause fur creation to slow dramatically\nRecommended for use on small areas only."

   ###########################################################################
   # Weight
   frame .ddhfur.frmstrands2
   pack .ddhfur.frmstrands2 -side top -fill x

   label .ddhfur.frmstrands2.lbl -text "==== WEIGHT ===="
   pack .ddhfur.frmstrands2.lbl -side top -fill x

   # Thickness
   ddhfur_spinbox .ddhfur.frmstrandsthick "Thickness" 0.001 99 0.001 ddhfur_sthick $width "Thickness of extruded hairs.\nOnly applies for hairs with 2 or more 'sides'."

   # Randomize thickness
   ddhfur_spinbox .ddhfur.frmstrandrandthick "% random thickness" 0 999 1 ddhfur_srandthick $width "Randomize the thickness of hairs by a percentage."

   # Sides
   ddhfur_spinbox .ddhfur.frmstrandsides "Sides per strand" 1 32 1 ddhfur_ssides $width "Number of sides each hair polygon contains.\nSet to 1 to generate line segments instead of polygons."

   # Random hair rotation
   frame .ddhfur.frmstrandrot
   pack .ddhfur.frmstrandrot -side top -fill x
   checkbutton .ddhfur.frmstrandrot.ddhfur_srot -text "Randomly rotate each strand" -variable ddhfur_srot -command {
      if { $ddhfur_realtime != "0" } {
         eval ac3d ddhfur_go $ddhfur_spu $ddhfur_randplace $ddhfur_sps $ddhfur_slen $ddhfur_srandlen $ddhfur_sthick $ddhfur_srandthick $ddhfur_ssides $ddhfur_srot $ddhfur_dranddir $ddhfur_gravx $ddhfur_gravy $ddhfur_gravz $ddhfur_smax $ddhfur_srootdepth $ddhfur_realtime 1 $ddhfur_randseed $ddhfur_remdup
      }
   }
   pack .ddhfur.frmstrandrot.ddhfur_srot  -side top -fill x -expand 1

   ###########################################################################
   # Direction
   frame .ddhfur.frmdir1
   pack .ddhfur.frmdir1 -side top -fill x

   label .ddhfur.frmdir1.lbl -text "==== DIRECTION ===="
   pack .ddhfur.frmdir1.lbl -side top -fill x

   # Randomize growth direction
   ddhfur_spinbox .ddhfur.frmdirranddir "% random direction" 0 999 1 ddhfur_dranddir $width "Randomize the direction each strand grows by a percentage.\nStrands get more chaotic as this number approaches 100%.\nStrands grow by the surface normal by default."

   # Gravity X
   ddhfur_spinbox .ddhfur.frmdirgravx "Gravity X" -999 999 1 ddhfur_gravx $width "Percent of gravitic force applied to hair along the X axis.\nSpecify a negative or positive number to pull hair along the +/- axis.\nThis number is somewhat arbitrary, so experiment."

   # Gravity Y
   ddhfur_spinbox .ddhfur.frmdirgravy "Gravity Y" -999 999 1 ddhfur_gravy $width "Percent of gravitic force applied to hair along the Y axis.\nSpecify a negative or positive number to pull hair along the +/- axis.\nThis number is somewhat arbitrary, so experiment."

   # Gravity Z
   ddhfur_spinbox .ddhfur.frmdirgravz "Gravity Z" -999 999 1 ddhfur_gravz $width "Percent of gravitic force applied to hair along the Z axis.\nSpecify a negative or positive number to pull hair along the +/- axis.\nThis number is somewhat arbitrary, so experiment."

   ###########################################################################
   # General
   frame .ddhfur.frmgen1
   pack .ddhfur.frmgen1 -side top -fill x

   label .ddhfur.frmgen1.lbl -text "==== GENERAL ===="
   pack .ddhfur.frmgen1.lbl -side top -fill x

   # Random seed
   ddhfur_spinbox .ddhfur.frmgenrandseed "Random seed" 0 10000000 1 ddhfur_randseed $width "Random seed.\nZero defaults to current time, to a 1 second resolution.\nSet to non-zero to get consistent results between previews."

   # Realtime updates
   frame .ddhfur.frmrealtime
   pack .ddhfur.frmrealtime -side top -fill x
   checkbutton .ddhfur.frmrealtime.ddhfur_realtime -text "Realtime updates" -variable ddhfur_realtime -command {
      eval ac3d ddhfur_sync_realtime $ddhfur_realtime
      if { $ddhfur_realtime != "0" } {
         eval ac3d ddhfur_go $ddhfur_spu $ddhfur_randplace $ddhfur_sps $ddhfur_slen $ddhfur_srandlen $ddhfur_sthick $ddhfur_srandthick $ddhfur_ssides $ddhfur_srot $ddhfur_dranddir $ddhfur_gravx $ddhfur_gravy $ddhfur_gravz $ddhfur_smax $ddhfur_srootdepth $ddhfur_realtime 1 $ddhfur_randseed $ddhfur_remdup
      }
   }
   pack .ddhfur.frmrealtime.ddhfur_realtime  -side top -fill x -expand 1

   ###########################################################################
   # Go
   frame .ddhfur.frmbtn
   pack .ddhfur.frmbtn -side top -fill x

   button .ddhfur.frmbtn.btnapply -text "Create fur" -command {
      if { $ddhfur_realtime != "0" } {
         .ddhfur.frmrealtime.ddhfur_realtime invoke
      }
      eval ac3d ddhfur_go $ddhfur_spu $ddhfur_randplace $ddhfur_sps $ddhfur_slen $ddhfur_srandlen $ddhfur_sthick $ddhfur_srandthick $ddhfur_ssides $ddhfur_srot $ddhfur_dranddir $ddhfur_gravx $ddhfur_gravy $ddhfur_gravz $ddhfur_smax $ddhfur_srootdepth 0 0 $ddhfur_randseed $ddhfur_remdup
   }
   pack .ddhfur.frmbtn.btnapply -side left -fill x -expand 1

   ###########################################################################
   # Hook to end realtime on close
   wm protocol .ddhfur WM_DELETE_WINDOW {
      if { $ddhfur_realtime != "0" } {
         .ddhfur.frmrealtime.ddhfur_realtime invoke
      }
      destroy .ddhfur
   }

}

proc ddhfur_dialog {} {

   global ddhfur_spu
   global ddhfur_randplace
   global ddhfur_sps
   global ddhfur_slen
   global ddhfur_srandlen
   global ddhfur_sthick
   global ddhfur_srandthick
   global ddhfur_ssides
   global ddhfur_srot
   global ddhfur_dranddir
   global ddhfur_gravx
   global ddhfur_gravy
   global ddhfur_gravz
   global ddhfur_smax
   global ddhfur_srootdepth
   global ddhfur_realtime
   global ddhfur_randseed
   global ddhfur_remdup

   if ![winfo exists .ddhfur] {
      set ddhfur_spu 100.0
      set ddhfur_randplace 100.0
      set ddhfur_sps 3
      set ddhfur_slen 0.5
      set ddhfur_srandlen 10
      set ddhfur_sthick 0.01
      set ddhfur_srandthick 0
      set ddhfur_ssides 2
      set ddhfur_srot 0
      set ddhfur_dranddir 20
      set ddhfur_gravx 0
      set ddhfur_gravy -20
      set ddhfur_gravz 0
      set ddhfur_smax 65536
      set ddhfur_srootdepth 0.01
      set ddhfur_realtime 0
      set ddhfur_randseed [ expr { int( rand() * 1000000 ) + 1 } ]
      set ddhfur_remdup 0
      ddhfur_makeframe
      # Startup in realtime mode
      .ddhfur.frmrealtime.ddhfur_realtime invoke
   }

   wm deiconify .ddhfur
   centre_window_on_screen .ddhfur

}

