[comp.windows.news] psPyro, a user extensible fireworks display screen saver

marshall@software.org (Eric Marshall) (05/31/89)

	psPyro is a user extensible fireworks display screen
saver.  psPyro is fashioned after the MacIntosh screen saver
program "Pyro", written by Bill Steinberg and Steve Brecher.
Although psPyro actually contains no code which determines if
the screen saver should be invoked, it should be possible to
incorporate psPyro into a "true" screen saver program, e.g. Stan
Switzer's NeWS-based BlankScreen program or some system-level
screen saver.  psPyro executes by randomly selecting a place
to fire each firework, randomly selects a direction in which
to fire the firework, randomly selects a width and height for
the firework trail to follow, and finally, randomly selects a
number of explosions to explode to conclude the firework firing.
This continues forever until either the mouse is moved or one
of the mouse buttons is pressed.

	User extensibility is achieved via definitions in the
/PSPyro dictionary, which is located in /systemdict.  Although
only a small number of psPyro's internal definitions were intended
to be user configurable, all can be modified.  The intended user
modifiable definitions are as follows:

	/unusable_screen_width - the percent of each side of the screen
				 which will not be used to fire a firework
				 from.  The default is 10% of the screen
				 width.

	/unusable_screen_height - the percent of the top of the screen which
				  will not be used by the trail of a firework.
				  The default is 10% of the screen height.

	/minimum_trail_height - the minimum height of a firework trail, in
				terms of a percentage of the screen height.
				The default is 75% of the screen height.

	/minimum_explosion_angle - the minimum angle a firework trail will
				   travel before an explosion occurs.
				   Logically, fireworks travel in a
				   counterclockwise direction, starting at
				   angle 0.  The default is 100.

	/maximum_explosion_angle - the maximum angle a firework trail will
				   travel before an explosion occurs.  The
				   default is 150.

	/minimum_#_explosions - the minimum number of explosions per firework.
				The default is 1.

	/maximum_#_explosions - the maximum number of explosions per firework.
				The default is 5.

	/trail_length - the length (in degrees) of the animated firework trail.
			The default is 3.

	/time_between_firings - the time (in seconds) between firework firings.
				The default is 2.

	/multiple_explosion_radius - the radius (in units) from the center
				     of the original explosion to the center
				     of multiple explosions.  The default
				     is 100.

	/delay_amount - the number of /pause's between firework trail updates.
			The default is 50.  I would have preferred to specify
			a time in seconds to delay for, but /sleep wasn't
			allowing for satisfactory animation.

	/explosion_kinds - the array which contains the names of the procedures
			   implementing the different explosions.

	A standard place to provide user specific definitions
for psPyro is in your user.ps file.  An example user.ps fragment
follows which specifies the values for the minimum and maximum
explosion angles, and which adds a new explosion to the list
of available explosions (default is 5 different explosions):

	systemdict begin
	  /PSPyro 20 dict def

	  PSPyro begin
	    /minimum_explosion_angle 45 def
	    /maximum_explosion_angle 90 def

	    /T { translate 0 0 moveto 1 setgray (Explosion) show } def

	    /explosion_kinds [ /T ] def
	  end
	end

	A few words are in order concerning the definition of
new explosions.  If /explosion_kinds is provided within /PSPyro,
then the contents of the provided array are ADDED to the internal
psPyro list of available explosions.  If /replace_explosion_kinds
is provided, the provided array is used to REPLACE the internal
psPyro list.  Also, each procedure named in the /explosion_kinds
and /replace_explosion_kinds arrays accept two parameters, the
x and y position of the center of the explosion.  The supplied
explosion procedures simply /translate to the specified location
and draw about the origin, but more sophisticated procedures
could use the positional information as the basis for altering
the explosion shape, e.g. if the explosion is to occur very high
or very low.  Go ahead and write some fancy explosions.  Collect
them, trade them, sell them :-)

	Unfortunately, I don't have access to a color monitor,
so there is no support for color anything.  Wouldn't it be nice
if someone with a color monitor ...

	psPyro was developed on a Sun 3/160 running NeWS 1.1
under SunOS 3.5.


Eric Marshall
Software Productivity Consortium
SPC Building
2214 Rock Hill Road
Herndon, VA 22070
(703) 742-7153

CSNET: marshall@software.org
ARPANET: marshall%software.org@relay.cs.net

----------------------------------------------------------------------
	I'm just an X programmer gone straight.


----- 8< ----- 8< ----- 8< ----- 8< ----- 8< ----- 8< ----- 8< ----- 8< -----

#! /usr/NeWS/bin/psh

%
%  psPyro, a user extensible fireworks display screen saver.
%

%
% Physical screen dimensions ------------------------------------------
%
/screen_width  0 def
/screen_height 0 def


%
% Global variables concerning current firework characteristics --------
%
/trail_width          0 def
/trail_height         0 def
/trail_start_x        0 def
/trail_going_left? true def
/explosion_angle      0 def


%
% Heuristics for firework creation and display (user configurable) ----
%
/unusable_screen_width  0.1  def     % set to percent of physical screen width
/unusable_screen_height 0.1  def     % set to percent of physical screen height
/minimum_trail_height   0.75 def     % set to percent of physical screen height

/minimum_explosion_angle 100 def
/maximum_explosion_angle 150 def

/minimum_#_explosions 1 def
/maximum_#_explosions 5 def

% the length of the firework trail
/trail_length 3 def

% time between firework firings (seconds)
/time_between_firings 2 def

% radius of multiple explosions from the center of the original explosion
/multiple_explosion_radius 100 def

% the number of /pause's between firework trail updates, and other stuff
/delay_amount 50 def


%
% Firework explosion definitions (user configurable) ------------------
%

% array to keep the names of the different explosions in
/explosion_kinds [
                   /dots_explosion
                   /circle_explosion
                   /star_explosion
                   /solid_circle_explosion
                   /colored_circle_explosion
                 ] def


%
%  Dots explosion.
%

/draw_one_quarter {
  30 0 moveto
   2 -2 rlineto
   2  2 rlineto
  -2  2 rlineto
  closepath
  fill

  20 10 3 3 rectpath fill
  10 20 3 3 rectpath fill
} def

/dots_explosion { % x y => -
  gsave
    translate

    gsave
      5 {
        1 setgray

        4 {
          draw_one_quarter

          90 rotate
        } repeat

        1.3 1.3 scale
      } repeat
    grestore

    gsave
      5 {
        0 setgray

        4 {
          draw_one_quarter

          90 rotate
        } repeat

        1.3 1.3 scale
      } repeat
    grestore
  grestore
} def


%
%  Circle explosion.
%

/draw_circle { % - => -
  0 0 10 0 360 arc stroke
} def

/circle_explosion { % x y => -
  gsave
    translate

    gsave

      6 {
        1.4 dup scale
        1 setgray
        little_delay
        draw_circle
      } repeat
    grestore

    gsave
      6 {
        1.4 dup scale
        0 setgray
        little_delay
        draw_circle
      } repeat
    grestore
  grestore
} def


%
%  Star explosion.
%

/draw_star { % - => -
  gsave
    8 {
      -30 0 moveto
      60 0 rlineto stroke
      22.5 rotate
    } repeat
  grestore
} def

/star_explosion { % x y => -
  gsave
    translate

    gsave
      4 {
        1.4 dup scale
        1 setgray
        little_delay little_delay
        draw_star
      } repeat
    grestore

    gsave
      4 {
        1.4 dup scale
        0 setgray
        little_delay
        draw_star
      } repeat
    grestore
  grestore
} def


%
%  Solid circle explosion.
%

/draw_dot { % - => -
  0 0 10 0 360 arc fill
} def

/solid_circle_explosion { % x y => -
  gsave
    translate

    gsave
      4 {
        1.4 dup scale
        1 setgray
        little_delay
        draw_dot
      } repeat

      0 setgray
      little_delay
      draw_dot
    grestore
  grestore
} def


%
%  Colored circle explosion.
%

/draw_dot { % - => -
  0 0 10 0 360 arc fill
} def

/colored_circle_explosion { % x y => -
  gsave
    translate

    gsave
      4 {
        1.4 dup scale
        random setgray
        little_delay
        draw_dot
      } repeat

      0 setgray
      little_delay
      draw_dot
    grestore
  grestore
} def


%
% Support utilities ---------------------------------------------------
%

%
%  A delay loop.
%
%  The prefered method, /sleep, wasn't working smoothly enough
%  to give good animation.
%
/little_delay {
  1 1 delay_amount {
    pause

    pop
  } for
} def


%
%  Apply the user preferences to the program.
%
/apply_user_preferences { % - => -
  systemdict /PSPyro known {
    PSPyro {
      1 index dup

      % if it's the special EXPLOSION_KINDS or REPLACE_EXPLOSION_KINDS name
      /explosion_kinds eq {
        % concatenate user's array with existing EXPLOSION_KINDS array
        pop
        exch pop
        /explosion_kinds [
        3 2 roll
        aload pop
        explosion_kinds aload pop
        ] def
      } {
        /replace_explosion_kinds eq {
          % replace entire EXPLOSION_KINDS array with user's array
          exch pop
          /explosion_kinds
          exch
          def
        } {
          % do assignment into userdict
          def
        } ifelse
      } ifelse
    } forall
  } if
} def


%
%  Draw one instance of a firework trail from:
%    angle     to angle
%        theta        theta + trail_length - 1
%
/draw_trail { % theta => -
  /theta exch def

  % erase the first degree from the previous trail
  0 setgray
  0 0 1 theta 1 sub theta arc stroke

  1 setgray

  theta 1 theta trail_length add 1 sub {
    0 0 1
    4 -1 roll
    dup 1 add arc stroke
  } for
} def


%
%  Animate the entire firework trail up until the explosion.
%
/animate_trail { % - => -
  % draw the first trail instance
  1 setgray

  0 1 trail_length 1 sub {
    0 0 1
    4 -1 roll
    dup 1 add arc stroke
  } for

  % draw the rest of the trail
  1 1 explosion_angle {
    draw_trail

    little_delay
  } for

  % remove the last trail instance
  0 setgray

  explosion_angle 1 explosion_angle trail_length add 1 sub {
    0 0 1
    4 -1 roll
    dup 1 add arc stroke
  } for
} def


%
%  Determine the characterictics of the next firework.
%
/determine_firework_characteristics { % - => -
  % randomly pick the starting position to fire the firework from
  /trail_start_x screen_width random mul def

  %
  % pick the direction of the firework trail
  %
  % if the picked starting position is too close to a screen
  % edge, force the direction to be towards the opposite screen
  % side, otherwise randomly pick the direction
  %
  trail_start_x unusable_screen_width le {
    /trail_going_left? false def
  } {
    trail_start_x screen_width unusable_screen_width sub ge {
      /trail_going_left? true def
    } {
      % randomly pick the direction
      /trail_going_left? random round 0 eq def
    } ifelse
  } ifelse

  % randomly pick the width of the firework trail
  trail_going_left? {
    /trail_width trail_start_x random mul def
  } {
    /trail_width screen_width trail_start_x sub random mul def
  } ifelse

  % randomly pick the height of the firework trail
  /trail_height screen_height random mul def

  trail_height minimum_trail_height le {
    /trail_height minimum_trail_height def
  } if

  % randomly pick the ending angle of the firework explosion
  /explosion_angle
    maximum_explosion_angle minimum_explosion_angle sub
    random mul
    minimum_explosion_angle add round def
} def


%
%  Animate a firework explosion.
%
/animate_explosion { % explosion_x explosion_y #explosions => -
  /#explosions exch def
  /explosion_y exch def
  /explosion_x exch def

  % randomly pick which explosion to use
  /which_explosion explosion_kinds explosion_kinds length random mul floor get def

  % explode the first of the explosions
  explosion_x explosion_y which_explosion cvx exec

  % explode the remaining number of explosions (possible none),
  % randomly picking each location based upon the initial explosion
  % location
  #explosions 1 sub {
    % randomly pick another explosion location
    360 random mul dup
    cos multiple_explosion_radius mul
    explosion_x add
    exch
    sin multiple_explosion_radius mul
    explosion_y add
    which_explosion cvx exec
  } repeat
} def


%
%  Continually make fireworks.
%
/make_fireworks { % - => -
  {
    {
      initmatrix
      sky setcanvas

      % create a new firework
      determine_firework_characteristics

      % translate to the center of the firework trail arc
      trail_start_x unusable_screen_width add
      trail_width 2 div

      trail_going_left? {
        sub
      } {
        add
      } ifelse

      0 translate

      trail_going_left? not {
        -1 1 scale
      } if

      % create the firing of the firework
      1 setgray
      trail_width 2 div 0 20 0 180 arc fill

      little_delay

      0 setgray
      trail_width 2 div 0 20 0 180 arc fill

      % create the firework trail
      trail_width 2 div trail_height scale

      animate_trail

      % calculate the location of the firework explosion
      initmatrix

      trail_start_x unusable_screen_width add
      trail_width 2 div

      trail_going_left? {
        sub
      } {
        add
      } ifelse

      explosion_angle trail_length add
      cos
      trail_width 2 div mul

      trail_going_left? {
        add
      } {
        sub
      } ifelse

      explosion_angle trail_length add sin trail_height mul

      % calculate the number of explosions for the firework
      maximum_#_explosions random mul round
      dup
      0 eq {
        pop
        minimum_#_explosions
      } if

      % create the explosion!
      animate_explosion

      % wait between firework firings
      time_between_firings 60 div sleep
    } loop
  } fork
} def


%
% Main ----------------------------------------------------------
%

% get the dimensions of the physical screen
clippath pathbbox
/screen_height exch def
/screen_width exch def

clear

% apply user preferences
apply_user_preferences

% calculate the actual values of the firework creation and display heuristics
/unusable_screen_width screen_width unusable_screen_width mul def
/unusable_screen_height screen_height unusable_screen_height mul def
/minimum_trail_height screen_height minimum_trail_height mul def

% calculate the usable screen area
/screen_height screen_height unusable_screen_height sub def
/screen_width screen_width unusable_screen_width dup add sub def

% create the black sky
clippath pathbbox
rectpath
/sky framebuffer newcanvas def
sky reshapecanvas
sky /Mapped true put
sky setcanvas
0 fillcanvas

% remove the cursor
/nouse /nouse_m sky setstandardcursor

% start the fireworks display
make_fireworks

% wait for a mouse movement or click to end the fireworks display
createevent dup begin
  /Name [
          /LeftMouseButton
          /MiddleMouseButton
          /RightMouseButton
          /MouseDragged
        ] def
  /Canvas sky def
end expressinterest
awaitevent
currentprocess killprocessgroup

gra@mrmarx.east.sun.com (Gary R. Adams) (06/01/89)

Quick and dirty color :

% Add color
/newcolor {3 {random dup .1 le {.3 add } if } repeat rgbcolor setcolor} def


Then replace all occurences of "1 setgray" with "1 setgray newcolor".
-------------
Gary R. Adams                           sun!suneast!mrmarx!gra
Window Systems & Applications           (508) 671-0416 "... it only takes a 
Entry Systems Software                  sparc to get a fire going ..."