tunnel3d.pp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. {
  2. Ported to FPC by Nikolay Nikolov ([email protected])
  3. }
  4. {
  5. Tunnel3D demo for OpenPTC 1.0 C++ API
  6. Realtime raytraced tunnel
  7. Copyright (c) 1998 Christian Nentwich ([email protected])
  8. This source code is licensed under the GNU LGPL
  9. and do not just blatantly cut&paste this into your demo :)
  10. }
  11. program Tunnel3D;
  12. {$MODE objfpc}
  13. uses
  14. ptc, Math;
  15. type
  16. PVector = ^TVector;
  17. TVector = array [0..2] of Single; { X,Y,Z }
  18. TMatrix = array [0..3, 0..3] of Single;{ FIRST = COLUMN
  19. SECOND = ROW
  20. [0, 0] [1, 0] [2, 0]
  21. [0, 1] [1, 1] [2, 1]
  22. [0, 2] [1, 2] [2, 2]
  23. (I know the matrices are the wrong way round, so what, the code is quite
  24. old :) }
  25. TRayTunnel = class
  26. private
  27. tunneltex: PUint8; { Texture }
  28. tunneltex_orig: PUint8; { Original start of texture memory block }
  29. pal: PUint8; { Original palette }
  30. lookup: PUint32; { Lookup table for lighting }
  31. sintab, costab: PSingle; { Take a guess }
  32. u_array, v_array, l_array: PInteger; { Raytraced coordinates and light }
  33. norms: PVector;
  34. radius, radius_sqr: Single;
  35. rot: TMatrix;
  36. pos, light: TVector; { Position in the tunnel, pos of }
  37. xa, ya, za: Integer; { lightsource, angles }
  38. lightstatus: Boolean; { Following the viewer ? }
  39. public
  40. constructor Create(rad: Single); { constructor takes the radius }
  41. destructor Destroy; override;
  42. procedure load_texture;
  43. procedure tilt(x, y, z: Integer); { Rotate relative }
  44. procedure tilt(x, y, z: Integer; abs: Uint8); { Absolute }
  45. procedure move(dx, dy, dz: Single); { Relative move }
  46. procedure move(x, y, z: Single; abs: Uint8); { Absolute }
  47. procedure movelight(dx, dy, dz: Single);
  48. procedure movelight(x, y, z: Single; abs: Uint8);
  49. procedure locklight(lock: Boolean); { Make the light follow the viewer }
  50. procedure interpolate; { Raytracing }
  51. procedure draw(dest: PUint32); { Draw the finished tunnel }
  52. end;
  53. { VECTOR ROUTINES }
  54. procedure vector_normalize(var v: TVector);
  55. var
  56. length: Single;
  57. begin
  58. length := v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  59. length := sqrt(length);
  60. if length <> 0 then
  61. begin
  62. v[0] := v[0] / length;
  63. v[1] := v[1] / length;
  64. v[2] := v[2] / length;
  65. end
  66. else
  67. begin
  68. v[0] := 0;
  69. v[1] := 0;
  70. v[2] := 0;
  71. end;
  72. end;
  73. procedure vector_times_matrix(const v: TVector; const m: TMatrix;
  74. var res: TVector);
  75. var
  76. i, j: Integer;
  77. begin
  78. for j := 0 to 2 do
  79. begin
  80. res[j] := 0;
  81. for i := 0 to 2 do
  82. res[j] := res[j] + (m[j, i] * v[i]);
  83. end;
  84. end;
  85. procedure matrix_idle(var m: TMatrix);
  86. begin
  87. FillChar(m, SizeOf(TMatrix), 0);
  88. m[0, 0] := 1;
  89. m[1, 1] := 1;
  90. m[2, 2] := 1;
  91. m[3, 3] := 1;
  92. end;
  93. procedure matrix_times_matrix(const m1, m2: TMatrix; var res: TMatrix);
  94. var
  95. i, j, k: Integer;
  96. begin
  97. for j := 0 to 3 do
  98. for i := 0 to 3 do
  99. begin
  100. res[i, j] := 0;
  101. for k := 0 to 3 do
  102. res[i, j] := res[i, j] + (m1[k, j] * m2[i, k]);
  103. end;
  104. end;
  105. procedure matrix_rotate_x(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
  106. var
  107. tmp, tmp2: TMatrix;
  108. begin
  109. matrix_idle(tmp);
  110. tmp[1, 1] := costab[angle];
  111. tmp[2, 1] := sintab[angle];
  112. tmp[1, 2] := -sintab[angle];
  113. tmp[2, 2] := costab[angle];
  114. matrix_times_matrix(tmp, m, tmp2);
  115. Move(tmp2, m, SizeOf(TMatrix));
  116. end;
  117. procedure matrix_rotate_y(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
  118. var
  119. tmp, tmp2: TMatrix;
  120. begin
  121. matrix_idle(tmp);
  122. tmp[0, 0] := costab[angle];
  123. tmp[2, 0] := -sintab[angle];
  124. tmp[0, 2] := sintab[angle];
  125. tmp[2, 2] := costab[angle];
  126. matrix_times_matrix(tmp, m, tmp2);
  127. Move(tmp2, m, SizeOf(TMatrix));
  128. end;
  129. procedure matrix_rotate_z(var m: TMatrix; angle: Integer; sintab, costab: PSingle);
  130. var
  131. tmp, tmp2: TMatrix;
  132. begin
  133. matrix_idle(tmp);
  134. tmp[0, 0] := costab[angle];
  135. tmp[1, 0] := sintab[angle];
  136. tmp[0, 1] := -sintab[angle];
  137. tmp[1, 1] := costab[angle];
  138. matrix_times_matrix(tmp, m, tmp2);
  139. Move(tmp2, m, SizeOf(TMatrix));
  140. end;
  141. constructor TRayTunnel.Create(rad: Single);
  142. var
  143. x, y: Single;
  144. i, j: Integer;
  145. tmp: TVector;
  146. begin
  147. radius := rad;
  148. radius_sqr := rad * rad;
  149. sintab := GetMem(1024 * SizeOf(Single)); { Set trigonometry and lookups }
  150. costab := GetMem(1024 * SizeOf(Single));
  151. u_array := GetMem(64 * 26 * SizeOf(Integer));
  152. v_array := GetMem(64 * 26 * SizeOf(Integer));
  153. l_array := GetMem(64 * 26 * SizeOf(Integer));
  154. norms := GetMem(64 * 26 * 3 * SizeOf(Single));
  155. lookup := GetMem(65 * 256 * SizeOf(Uint32));
  156. pal := GetMem(768 * SizeOf(Uint8));
  157. for i := 0 to 1023 do
  158. begin
  159. sintab[i] := sin(i * pi / 512);
  160. costab[i] := cos(i * pi / 512);
  161. end;
  162. { Generate normal vectors }
  163. y := -100;
  164. for j := 0 to 25 do
  165. begin
  166. x := -160;
  167. for i := 0 to 40 do
  168. begin
  169. tmp[0] := x;
  170. tmp[1] := y;
  171. tmp[2] := 128;
  172. vector_normalize(tmp);
  173. norms[j * 64 + i] := tmp;
  174. x := x + 8;
  175. end;
  176. y := y + 8;
  177. end;
  178. { Reset tunnel and light position and all angles }
  179. pos[0] := 0; pos[1] := 0; pos[2] := 0;
  180. light[0] := 1; light[1] := 1; light[2] := 0;
  181. xa := 0; ya := 0; za := 0;
  182. lightstatus := False;
  183. { Normalize light vector to length 1.0 }
  184. vector_normalize(light);
  185. end;
  186. destructor TRayTunnel.Destroy;
  187. begin
  188. FreeMem(tunneltex_orig);
  189. FreeMem(pal);
  190. FreeMem(lookup);
  191. FreeMem(norms);
  192. FreeMem(l_array);
  193. FreeMem(v_array);
  194. FreeMem(u_array);
  195. FreeMem(costab);
  196. FreeMem(sintab);
  197. end;
  198. procedure TRayTunnel.load_texture;
  199. var
  200. texfile: File;
  201. tmp: PUint8 = nil;
  202. i, j: Uint32;
  203. r, g, b: Uint32;
  204. newoffs: Integer;
  205. begin
  206. try
  207. { Allocate tunnel texture 65536+33 bytes too big }
  208. if tunneltex_orig <> nil then
  209. begin
  210. FreeMem(tunneltex_orig);
  211. tunneltex_orig := nil;
  212. end;
  213. tunneltex_orig := GetMem(2*65536 + 33);
  214. tmp := GetMem(65536);
  215. { Align the texture on a 64k boundary }
  216. tunneltex := tunneltex_orig;
  217. while (PtrUInt(tunneltex) and $FFFF) <> 0 do
  218. Inc(tunneltex);
  219. AssignFile(texfile, 'tunnel3d.raw');
  220. Reset(texfile, 1);
  221. try
  222. BlockRead(texfile, pal^, 768);
  223. BlockRead(texfile, tmp^, 65536);
  224. finally
  225. CloseFile(texfile);
  226. end;
  227. { Generate lookup table for lighting (65 because of possible inaccuracies) }
  228. for j := 0 to 64 do
  229. for i := 0 to 255 do
  230. begin
  231. r := pal[i * 3] shl 2;
  232. g := pal[i * 3 + 1] shl 2;
  233. b := pal[i * 3 + 2] shl 2;
  234. r := (r * j) shr 6;
  235. g := (g * j) shr 6;
  236. b := (b * j) shr 6;
  237. if r > 255 then
  238. r := 255;
  239. if g > 255 then
  240. g := 255;
  241. if b > 255 then
  242. b := 255;
  243. lookup[j * 256 + i] := (r shl 16) or (g shl 8) or b;
  244. end;
  245. { Arrange texture for cache optimised mapping }
  246. for j := 0 to 255 do
  247. for i := 0 to 255 do
  248. begin
  249. newoffs := ((i shl 8) and $F800) + (i and $0007) + ((j shl 3) and $7F8);
  250. (tunneltex + newoffs)^ := (tmp + j * 256 + i)^;
  251. end;
  252. finally
  253. FreeMem(tmp);
  254. end;
  255. end;
  256. procedure TRayTunnel.interpolate;
  257. var
  258. ray, intsc, norm, lvec: TVector;
  259. x, y, a, b, c, discr, t, res: Single;
  260. i, j: Integer;
  261. begin
  262. if lightstatus then { Lightsource locked to viewpoint }
  263. light := pos;
  264. matrix_idle(rot);
  265. matrix_rotate_x(rot, xa and $3FF, sintab, costab);
  266. matrix_rotate_y(rot, ya and $3FF, sintab, costab);
  267. matrix_rotate_z(rot, za and $3FF, sintab, costab);
  268. { Constant factor }
  269. c := 2 * (pos[0] * pos[0] + pos[1] * pos[1] - radius_sqr);
  270. { Start raytracing }
  271. y := -100;
  272. for j := 0 to 25 do
  273. begin
  274. x := -160;
  275. for i := 0 to 40 do
  276. begin
  277. vector_times_matrix(norms[(j shl 6) + i], rot, ray);
  278. a := 2 * (ray[0] * ray[0] + ray[1] * ray[1]);
  279. b := 2 * (pos[0] * ray[0] + pos[1] * ray[1]);
  280. discr := b * b - a * c;
  281. if discr > 0 then
  282. begin
  283. discr := sqrt(discr);
  284. t := (- b + discr) / a;
  285. { Calculate intersection point }
  286. intsc[0] := pos[0] + t * ray[0];
  287. intsc[1] := pos[1] + t * ray[1];
  288. intsc[2] := pos[2] + t * ray[2];
  289. { Calculate texture index at intersection point (cylindrical mapping) }
  290. { try and adjust the 0.2 to stretch/shrink the texture }
  291. u_array[(j shl 6) + i] := Integer(Trunc(intsc[2] * 0.2) shl 16);
  292. v_array[(j shl 6) + i] := Trunc(abs(arctan2(intsc[1], intsc[0]) * 256 / pi)) shl 16;
  293. { Calculate the dotproduct between the normal vector and the vector }
  294. { from the intersection point to the lightsource }
  295. norm[0] := intsc[0] / radius;
  296. norm[1] := intsc[1] / radius;
  297. norm[2] := 0;
  298. lvec[0] := intsc[0] - light[0];
  299. lvec[1] := intsc[1] - light[1];
  300. lvec[2] := intsc[2] - light[2];
  301. vector_normalize(lvec);
  302. res := lvec[0] * norm[0] + lvec[1] * norm[1] + lvec[2] * norm[2];
  303. { Scale the light a bit }
  304. res := Sqr(res);
  305. if res < 0 then
  306. res := 0;
  307. if res > 1 then
  308. res := 1;
  309. res := res * 63;
  310. { Put it into the light array }
  311. l_array[(j shl 6) + i] := Trunc(res) shl 16;
  312. end
  313. else
  314. begin
  315. u_array[(j shl 6) + i] := 0;
  316. v_array[(j shl 6) + i] := 0;
  317. l_array[(j shl 6) + i] := 0;
  318. end;
  319. x := x + 8;
  320. end;
  321. y := y + 8;
  322. end;
  323. end;
  324. procedure TRayTunnel.draw(dest: PUint32);
  325. var
  326. x, y, lu, lv, ru, rv, liu, liv, riu, riv: Integer;
  327. iu, iv, i, j, ll, rl, lil, ril, l, il: Integer;
  328. iadr, adr, til_u, til_v, til_iu, til_iv: DWord;
  329. bla: Uint8;
  330. begin
  331. for j := 0 to 24 do
  332. for i := 0 to 39 do
  333. begin
  334. iadr := (j shl 6) + i;
  335. { Set up gradients }
  336. lu := u_array[iadr]; ru := u_array[iadr + 1];
  337. liu := (u_array[iadr + 64] - lu) div 8;
  338. riu := (u_array[iadr + 65] - ru) div 8;
  339. lv := v_array[iadr]; rv := v_array[iadr + 1];
  340. liv := (v_array[iadr + 64] - lv) div 8;
  341. riv := (v_array[iadr + 65] - rv) div 8;
  342. ll := l_array[iadr]; rl := l_array[iadr + 1];
  343. lil := (l_array[iadr + 64] - ll) div 8;
  344. ril := (l_array[iadr + 65] - rl) div 8;
  345. for y := 0 to 7 do
  346. begin
  347. iu := (ru - lu) div 8;
  348. iv := (rv - lv) div 8;
  349. l := ll;
  350. il := (rl - ll) div 8;
  351. { Mess up everything for the sake of cache optimised mapping :) }
  352. til_u := DWord(((lu shl 8) and $F8000000) or ((lu shr 1) and $00007FFF) or (lu and $00070000));
  353. til_v := DWord(((lv shl 3) and $07F80000) or ((lv shr 1) and $00007FFF));
  354. til_iu := DWord((((iu shl 8) and $F8000000) or ((iu shr 1) and $00007FFF) or
  355. (iu and $00070000)) or $07F88000);
  356. til_iv := DWord((((iv shl 3) and $07F80000) or ((iv shr 1) and $00007FFF)) or $F8078000);
  357. adr := til_u + til_v;
  358. for x := 0 to 7 do
  359. begin
  360. { Interpolate texture u,v and light }
  361. til_u := DWord(til_u + til_iu);
  362. til_v := DWord(til_v + til_iv);
  363. Inc(l, il);
  364. adr := adr shr 16;
  365. til_u := til_u and DWord($F8077FFF);
  366. til_v := til_v and $07F87FFF;
  367. bla := (tunneltex + adr)^;
  368. adr := til_u + til_v;
  369. { Look up the light and write to buffer }
  370. (dest + ((j shl 3) + y) * 320 + (I shl 3) + x)^ := lookup[((l and $3F0000) shr 8) + bla];
  371. end;
  372. Inc(lu, liu); Inc(ru, riu);
  373. Inc(lv, liv); Inc(rv, riv);
  374. Inc(ll, lil); Inc(rl, ril);
  375. end;
  376. end;
  377. end;
  378. { tilt rotates the viewer in the tunnel in a relative / absolute way }
  379. procedure TRayTunnel.tilt(x, y, z: Integer);
  380. begin
  381. xa := (xa + x) and $3FF;
  382. ya := (ya + y) and $3FF;
  383. za := (za + z) and $3FF;
  384. end;
  385. procedure TRayTunnel.tilt(x, y, z: Integer; abs: Uint8);
  386. begin
  387. xa := x and $3FF;
  388. ya := y and $3FF;
  389. za := z and $3FF;
  390. end;
  391. { Relative / absolute move }
  392. procedure TRayTunnel.move(dx, dy, dz: Single);
  393. begin
  394. pos[0] := pos[0] + dx;
  395. pos[1] := pos[1] + dy;
  396. pos[2] := pos[2] + dz;
  397. end;
  398. procedure TRayTunnel.move(x, y, z: Single; abs: Uint8);
  399. begin
  400. pos[0] := x;
  401. pos[1] := y;
  402. pos[2] := z;
  403. end;
  404. { Relative / absolute move for the lightsource }
  405. procedure TRayTunnel.movelight(dx, dy, dz: Single);
  406. begin
  407. light[0] := light[0] + dx;
  408. light[1] := light[1] + dy;
  409. light[2] := light[2] + dz;
  410. end;
  411. procedure TRayTunnel.movelight(x, y, z: Single; abs: Uint8);
  412. begin
  413. light[0] := x;
  414. light[1] := y;
  415. light[2] := z;
  416. end;
  417. { Lock lightsource to the viewer }
  418. procedure TRayTunnel.locklight(lock: Boolean);
  419. begin
  420. lightstatus := lock;
  421. end;
  422. var
  423. console: IPTCConsole;
  424. surface: IPTCSurface;
  425. format: IPTCFormat;
  426. tunnel: TRayTunnel = nil;
  427. posz, phase_x, phase_y: Single;
  428. angle_x, angle_y: Integer;
  429. buffer: PUint32;
  430. begin
  431. try
  432. try
  433. format := TPTCFormatFactory.CreateNew(32, $00FF0000, $0000FF00, $000000FF);
  434. console := TPTCConsoleFactory.CreateNew;
  435. console.open('Tunnel3D demo', 320, 200, format);
  436. surface := TPTCSurfaceFactory.CreateNew(320, 200, format);
  437. { Create a tunnel, radius=700 }
  438. tunnel := TRayTunnel.Create(700);
  439. tunnel.load_texture;
  440. { Light follows the viewer }
  441. tunnel.locklight(True);
  442. posz := 80; phase_x := 0; phase_y := 0;
  443. angle_x := 6; angle_y := 2;
  444. while not console.KeyPressed do
  445. begin
  446. buffer := surface.lock;
  447. try
  448. tunnel.interpolate;
  449. { Draw to offscreen buffer }
  450. tunnel.draw(buffer);
  451. finally
  452. surface.unlock;
  453. end;
  454. { and copy to screen }
  455. surface.copy(console);
  456. console.update;
  457. tunnel.tilt(angle_x, angle_y, 0);
  458. tunnel.move(sin(phase_x), cos(phase_y), posz);
  459. phase_x := phase_x + 0.2;
  460. phase_y := phase_y + 0.1;
  461. end;
  462. finally
  463. if Assigned(console) then
  464. console.close;
  465. tunnel.Free;
  466. end;
  467. except
  468. on error: TPTCError do
  469. error.report;
  470. end;
  471. end.