Program TunnelEffect;
{$G+,A+}
(* This tunnel effect was done by Kari Salminen (a.k.a. Il£vatar /
Vandals) at the Scenario '96 party place on Wookiecoders' computer (Hi!).
This source code is public domain and can be freely molested in anyway
imaginable. If you want to use this in your intro/demo you have my full
approval for that. No need for credits either... this is quite simple stuff
and nowadays tunnels are seen in far too many productions, so why bother
about who did it, because we've all seen it before... and it's quite
boring!
Address:
Kari Salminen
Patsaskuja 17
FIN-29600 Noormarkku
E-mail:
kari.salminen@salbox.nullnet.fi
BBS:
Borealis BBS
+358 2 637 6222
*)
uses crt;
type
fullSeg = array [0..65534] of byte;
var
anglePos, depthPos : byte;
virtSeg, anglesSeg, depthsSeg,
pos, textureSeg, tunnel1Seg,
tunnel2Seg : word;
x, y, angle, depth, distance : integer;
i, frame : longint;
a : real;
pal : array [0..767] of byte;
video : array [0..65534] of byte absolute $a000:0;
posConv : array [0..511] of word;
sine : array [0..4095] of longint;
virtScr : ^fullSeg;
texture, tunnel1, tunnel2 : ^fullSeg;
f : file;
const
r = 32;
d = 128;
angleConv = 0;
depthConv = 256;
{$L tunasm.obj}
{$F+}
Procedure doTunnel;external;
{$F-}
Procedure setVideoMode (videoMode : word);assembler;
asm
mov ax, [videoMode]
int 10h
end;
Procedure setMode80x50;assembler;
asm
mov ax, 0003h
int 10h
mov ax, 1112h
xor bl, bl
int 10h
end;
Procedure copyVirtScr (fromSeg, toSeg : word);assembler;
asm
push ds
mov es, [toSeg]
mov ds, [fromSeg]
xor si, si
xor di, di
mov cx, (320*200)/4
db 66h; rep movsw
pop ds
end;
Procedure copyAndClrVirtScr (fromSeg, toSeg : word);assembler;
asm
push ds
mov es, [toSeg]
mov ds, [fromSeg]
xor si, si
mov cx, (320*200)/4
@@copyLoop:
db 66h; xor ax, ax
db 66h; xchg ds:[si], ax
db 66h; mov es:[si], ax
add si, 4
dec cx
jnz @@copyLoop
pop ds
end;
Procedure waitVr;assembler;
asm
mov dx, 03dah
@@vrOn:
in al, dx
test al, 8
jnz @@vrOn
@@vrOff:
in al, dx
test al, 8
jz @@vrOff
end;
Function sar16 (value : longint) : integer;assembler;
asm
mov ax, [word ptr value+2]
end;
Function sal16 (value : integer) : longint;assembler;
asm
xor ax, ax
mov dx, [value]
end;
Procedure clrVirtScr (scrSeg : word);assembler;
asm
xor di, di
mov es, [scrSeg]
mov cx, (320*200)/4
db 66h; xor ax, ax
db 66h; rep stosw
end;
function fileExists(FileName: String): Boolean;
var
F: file;
begin
{$I-}
Assign(F, FileName);
FileMode := 0;
Reset(F);
Close(F);
{$I+}
FileExists := (IOResult = 0) and (FileName <> '');
end;
begin
new (virtScr);
new (tunnel1);
new (tunnel2);
new (texture);
virtSeg:=seg (virtScr^)+((ofs (virtScr^)+15) shr 4);
textureSeg:=seg (texture^)+((ofs (texture^)+15) shr 4);
tunnel1Seg:=seg (tunnel1^)+((ofs (tunnel1^)+15) shr 4);
tunnel2Seg:=seg (tunnel2^)+((ofs (tunnel2^)+15) shr 4);
for i:=0 to 4095 do sine [i]:=round(sin(i/4095*2*pi)*65536);
setVideoMode ($0013);
directvideo:=false;
if not fileExists (paramstr (1)) then
begin
setMode80x50;
writeln ('Error opening texture file: ''',paramstr (1),'''.');
halt;
end;
assign (f, paramstr (1));
reset (f, 1);
seek (f, 10);
blockread (f, pal, 256*3);
blockread (f, mem [textureSeg:0], 32768);
blockread (f, mem [textureSeg:32768], 32768);
close (f);
fillchar (tunnel1^, 320*200, 0);
fillchar (tunnel2^, 320*200, 0);
(* I calculate the tunnel data by drawing circles in the tunnel tables.
Angle is got straight from the angle loop counter and depth is
got by calculating (tunnel_radius*eye_distance_from_screen)/distance_
of_pixel_onscreen_from_the_screen_middlepoint... easy!-) *)
for distance:=0 to 188 do
begin
if distance=0 then
depth:=(r*d)
else
depth:=(r*d) div distance;
for angle:=0 to 4095 do
begin
x:=sar16(sine [(angle+1024) and 4095]*distance)+160;
y:=sar16(sine [angle]*distance)+100;
if (x >= 0) and (x < 320) and (y >= 0) and (y < 100) then
begin
mem [tunnel1Seg:((x+y*320) shl 1)+0]:=angle shr 4;
mem [tunnel1Seg:((x+y*320) shl 1)+1]:=depth;
end
else
if (x >= 0) and (x < 320) and (y >= 100) and (y < 200) then
begin
mem [tunnel2Seg:((x+(y-100)*320) shl 1)+0]:=angle shr 4;
mem [tunnel2Seg:((x+(y-100)*320) shl 1)+1]:=depth;
end;
if port [$60]=1 then
begin
setMode80x50;
halt;
end;
end;
gotoxy (1, 1);
writeln ((distance*100) div 188,' % done.');
end;
clrVirtScr (virtSeg);
port [$03c8]:=0;
for i:=0 to 767 do port [$3c9]:=pal [i];
repeat
for i:=0 to 255 do posConv [i+angleConv]:=byte(i+anglePos)+
(sar16(sine [(frame shl 5+(i shl 5)) and 4095]*16) shl 8);
for i:=0 to 255 do posConv [i+depthConv]:=(byte(i+depthPos) shl 8)+
sar16(sine [(frame shl 5+(i shl 5)) and 4095]*16);
(* Here the angleConv and depthConv tables are put to virtSeg right
after the 320x200 picture... this way they are easily accessible
with the same segment register we use to access virtual screen. *)
for i:=0 to 256*2-1 do memw [virtSeg:320*200+(i shl 1)]:=posConv [i];
(* This is the Pascal version of the tunnel inner loop:
for i:=0 to 320*100-1 do
mem [virtSeg:i]:=
mem [textureSeg:posConv[angleConv+tunnel1^[(i shl 1)+0]]+posConv[depthConv+tunnel1^[(i shl 1)+1]]];
for i:=0 to 320*100-1 do
mem [virtSeg:i+32000]:=
mem [textureSeg:posConv[angleConv+tunnel2^[(i shl 1)+0]]+posConv[depthConv+tunnel2^[(i shl 1)+1]]];
*)
doTunnel;
waitVr;
copyVirtScr (virtSeg, $a000);
inc (anglePos);
inc (depthPos, 4);
inc (frame);
until port[$60]=1;
setMode80x50;
dispose (virtScr);
dispose (tunnel1);
dispose (tunnel2);
dispose (texture);
end.
(*
I've put here an explanation of how the tunnel effect is done... by
me of coz'... but a while ago :-). The method of calculating the tunnel
data by writing circles isn't explained at all in the following text, but
you can look it up from the upper code... hopefully %-).
But to the point... if you make X something like COSà*DISTANCE you
get from this equation under me something completely different:
COSà*R*D COSà*R*D R*D
Z = ÄÄÄÄÄÄÄÄ becomes Z = ÄÄÄÄÄÄÄÄÄÄÄÄÄ which becomes Z = ÄÄÄÄÄÄÄÄ -> nice!
X COSà*DISTANCE DISTANCE
R = Radius of the tunnel
D = Distance of the eye from the projection plane i.e. screen
DISTANCE = Distance of the pixel from the screen's middlepoint
Just look at the upper code to understand?-) Hopefully...
*)
{
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
80x50! ³ ® How to make a 3D texture mapped tunnel effect ¯ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
... by Il£vatar of Nordic Line (a.k.a. Kari Salminen) 060795
BTW this the first documentation for an effect I have ever written, because
usually I just keep those thingies in my mind and don't release them...
It's 6:30 am at the time I'm writing this so let's get to the point...
First you'll need about 128Kb of free memory for the precalculations... ;)
Check... Ok.
Then you need a nice 256x256/256c. picture to be the texture map...
Check... Ok.
So far so fine - but how is the effect done... Ok, a little piece of maths
is coming right up...
I suppose that you know the common perspective projection that is :
(Some variables first though...)
Xp,Yp = Projected X and Y-coordinates
X,Y,Z = The original coordinates of a 3D point
D = Eye's distance from the projection plane
P = Projection plane = screen (Only needed for the picture)
We can derive from the picture's similar triangles the following stuff :
(Hey you there look at the nice ascii triangles below!)
Y Yp ³ Y*D
ÄÄÄ = ÄÄÄ ³*D --> ÄÄÄ = Yp
D+Z D ³ D+Z
/³ So the Y-perspective projection calculation is :
/ ³³
/ ³³ Yp = Y*D/(D+Z)
/ ³³
/ ³³ And the X is calculated similarly :
/ ³³
/ ³³ Xp = X*D/(D+Z)
/ ³³
/³³ ³³ Check... Ok.
/ ³³ ³³
/ ³ ³
/ ³Yp ³Y
/ ³ ³
/ ³³ ³³
/ ³³ ³³
/ ³ ³³
ÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÙ
<--D-->P<--Z-->
And I suppose that you know something about radians and trigonometry too.
(More ascii art coming up...)
In a circle with a radius of 1 the X-coordinate is cosà and
the Y-coordinate is sinà... simple 8:)
³\ Usually cosà=b/c and sinà=a/c but when c=1 then
³ \ cosà=b/1 and sinà=a/1 so it all comes down to
³ \ c=radius cosà=b and sinà=a and yet more simplified
³ \ cosà=x and sinà=y... great ;)
a=y³ \
³ \ Check... Ok.
³ \
³ à\
ÀÄÄÄÄÄÄÄÄ
b=x
Now you know all the basic math to begin to make the tunnel effecto...
The tunnel is an infinite series of circles repeated another after
another. So now we have to make a mathematical equation for the tunnel...
Let's give it a shot! (BOOM!)
BTW : We can discard the D from the D+Z (divider) of the perspective
projection equation. So Xp = X*D/(D+Z) comes Xp = X*D/Z and
Yp = Y*D/(D+Z) comes Yp = Y*D/Z, this is sufficient for the tunnel
calculation and works fine otherwise too.
R = Constant radius of the tunnel (Determined by YOU!).
D = Eye's constant distance from the screen (Determined by YOU!).
You can try different values for R and D but I suggest that something
from 32 to 256 are good values for R and D. More math coming up...
COSà*R*D
ÄÄÄÄÄÄÄÄ = Xp (Equation for the projected X-coordinate on the tunnel wall)
Z
SINà*R*D
ÄÄÄÄÄÄÄÄ = Yp (Equation for the projected Y-coordinate on the tunnel wall)
Z
What we now need is the à (angle) and the Z (depth)...
Let's first solve the Z...
(You can use the equation with SINà too, if you like to.)
COSà*R*D ³ ³ COSà*R*D
ÄÄÄÄÄÄÄÄ = Xp ³*Z --> Xp*Z = COSà*R*D ³:Xp --> Z = ÄÄÄÄÄÄÄÄ
Z ³ ³ Xp
Ok... And now going for the angle!
COSà*R*D SINà*R*D ³ COSà SINà ³ COSà*Yp ³
ÄÄÄÄÄÄÄÄ = ÄÄÄÄÄÄÄÄ ³:R*D --> ÄÄÄÄ = ÄÄÄÄ ³*Yp --> ÄÄÄÄÄÄÄ = SINà ³:COSà
Xp Yp ³ Xp Yp ³ Xp ³
Yp SINà Yp Yp
ÄÄ = ÄÄÄÄ --> ÄÄ = TANà --> à = ATAN ÄÄ
Xp COSà Xp Xp
Math equations... Check... Ok.
Now you just go thru the whole screen from
-WIDTH/2 TO WIDTH/2-1 for the X and
-HEIGHT/2 TO HEIGHT/2-1 for the Y.
For a 320x200 screen you would go from -160 to 159 for the X and from -100
to 99 for the Y.
And for every pixel you calculate the following :
Y
à = ATAN Ä (Calculate this first for every pixel)
X
When you calculate this value, the ARCTAN function of you favorite
programming language will give the angle in radians _VERY_ probably. So we
need to convert the à from radians to degrees with 256 angles circle. That
is done by multiplying the à by half of the angles in the circle and
dividing this by ã (pi) --> à = ARCTAN(Y/X)*(256/2)/ã.
^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the angle in the tunnel wall at the current pixel. So what do
we do with it? We must have already allocated a 64Kb precalculation table
for the angles... So we store this value in the table at the position
X+WIDTH/2+(Y+HEIGHT/2)*WIDTH... Simple. With a 320x200 screen we would
put the value to X+160+(Y+100)*320. Remember that X goes from -160 to 159
and Y from -100 to 99. Angle takes values 0-255. We can think of the angle
as the X-texture coordinate, so increasing the texture X-coordinate would
rotate the tunnel.
COSà*R*D
Z = ÄÄÄÄÄÄÄÄ (Calculate this too for every pixel)
X
Because we have just calculated the à we can put it in this equation and
voila! It works... (BTW : You _MUST_ use the radian version of the just
calculated angle value, because SIN and COS functions need their parameters
in radians. So you should convert angle to degrees only after you have
calculated this Z-value!) Now we have the Z value too... We must have
already allocated a 64Kb precalculation table for the depths too... Now we
put this value in the table at the position X+WIDTH/2+(Y+HEIGHT/2)*WIDTH...
With a 320x200 screen we would put the value to X+160+(Y+100)*320. Remember
that X goes from -160 to 159 and Y from -100 to 99. Depth takes values
0-255. We can think of the depth value as the Y-texture coordinate, so
increasing the texture Y-coordinate would zoom in the tunnel.
Once we have gone all this precalculating we just do this :
We go from 0 to WIDTH-1 for X and from 0 to HEIGHT-1 for Y and
look up the X-texture coordinate from the angle-lookup-table and
look up the Y-texture coordinate from the depth-lookup-table. If we want
to rotate or zoom the tunnel we can add texture-x-adder to the retrieved
angle value and texture-y-adder to the retrieved depth value. Then we just
take the pixel from the texture map at the retrieved X and Y coordinates
and put it onscreen, voila! A rotating and zooming 3D texture mapped tunnel
with a _FAST_ method... (Needs a bit of precalculating though...;))
An example in Pascal :
for y:=0 to 199 do
for x:=0 to 319 do
begin
texture_x:=(angles[x+y*320]+texture_x_adder) and 255;
texture_y:=(depths[x+y*320]+texture_y_adder) and 255;
video_screen[x+y*320]:=texture_map[texture_x+texture_y*256];
end;
Quite trivial if I wouldn't say...
PS. There can be some messing around with the arcus tangent function,
because when I precalculated the angles with Pascal, the tunnel
went partly to the wrong direction... You can correct this by
precalculating only one fourth of the precalculation tables and by
flipping and mirroring that data to construct the other three fourths
of the precalculation table. Have fun!
This fast (And quite crappy too I think) explanation of a 3D tunnel effect
was done by Il£vatar of Nordic Line (a.k.a. Kari Salminen). I was first
inspired by the CapaCala's "The Real Thing" demo's sewer zoomer part.
So I went on and thought of ways of doing such an effect and came up with
this one. After that I have seen this kind of effect in Napalm's "Noname00"
and Fascination's "Hive" (With some crossfading added though) and in one
demo by Ground Zero...
If you want to contact me for some strange reason :
You can e-mail me at :
ksalmi@kummeli.edutec.pori.fi (Primary)
kari.salminen@under.nullnet.fi (Secondary)
Or snail mail me at :
Kari Salminen
Patsaskuja 17
29600 Noormarkku
Finland
There can be some typos in the text so if something looks _HORRIBLY_ wrong
then it probably is. After all I hope you figured out those things that I
wrote about... If you didn't you can always send me a letter or an e-mail.
Hope this helped! Have fun and live long (and prosper ;))
Clock check... 9:19 am, I must be off to sleep now... Bye!
Il£vatar of Nordic Line 060795
}