Sophie

Sophie

distrib > Mageia > 6 > i586 > media > core-release > by-pkgid > 1e8ff6f62a270e7384a562d4a108fbe0 > files > 19

maelstrom-3.0.6-19.mga6.i586.rpm


The first challenge was figuring out how to get the Macintosh resources
into a form that could be dissected from within UNIX.  First, I tried
copying files over from "Executor", a Linux based X11 Macintosh emulator.
Unfortunately, Executor wraps the resource fork in a special header, 
rendering it difficult to decipher.  The solution was to BinHex the files
on a Macintosh, and then use the Linux utility 'mcvert' to extract the
resource fork from the archive.  I also used the ALPHA 'hfs' filesystem
for Linux, but got a file that was different than the one 'mcvert' unwrapped,
so I didn't trust it.

The second problem was finding out the format of the Macintosh resource
fork, so that the resources could be extracted.  This was solved by going
to the bookstore and looking it up in the "Inside Macintosh" series 
published by Addison and Wesley.  This series has detailed specifications
on the internals of the Macintosh operating system.  This was pointed out
by Andrew Welch, the author of Maelstrom.

The next tricky part was after I had written code to look at the various
parts of the resource fork of the Maelstrom game, the values seemed way
too large to be correct.  The offset for the Resource Map was 4 MegaBytes
into the file, when the file was only .5 Megabyte large.  This was caused
by the fact that Linux is little-endian, while Macintoshes are big-endian.
This was solved by running ntohl() and ntohs() on the offset/value fields 
of the resource fork.

I wrote a utility to list and extract all resources from a Macintosh binary 
resource fork.

I extracted the sprites from the Maelstrom resources, and attempted to view
them in a VGA console, using custom-crafted bitmap viewing utilities. 
The sprites appeared, but appeared in psychedelic colors.  Evidently, 
the colormap was wrong.  I played with the colormap, setting it to the 
standard colormap, which didn't help.  I used ResEdit on the Macintosh
to look into the ColorMap resources of Maelstrom.  ResEdit has a colormap
editor that can display and set the colormap of a Macintosh application.
I used it to find out the proper colormap for displaying Maelstrom sprites.

Next was the sounds.  I used the resource parsing code to extract the
sound resources, and then used "Inside Macintosh: Sounds" to look at
the format of the sounds used in Maelstrom.  It turns out they are all
either format 1 or format 2 sampled sound bites which can be directly
played on /dev/dsp under Linux.

Then, I worked with the colormap resources.  I found the format for the
colormap resources in "Inside Macintosh: Imaging with QuickDraw"
I used this description to write a utility that converts a binary clut
resource into a C header file describing the colormap.

Next was the PICT resources: the title screen, credits, and info screens.
I looked at the "Inside Macintosh: Imaging with QuickDraw" for the PICT
resource format, and freaked when I saw it was a collection of opcodes 
for the QuickDraw routines.  Then I found that the first part of the 
Maelstrom PICTs seemed to be a colormap.

The solution to the problem of understanding PICT resources was going
to 	ftp.sunet.se:/pub/mac/mirror-umich/graphics/graphicsutil
and retrieving the programs "GraphicConverter" and "pictmanagementutils"
I used these programs to extract the PICT resources and save them
as raw PPM files.  Then I wrote a simple program to display raw PPM
files on the VGA console.

I copied the raw PPM files from the macintosh disk to the Linux system
by mounting it with the 'hfs' module loaded, and then doing a straight
copy of the data portions of the files.  The 'hfs' module for Linux
was written by Paul H. Hargrove, hargrove@sccm.stanford.edu

I then translated PPM files to XPM format with xv, performing image 
enhancement.  I then wrote a utility to merge the colormaps of the
pixmaps and perform a check to see if all the colors are in the 
standard colormap of Maelstrom.  They are, so I can convert them all
into raw pixel data -- a form more suitable for blitting to the screen.
This form also takes less disk-storage than either the raw PPM data
or the textual XPM data.

When loading up Maelstrom, I thought it might be nice to create a color
icon for the window manager to use.  I created it by resizing and 
simplifying the main title image of Maelstrom with xv and then turning
it into a standard colormap xpm icon.  The program 'xboing', written by
Justin C. Kibell, jck@citri.edu.au, creates its own color icon.  I looked
at the code for this, and used the technique with Maelstrom.
The technique is basically to create a pixmap on the X server and then
tell the window manager to use it for an icon.  This must be done before
the window is mapped on the screen, otherwise the window manager ignores
its hint.

Then, I tried allocating a private colormap for displaying Maelstrom
graphics.  Well, that works, and the code is still in place to support
that, but the Maelstrom colormap turns the rest of the screen flourescent
colors.  I looked at the code for 'xv', written by John Bradley, and
found a good algorithm for mapping the colors needed to the existing 
color palette.  I have ideas for more advanced algorithms of color 
allocation, but the direct mapping method seems to work well enough for
me.  One problem with this approach is that once Maelstrom is started, 
it locks all the colors in the colormap, preventing applications from 
allocating new shared color cells.

One of the primary factors affecting game playability is the speed of
real-time animation.  One of the best ways to achieve high-speed animation
in the X11 environment is to use the MITSHM extension.  The standard 
method of working with X shared memory is to create a small (300x200) 
shared image, manipulate the image and then "put" it on the server 
with XShmPutImage().  Reasonable speed can be achieved with this method, 
however this is not fast enough for the needs of Maelstrom.  Maelstrom 
is played in a 640x480 window, and copying a 2.5 megabyte image into 
the screen contents would take way too long.  I get around this problem 
by creating a shared Pixmap, and making it the background of the window.  
Background pixmaps are tiled, so the background Pixmap has to be exactly 
the same size as the window.  Now, the background of the window can be 
directly manipulated, and refreshed with XClearArea().  This approach seems 
to be much faster than working with an XImage, and blitting it to the window.
One side-advantage of this approach is that graphics can be drawn in
the foreground of the window, without affecting the animated background.
Another benefit of this approach is that a redraw on an expose event 
simply consists of an XClearWindow() call, without having to redraw
the entire contents of the graphics window.

Next was sound mixing.  I used a simplified version of the sound mixing
code in 'sfxserver', written by Terry Evans, tevans@cs.utah.edu, for
sound mixing.  Each loop of the mixer compiles multiple channels of
the sound mixer into a single chunk of sampled data which is sent to 
the audio device.  The original idea was to have a continuous loop,
first checking for input, and then writing a chunk of sound (or silence)
to the sound device.  This resulted in slow response time, because the
a sound event had to wait the entire cycle of compiling the sound channels
until it could be acted upon.  I modified the original concept to support
asynchronous input.  Now, the loop has been simplified to a simple continuous
play of the sound channels, but at any time during the compilation of a
sound chunk, new sound data can be placed into channels, or removed from 
the mixing channels.  This allows nearly instantaneous mixing of sound effects,
in response to sound events.

I decided to run the sound server as a separate process from Maelstrom.
The sound server has to continuously play sampled data (sound or silence)
and respond instantly to sound events.  Maelstrom has to continuously
update animated graphics.  I thought the best way to perform both of these
functions simultaneously was to do them in separately running processes,
communicating through a private UNIX domain socket.

PROBLEM: After we allocate a full colormap, Maelstrom wants to allocate
other colors.  We have completely filled the colormap, yet... what can
we do?  We can completely take over the root window colormap...
That's not polite, but what else can we do?

PROBLEM: After we allocate a full colormap, Maelstrom wants to allocate
other colors.  We have completely filled the colormap, yet... what can
we do?  We can completely take over the root window colormap...
That's not polite, but what else can we do?
... Try to allocate the closest color in the colormap and see what we
come up with...

The best thing might be to grab a copy of the current screen and see 
what colors are actually in use, and reserve all the rest.  Hmmm??

Mapping to the closest color already in the colormap seems like it
works.  It's not perfect, but it is polite, and prevents lots of 
gyrations trying to blend a private and public colormap.

I tried to allocate all the colors, and then map the ones that were
left.  That didn't work.  The colormap was left full, and the colors
that needed to be mapped didn't have any colors to map to.

I wrote a routine to convert the Macintosh color icon resource into 
both XPM and Linux-Maelstrom sprite format.

The next challenge is the fonts...

I found a program called 'mac2bdf' that converts Macintosh FONT resources
into UNIX bdf fonts.  I'm using the information here to learn about the
Macintosh font format.  My plan is to write a "Font Server" that can 
translate text, given font and pointsize, into a blittable Sprite.

The font server works nicely. :)

Next challenge is how to blit the sprites.  The best way is probably to 
keep an off-screen buffer and save the area behind the sprites to this
buffer.  When the sprite is removed, the screen is restored from this
off-screen buffer.  If this "back-buffer" is too big, we can possibly
save a shred of the background in the Sprite data structure and use that
to restore the background.  The off-screen buffer is a more robust
implementation in that it allows graphics pen styles, such as XOR, OR,
etc.

Saving the back-buffer in the sprites is a bad idea because two or
three sprites may go over single point, and each would have a different
idea of what the background looks like.

The standard way of blitting sprites is to cycle through the sprite,
checking against the mask, and put the pixel if it is "live".  This 
requires a loop iteration for each pixel in each scanline.  This can
be fairly time consuming.  A much faster method was outlined by Andrew
Welch, and that is to compile the sprite into a continuous stream of
pixels, arranged by a series of opcodes describing where the next pixel
should go.

	By compiling the sprites, I achieved a speed up of 45% 
over the old sprite/mask method.  An additional advantage is that
the compiled sprites take much less storage space -- an average of
50% less space.

Sound:  Under certain conditions, Maelstrom wants to wait until a
sound is completed.  In the original code, Maelstrom looped, polling
the server, waiting for the sound to finish playing.  Unfortunately,
polling the server interrupts the write() which must be restarted.
In continuous polling, the write keeps having to be restarted and
the sound never finishes playing.  If the sound status is kept by
the Maelstrom process, with the sound server sending periodic updates,
then the sound server can be greatly simplified and will not be
continuously polled.  This is the new approach I will take.

This approach works well enough.  The sound server takes very little
CPU time, as most of it's time is spent waiting for device writes to
complete.  One of the challenges I ran into, in writing interactive
sound code, is you need quick response time to user events.  For example,
if the player presses the "Thrust" key, you need to be able to start the
thrust sound, and stop it almost immediately.  One of the parameters 
you can tune to get this is the fragment size of the sound-card writes.
If you have a large fragment size, then large amounts of sound are
quickly processed and sent to the sound device.  Once it is in the 
sound device buffer, the sound cannot be cancelled.  However, as the
size of the audio-fragment decreases, you begin to have better control
over the timing, starts and stops of the interractive sound.

	For Maelstrom, I found that a fragment size of 1024 (2^10)
was about right for good response time without instantaneous response.

	Instantaneous response is not, in general, a good way to
program an interactive game, such as Asteroids or Maelstrom.  The
player expects to see an organic, analog universe, not one that 
reacts instantly to conditions.  In the real world, cause and effect
relationships are not instantaneous or even immediately obvious.
For example, when a jet passes overhead, the sound of the jet trails
the sight of the jet by a good distance.  Another example is acceleration.
When gravity takes hold of a body, the body gradually accelerates to a 
maximum velocity -- it doesn't immediately reach its terminal velocity.
These organic and semi-random natural conditions can be simulated in
the computer using random vectors and calculated delays to produce an 
environment more suitable for interactive play.

	Timing is a crucial part of interactive games.  In Maelstrom,
large portions of code are devoted to boundary condition detection and
timing conventions.  At each frame update, all sorts of time-dependent
conditions are checked -- how much time has it been since the last
screen update, is it time for a keyboard check, has the shield ran out,
etc, etc, all these are crucial components to a well-rounded game.
If things are handled too quickly, the player no longer enjoys playing
the game -- there is no time to repond.  If things are handled too 
slowly, the game appears sluggish and is no longer fast-action fun.

	I'm working on clipping.  Right now, clipping is handled by
dynamically recompiling the compiled sprite when it's near the edge of
the clipping rectangle.  This is okay when there are only a few sprites
on the screen, but when there are more than about ten or twenty, this
becomes too slow.
	I'm going to try to add code that will detect if the sprite
is near the edge of the clipping rectangle.  If it is, it will call
the Blit_Sprite() function, instead of the Blit_CSprite() function.
It is faster to clip a pixmap than it is to recompile a compiled
sprite, I think.