123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- {
- Ported to FPC by Nikolay Nikolov ([email protected])
- }
- {
- Tunnel3D demo for OpenPTC 1.0 C++ API
- Realtime raytraced tunnel
- Copyright (c) 1998 Christian Nentwich ([email protected])
- 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.
|