Jekyll2023-05-24T15:18:03+00:00http://www.independent-software.com/Independent SoftwareIndependent Software crafts beautiful, professional websites and database solutions in Maputo, Mozambique.Alexander van OostenrijkDenthor/Asphyxia’s VGA trainers: Texture mapping2023-02-06T16:32:00+00:002023-02-06T16:32:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-21-texture-mapping<p>This trainer is on texture mapping. I know, I know, I said light sourcing, then
Gouraud, then texture mapping, but I got enough mail (a deluge in fact ;)
telling me to do texture mapping…</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="free-direction-texture-mapping">Free Direction Texture Mapping</h2>
<p>There are two things you should know before we begin.</p>
<p>Firstly, I am cheating. The texture mapping I am going to show you is not
perspective-correct, with clever divides for z-placement etc. This method
looks almost as good and is quite a bit faster too.</p>
<p>Secondly, you will find it all rather easy. The reason for this is that it’s
all rather simple. I first made the routine by sitting down with some paper
and a pencil and had it on the machine in a few hours. A while later when
people on the net started discussing their methods, they were remarkably
similar.</p>
<p>Let me show you what I mean.</p>
<p>Let us assume you have a texture of 128x128 (a straight array of bytes
<code class="highlighter-rouge">[0..127, 0..127]</code>) which you want to map onto the side of a polygon. The
problem of course being that the polygon can be all over the place, with
one side longer then the other etc.</p>
<p>Our first step is to make sure we know which end is up. Let me
demonstrate:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 1
+
/ \
/ \
4 + + 2
\ /
\ /
+
3</code></pre></figure>
<p>Let us say that the above is the chosen polygon. We have decided that point
1 is the top left, point 3 is bottom right. This means that</p>
<ul>
<li>1 - 2 is the top of the texture</li>
<li>2 - 3 is the right of the texture</li>
<li>3 - 4 is the bottom of the texture</li>
<li>4 - 1 is the left of the texture</li>
</ul>
<p>The same polygon, but rotated:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 3
+
/ \
/ \
2 + + 4
\ /
\ /
+
1</code></pre></figure>
<p>Although the positions of the points are different, point 1 is still the
top-left of our texture.</p>
<h2 id="how-to-put-it-to-screen">How to put it to screen</h2>
<p>Okay, so now you have four points and know which one of them is also the
top-left of our texture. What next?</p>
<p>If you think back to our tutorial on polygons, you will remember we draw it
scanline by scanline. We do texture mapping the same way.</p>
<p>Let’s look at that picture again:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 1
+
a / \ b
/ \
4 + + 2
\ /
\ /
+
3</code></pre></figure>
<p>We know that point 1 is at <code class="highlighter-rouge">[0,0]</code> in our texture. Point 2 is at <code class="highlighter-rouge">[127,0]</code>,
Point 3 is at <code class="highlighter-rouge">[127,127]</code>, and point 4 is at <code class="highlighter-rouge">[0,127]</code>.</p>
<p>The clever bit, and the entire key to texture mapping, is making the
logical leap that precisely half way between Point 1 and Point 2 (b), we are at
<code class="highlighter-rouge">[64,0]</code> in our texture. (a) is in the same manner at <code class="highlighter-rouge">[0,64]</code>.</p>
<p>That’s it. All we need to know per y scanline is:</p>
<ul>
<li>The starting position on the x axis of the polygon line</li>
<li>The position on the x in the texture map referenced by that point</li>
<li>The position on the y in the texture map referenced by that point</li>
<li>The ending position on the x axis of the polygon line</li>
<li>The position on the x in the texture map referenced by that point</li>
<li>The position on the y in the texture map referenced by that point</li>
</ul>
<p>Let me give you an example. Let’s say that (a) and (b) from the above
picture are on the same y scanline. We know that the x of that scanline is
(say) 100 pixels at the start and 200 pixels at the end, making it’s width
100 pixels.</p>
<p>We know that on the left hand side, the texture is at <code class="highlighter-rouge">[0,64]</code>, and at the
right hand side, the texture is at <code class="highlighter-rouge">[64,0]</code>. In 100 pixels we have to
traverse our texture from <code class="highlighter-rouge">[0,64]</code> to <code class="highlighter-rouge">[64,0]</code>.</p>
<p>Assume at the start we have figured out the starting and ending points in
the texture:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="n">textureX</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">textureY</span> <span class="p">=</span> <span class="m">64</span><span class="p">;</span>
<span class="n">textureEndX</span> <span class="p">=</span> <span class="m">64</span><span class="p">;</span>
<span class="n">textureEndY</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">dx</span> <span class="p">:=</span> <span class="p">(</span><span class="n">TextureEndX</span><span class="p">-</span><span class="n">TextureX</span><span class="p">)/(</span><span class="n">maxx</span><span class="p">-</span><span class="n">minx</span><span class="p">);</span>
<span class="n">dy</span> <span class="p">:=</span> <span class="p">(</span><span class="n">TextureEndY</span><span class="p">-</span><span class="n">TextureY</span><span class="p">)/(</span><span class="n">maxx</span><span class="p">-</span><span class="n">minx</span><span class="p">);</span>
<span class="k">for</span> <span class="n">loop1</span> <span class="p">:=</span> <span class="n">minx</span> <span class="k">to</span> <span class="n">maxx</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="n">PutPixel</span> <span class="p">(</span><span class="n">loop1</span><span class="p">,</span> <span class="n">ypos</span><span class="p">,</span> <span class="n">texture</span> <span class="p">[</span><span class="n">textureX</span><span class="p">,</span> <span class="n">textureY</span><span class="p">],</span> <span class="n">VGA</span><span class="p">);</span>
<span class="n">textureX</span> <span class="p">=</span> <span class="n">textureX</span> <span class="p">+</span> <span class="n">dx</span><span class="p">;</span>
<span class="n">textureY</span> <span class="p">=</span> <span class="n">textureY</span> <span class="p">+</span> <span class="n">dy</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>Do the above for all the scanlines, and you have a texture mapped polygon!
It’s that simple.</p>
<p>We find our beginning and ending positions in the usual fashion. We know
that Point 1 is <code class="highlighter-rouge">[0,0]</code>. We know that Point 2 is <code class="highlighter-rouge">[127,0]</code>. We know the number
of scanlines on the y axis between Point 1 and Point 2.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> textureDX = 127/abs (point2.y - point1.y)</code></pre></figure>
<p>We run though all the y scanlines, starting from <code class="highlighter-rouge">[0,0]</code> and adding the above
formula to the X every time. When we hit the last scanline, we will be at
point <code class="highlighter-rouge">[127,0]</code> in the texture.</p>
<p>Repeat for all four sides, and you have the six needed variables per
scanline.</p>
<h2 id="in-closing">In closing</h2>
<p>As you can see, texture mapping (this type at least) is quite easy, and
produces quite a good result. You will however notice a bit of distortion
if you bring the polygon too close. This can be fixed by a) Subdividing the
polygon, so the one is made up of four or more smaller polygons. Much
bigger, but works; b) Using more accurate fixed point; or c) Figuring out
perspective correct texture mapping, mapping along constant-z lines etc.</p>Alexander van OostenrijkThis trainer is on texture mapping. I know, I know, I said light sourcing, then Gouraud, then texture mapping, but I got enough mail (a deluge in fact ;) telling me to do texture mapping…Denthor/Asphyxia’s VGA trainers: Hidden face removal2023-02-06T16:27:00+00:002023-02-06T16:27:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-20-hidden-face-removal<p>This trainer is on 3D hidden face removal and face sorting. I was going to
add shading, but that can wait until a later trainer. For convenience I
will build on the 3D code from <a href="/denthor-asphyxia-vga-trainer-8-3d-visualization.html">Part 8</a>. The maths for
face removal is a bit tricky, but just think back to your old high school
trigonometry classes.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="face-sorting">Face Sorting</h2>
<p>There are many ways to sort faces in a 3D object. For now, I will show you
just about the easiest one of the lot.</p>
<p>Say you have two polygons….</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> ------P1
------------------P2
Eye</code></pre></figure>
<p>As you can see, <code class="highlighter-rouge">P1</code> has to be drawn before <code class="highlighter-rouge">P2</code>. The easiest way to do this is
as follows:</p>
<p>On startup, find the midpoint of each of the polys, through the easy
equations:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">x = (P2.1.x + P2.2.x + P2.3.x + p2.4.x)/4
y = (P2.1.y + P2.2.y + P2.3.y + p2.4.y)/4
z = (P2.1.z + P2.2.z + P2.3.z + p2.4.z)/4</code></pre></figure>
<p>NOTE: For a triangle you would obviously only use three points and divide
by three.</p>
<p>Anyway, now you have the X,Y,Z of the midpoint of the polygon. You can then
rotate this point with the others. When it is time to draw, you can
compare the resulting Z of the midpoint, sort all of the Z items, and then
draw them from back to front.</p>
<p>In the sample program I use a simple bubble sort… basically, I check the
first two values against each other, and swap them if the first is bigger
than the second. I continue doing this to all the numbers until I run
through the entire list without swapping once. Bubble sorts are standard
computer science topics… perhaps borrow a text book to find out
more about them and other (better) sorting methods.</p>
<p>The above isn’t perfect, but it should work 90% of the time. But it still
means that when you are drawing a cube, you have to draw all 6 sides every
frame, even though only three or so are visible. That is where hidden face
removal comes in…</p>
<h2 id="hidden-face-removal">Hidden Face Removal</h2>
<p>Pick up something square. A stiffy disk will do fine. Face it towards you,
and number all the corners from one to four in a clockwise direction.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 1 +-------------+ 2
| |
| |
| |
| |
4 +-------------+ 3</code></pre></figure>
<p>Now rotate the stiffy disk on all three axes, making sure that you can
still see the front of the disk. You will notice that whenever you can see
the front of the disk, the four points are still in alphabetical order. Now
rotate it so that you can see the back of the stiffy. Your points will now
be:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 2 +-------------+ 1
| |
| |
| |
| |
3 +-------------+ 4</code></pre></figure>
<p>The points are now anti-clockwise! This means, in its simplest form, that
if you define all your polygon points in a clockwise order, when drawing you
ignore the polys that are anticlockwise. (Obviously when you define the 3D
object, you define the polygons facing away from you in an anticlockwise
order.)</p>
<p>To find out whether a poly’s points are clockwise or not, we need to find
its normal. Here is where things start getting fun.</p>
<p>In school, you are told that a normal is perpendicular to the plane. In
ASCII:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> | Normal
|
|
--------------------------- Polygon</code></pre></figure>
<p>As you can see, the normal is at 90 degrees to the surface of the poly. We
must extend this to three dimensions for our polygons. You’ll have to trust
me on that, I can’t draw it in ASCII :)</p>
<p>To find a normal, you only need three points from your poly (ABC):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">A(x0,y0,z0), B(X1,Y1,Z1), C(X2,Y2,Z2)</code></pre></figure>
<p>then the vector normal = AB^AC = (Xn,Yn,Zn) with</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">Xn=(y1-y0)(z0-z2)-(z1-z0)(y0-y2)
Yn=(z1-z0)(x0-x2)-(x1-x0)(z0-z2)
Zn=(x1-x0)(y0-y2)-(y1-y0)(x0-x2)</code></pre></figure>
<p>We are interested in the Z normal, so we will use the function:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">normal:=(x1-x0)(y0-y2)-(y1-y0)(x0-x2);</code></pre></figure>
<p>The result is something of a sine wave when you rotate the poly in three
dimensions. A negative value means that the poly is facing you, a positive
value means that it is pointing away.</p>
<p>The above means that with a mere two MULs you can discount an entire poly
and not draw it. This method is perfect for “closed” objects such as cubes
etc.</p>
<p>I am anything but a maths teacher, so go borrow someone’s math book to find
out more about surface normals. Trust me, there is a lot more written about
them than you think.</p>
<p>An extension of calculating your normal is finding out about light-sourcing
your polygons. Watch for more information in one of the next few trainers.</p>
<p>A combination of the above two routines should work quite nicely in
creating 3d objects with little or no overlapping.</p>Alexander van OostenrijkThis trainer is on 3D hidden face removal and face sorting. I was going to add shading, but that can wait until a later trainer. For convenience I will build on the 3D code from Part 8. The maths for face removal is a bit tricky, but just think back to your old high school trigonometry classes.Denthor/Asphyxia’s VGA trainers: Flame effect2023-02-06T16:11:00+00:002023-02-06T16:11:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-19-flame-effect<p>This trainer is on assembler. For those people who already know assembler
quite well, this tutorial is also on the flame effect.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="assembler---the-short-version">Assembler - the short version</h2>
<p>Okay, there are many assembler trainers out there, many of which are
probably better than this one. I will focus on the areas of assembler that
I find important… if you want more, go buy a book (go for the Michael
Abrash ones), or scour the ‘net for others.</p>
<p>First, let us start off with the basic set up of an assembler program.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">DOSSEG</code></pre></figure>
<p>This tells your assembler program to order your segments in the same manner
that high level languages do.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">.MODEL <MODEL></code></pre></figure>
<p><code class="highlighter-rouge"><MODEL></code> can be:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">Tiny Code + Data < 64k (Can be made a COM file)
Small Code < 64k Data < 64k
Medium Code > 64k Data < 64k
Compact Code < 64k Data > 64k
Large Code > 64k Data > 64k
Huge Arrays > 64k</code></pre></figure>
<p>Enable 286 instructions … can be <code class="highlighter-rouge">.386</code> ; <code class="highlighter-rouge">.386P</code> etc.:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">.286</code></pre></figure>
<p>Set the stack. <code class="highlighter-rouge"><size></code> will be the size of your stack. I usually use <code class="highlighter-rouge">200h</code>:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">.STACK <size></code></pre></figure>
<p>Tells the program that the data is about to follow. (Everything after this
will be placed in the data segment):</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">.DATA</code></pre></figure>
<p>Tells the program that the code is about to follow. (Everything after this
will be placed in the code segment)</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">.CODE</code></pre></figure>
<p>Tells the program that this is where the code begins:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">START:</code></pre></figure>
<p>Tells the program that this is where the code ends:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">END START</code></pre></figure>
<p>To compile and run an assembler file, we run:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">tasm bob
tlink bob</code></pre></figure>
<p>I personally use <code class="highlighter-rouge">tasm</code>; you will have to find out how your assembler works.</p>
<p>Now, if we ran the above file as follows:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">DOSSEG
.MODEL SMALL
.286
.STACK 200h
.DATA
.CODE
START
END START</code></pre></figure>
<p>You would think that is would just exit to DOS immediately, right? Wrong.
You have to specifically give DOS back control, by doing the following:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">START
mov ax,4c00h
int 21h
END START</code></pre></figure>
<p>Now if you compiled it, it would run and do nothing.</p>
<p>Okay, let us kick off with registers.</p>
<p>Firstly: A bit is a value that is either 1 or 0.</p>
<p>This is obviously quite limited, but if we start counting in them, we can
get larger numbers. Counting with ones and zeros is known as binary, and we
call it base 2. Counting in normal decimal is known as base 10, and
counting in hexadecimal is known as base 16.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> Base 2 (Binary) Base 10 (Decimal) Base 16 (Hexadecimal)
0 0 0
1 1 1
10 2 2
11 3 3
100 4 4
101 5 5
110 6 6
111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F</code></pre></figure>
<p>As you can see, you need four bits to count up to 15, and we call this a
<em>nibble</em>. With eight bits, we can count up to 255, and we call this a <em>byte</em>.
With sixteen bits, we can count up to 65535, and we call this a <em>word</em>. With
thirty-two bits, we can count up to lots, and we call this a <em>double word</em>.</p>
<p>A quick note: Converting from binary to hexadecimal is actually quite easy. You
break up the binary into groups of four bits, starting on the right, and
convert these groups of four to hex.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 1010 0010 1111 0001
= A 2 F 1</code></pre></figure>
<p>Converting to decimal is a bit more difficult. What you do, is you multiply
each number by its base to the power of its index…</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> A2F1 hex
= (A*16^3) + (2*16^2) + (F*16^1) + (1*16^0)
= (10*4096) + (2*256) + (15*16) + (1)
= 40960 + 512 + 240 + 1
= 41713 decimal</code></pre></figure>
<p>The same system can be used for binary.</p>
<p>To convert from decimal to another base, you divide the decimal value by the
desired base, keeping a note of the remainders, and then read the results
backwards.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 16 | 41713
16 | 2607 r 1 (41713 / 16 = 2607 r 1)
16 | 162 r F (2607 / 16 = 162 r 15)
16 | 10 r 2 (162 / 16 = 10 r 2)
| 0 r A (10 / 16 = 0 r 10)</code></pre></figure>
<p>Read the remainders backwards, our number is <code class="highlighter-rouge">A2F1</code> hex. Again, the same
method can be used for binary.</p>
<p>The reason why hex is popular is obvious: using bits, it is impossible
to get a reasonable base 10 (decimal) system going, and binary gets unwieldly
at high values. Don’t worry too much though: most assemblers (like <code class="highlighter-rouge">tasm</code>)
will convert all your decimal values to hex for you.</p>
<p>You have four general purpose registers: <code class="highlighter-rouge">AX</code>, <code class="highlighter-rouge">BX</code>, <code class="highlighter-rouge">CX</code> and <code class="highlighter-rouge">DX</code>.
Think of them as variables that you will always have. On a 286, these
registers are 16 bytes long, or one <em>word</em>.</p>
<p>As you know, a <em>word</em> consists of two bytes, and in assembler you can access
these bytes individually. They are separated into high bytes and low bytes per
word.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> High byte | Low byte
0000 0000 | 0000 0000 bits
[--------Word-------]</code></pre></figure>
<p>The method of access is easy. The high byte of <code class="highlighter-rouge">AX</code> is <code class="highlighter-rouge">AH</code>, and the low byte is
<code class="highlighter-rouge">AL</code>. You can also access <code class="highlighter-rouge">BH</code>, <code class="highlighter-rouge">BL</code>, <code class="highlighter-rouge">CH</code>, <code class="highlighter-rouge">CL</code>, <code class="highlighter-rouge">DH</code> and <code class="highlighter-rouge">DL</code>.</p>
<p>A 386 has extended registers: <code class="highlighter-rouge">EAX</code>, <code class="highlighter-rouge">EBX</code>, <code class="highlighter-rouge">ECX</code>, <code class="highlighter-rouge">EDX</code>… you can access the
lower word normally (as <code class="highlighter-rouge">AX</code>, with bytes <code class="highlighter-rouge">AH</code> and <code class="highlighter-rouge">AL</code>), but you cannot access the
high word directly … you must <code class="highlighter-rouge">ror EAX,16</code> (rotate the binary value through
16 bits), after which the high word and low word swap … do it again to
return them. Acessing <code class="highlighter-rouge">EAX</code> as a whole is no problem:
<code class="highlighter-rouge">mov eax, 10; add eax,ebx</code> … these are all valid.</p>
<p>Next come segments. As you have probably heard, computer memory is divided
into various 64k segments (note: 64k = 65,536 bytes, sound familiar?) A
segment register points to which segment you are looking at. An offset
register points to how far into that segment you are looking. One way
of looking at it is like looking at a 2D array… the segments are your
columns and your offsets are your rows. Segments and offsets are displayed
as Segment:Offset … so <code class="highlighter-rouge">$a000:50</code> would mean the fiftieth byte in segment
<code class="highlighter-rouge">$a000</code>.</p>
<p>The segment registers are <code class="highlighter-rouge">ES</code>, <code class="highlighter-rouge">DS</code>, <code class="highlighter-rouge">SS</code> and <code class="highlighter-rouge">CS</code>. A 386 also has <code class="highlighter-rouge">FS</code> and <code class="highlighter-rouge">GS</code>.
These values are words (0-65,535), and you cannot access the high or low bytes
separately. <code class="highlighter-rouge">CS</code> points to your code segment, and usually if you touch this
your program will explode. <code class="highlighter-rouge">SS</code> points to your stack segment, again, this
baby is dangerous. <code class="highlighter-rouge">DS</code> points to your data segment, and can be altered, if
you put it back after you use it, and don’t use any global variables while
it is altered. <code class="highlighter-rouge">ES</code> is your extra segment, and you can do what you want with
it.</p>
<p>The offset registers are <code class="highlighter-rouge">DI</code>, <code class="highlighter-rouge">SI</code>, <code class="highlighter-rouge">IP</code>, <code class="highlighter-rouge">SP</code>, <code class="highlighter-rouge">BP</code>. Offset registers are generally
associated with specific segment registers, as follows:
<code class="highlighter-rouge">ES:DI</code> <code class="highlighter-rouge">DS:SI</code> <code class="highlighter-rouge">CS:IP</code> <code class="highlighter-rouge">SS:SP</code> … On a 286, <code class="highlighter-rouge">BX</code> can be used instead of the above
offset registers, and on a 386, any register may be used. <code class="highlighter-rouge">DS:BX</code> is therefore
valid.</p>
<p>If you create a global variable (let’s say <code class="highlighter-rouge">bob</code>), when you access that
variable, the compiler will actually look for it in the data segment.
This means that the statement:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">ax = bob</code></pre></figure>
<p>could be</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">ax = ds:[15]</code></pre></figure>
<p>A quick note: A value may be signed or unsigned. An unsigned word has a
range from 0 to 65,535. A signed word is called an <em>integer</em> and has a range
-32,768 to 32,767. With a signed value, if the leftmost bit is equal to 1,
the value is in the negative.</p>
<p>Next, let us have a look at the stack. Let us say that you want to save the
value in <code class="highlighter-rouge">ax</code>, use <code class="highlighter-rouge">ax</code> to do other things, then restore it to its origional
value afterwards. This is done by utilizing the stack. Have a look at the
following code:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov ax, 50 ; ax is equal to 50
push ax ; push ax onto the stack
mov ax, 27 ; ax is equal to 27
pop ax ; pop ax off the stack</code></pre></figure>
<p>At this point, <code class="highlighter-rouge">ax</code> is equal to 50.</p>
<p>Remember we defined the stack to be <code class="highlighter-rouge">200h</code> further up? This is part of the
reason we have it. When you push a value onto the stack, that value is
recorded on the stack heap (referenced by <code class="highlighter-rouge">SS:SP</code>, <code class="highlighter-rouge">SP</code> is incremented) When you
pop a value off the stack, the value is placed into the variable you are
popping it back in to, <code class="highlighter-rouge">SP</code> is decremented and so forth. Note that the computer
does not care what you pop the value back into.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov ax, 50
push ax
pop bx</code></pre></figure>
<p>This would set the values of both <code class="highlighter-rouge">ax</code> and <code class="highlighter-rouge">bx</code> to <code class="highlighter-rouge">50</code>. (There are faster ways
of doing this, pushing and popping are fairly fast though).</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">push ax
push bx
pop ax
pop bx</code></pre></figure>
<p>This would swap the values of <code class="highlighter-rouge">ax</code> and <code class="highlighter-rouge">bx</code>. As you can see, to pop the values back
in to the original variables, you must pop them back in the opposite
direction to which you pushed them.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">push ax
push bx
push cx
pop cx
pop bx
pop ax</code></pre></figure>
<p>would result in no change for any of the registers.</p>
<p>When a procedure is called, all the parameters for that procedure are pushed
onto the stack. These can actually be read right off the stack, if you want
to.</p>
<p>As you have already seen, the <code class="highlighter-rouge">mov</code> command moves a value…</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov <dest>, <source></code></pre></figure>
<p>Note that <code class="highlighter-rouge">dest</code> and <code class="highlighter-rouge">source</code> must be the same number of bits long.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov ax, dl</code></pre></figure>
<p>would not work, and neither would</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov cl,bx</code></pre></figure>
<p>However:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">mov cx,dx
mov ax,50
mov es,ax</code></pre></figure>
<p>are all valid.</p>
<p><code class="highlighter-rouge">shl</code> I have explained before, it is where all the bits in a register are
shifted one to the left and a zero added on to the right. This is the
equivalent of multiplying the value by two. <code class="highlighter-rouge">shr</code> works in the opposite
direction.</p>
<p><code class="highlighter-rouge">rol</code> does the same, except that the bit that is removed from the left is
replaced on the right hand side. <code class="highlighter-rouge">ror</code> works in the opposite direction.</p>
<p><code class="highlighter-rouge">div <value></code> divides the value in <code class="highlighter-rouge">ax</code> by value and returns the result in
<code class="highlighter-rouge">al</code> if value is a byte, placing the remainder in <code class="highlighter-rouge">ah</code>. If value is a word,
the double word <code class="highlighter-rouge">DX:AX</code> is divided by value, the result being placed in <code class="highlighter-rouge">ax</code>
and the remainder in <code class="highlighter-rouge">dx</code>. Note that this only works for unsigned values.</p>
<p><code class="highlighter-rouge">idiv <value></code> does the same as above, but for signed variables.</p>
<p><code class="highlighter-rouge">mul <value></code> If value is a byte, <code class="highlighter-rouge">al</code> is multiplied by value and the result
is stored in <code class="highlighter-rouge">ax</code>. If value is a word, <code class="highlighter-rouge">ax</code> is multiplied by value and the
result is stored in the double word <code class="highlighter-rouge">DX:AX</code>.</p>
<p><code class="highlighter-rouge">imul <value></code> does the same as above, but for signed variables.</p>
<p>The <code class="highlighter-rouge">j*</code> commands are fairly simple: if a condition is met, jump to a certain
label.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">jz <label> Jump if zero
ja <label> Jump above (unsigned)
jg <label> Jump greater (signed)</code></pre></figure>
<p>and so forth.</p>
<p>An example:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">cmp ax,50 ; Compare ax to 50
je @Equal ; If they are equal, jump to label @equal
call MyProc ; Runs procedure MyProc and then returns to the next line of code.</code></pre></figure>
<p>Procedures are declared as follows:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">MyProc proc near
ret ; Must be here to return from where it was called
MyProc endp</code></pre></figure>
<p>Variables are also easy:</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">bob db 50</code></pre></figure>
<p>creates a variable <code class="highlighter-rouge">bob</code>, a byte, with an initial value of 50.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">bob2 dw 50</code></pre></figure>
<p>creates a variable <code class="highlighter-rouge">bob2</code>, a word, with an initial value of 50.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">bob3 db 1,2,3,4,5,65,23</code></pre></figure>
<p>creates <code class="highlighter-rouge">bob3</code>, an array of 7 bytes.</p>
<figure class="highlight"><pre><code class="language-asm" data-lang="asm">bob4 db 100 dup (?)</code></pre></figure>
<p>creates <code class="highlighter-rouge">bob4</code>, an array of 100 bytes, with no starting value.</p>
<p>Go back and look at <a href="/denthor-asphyxia-vga-trainer-7-animation.html">Part 7</a> for a whole lot more assembler commands, and get
some sort of reference guide to help you out with others. I personally use
the Norton Guides help file to program assembler.</p>
<h2 id="fire-routines">Fire Routines</h2>
<p>To demonstrate how to write an assembler program, we will write a fire
routine in 100% assembler. The theory is simple.</p>
<p>Set the palette to go from white to yellow to red to blue to black.
Create a 2D array representing the screen on the computer.
Place high values at the bottom of the array (screen)
for each element, do the following:</p>
<ul>
<li>Take the average of the four elements under it:</li>
</ul>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> * Current element
123
4 Other elements</code></pre></figure>
<ul>
<li>Get the average of the four elements, and place the result in the current element.</li>
<li>Repeat</li>
</ul>
<p>Easy, no? I first saw a fire routine in the Iguana demo, and I just had to
do one ;) … it looks very effective.</p>
<p>With the sample file, I have created a batch file, <code class="highlighter-rouge">make.bat</code>. It basically
says:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">tasm fire
tlink fire</code></pre></figure>
<p>So to build and run the fire program, type:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">make
fire</code></pre></figure>
<p>The source file is commented quite well, so there shouldn’t be any problems.</p>
<h2 id="in-closing">In closing</h2>
<p>As you can see, the sample program is in 100% assembler. For the next tutorial
I will return to Pascal, and hopefully your newfound assembler skills will
help you there too.</p>Alexander van OostenrijkThis trainer is on assembler. For those people who already know assembler quite well, this tutorial is also on the flame effect.Denthor/Asphyxia’s VGA trainers: File packing2023-02-06T15:22:00+00:002023-02-06T15:22:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-18-file-packing<p>This trainer is about reading PCX files, file packing, and putting everything
into one executable file.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="loading-a-pcx-file">Loading a PCX file</h2>
<p>This is actually quite easy. The PCX file is divided into three sections,
namely a 128 byte header, a data section, and a 768 byte palette.</p>
<p>You can usually ignore the 128 byte header, but here it is:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">0 Manufacturer 10 = ZSoft .PCX file
1 Version
2 Encoding
3 Bits Per Pixel
4 XMin, Ymin, XMax, YMax (2 bytes each)
12 Horizontal Resolution (2 bytes)
14 Vertical Resolution (2 bytes)
16 Color palette setting (48 bytes)
64 Reserved
65 Number of color planes
66 Bytes per line (2 bytes)
68 1 = Color 2 = Grayscale (2 bytes)
70 Blank (58 bytes)</code></pre></figure>
<p>That makes 128 bytes.</p>
<p>The palette file, which is 768 bytes, is situated at the very end of the
PCX file. The 769’th byte back should be the decimal 12, which indicates
that a VGA color palette is to follow.</p>
<p>There is one thing that we have to do to get the palette correct in VGA…
the PCX palette values for R,G,B are 0 to 255 respectively. To convert this
to our standard (VGA) palette, we must divide the R,G,B values by 4, to get
them into a range of 0 to 63.</p>
<p>Actually decoding the image is very simple. Starting after the 128 byte
header, we read a byte.</p>
<p>If the top two bits of this byte are not set, we dump the value to the screen.</p>
<p>We check bits as follows:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">if ((temp and $c0) = $c0) then ...(bits are set)... else ...(bits are not set)</code></pre></figure>
<p>C0 in hex = 11000000 in binary = 192 in decimal</p>
<p>Let’s look at that more closely…</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> temp and c0h
temp and 11000000b</code></pre></figure>
<p>That means, when represented in bit format, both corresponding bits have
to be set to one for the result to be one.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">0 and 0 = 0 1 and 0 = 0 0 and 1 = 0 1 and 1 = 1</code></pre></figure>
<p>In the above case then, both of the top two bits of temp have to be set for
the result to equal <code class="highlighter-rouge">11000000b</code>. If it does not equal that, the top two bits
are not both set, and we can put the pixel.</p>
<p>So:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="k">if</span> <span class="p">((</span><span class="n">temp</span> <span class="k">and</span> <span class="p">$</span><span class="n">c0</span><span class="p">)</span> <span class="p">=</span> <span class="p">$</span><span class="n">c0</span><span class="p">)</span> <span class="k">then</span> <span class="k">BEGIN</span>
<span class="k">END</span> <span class="k">else</span> <span class="k">BEGIN</span>
<span class="n">putpixel</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="n">temp</span><span class="p">,</span><span class="n">vga</span><span class="p">);</span>
<span class="k">inc</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>If the top two bits <em>are</em> set, things change. The bottom six bits become
a loop counter, which the next byte is repeated.</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="k">if</span> <span class="p">((</span><span class="n">temp</span> <span class="k">and</span> <span class="p">$</span><span class="n">c0</span><span class="p">)</span> <span class="p">=</span> <span class="p">$</span><span class="n">c0</span><span class="p">)</span> <span class="k">then</span> <span class="k">BEGIN</span>
<span class="cm">{ Read in next byte, temp2 here.}</span>
<span class="k">for</span> <span class="n">loop1</span><span class="p">:=</span><span class="m">1</span> <span class="k">to</span> <span class="p">(</span><span class="n">temp</span> <span class="k">and</span> <span class="p">$</span><span class="m">3f</span><span class="p">)</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="n">putpixel</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="n">temp2</span><span class="p">,</span><span class="n">vga</span><span class="p">);</span>
<span class="k">inc</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span>
<span class="k">END</span> <span class="k">else</span> <span class="k">BEGIN</span>
<span class="n">putpixel</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="n">temp</span><span class="p">,</span><span class="n">vga</span><span class="p">);</span>
<span class="k">inc</span> <span class="p">(</span><span class="n">screenpos</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>There is our PCX loader. You will note that by and’ing <code class="highlighter-rouge">temp</code> by <code class="highlighter-rouge">$3f</code>; I am
and’ing it by <code class="highlighter-rouge">00111111b</code>… in other words, clearing the top two bits.</p>
<p>The sample program has the above in assembler, but it is the same procedure…
and you can read the next tutorial for more info on how to code in assembler.</p>
<p>In the sample I assume that the pic is 320x200, with a maximum size of 66,432
bytes.</p>
<h2 id="file-packing">File packing</h2>
<p>The question is simple: how do I get all my files into one executable?
Having many small data files can start to look unprofessional.</p>
<p>An easy way to have one .exe and one .dat file when dealing with many
cels etc. is easy… you would alter your load procedure, which looks
as follows:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">VAR</span> <span class="n">temp</span> <span class="p">:</span> <span class="k">Array</span> <span class="p">[</span><span class="m">1..5</span><span class="p">,</span><span class="m">1..256</span><span class="p">]</span> <span class="k">of</span> <span class="kt">byte</span><span class="p">;</span>
<span class="k">Procedure</span> <span class="n">Init</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic1.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">1</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic2.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">2</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic3.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">3</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic4.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">4</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic5.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">5</span><span class="p">]);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>For one compile you would do this:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">VAR</span> <span class="n">temp</span> <span class="p">:</span> <span class="k">Array</span> <span class="p">[</span><span class="m">1..5</span><span class="p">,</span><span class="m">1..256</span><span class="p">]</span> <span class="k">of</span> <span class="kt">byte</span><span class="p">;</span>
<span class="k">Procedure</span> <span class="n">Init</span><span class="p">;</span>
<span class="k">VAR</span> <span class="n">f</span><span class="p">:</span><span class="k">File</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic1.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">1</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic2.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">2</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic3.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">3</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic4.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">4</span><span class="p">]);</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'pic5.cel'</span><span class="p">,</span><span class="n">temp</span><span class="p">[</span><span class="m">5</span><span class="p">]);</span>
<span class="n">assign</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="s">'pic.dat'</span><span class="p">);</span>
<span class="n">rewrite</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="m">1</span><span class="p">);</span>
<span class="n">blockwrite</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">temp</span><span class="p">,</span><span class="n">sizeof</span><span class="p">(</span><span class="n">temp</span><span class="p">));</span>
<span class="n">close</span> <span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>From then on, you would do:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">VAR</span> <span class="n">temp</span> <span class="p">:</span> <span class="k">Array</span> <span class="p">[</span><span class="m">1..5</span><span class="p">,</span><span class="m">1..256</span><span class="p">]</span> <span class="k">of</span> <span class="kt">byte</span><span class="p">;</span>
<span class="k">Procedure</span> <span class="n">Init</span><span class="p">;</span>
<span class="k">VAR</span> <span class="n">f</span><span class="p">:</span><span class="k">File</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">assign</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="s">'pic.dat'</span><span class="p">);</span>
<span class="n">reset</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="m">1</span><span class="p">);</span>
<span class="n">blockread</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">temp</span><span class="p">,</span><span class="n">sizeof</span><span class="p">(</span><span class="n">temp</span><span class="p">));</span>
<span class="n">close</span> <span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>This means that instead of five data files, you now have one! You have also
stripped the 800 byte cel header too. Note that this will work for any
form of data, not just cel files.</p>
<p>The next logical step is to include this data in the .exe file, but the
question is how? In an earlier tutorial, I converted my data file to
constants and placed it inside my main program. This is not good mainly
because of space restrictions … you can only have so many constants, and
what if your data file is two megs big?</p>
<p>Attached with this tutorial is a solution. I have written a program that
combines your data files with your executable file, no matter how big
the data is. The only thing you have to worry about is a small change in
your data loading methods. Let’s find out what.</p>
<h2 id="using-the-file-packer">Using the file packer</h2>
<p>Normally you would load your data as follows:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">Procedure</span> <span class="n">Init</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="s">'bob.bob'</span><span class="p">,</span><span class="n">temp</span><span class="p">);</span>
<span class="n">loadpcx</span> <span class="p">(</span><span class="s">'den.pcx'</span><span class="p">,</span><span class="n">VGA</span><span class="p">);</span> <span class="cm">{ Load a PCX file }</span>
<span class="n">loaddat</span> <span class="p">(</span><span class="s">'data.dat'</span><span class="p">,</span><span class="n">lookup</span><span class="p">);</span> <span class="cm">{ Load raw data into lookup }</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>Easy, hey? Now, using the file packer, you would change this to:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">USES</span> <span class="n">fpack</span><span class="p">;</span>
<span class="k">Procedure</span> <span class="n">Init</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">total</span> <span class="p">:=</span> <span class="m">3</span><span class="p">;</span>
<span class="n">infodat</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">:=</span> <span class="s">'bob.bob'</span><span class="p">;</span>
<span class="n">infodat</span><span class="p">[</span><span class="m">2</span><span class="p">]</span> <span class="p">:=</span> <span class="s">'den.pcx'</span><span class="p">;</span>
<span class="n">infodat</span><span class="p">[</span><span class="m">3</span><span class="p">]</span> <span class="p">:=</span> <span class="s">'data.dat'</span><span class="p">;</span>
<span class="n">loadcel</span> <span class="p">(</span><span class="m">1</span><span class="p">,</span><span class="n">temp</span><span class="p">);</span>
<span class="n">loadpcx</span> <span class="p">(</span><span class="m">2</span><span class="p">,</span><span class="n">VGA</span><span class="p">);</span>
<span class="n">loaddat</span> <span class="p">(</span><span class="m">3</span><span class="p">,</span><span class="n">lookup</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>Not too difficult? Now, this is still using the normal data files on your
hard drive. You would then run PACK.EXE, select the program’s .exe as the
base, then select “bob.bob”, “den.pcx” and “data.dat”, in order (1, 2, 3).
Hit “c” to contine, and it will combine the files. Your programs .exe file
will be able to run independently of the separate data files on disk,
because the data files are imbedded with the .exe.</p>
<p>Let us take a closer look at the load procedures. Normally a load procedure
would look as follows:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">Procedure</span> <span class="n">LoadData</span> <span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="k">string</span><span class="p">;</span> <span class="n">p</span><span class="p">:</span><span class="kt">pointer</span><span class="p">);</span>
<span class="k">VAR</span> <span class="n">f</span><span class="p">:</span><span class="k">file</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="n">assign</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">name</span><span class="p">);</span>
<span class="n">reset</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="m">1</span><span class="p">);</span>
<span class="n">blockread</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">p</span><span class="p">^,</span><span class="n">filesize</span><span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="n">close</span> <span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>In FPack.pas, we do the following:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"><span class="k">Function</span> <span class="n">LoadData</span> <span class="p">(</span><span class="n">num</span><span class="p">:</span><span class="kt">integer</span><span class="p">;</span> <span class="n">p</span><span class="p">:</span><span class="kt">pointer</span><span class="p">):</span><span class="kt">Boolean</span><span class="p">;</span>
<span class="k">VAR</span> <span class="n">f</span><span class="p">:</span><span class="k">file</span><span class="p">;</span>
<span class="k">BEGIN</span>
<span class="k">If</span> <span class="n">pack</span> <span class="k">then</span> <span class="k">BEGIN</span>
<span class="n">assign</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">paramstr</span><span class="p">(</span><span class="m">0</span><span class="p">));</span>
<span class="n">reset</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="m">1</span><span class="p">);</span>
<span class="n">seek</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">infopos</span><span class="p">[</span><span class="n">num</span><span class="p">]);</span>
<span class="n">blockread</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">p</span><span class="p">^,</span> <span class="n">infopos</span><span class="p">[</span><span class="n">num</span><span class="p">+</span><span class="m">1</span><span class="p">]-</span><span class="n">infopos</span><span class="p">[</span><span class="n">num</span><span class="p">]);</span>
<span class="n">close</span> <span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">END</span> <span class="k">else</span> <span class="k">BEGIN</span>
<span class="n">assign</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="n">infodat</span><span class="p">[</span><span class="n">num</span><span class="p">]);</span>
<span class="n">reset</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="m">1</span><span class="p">);</span>
<span class="n">blockread</span> <span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">p</span><span class="p">^,</span> <span class="n">filesize</span> <span class="p">(</span><span class="n">f</span><span class="p">));</span>
<span class="n">close</span> <span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">END</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>As you can see, we just have two special cases depending on whether or not
the .exe file has been packed yet.</p>
<p><em>NOTE: After you have packed the file, you CAN NOT pklite it. You can
however pklite the .exe_before you run pack.exe … in other
words, you cannot use pklite to try pack your data files.</em></p>
<p>PACK.EXE does have a limitation … you can only pack 24 data files together.
If you would like it to do more, mail me … It should be easy to increase the
number.</p>
<p>In the sample program, FINAL.EXE is the same as temp.pas, except it has
its PCX embedded inside it. I ran pack2.exe, selected final.exe and
eye.pcx, hit “C”, and there it was. You will notice that eye.pcx is not
included in the directory … it is now part of the exe!</p>
<h2 id="in-closing">In closing</h2>
<p>Well, that’s about it for this trainer… next one (as I have mentioned
twice already ;) will be on assembler, with a flame routine thrown in.</p>
<p>This tut has been a bit of a departure from normal tuts … aside from the
PCX loading routines, the rest has been “non programming” oriented …
don’t worry, next week’s one will be back to normal.</p>Alexander van OostenrijkThis trainer is about reading PCX files, file packing, and putting everything into one executable file.Denthor/Asphyxia’s VGA trainers: Pixel morphing2023-02-06T15:16:00+00:002023-02-06T15:16:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-17-pixel-morphing<p>This trainer is on a few demo effects (pixel morphs and static).</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="pixel-morphing">Pixel Morphing</h2>
<p>Have you ever lain down on your back in the grass and looked up at the
cloudy sky? If you have, you have probably seen the clouds move together
and create wonderful shapes… that cloud plus that cloud together make a
whale… a ship… a face etc.</p>
<p>We can’t quite outdo Mother Nature, but we can sure give it a shot. The
effect I am going to show you is where various pixels at different starting
points move together and create an overall picture.</p>
<p>The theory behind it is simple: each pixel has bits of data associated
with it, most important of which is as follows:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">This is my color
This is where I am
This is where I want to be.</code></pre></figure>
<p>The pixel, keeping its color, goes from where it is to where it wants to
be. Our main problem is <em>how</em> it moves from where it is to where it wants
to be. A obvious approach would be to say “If its destination is above it,
decrement its <code class="highlighter-rouge">y</code> value, if the destination is to the left, decrement its <code class="highlighter-rouge">x</code>
value and so on.”</p>
<p>This would be bad. The pixel would only ever move at set angles, as you can
see below:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> Dest O-----------------\
\ <--- Path
\
\
O Source</code></pre></figure>
<p>Doesn’t look very nice, does it? The pixels would also take different times
to get to their destination, whereas we want them to reach their points at
the same time, i.e.:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> Dest 1 O-------------------------------O Source 1
Dest 2 O-----------------O Source 2</code></pre></figure>
<p>Pixels 1 and 2 must get to their destinations at the same time for the best
effect. The way this is done by defining the number of frames or “hops”
needed to get from source to destination. For example, we could tell pixel
one it is allowed 64 hops to get to its destination, and the same for
point 2, and they would both arrive at the same time, even though pixel 2
is closer.</p>
<p>The next question, it how do we move the pixels in a straight line? This is
easier than you think…</p>
<p>Let us assume that for each pixel, <code class="highlighter-rouge">x1,y1</code> is where it is, and <code class="highlighter-rouge">x2,y2</code> is where
it wants to be.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> (x2-x1) = The distance on the X axis between the two points
(y2-y1) = The distance on the Y axis between the two points</code></pre></figure>
<p>If we do the following:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> dx := (x2-x1)/64;</code></pre></figure>
<p>we come out with a value in <code class="highlighter-rouge">dx</code> which is very useful. If we added <code class="highlighter-rouge">dx</code> to <code class="highlighter-rouge">x1</code> 64
times, the result would be <code class="highlighter-rouge">x2</code>! Let us check…</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> dx = (x2-x1)/64
dx*64 = x2-x1 { Multiply both sides by 64 }
dx*64+x1 = x2 { Add x1 to both sides }</code></pre></figure>
<p>This is high school math stuff, and is pretty self explanatory. So what we
have is the x movement for every frame that the pixel has to undergo. We
find the y movement in the same manner.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> dy := (y2-y1)/64;</code></pre></figure>
<p>So our program is as follows:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="cm">{ Set x1,y1 and x2,y2 values }</span>
<span class="n">dx</span><span class="p">:=</span> <span class="p">(</span><span class="n">x2</span><span class="p">-</span><span class="n">x1</span><span class="p">)/</span><span class="m">64</span><span class="p">;</span>
<span class="n">dy</span><span class="p">:=</span> <span class="p">(</span><span class="n">y2</span><span class="p">-</span><span class="n">y1</span><span class="p">)/</span><span class="m">64</span><span class="p">;</span>
<span class="k">for</span> <span class="n">loop1</span><span class="p">:=</span><span class="m">1</span> <span class="k">to</span> <span class="m">64</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="n">putpixel</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span><span class="n">y1</span><span class="p">)</span>
<span class="n">wait</span><span class="p">;</span>
<span class="n">clear</span> <span class="n">pixel</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span><span class="n">y1</span><span class="p">);</span>
<span class="n">x1</span><span class="p">:=</span><span class="n">x1</span><span class="p">+</span><span class="n">dx</span><span class="p">;</span>
<span class="n">y1</span><span class="p">:=</span><span class="n">y1</span><span class="p">+</span><span class="n">dy</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>If there was a compiler that could use the above pseudocode, it would move
the pixel from x1,y1 to x2,y2 in 64 steps.</p>
<p>So, what we do is set up an array of many pixels with this information, and
move them all at once… voilá, we have pixel morphing! It is usually best
to use a bitmap which defines the color and destination of the pixels, then
randomly scatter them around the screen.</p>
<p>Why not use pixel morphing on a base object in 3d? It would be the work of
a moment to add in a Z axis to the above.</p>
<p>The sample program uses fixed point math in order to achieve high speeds,
but it is basically the above algorithm.</p>
<h2 id="static">Static</h2>
<p>A static screen was one of the first effects Asphyxia ever did. We never
actually released it because we couldn’t find anywhere it would fit. Maybe
you can.</p>
<p>The easiest way to get a screen of static is to tune your TV into an unused
station … you even get the cool noise effect too. Those people who build
TVs really know how to code ;-)</p>
<p>For us on a PC however, it is not as easy to generate a screen full of
static (unless you desperately need a new monitor)</p>
<p>What we do is this:</p>
<ul>
<li>Set colors 1-16 to various shades of grey.</li>
<li>Fill the screen up with random pixels between colors 1 and 16</li>
<li>Rotate the palette of colors 1 to 16.</li>
</ul>
<p>That’s it! You have a screenful of static! To get two images in one static
screen, all you need to do is fade up/down the specific colors you are
using for static in one of the images.</p>
<p>A nice thing about a static screen is that it is just palette rotations
… you can do lots of things in the foreground at the same time (such as a
scroller).</p>
<h2 id="in-closing">In closing</h2>
<p>Well, that is about it … as I say, I will be doing more theory stuff in
future, as individual demo effects can be thought up if you know the base
stuff.</p>
<p>Note the putpixel in this GFX3.PAS unit … it is <em>very</em> fast .. but
remember, just calling a procedure eats clock ticks… so embed putpixels
in your code if you need them. Most of the time a putpixel is not needed
though.</p>Alexander van OostenrijkThis trainer is on a few demo effects (pixel morphs and static).Denthor/Asphyxia’s VGA trainers: Bitmap scaling2023-02-06T15:06:00+00:002023-02-06T15:06:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-16-bitmap-scaling<p>This trainer is on the scaling of an arbitrary sized bitmap to
screen in two dimensions. The sample program seems to work quite quickly,
and the code is documented. The scaling procedure is however totally in
assembler… hopefully this won’t cause too many problems.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="what-is-scaling">What is scaling?</h2>
<p>I think that most of you know this one already, but here goes. Let us say
you have a picture (10x10 pixels) and you want to draw it to a different
size (say 5x7 pixel), the process of altering the picture to fit into the
new size is called scaling. Scaling only works on rectangular areas.</p>
<p>With scaling to can easily stretch and shrink your bitmaps.</p>
<h2 id="okay-so-how-do-we-code-it">Okay, so how do we code it?</h2>
<p>Right. The way I am going to do scaling is as follows:</p>
<p>For the horizontal area, I am going to calculate a certain step value. I
will then trace along the bitmap, adding this step to my position, and
place the nearest pixel on to the screen. Let me explain this simpler…</p>
<p>Let us say I have a 10 pixel wide bitmap. I want to squish it into 5 pixels.
Along the bitmap, I would draw every second pixel to screen. In ASCII:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> 1234567890 13579
+--------+ +---+
| | | |
| bitmap | | |dest
| | | |
+--------+ +---+</code></pre></figure>
<p>As you can see, by stepping through every second pixel, I have shrunk the
bitmap to a width of 5 pixels.</p>
<p>The equation is as follows:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">step = origionalwidth / wantedwidth;</code></pre></figure>
<p>Let us say we have a 100 pixel wide bitmap, which we want to get to 20 pixels.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">step = 100 / 20
step = 5</code></pre></figure>
<p>If we draw every fifth pixel from the original bitmap, we have scaled it down
correctly! This also works for all values, if step is of type <code class="highlighter-rouge">real</code>.</p>
<p>We also find the step for the height in the same way.</p>
<p>Our horizontal loop is as follows:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="k">For</span> <span class="n">loop1</span><span class="p">:=</span><span class="m">1</span> <span class="k">to</span> <span class="n">wantedwidth</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="n">putpixel</span> <span class="p">(</span><span class="n">loop1</span><span class="p">,</span><span class="n">height</span><span class="p">,</span><span class="n">bitmap</span><span class="p">[</span><span class="n">round</span> <span class="p">(</span><span class="n">curpos</span><span class="p">)],</span><span class="n">vga</span><span class="p">);</span>
<span class="n">curpos</span><span class="p">:=</span><span class="n">curpos</span><span class="p">+</span><span class="n">xstep</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>And the vertical loop is much the same. Easy huh? So east in fact, I wrote the
procedure in pure assembler for you ;-) … don’t worry, it’s commented.</p>
<p>In the sample program, instead of using reals I have used fixed point math.
Refer to <a href="/denthor-asphyxia-vga-trainer-14-glenz.html">Part 14</a> if you have any hassles with fixed point, it is fairly straightforward.</p>
<p>I also use psuedo 3-d perspective transforms to get the positions smooth…
after <a href="/denthor-asphyxia-vga-trainer-8-3d-visualization.html">Part 8</a>, this should be a breeze.</p>
<p>There are no new commands in the assembler for you, so you should get by with what
you learned in tut7/8 … whew! A lot of back referencing there ;) We really are
building on our knowledge :)</p>
<h2 id="in-closing">In closing</h2>
<p>Well, that is about it. As you can see the concept is easy, and in fact
fairly obvious, but that didn’t stop me from having to sit down with a
pencil and a piece of paper a few months ago and puzzle it out ;-)</p>Alexander van OostenrijkThis trainer is on the scaling of an arbitrary sized bitmap to screen in two dimensions. The sample program seems to work quite quickly, and the code is documented. The scaling procedure is however totally in assembler… hopefully this won’t cause too many problems.Denthor/Asphyxia’s VGA trainers: Plasmas2023-02-06T15:06:00+00:002023-02-06T15:06:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-15-plasma<p>Plasmas are a great way to wow your friends by their weird shapes and forms.
I was at one stage going to write a game where the bad guy just had two
circular plasmas instead of eyes… I am sure you will find creative and
inventive new ways of doing and using plasmas.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="how-do-plasmas-work">How do plasmas work?</h2>
<p>I will only cover one type of plasma here: a realtime plasma of course.
Other types of plasmas include a static picture with a pallette rotation.</p>
<p>When you get right down to it, this method of realtime plasmas is merely an
intersection of four COS waves. We get our color at a particular point by
saying:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
col := costbl[one]+costbl[two]+costbl[three]+costbl[four];</code></pre></figure>
<p>The trick is getting the four indexes of that cos table array to create
something that looks nice. This is how we organize it: have two of them
being indexes for vertical movement and two of them being indexes for
horizontal movement.</p>
<p>This means that by changing these values we can move along the plasma. To
draw an individual screen, we pass the values of the four to another four
so that we do not disturb the original values. For every pixel across, we
add values to the first two indexes, then display the next pixel. For
every row down, we add values to the second two indexes. Sound complex
enough? Good, because that what we want, a complex shape on the screen.</p>
<p>By altering the original four values, we can get all sorts of cool movement
and cycling of the plasma. The reason we use a cos table is as follows:
a cos table has a nice curve in the value of the numbers… when you
put two or more together, it is possible to get circular pictures…
circles are hard to do on a computer, so this makes it a lot easier…</p>
<p>Okay, now you can have a look at the source file, all I do is put the above
into practice. I did add one or two things though…</p>
<p>Background: This is just a large array, with the values in the array being
added to the plasma at that pixel.</p>
<p>Psychedelic: This cycles through about 7000 colors instead of just rotating
through the base 256.</p>
<h2 id="clever-fading">Clever fading</h2>
<p>You will notice when the sample program fades in and out that the colors
all reach their destination at the same time … in other words, they don’t
all increment by one until they hit the right color then stop. When done
in that way the fading does not look as professional.</p>
<p>Here is how we do a step-crossfade:</p>
<p>Each r,g,b value can be between 0 and 64. Have the palette we want to get
to in <code class="highlighter-rouge">bob</code> and the temporary pallette in <code class="highlighter-rouge">bob2</code>. For each step, from 0 to 63
do the following:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal">
<span class="n">bob2</span><span class="p">[</span><span class="n">loop1</span><span class="p">].</span><span class="n">r</span><span class="p">:=</span><span class="n">bob</span><span class="p">[</span><span class="n">loop1</span><span class="p">].</span><span class="n">r</span><span class="p">*</span><span class="n">step</span><span class="p">/</span><span class="m">64</span><span class="p">;</span></code></pre></figure>
<p>That means if we are halfway through the crossfade (step=32) and the red
value is meant to get to 16, our equation looks like this:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal">
<span class="n">r</span><span class="p">:=</span><span class="m">16</span><span class="p">*</span><span class="m">32</span><span class="p">/</span><span class="m">64</span>
<span class="n">r</span><span class="p">=</span><span class="m">8</span></code></pre></figure>
<p>Which is half of the way to where it wants to be. This means all colors will
fade in/out with the same ratios… and look nicer.</p>
<h2 id="rotating-the-pallette">Rotating the pallette</h2>
<p>I have done this one before, I think. Here it is:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
move color 0 into temp
move color 1 into color 0
move color 2 into color 1
move color 3 into color 2
and so on till color 255
move temp into color 255</code></pre></figure>
<p>And your palette is rotating. Easy huh? Recheck <a href="/denthor-asphyxia-vga-trainer-2-palette.html">Part 2</a> for more info on
palette rotation.</p>
<h2 id="in-closing">In closing</h2>
<p>The tutorial was a bit short this time, but that is mostly because the
sample file is self-explanatory. The file can however be speeded up, and
of course you can add certain things which will totally change the look
of the plasma.</p>Alexander van OostenrijkPlasmas are a great way to wow your friends by their weird shapes and forms. I was at one stage going to write a game where the bad guy just had two circular plasmas instead of eyes… I am sure you will find creative and inventive new ways of doing and using plasmas.Denthor/Asphyxia’s VGA trainers: Glenzing, Faster polygons, fixed-point math2023-02-06T14:56:09+00:002023-02-06T14:56:09+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-14-glenz<p>Well, this trainer is mainly on four things: glenzing, faster polygons,
fixed-point math and assembler. The sample program is basically
<a href="/denthor-asphyxia-vga-trainer-9-3d-solids.html">Part 9</a>
rewritten to include the above. I’ll go through them in order, and hopefully you won’t have any hassles
grasping the concepts.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="what-is-glenzing">What is glenzing?</h2>
<p>This is an easy one. Imagine, in a 3D object, that all the sides are
made out of colored glass. That means that every time you look through
a side, everything behind it is tinged in a certain color.</p>
<p>In ASCII:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +---------+
| <--|---Light blue
| |
+--------+ |
| | <-|-----|---Dark blue
| +---|-----+
| <--|---------Light blue
+--------+</code></pre></figure>
<p>So where the two sides overlap, the color values of the two sides are
added. Easy huh? It is also easy to code. This is how you do it:</p>
<ul>
<li>Set up your palette to be a nice run of colors.</li>
<li>Draw your first poly.</li>
<li>While drawing poly 1, instead of plonking down a set pixel color, grab the background pixel, add 1 to it, then put the result down.</li>
<li>Draw your second poly.</li>
<li>While drawing poly 2, instead of plonking down a set pixel color, grab the background pixel, add 2 to it, then put the result down.</li>
<li>and so forth.</li>
</ul>
<p>So if the color behind poly 1 was 5, you would place pixel 6 down instead.</p>
<p>If you do this for every single pixel of every single side of your 3D
object, you then have glenzing going. This is obviously slightly slower
than just drawing an item straight, but in the sample program it goes
quite quickly. This is because of the following sections.</p>
<h2 id="faster-polygons">Faster polygons</h2>
<p>In <a href="/denthor-asphyxia-vga-trainer-9-3d-solids.html">Part 9</a>, you probably noticed that we were using a multiply for every
single line of the poly that we drew. This is not good. Let’s find out
how to speed it up, shall we?</p>
<p>With the multiply method, we went through every line, to find out the
minimum x and maximum x value for that line.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +
------/---\------- Find min x and max x, draw a line
/ \ between them.
+ +
\ /
\ /
+</code></pre></figure>
<p>How about if we found out all the min and max x’s for every line first,
then just went through an array drawing them. We could do it by
“scanning” each side in turn. Here is how we do it:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> + 1
/
/
2 +</code></pre></figure>
<p>We go from point one to point two. For every single y we go down, we
move a constant x value. This value is found like this:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">xchange := (x1-x2)/(y1-y2)</code></pre></figure>
<p>Remember gradients? This is how you calculated the slope of a line <em>waaay</em>
back in school. You never thought it would be any use, didn’t you?</p>
<p>Anyway, with this value, we can do the following:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="k">For</span> <span class="n">loop1</span><span class="p">:=</span><span class="n">y1</span> <span class="k">to</span> <span class="n">y2</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="p">[</span> <span class="n">Put</span> <span class="n">clever</span> <span class="n">stuff</span> <span class="n">here</span> <span class="p">]</span>
<span class="n">x</span><span class="p">:=</span><span class="n">x</span><span class="p">+</span><span class="n">xchange</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>and we will go through all the x-values we need for that line. Clever,
huh?</p>
<p>Now for the clever bit. You have an array, from 0 to 199 (which is all
the possible y-values your onscreen poly can have). Inside this is two
values, which will be your min x and your max x. You start off with the
min x being a huge number, and the max x being a low number. Then you
scan a side. For each y, check to see if one of the following has
happened:</p>
<ul>
<li>If the x value is smaller than the xmin value in your array, make the xmin value equal to the x value</li>
<li>If the x value is larger than the xmax value in your array, make the xmax value equal to the x value</li>
</ul>
<p>The loop now looks like this:</p>
<figure class="highlight"><pre><code class="language-pascal" data-lang="pascal"> <span class="k">For</span> <span class="n">loop1</span><span class="p">:=</span><span class="n">y1</span> <span class="k">to</span> <span class="n">y2</span> <span class="k">do</span> <span class="k">BEGIN</span>
<span class="k">if</span> <span class="n">x</span><span class="p">></span><span class="n">poly</span><span class="p">[</span><span class="n">loop1</span><span class="p">,</span><span class="m">1</span><span class="p">]</span> <span class="k">then</span> <span class="n">poly</span><span class="p">[</span><span class="n">loop1</span><span class="p">,</span><span class="m">1</span><span class="p">]:=</span><span class="n">x</span><span class="p">;</span>
<span class="k">if</span> <span class="n">x</span><span class="p"><</span><span class="n">poly</span><span class="p">[</span><span class="n">loop1</span><span class="p">,</span><span class="m">1</span><span class="p">]</span> <span class="k">then</span> <span class="n">poly</span><span class="p">[</span><span class="n">loop1</span><span class="p">,</span><span class="m">1</span><span class="p">]:=</span><span class="n">x</span><span class="p">;</span>
<span class="n">x</span><span class="p">:=</span><span class="n">x</span><span class="p">+</span><span class="n">xchange</span><span class="p">;</span>
<span class="k">END</span><span class="p">;</span></code></pre></figure>
<p>Easy? Do this for all four sides (you can change this for polys with
different numbers of sides), and then you have all the x min and x max
values you need to draw your polygon.</p>
<p>In the sample program, if you replaced the <code class="highlighter-rouge">Hline</code> procedure with one that
draws solid lines, you could use the given drawpoly for solids.</p>
<p>Even this procedure is sped up by the next section, on fixed point.</p>
<h2 id="what-is-fixed-point">What is fixed point?</h2>
<p>Have you ever noticed how slow reals are? I mean <em>slooooow</em>. You can get a
massive speed increase in most programs by replacing your reals with
integers, words etc. But, I hear you cry, what happens to the much
needed fraction bit after the decimal point? The answer? You keep it.
Here’s how.</p>
<p>Let us say you have a word, which is 16 bits. If you want to use it as a
fixed point value, you can separate it into 2 sections, one of which
holds the whole value, and one which holds the fraction.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">00000000 00000000 <-Bits
Whole Fraction</code></pre></figure>
<p>The number 6.5 would therefore be shown as follows:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
Top byte : 6
Bottom byte : 128</code></pre></figure>
<p>128 is half (or .5) of 256, and in the case of the fraction section, 256
would equal one whole number.</p>
<p>So let us say we had <code class="highlighter-rouge">6.5 * 2</code>. Using reals this would be a slow <code class="highlighter-rouge">mul</code>, but
with fixed point …</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
Top Byte : 6
Bottom Byte : 128
Value : 1664 <-This is the true value of the word
ie. (top byte*256)+bottom byte).
this is how the computer sees the
word.
1664 shl 1 = 3328 <-shl 1 is the same as *2, just faster.
Top byte : 13
Bottom byte : 0</code></pre></figure>
<p>As you can see, we got the correct result! And in a fraction of the time
that a multiplication of a real would have taken us. You can add and
subtract fixed point values with no hassles, and multiply and divide
them by normal values too. When you need the whole value section, you
can just read the high byte, or do the following:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
whole = word shr 8
eg 1664 shr 8 = 6</code></pre></figure>
<p>As you can see, the fraction is truncated. Obviously, the more bits you
set aside for the fraction section, the more accurate your calculation
is, but the lesser the maximum whole number you can have. For example,
in the above numbers, the maximum value of your whole number was 256, a
far cry from the 65,535 a normal (non fixed point) word’s maximum.</p>
<p>There are a lot of hassles using fixed point (go on, try shift a
negative value), most of which have to do with the fact that you have
severely decreased the maximum number you may have, but trust me, the
speed increase is worth it (With long integers, and/or extended 386
registers, you can even have 16x16 fixed point, which means high
accuracy and high maximum values)</p>
<p>Try write a program using fixed point. It is not difficult and you will
get it perfect easily. Trust me, I’m a democoder ;-)</p>
<h2 id="assembler">Assembler</h2>
<p>In the sample program I used one or two assembler commands that I havent
discussed with you. Here they are:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">
imul value This is the same as mul, but for integer values. It
multiplies ax by the value. If the value is a word,
it returns the result in DX:AX
sal register,value This is the same as shl, but it is arithmetic,
in other words it works on integers. If you
had to shl a negative value, the result would
mean nothing to you.
rcl register,value This is the same as shl, but after you have
shifted, the value in the carry flag is placed
in the now-vacated rightmost bit. The carry
flag is set when you do an operation where the
result is greater than the upmost possible
value of the variable (usually 65535 or 32767)
eg mov ax,64000
shl ax,1 {<- Carry flag now = 1}</code></pre></figure>
<p>For more info on shifting etc, re-read
<a href="/denthor-asphyxia-vga-trainer-7-animation.html">Part 7</a>, it goes into the concept
in detail.</p>
<p>The sample program is basically
<a href="/denthor-asphyxia-vga-trainer-9-3d-solids.html">Part 9</a> rewritten. To see how the
assembler stuff is working, do the following … Go into 50 line mode
(-Much- easier to debug), then hit [Alt - D] then R. A little box with
all your registers, segments etc and their values will pop up. Move it
down to where you want it, then go back to the program screen (Hit Alt
and its number together), and resize it so that you have both it and
the register box onscreen at once (Alt - 5 to resize) … then use F4,
F7 and F8 to trace though the program (you know how). The current value
of the registers will always be in that box.</p>
<h2 id="in-closing">In closing</h2>
<p>Well, that is about it. The sample program may start as being a little
intimidating to some when they first look at it, just remember to read
it with <a href="/denthor-asphyxia-vga-trainer-9-3d-solids.html">Part 9</a>, very little is different, it’s just with fixed point and
a bit of assembler.</p>Alexander van OostenrijkWell, this trainer is mainly on four things: glenzing, faster polygons, fixed-point math and assembler. The sample program is basically Part 9 rewritten to include the above. I’ll go through them in order, and hopefully you won’t have any hassles grasping the concepts.Denthor/Asphyxia’s VGA trainers: Starfields2023-02-06T14:54:00+00:002023-02-06T14:54:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-13-starfields<p>This trainer is on starfields, which is by request of more than one
person. This is quite an easy effect, and you should have no trouble
grasping the concept behind it. I will be doing a 3D starfield; a
horizontal starfield is very easy with you merely incrementing a x-value
for each star for each frame.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="what-is-a-3d-starfield">What is a 3D starfield?</h2>
<p>I am not even sure if I should do this bit. Go watch any episode of Star
Trek, the movies, Star Wars, or just about any sci-fi movie. Somewhere
there will be a scene where you can see stars whizzing past the
viewscreen, with the ones that are further away moving slower than the
ones that are passed quite close to.</p>
<p>This is a 3D starfield. If you look closely, you will see that all the
stars seem to originate from a point, the point you are travelling
towards. This is an illusion which thankfully happens automatically,
you don’t have to code for it ;)</p>
<p>Starfields look very nice, and can make a big difference to an otherwise
black background. It also makes a great screen saver ;-)</p>
<h2 id="how-do-they-work">How do they work?</h2>
<p>This is actually quite simple. Imagine if you will, each star in the
heavens having an x,y and z coordinate, with you being at 0,0,0. Easy?
Right. Now, if you were to say move forward, i.e. increase your z value,
to you you will still be at 0,0,0 but all the stars z values would
have appeared to decrease by the exact same amount.</p>
<p>In easier language, we decrease the z value of all the stars so that
they come closer to you, and eventually whizz past.</p>
<p>This solves all our problems. Stars that are close to us on the x and y
scales will pass us by faster than those that are very far from us on
the x and y scales. The only thing we must watch out for is that no star
is at 0,0 , i.e. exactly in front of us, otherwise there will be a
collision which will not look good.</p>
<h2 id="how-do-we-code-this">How do we code this?</h2>
<p>The first thing to be done is to generate our starfield. This is quite
easy, with us choosing x values between -160 and 160, and y values
between -100 and 100 randomly. Each z is sequentially greater for each
star so that we don’t get large areas with no stars. We must remember to
check that there are no stars at 0,0!</p>
<p>Okay, now we start the actual viewing section. Here are the steps:</p>
<p>1) Convert our 3-d coordinates into their 2-d versions. Have a look at tut 8
to see how this is done, but basically we divide by z.</p>
<p>2) Clear away all old stars that may be on the screen.</p>
<p>3) Draw all our stars according to our 2-d values we have calculated in
1)</p>
<p>4) Move all the stars either closer to us or further away from us by
decreasing or increasing their z values respectively.</p>
<p>5) If a star’s z value has passed into the negative, place it at the
very back of our “queue” so that it will come around again</p>
<p>6) Jump back to 1) ad-infinitum.</p>
<p>That is, as they say, it. In our sample program the steps have been
neatly placed into individual procedures for easy reading.</p>
<h2 id="what-next">What next?</h2>
<p>Okay, so now we have a cool looking starfield. What next? How about
adding left and right motion? A menu or a scrolly in the foreground? How
about figuring out how a star tunnel works? A cool 3d routine going in
front of the stars?</p>
<p>A starfield can make just about any routine look just that much more
professional, and can itself be improved to be a great effect all on
its own.</p>Alexander van OostenrijkThis trainer is on starfields, which is by request of more than one person. This is quite an easy effect, and you should have no trouble grasping the concept behind it. I will be doing a 3D starfield; a horizontal starfield is very easy with you merely incrementing a x-value for each star for each frame.Denthor/Asphyxia’s VGA trainers: Chain-4 Scrolling2023-02-06T14:49:00+00:002023-02-06T14:49:00+00:00http://www.independent-software.com/denthor-asphyxia-vga-trainer-12-chain4-scrolling<p>This trainer is on full-screen scrolling in Chain-4, by request. This is
actually very easy to do (and smooth), and holds a lot of potential, as
I am sure you can immediately imagine.</p>
<!--more-->
<div style="background:steelblue; color: white; padding-top: 15px; border-top: solid 1px #333; border-bottom: solid 1px #333; margin-bottom: 32px;">
<pre style="background: none; text-align: center">
DENTHOR, coder for ...
_____ _____ ____ __ __ ___ ___ ___ ___ __ _____
/ _ \ / ___> | _ \ | |_| | \ \/ / \ \/ / | | / _ \
| _ | \___ \ | __/ | _ | \ / > < | | | _ |
\_/ \_/ <_____/ |__| |__| |__| |__| /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!
</pre>
<p style="text-align:center; font-weight: bold">Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the
creation of demo effects in the 90s. I reproduce them here, as they offer
so much insight into the demo scene of the time.
</p>
<p style="text-align: center">
These articles apply some formatting to Denthor's original ASCII files, plus
a few typo fixes.
</p>
</div>
<h2 id="what-is-full-screen-scrolling">What is full-screen scrolling?</h2>
<p>I seem to recall doing this in a previous tutorial, but here goes again!
Full-screen scrolling is when the entire screen moves in a particular
direction, with the new picture scrolling on to the screen. Um. Think of
movie credits. The screen, filled with text, is scrolled off the top of
the screen while the new text is scrolled on from the bottom. This is
full-screen scrolling.</p>
<p>Full-screen scrolling is not limited to movie credits. Games like Raptor
have you flying over a scrolling landscape while you are shooting down
the bad guys. In this tutorial we will be doing vertical scrolling, but
the code can very easily be altered for horizontal scrolling too.</p>
<p>Remember that we will be using Chain-4 to do our scrolling, so you may
want to brush up on <a href="/denthor-asphyxia-vga-trainer-10-chain4.html">Part 1</a> in which that was covered. I will assume a brief knowledge of how chain-4 works for this tutorial.</p>
<h2 id="the-theory">The theory</h2>
<p>The theory behind full-screen scrolling in Chain-4 is actually very
simple.</p>
<p>Picture if you will, a screen that is two monitors high. Chain-4
actually has four, but for this we only need two. Now, for this screen
that is two monitors high, we can only see one monitor’s worth. Here it
is in ASCII:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +-------------+ Screen two monitors high
| |
| |
| |
| |
|+-----------+|
|| ||
|| ||<- This is the bit we can see, one monitor's worth
|| ||
|+-----------+|
+-------------+</code></pre></figure>
<p>We can move the bit we can see up or down the enlarged screen. So, for
example, if the screen two monitors high had a picture on it, we could
move the bit we see up and down to try glimpse the entire picture. Think
of it in this way: the screen is a large painting, but we can only see
though a small magnifying glass. We can move this magnifing glass around
the painting, but can never see the painting all at once.</p>
<p>This actually works in our favour. Anything done outside the bit we are
looking through cannot be seen, so we can do our work without changing
our screen.</p>
<p>On to scrolling. The method we will use this time is as follows:</p>
<p>1) Draw the next line to be seen just above and just below the part we
can see.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +------------+ The enlarged screen
| |
| |
|111111111111| The new part of the picture
|+----------+|
|| || The bit we can see
|+----------+|
|111111111111| The new part of the picture
+------------+</code></pre></figure>
<p>2) Move the view up one pixel so that the new part of the picture is
visible at the top of the screen.</p>
<p>3) Repeat Steps 1) and 2) until the whole screen is filled. Our screen
will look as follows :</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +---------------+
|+-------------+|
||3333333333333||
||2222222222222|| Viewscreen
||1111111111111||
|+-------------+|
|333333333333333|
|222222222222222|
|111111111111111|
+---------------+</code></pre></figure>
<p>Check this picture with steps 1) and 2), you will see that this is
correct.</p>
<p>4) Set our viewport to the bottom of the enlarged screen.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text"> +---------------+
|333333333333333|
|222222222222222|
|111111111111111|
|+-------------+|
||3333333333333||
||2222222222222|| New position of viewscreen
||1111111111111||
|+-------------+|
+---------------+</code></pre></figure>
<p>As you can see, the bit we will be looking at is exactly the same as
before, we are now just at the bottom of the larger screen instead of
the top!</p>
<p>5) Jump back to 1). The entire sequence can begin again, and we can have
infinite scrolling in the upward direction. Clever huh?</p>
<h2 id="our-code">Our code</h2>
<p>In the sample code, we have 21 different icons. What we do is decide
what the next row of icons is going to consist of. We then draw the next
line of pixels above and below the viewscreen according to what icons we
are displaying. We then scroll up one pixel and begin again. When we
have completed a row of icons, we randomly select a new row and begin
again. Our icons are 16x16, so exactly 20 fit across a 320 pixel screen.</p>
<p>When we hit the top of our enlarged screen, we flip down to the bottom
which looks exactly the same as the screen we have left. In this manner
we have obtained smooth, infinite full-screen scrolling!</p>
<h2 id="extra-bits">Extra bits</h2>
<p>As you will see from the code, it would be the work of but a few minutes
to extend our landscape across the two unused screens, thereby allowing
limited horizontal movement along with our vertical movement. In fact,
the entire routine could easily be made to be a horizontal scrolling
routine.</p>
<p>A map of sorts could be generated, with one byte equaling one terrain
type. In this manner, the terrain scrolled over could be set, as in a
flying game (Flying Shark, Raptor etc). The terrain could also easily be
replaced with letters for our movie-style credits.</p>
<p>Free direction scrolling, i.e. scrolling in all directions, is a very
different matter, with very different methods to get it to work. Perhaps
this will be discussed in a later trainer. But for now, work with this,
know it, understand it, and think up many great things to do with it!
How about a full-screen text scrolly? A game? Go wild!</p>
<h2 id="in-closing">In closing</h2>
<p>Well, I hope you enjoyed this, the latest trainer. The sample program is
a little short, but that is because the concept is so simple. Attached
is a file, PICCS.DAT, which contains the terrain and letters for the
sample program. They were .CEL’s, which I loaded into the <code class="highlighter-rouge">des^</code> variable,
which I then dumped to disk, for easy access later. The .CEL’s were
drawn on short notice, but still produces some nice terrain.</p>
<p>I have received a few requests for future trainers, and most of them are
for effects, so I guess that is what will be done from now on. A
surprising number have told me not to do a sound trainer and stick with
graphics stuff, with only a few asking for sound stuff, so I will hold
off on that until there is more of a demand.</p>
<p>I am still open to suggestions for future trainers, and of course
suggestions for improving the series. Leave me mail!</p>Alexander van OostenrijkThis trainer is on full-screen scrolling in Chain-4, by request. This is actually very easy to do (and smooth), and holds a lot of potential, as I am sure you can immediately imagine.