mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-22 23:09:24 +02:00
566 lines
14 KiB
ObjectPascal
566 lines
14 KiB
ObjectPascal
{
|
|
Ported to FPC by Nikolay Nikolov (nickysn@users.sourceforge.net)
|
|
}
|
|
|
|
{
|
|
Tunnel3D demo for OpenPTC 1.0 C++ API
|
|
|
|
Realtime raytraced tunnel
|
|
Copyright (c) 1998 Christian Nentwich (brn@eleet.mcb.at)
|
|
This source code is licensed under the GNU LGPL
|
|
|
|
and do not just blatantly cut&paste this into your demo :)
|
|
}
|
|
|
|
program Tunnel3D;
|
|
|
|
{$MODE objfpc}
|
|
|
|
uses
|
|
ptc, Math;
|
|
|
|
type
|
|
PVector = ^TVector;
|
|
TVector = array [0..2] of Single; { X,Y,Z }
|
|
TMatrix = array [0..3, 0..3] of Single;{ FIRST = COLUMN
|
|
SECOND = ROW
|
|
|
|
[0, 0] [1, 0] [2, 0]
|
|
[0, 1] [1, 1] [2, 1]
|
|
[0, 2] [1, 2] [2, 2]
|
|
(I know the matrices are the wrong way round, so what, the code is quite
|
|
old :) }
|
|
|
|
TRayTunnel = class
|
|
private
|
|
tunneltex: PUint8; { Texture }
|
|
tunneltex_orig: PUint8; { Original start of texture memory block }
|
|
pal: PUint8; { Original palette }
|
|
lookup: PUint32; { Lookup table for lighting }
|
|
|
|
sintab, costab: PSingle; { Take a guess }
|
|
|
|
u_array, v_array, l_array: PInteger; { Raytraced coordinates and light }
|
|
norms: PVector;
|
|
|
|
radius, radius_sqr: Single;
|
|
rot: TMatrix;
|
|
|
|
pos, light: TVector; { Position in the tunnel, pos of }
|
|
xa, ya, za: Integer; { lightsource, angles }
|
|
|
|
lightstatus: Boolean; { Following the viewer ? }
|
|
|
|
public
|
|
constructor Create(rad: Single); { constructor takes the radius }
|
|
destructor Destroy; override;
|
|
|
|
procedure load_texture;
|
|
|
|
procedure tilt(x, y, z: Integer); { Rotate relative }
|
|
procedure tilt(x, y, z: Integer; abs: Uint8); { Absolute }
|
|
|
|
procedure move(dx, dy, dz: Single); { Relative move }
|
|
procedure move(x, y, z: Single; abs: Uint8); { Absolute }
|
|
|
|
procedure movelight(dx, dy, dz: Single);
|
|
procedure movelight(x, y, z: Single; abs: Uint8);
|
|
|
|
procedure locklight(lock: Boolean); { Make the light follow the viewer }
|
|
|
|
procedure interpolate; { Raytracing }
|
|
|
|
procedure draw(dest: PUint32); { Draw the finished tunnel }
|
|
end;
|
|
|
|
{ VECTOR ROUTINES }
|
|
procedure vector_normalize(var v: TVector);
|
|
var
|
|
length: Single;
|
|
begin
|
|
length := v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
|
|
length := sqrt(length);
|
|
if length <> 0 then
|
|
begin
|
|
v[0] := v[0] / length;
|
|
v[1] := v[1] / length;
|
|
v[2] := v[2] / length;
|
|
end
|
|
else
|
|
begin
|
|
v[0] := 0;
|
|
v[1] := 0;
|
|
v[2] := 0;
|
|
end;
|
|
end;
|
|
|
|
procedure vector_times_matrix(const v: TVector; const m: TMatrix;
|
|
var res: TVector);
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
for j := 0 to 2 do
|
|
begin
|
|
res[j] := 0;
|
|
for i := 0 to 2 do
|
|
res[j] := res[j] + (m[j, i] * v[i]);
|
|
end;
|
|
end;
|
|
|
|
procedure matrix_idle(var m: TMatrix);
|
|
begin
|
|
FillChar(m, SizeOf(TMatrix), 0);
|
|
m[0, 0] := 1;
|
|
m[1, 1] := 1;
|
|
m[2, 2] := 1;
|
|
m[3, 3] := 1;
|
|
end;
|
|
|
|
procedure matrix_times_matrix(const m1, m2: TMatrix; var res: TMatrix);
|
|
var
|
|
i, j, k: Integer;
|
|
begin
|
|
for j := 0 to 3 do
|
|
for i := 0 to 3 do
|
|
begin
|
|
res[i, j] := 0;
|
|
for k := 0 to 3 do
|
|
res[i, j] := res[i, j] + (m1[k, j] * m2[i, k]);
|
|
end;
|
|
end;
|
|
|
|
procedure matrix_rotate_x(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
|
|
var
|
|
tmp, tmp2: TMatrix;
|
|
begin
|
|
matrix_idle(tmp);
|
|
tmp[1, 1] := costab[angle];
|
|
tmp[2, 1] := sintab[angle];
|
|
tmp[1, 2] := -sintab[angle];
|
|
tmp[2, 2] := costab[angle];
|
|
matrix_times_matrix(tmp, m, tmp2);
|
|
Move(tmp2, m, SizeOf(TMatrix));
|
|
end;
|
|
|
|
procedure matrix_rotate_y(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
|
|
var
|
|
tmp, tmp2: TMatrix;
|
|
begin
|
|
matrix_idle(tmp);
|
|
tmp[0, 0] := costab[angle];
|
|
tmp[2, 0] := -sintab[angle];
|
|
tmp[0, 2] := sintab[angle];
|
|
tmp[2, 2] := costab[angle];
|
|
matrix_times_matrix(tmp, m, tmp2);
|
|
Move(tmp2, m, SizeOf(TMatrix));
|
|
end;
|
|
|
|
procedure matrix_rotate_z(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
|
|
var
|
|
tmp, tmp2: TMatrix;
|
|
begin
|
|
matrix_idle(tmp);
|
|
tmp[0, 0] := costab[angle];
|
|
tmp[1, 0] := sintab[angle];
|
|
tmp[0, 1] := -sintab[angle];
|
|
tmp[1, 1] := costab[angle];
|
|
matrix_times_matrix(tmp, m, tmp2);
|
|
Move(tmp2, m, SizeOf(TMatrix));
|
|
end;
|
|
|
|
constructor TRayTunnel.Create(rad: Single);
|
|
var
|
|
x, y: Single;
|
|
i, j: Integer;
|
|
tmp: TVector;
|
|
begin
|
|
radius := rad;
|
|
radius_sqr := rad * rad;
|
|
|
|
sintab := GetMem(1024 * SizeOf(Single)); { Set trigonometry and lookups }
|
|
costab := GetMem(1024 * SizeOf(Single));
|
|
u_array := GetMem(64 * 26 * SizeOf(Integer));
|
|
v_array := GetMem(64 * 26 * SizeOf(Integer));
|
|
l_array := GetMem(64 * 26 * SizeOf(Integer));
|
|
norms := GetMem(64 * 26 * 3 * SizeOf(Single));
|
|
|
|
lookup := GetMem(65 * 256 * SizeOf(Uint32));
|
|
pal := GetMem(768 * SizeOf(Uint8));
|
|
|
|
for i := 0 to 1023 do
|
|
begin
|
|
sintab[i] := sin(i * pi / 512);
|
|
costab[i] := cos(i * pi / 512);
|
|
end;
|
|
|
|
{ Generate normal vectors }
|
|
y := -100;
|
|
for j := 0 to 25 do
|
|
begin
|
|
x := -160;
|
|
for i := 0 to 40 do
|
|
begin
|
|
tmp[0] := x;
|
|
tmp[1] := y;
|
|
tmp[2] := 128;
|
|
vector_normalize(tmp);
|
|
norms[j * 64 + i] := tmp;
|
|
x := x + 8;
|
|
end;
|
|
y := y + 8;
|
|
end;
|
|
|
|
{ Reset tunnel and light position and all angles }
|
|
pos[0] := 0; pos[1] := 0; pos[2] := 0;
|
|
light[0] := 1; light[1] := 1; light[2] := 0;
|
|
|
|
xa := 0; ya := 0; za := 0;
|
|
|
|
lightstatus := False;
|
|
|
|
{ Normalize light vector to length 1.0 }
|
|
vector_normalize(light);
|
|
end;
|
|
|
|
destructor TRayTunnel.Destroy;
|
|
begin
|
|
FreeMem(tunneltex_orig);
|
|
FreeMem(pal);
|
|
FreeMem(lookup);
|
|
FreeMem(norms);
|
|
FreeMem(l_array);
|
|
FreeMem(v_array);
|
|
FreeMem(u_array);
|
|
FreeMem(costab);
|
|
FreeMem(sintab);
|
|
end;
|
|
|
|
procedure TRayTunnel.load_texture;
|
|
var
|
|
texfile: File;
|
|
tmp: PUint8 = nil;
|
|
i, j: Uint32;
|
|
r, g, b: Uint32;
|
|
newoffs: Integer;
|
|
begin
|
|
try
|
|
{ Allocate tunnel texture 65536+33 bytes too big }
|
|
|
|
if tunneltex_orig <> nil then
|
|
begin
|
|
FreeMem(tunneltex_orig);
|
|
tunneltex_orig := nil;
|
|
end;
|
|
tunneltex_orig := GetMem(2*65536 + 33);
|
|
tmp := GetMem(65536);
|
|
|
|
{ Align the texture on a 64k boundary }
|
|
tunneltex := tunneltex_orig;
|
|
while (PtrUInt(tunneltex) and $FFFF) <> 0 do
|
|
Inc(tunneltex);
|
|
|
|
AssignFile(texfile, 'tunnel3d.raw');
|
|
Reset(texfile, 1);
|
|
try
|
|
BlockRead(texfile, pal^, 768);
|
|
BlockRead(texfile, tmp^, 65536);
|
|
finally
|
|
CloseFile(texfile);
|
|
end;
|
|
|
|
{ Generate lookup table for lighting (65 because of possible inaccuracies) }
|
|
|
|
for j := 0 to 64 do
|
|
for i := 0 to 255 do
|
|
begin
|
|
r := pal[i * 3] shl 2;
|
|
g := pal[i * 3 + 1] shl 2;
|
|
b := pal[i * 3 + 2] shl 2;
|
|
r := (r * j) shr 6;
|
|
g := (g * j) shr 6;
|
|
b := (b * j) shr 6;
|
|
if r > 255 then
|
|
r := 255;
|
|
if g > 255 then
|
|
g := 255;
|
|
if b > 255 then
|
|
b := 255;
|
|
lookup[j * 256 + i] := (r shl 16) or (g shl 8) or b;
|
|
end;
|
|
|
|
{ Arrange texture for cache optimised mapping }
|
|
|
|
for j := 0 to 255 do
|
|
for i := 0 to 255 do
|
|
begin
|
|
newoffs := ((i shl 8) and $F800) + (i and $0007) + ((j shl 3) and $7F8);
|
|
(tunneltex + newoffs)^ := (tmp + j * 256 + i)^;
|
|
end;
|
|
finally
|
|
FreeMem(tmp);
|
|
end;
|
|
end;
|
|
|
|
procedure TRayTunnel.interpolate;
|
|
var
|
|
ray, intsc, norm, lvec: TVector;
|
|
x, y, a, b, c, discr, t, res: Single;
|
|
i, j: Integer;
|
|
begin
|
|
if lightstatus then { Lightsource locked to viewpoint }
|
|
light := pos;
|
|
|
|
matrix_idle(rot);
|
|
matrix_rotate_x(rot, xa and $3FF, sintab, costab);
|
|
matrix_rotate_y(rot, ya and $3FF, sintab, costab);
|
|
matrix_rotate_z(rot, za and $3FF, sintab, costab);
|
|
|
|
{ Constant factor }
|
|
c := 2 * (pos[0] * pos[0] + pos[1] * pos[1] - radius_sqr);
|
|
|
|
{ Start raytracing }
|
|
y := -100;
|
|
for j := 0 to 25 do
|
|
begin
|
|
x := -160;
|
|
for i := 0 to 40 do
|
|
begin
|
|
vector_times_matrix(norms[(j shl 6) + i], rot, ray);
|
|
|
|
a := 2 * (ray[0] * ray[0] + ray[1] * ray[1]);
|
|
b := 2 * (pos[0] * ray[0] + pos[1] * ray[1]);
|
|
|
|
discr := b * b - a * c;
|
|
if discr > 0 then
|
|
begin
|
|
discr := sqrt(discr);
|
|
t := (- b + discr) / a;
|
|
|
|
{ Calculate intersection point }
|
|
intsc[0] := pos[0] + t * ray[0];
|
|
intsc[1] := pos[1] + t * ray[1];
|
|
intsc[2] := pos[2] + t * ray[2];
|
|
|
|
{ Calculate texture index at intersection point (cylindrical mapping) }
|
|
{ try and adjust the 0.2 to stretch/shrink the texture }
|
|
u_array[(j shl 6) + i] := Integer(Trunc(intsc[2] * 0.2) shl 16);
|
|
v_array[(j shl 6) + i] := Trunc(abs(arctan2(intsc[1], intsc[0]) * 256 / pi)) shl 16;
|
|
|
|
{ Calculate the dotproduct between the normal vector and the vector }
|
|
{ from the intersection point to the lightsource }
|
|
norm[0] := intsc[0] / radius;
|
|
norm[1] := intsc[1] / radius;
|
|
norm[2] := 0;
|
|
|
|
lvec[0] := intsc[0] - light[0];
|
|
lvec[1] := intsc[1] - light[1];
|
|
lvec[2] := intsc[2] - light[2];
|
|
vector_normalize(lvec);
|
|
|
|
res := lvec[0] * norm[0] + lvec[1] * norm[1] + lvec[2] * norm[2];
|
|
|
|
{ Scale the light a bit }
|
|
res := Sqr(res);
|
|
if res < 0 then
|
|
res := 0;
|
|
if res > 1 then
|
|
res := 1;
|
|
res := res * 63;
|
|
|
|
{ Put it into the light array }
|
|
l_array[(j shl 6) + i] := Trunc(res) shl 16;
|
|
end
|
|
else
|
|
begin
|
|
u_array[(j shl 6) + i] := 0;
|
|
v_array[(j shl 6) + i] := 0;
|
|
l_array[(j shl 6) + i] := 0;
|
|
end;
|
|
x := x + 8;
|
|
end;
|
|
y := y + 8;
|
|
end;
|
|
end;
|
|
|
|
procedure TRayTunnel.draw(dest: PUint32);
|
|
var
|
|
x, y, lu, lv, ru, rv, liu, liv, riu, riv: Integer;
|
|
iu, iv, i, j, ll, rl, lil, ril, l, il: Integer;
|
|
iadr, adr, til_u, til_v, til_iu, til_iv: DWord;
|
|
bla: Uint8;
|
|
begin
|
|
for j := 0 to 24 do
|
|
for i := 0 to 39 do
|
|
begin
|
|
iadr := (j shl 6) + i;
|
|
|
|
{ Set up gradients }
|
|
lu := u_array[iadr]; ru := u_array[iadr + 1];
|
|
liu := (u_array[iadr + 64] - lu) div 8;
|
|
riu := (u_array[iadr + 65] - ru) div 8;
|
|
|
|
lv := v_array[iadr]; rv := v_array[iadr + 1];
|
|
liv := (v_array[iadr + 64] - lv) div 8;
|
|
riv := (v_array[iadr + 65] - rv) div 8;
|
|
|
|
ll := l_array[iadr]; rl := l_array[iadr + 1];
|
|
lil := (l_array[iadr + 64] - ll) div 8;
|
|
ril := (l_array[iadr + 65] - rl) div 8;
|
|
|
|
for y := 0 to 7 do
|
|
begin
|
|
iu := (ru - lu) div 8;
|
|
iv := (rv - lv) div 8;
|
|
l := ll;
|
|
il := (rl - ll) div 8;
|
|
|
|
{ Mess up everything for the sake of cache optimised mapping :) }
|
|
til_u := DWord(((lu shl 8) and $F8000000) or ((lu shr 1) and $00007FFF) or (lu and $00070000));
|
|
til_v := DWord(((lv shl 3) and $07F80000) or ((lv shr 1) and $00007FFF));
|
|
til_iu := DWord((((iu shl 8) and $F8000000) or ((iu shr 1) and $00007FFF) or
|
|
(iu and $00070000)) or $07F88000);
|
|
til_iv := DWord((((iv shl 3) and $07F80000) or ((iv shr 1) and $00007FFF)) or $F8078000);
|
|
|
|
adr := til_u + til_v;
|
|
|
|
for x := 0 to 7 do
|
|
begin
|
|
{ Interpolate texture u,v and light }
|
|
til_u := DWord(til_u + til_iu);
|
|
til_v := DWord(til_v + til_iv);
|
|
Inc(l, il);
|
|
|
|
adr := adr shr 16;
|
|
|
|
til_u := til_u and DWord($F8077FFF);
|
|
til_v := til_v and $07F87FFF;
|
|
|
|
bla := (tunneltex + adr)^;
|
|
|
|
adr := til_u + til_v;
|
|
|
|
{ Look up the light and write to buffer }
|
|
(dest + ((j shl 3) + y) * 320 + (I shl 3) + x)^ := lookup[((l and $3F0000) shr 8) + bla];
|
|
end;
|
|
|
|
Inc(lu, liu); Inc(ru, riu);
|
|
Inc(lv, liv); Inc(rv, riv);
|
|
Inc(ll, lil); Inc(rl, ril);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ tilt rotates the viewer in the tunnel in a relative / absolute way }
|
|
procedure TRayTunnel.tilt(x, y, z: Integer);
|
|
begin
|
|
xa := (xa + x) and $3FF;
|
|
ya := (ya + y) and $3FF;
|
|
za := (za + z) and $3FF;
|
|
end;
|
|
|
|
procedure TRayTunnel.tilt(x, y, z: Integer; abs: Uint8);
|
|
begin
|
|
xa := x and $3FF;
|
|
ya := y and $3FF;
|
|
za := z and $3FF;
|
|
end;
|
|
|
|
{ Relative / absolute move }
|
|
procedure TRayTunnel.move(dx, dy, dz: Single);
|
|
begin
|
|
pos[0] := pos[0] + dx;
|
|
pos[1] := pos[1] + dy;
|
|
pos[2] := pos[2] + dz;
|
|
end;
|
|
|
|
procedure TRayTunnel.move(x, y, z: Single; abs: Uint8);
|
|
begin
|
|
pos[0] := x;
|
|
pos[1] := y;
|
|
pos[2] := z;
|
|
end;
|
|
|
|
{ Relative / absolute move for the lightsource }
|
|
procedure TRayTunnel.movelight(dx, dy, dz: Single);
|
|
begin
|
|
light[0] := light[0] + dx;
|
|
light[1] := light[1] + dy;
|
|
light[2] := light[2] + dz;
|
|
end;
|
|
|
|
procedure TRayTunnel.movelight(x, y, z: Single; abs: Uint8);
|
|
begin
|
|
light[0] := x;
|
|
light[1] := y;
|
|
light[2] := z;
|
|
end;
|
|
|
|
{ Lock lightsource to the viewer }
|
|
procedure TRayTunnel.locklight(lock: Boolean);
|
|
begin
|
|
lightstatus := lock;
|
|
end;
|
|
|
|
var
|
|
console: IPTCConsole;
|
|
surface: IPTCSurface;
|
|
format: IPTCFormat;
|
|
tunnel: TRayTunnel = nil;
|
|
posz, phase_x, phase_y: Single;
|
|
angle_x, angle_y: Integer;
|
|
buffer: PUint32;
|
|
begin
|
|
try
|
|
try
|
|
format := TPTCFormatFactory.CreateNew(32, $00FF0000, $0000FF00, $000000FF);
|
|
|
|
console := TPTCConsoleFactory.CreateNew;
|
|
console.open('Tunnel3D demo', 320, 200, format);
|
|
|
|
surface := TPTCSurfaceFactory.CreateNew(320, 200, format);
|
|
|
|
{ Create a tunnel, radius=700 }
|
|
tunnel := TRayTunnel.Create(700);
|
|
|
|
tunnel.load_texture;
|
|
|
|
{ Light follows the viewer }
|
|
tunnel.locklight(True);
|
|
|
|
posz := 80; phase_x := 0; phase_y := 0;
|
|
angle_x := 6; angle_y := 2;
|
|
|
|
while not console.KeyPressed do
|
|
begin
|
|
buffer := surface.lock;
|
|
try
|
|
tunnel.interpolate;
|
|
|
|
{ Draw to offscreen buffer }
|
|
tunnel.draw(buffer);
|
|
finally
|
|
surface.unlock;
|
|
end;
|
|
|
|
{ and copy to screen }
|
|
surface.copy(console);
|
|
|
|
console.update;
|
|
|
|
tunnel.tilt(angle_x, angle_y, 0);
|
|
tunnel.move(sin(phase_x), cos(phase_y), posz);
|
|
|
|
phase_x := phase_x + 0.2;
|
|
phase_y := phase_y + 0.1;
|
|
end;
|
|
finally
|
|
if Assigned(console) then
|
|
console.close;
|
|
tunnel.Free;
|
|
end;
|
|
except
|
|
on error: TPTCError do
|
|
error.report;
|
|
end;
|
|
end.
|