diff --git a/doc/file_hmp_spec.txt b/doc/file_hmp_spec.txt
index ac080db..e47a69f 100644
--- a/doc/file_hmp_spec.txt
+++ b/doc/file_hmp_spec.txt
@@ -1,21 +1,26 @@
-12B: zeros
-4B float: always 0x3f000000 (0.5)
-4B float: terrain height scale
-4B float: always 0x3f000000
-2B int : tile count
-2B int : ?
-4B int : offset to tiles
-4B int : offset to some data?
-2B int : width in BLK
-2B int : height in BLK
-array[width * height] of 2B int: tile indices
-xB ?
-tiles
+HMP
{
- 2B int: texmap idx (from texture index file)
- 1B int: ?
- 1B uint: lo - minimum height in tile (probably for terrain LOD?)
- 1B uint: hi - maximum height in tile
- array[25] of uint8: - 5x5 heights
+ 12B: zeros
+ 4B float: always 0x3f000000 (0.5)
+ 4B float: terrain height scale
+ 4B float: always 0x3f000000
+ 2B int : tile count
+ 2B int : ?
+ 4B int : offset to tiles
+ 4B int : offset to some data?
+ 2B int : width in BLK
+ 2B int : height in BLK
+ array[width * height] of 2B int: tile indices
+ xB ?
+ array[tile count] of TILE
+ 2B 0x0000
}
-2B 0x0000
+
+TILE [30B]
+{
+ 2B int: texmap idx (from texture index file)
+ 1B int: ?
+ 1B int: lo - minimum height in tile (probably for terrain LOD? clipping?)
+ 1B int: hi - maximum height in tile
+ array[25] of int8: 5x5 height values
+}
\ No newline at end of file
diff --git a/terrain_viewer/rs_world.pas b/terrain_viewer/rs_world.pas
index ab64263..b307f7a 100644
--- a/terrain_viewer/rs_world.pas
+++ b/terrain_viewer/rs_world.pas
@@ -20,9 +20,9 @@ type
TTile = packed record
texture_index: word;
unknown_attrib: byte;
- height_lo: byte;
- height_hi: byte;
- heights: array[0..24] of byte;
+ height_lo: shortint;
+ height_hi: shortint;
+ heights: array[0..24] of shortint;
end;
PTile = ^TTile;
@@ -37,12 +37,6 @@ type
texture_index_map: array of integer;
end;
- TVertex3f = record
- x, y, z: single;
- u, v: single
- end;
- PVertex3f = ^TVertex3f;
-
{ TWorld }
TWorld = class
@@ -55,7 +49,6 @@ type
public
heightmap: THeightmap;
- vertex_array: PVertex3f;
vertex_count: integer;
property TileWidth: word read heightmap.width;
@@ -217,7 +210,7 @@ begin
heightmap.blk := blk;
//tiles
- //writeln('tiles: ', tile_count);
+ //writeln('filepos: ', FilePos(f)); writeln('tile pos: ', tile_offset);
Seek(f, tile_offset);
heightmap.tile_count := tile_count;
heightmap.tiles := getmem(tile_count * 30);
@@ -241,13 +234,11 @@ end;
constructor TWorld.Create;
begin
height_texture := nil;
- vertex_array := nil;
end;
destructor TWorld.Destroy;
begin
if height_texture <> nil then Freemem(height_texture);
- if vertex_array <> nil then Freemem(vertex_array);
inherited Destroy;
end;
diff --git a/terrain_viewer/terrain_mesh.pas b/terrain_viewer/terrain_mesh.pas
index 675c9da..43af903 100644
--- a/terrain_viewer/terrain_mesh.pas
+++ b/terrain_viewer/terrain_mesh.pas
@@ -4,9 +4,9 @@ unit terrain_mesh;
interface
uses
- Classes, SysUtils,
+ Classes, SysUtils, matrix,
gl, glext, glu,
- rs_world;
+ rs_world, vector_util;
type
TRenderOpts = record
@@ -17,17 +17,21 @@ type
fg_to_draw: integer;
end;
- TTerrainBlock = packed record
+ TTerrainBlock = record
texture_index: integer;
- vertices: array[0..25] of TVertex3f;
- end;
+ vertices: array[0..24] of Tvector3_single; //25*3*4 = 300B
+ normals: array[0..24] of Tvector3_single; //25*3*4 = 300B
+ end; //~600B per block
{ TTerrainMesh }
TTerrainMesh = class
private
terrain: TWorld;
blocks: array of array of TTerrainBlock;
+ block_texcoords: array[0..24] of Tvector2_single; //static, 25*2*4 = 200B
+ block_face_indices: array[0..16*2*3 - 1] of byte; //static, 96B
textures_glidx: array of integer;
+ procedure InitBlockStaticData;
procedure TransformTiles;
public
destructor Destroy; override;
@@ -44,36 +48,60 @@ implementation
}
procedure TTerrainMesh.TransformTiles;
- //basex/y - offset in vertices
- //TODO solve flipped coords
- procedure TileToBlock(var blk: TTerrainBlock; var tile: TTile; basex, basey: integer);
+ //basex/y - position in block units for given dimension (0..block_size-1)
+ //todo fix: the params are flipped.. and so are the calculations
+ function TileToBlock(var tile: TTile; basex, basey: integer): TTerrainBlock;
const
h_scale = 0.5;
var
x, y: integer;
- v: TVertex3f;
- width_half, height_half: integer; //size in vertices
v_scale: single;
begin
- width_half := terrain.TileWidth * 2;
- height_half := terrain.TileHeight * 2;
+ result.texture_index := tile.texture_index;
+ //dim * vertices_per_tile - half_tile_dim * vertices_per_tile
+ basey := basey * 4 - terrain.TileHeight * 2;
+ basex := basex * 4 - terrain.TileWidth * 2;
v_scale := terrain.heightmap.y_scale;
for y := 0 to 4 do
for x := 0 to 4 do begin
- v.x := (-width_half + basex + x) * h_scale;
- v.z := (-height_half + basey + y) * h_scale;
- v.v := (1 - x/4);
- v.u := y/4;
- v.y := shortint(tile.heights[y+x*5]) * v_scale;
- v.y := -v.y;
-
- blk.vertices[y * 5 + x] := v;
+ result.vertices[y * 5 + x].init( //x,y,z
+ (basex + x) * h_scale,
+ tile.heights[y+x*5] * v_scale * -1,
+ (basey + y) * h_scale
+ );
+ end;
+ end;
+
+ //todo do proper per-vertex normal:
+ //this only calculates face normals and sets them to face's vertices, overwriting the value set
+ //from previous face
+ procedure FakeNormals(var blk: TTerrainBlock);
+ procedure SetTriData(const tri_idx: integer; const i0, i1, i2: byte);
+ var
+ normal: Tvector3_single;
+ begin
+ normal := GetNormal(blk.vertices[i0], blk.vertices[i1], blk.vertices[i2]);
+ blk.normals[i0] := normal;
+ blk.normals[i1] := normal;
+ blk.normals[i2] := normal;
+ end;
+ const
+ VertexStride = 5;
+ var
+ x, y, i, tri_idx: integer;
+ begin
+ tri_idx := 0;
+ for y := 0 to 3 do
+ for x := 0 to 3 do begin
+ i := y * VertexStride + x;
+ SetTriData(tri_idx, i+1, i, i+VertexStride);
+ SetTriData(tri_idx + 1, i+1, i+VertexStride, i+VertexStride+1);
+ tri_idx += 2;
end;
- blk.texture_index := tile.texture_index;
end;
var
- x, y, i, tile_idx: integer;
+ x, y, tile_idx: integer;
blk: TTerrainBlock;
tile: TTile;
begin
@@ -83,12 +111,46 @@ begin
tile_idx := terrain.heightmap.blk[y * terrain.TileWidth + x];
tile := terrain.heightmap.tiles[tile_idx];
- TileToBlock(blk, tile, y * 4, x * 4);
+ blk := TileToBlock(tile, y, x);
+ FakeNormals(blk);
blocks[y, x] := blk;
end;
end;
end;
+{ InitBlockStaticData
+ Initializes data shared between tiles: face vertex indices, texture coords
+}
+procedure TTerrainMesh.InitBlockStaticData;
+
+ procedure SetTriData(const tri_idx: integer; const i0, i1, i2: byte);
+ begin
+ block_face_indices[tri_idx * 3 + 0] := i0;
+ block_face_indices[tri_idx * 3 + 1] := i1;
+ block_face_indices[tri_idx * 3 + 2] := i2;
+ end;
+
+const
+ VertexStride = 5;
+var
+ x, y, i, tri_idx: integer;
+begin
+ tri_idx := 0;
+ //init face indices
+ for y := 0 to 3 do
+ for x := 0 to 3 do begin
+ i := y * VertexStride + x;
+ SetTriData(tri_idx, i+1, i, i+VertexStride);
+ SetTriData(tri_idx+1, i+1, i+VertexStride, i+VertexStride+1);
+ tri_idx += 2;
+ end;
+ //init uv coords
+ for y := 0 to 4 do
+ for x := 0 to 4 do begin
+ block_texcoords[y * 5 + x].init(y/4, 1 - x/4); //u, v
+ end;
+end;
+
destructor TTerrainMesh.Destroy;
begin
inherited Destroy;
@@ -100,6 +162,10 @@ begin
terrain.LoadFromFiles('hmp_0', 'lv_0.text', 'lv_0.tex');
//terrain.LoadFromFiles('hmp_1', 'lv_1.text', 'lv_1.tex');
TransformTiles;
+ InitBlockStaticData;
+ WriteLn(Format('terrain size: %dx%d, tris: %d',
+ [terrain.TileWidth, terrain.TileHeight,
+ terrain.TileWidth * terrain.TileHeight * 4 * 4 * 2]));
end;
//generate textures. TODO texture atlas?
@@ -139,91 +205,42 @@ begin
end;
-//cross product + normalize
-function GetNormalv(const v0, v1, v2: TVertex3f): TVertex3f;
-var
- a, b: TVertex3f;
- len: single;
-begin
- a.x := v0.x - v1.x;
- a.y := v0.y - v1.y;
- a.z := v0.z - v1.z;
-
- b.x := v1.x - v2.x;
- b.y := v1.y - v2.y;
- b.z := v1.z - v2.z;
-
- result.x := (a.y * b.z) - (a.z * b.y);
- result.y := (a.z * b.x) - (a.x * b.z);
- result.z := (a.x * b.y) - (a.y * b.x);
-
- len := sqrt( sqr(result.x) + sqr(result.y) + sqr(result.z) );
- if len = 0 then len := 1;
-
- result.x /= len;
- result.y /= len;
- result.z /= len;
-end;
-
-
//draw vertices from each block
procedure TTerrainMesh.DrawGL(opts: TRenderOpts);
-
- procedure RenderBlock(var blk: TTerrainBlock);
- procedure RenderTri(i0, i1, i2:integer);
- var
- v, n: TVertex3f;
- begin
- n := GetNormalv(blk.vertices[i0], blk.vertices[i1], blk.vertices[i2]);
- v := blk.vertices[i0];
- glNormal3f(n.x, n.y, n.z);
- glTexCoord2f(v.u, v.v);
- glVertex3fv(@v);
- v := blk.vertices[i1];
- glTexCoord2f(v.u, v.v);
- glVertex3fv(@v);
- v := blk.vertices[i2];
- glTexCoord2f(v.u, v.v);
- glVertex3fv(@v);
- end;
- var
- i, x, y, stride: integer;
- begin
- stride := 5;
- glBindTexture(GL_TEXTURE_2D, textures_glidx[blk.texture_index]);
-
- glBegin(GL_TRIANGLES);
- //glColor3f(0, 1, 0);
- for y := 0 to 3 do
- for x := 0 to 3 do begin
- //do two triangles
- i := y * stride + x;
- RenderTri(i+1, i, i+stride);
- RenderTri(i+1, i+stride, i+stride+1);
- end;
- glEnd;
- { //only 2 tris per block
- glBegin(GL_TRIANGLES);
- i := y * stride + x;
- RenderTri(0, 4, 24);
- RenderTri(24, 20, 0);
- glEnd;
- }
- end;
-
var
x, y: integer;
blk: TTerrainBlock;
+ last_tex_index: integer;
begin
if opts.wireframe then
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
else
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
glEnable(GL_TEXTURE_2D);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ last_tex_index := -1;
+
for y := 0 to terrain.TileHeight - 1 do
for x := 0 to terrain.TileWidth - 1 do begin
blk := blocks[y, x];
- RenderBlock(blk);
+ //repeated texture binding slows this down a lot (68->30fps)
+ //todo sort by texture?
+ if last_tex_index <> blk.texture_index then begin
+ last_tex_index := blk.texture_index;
+ glBindTexture(GL_TEXTURE_2D, textures_glidx[last_tex_index]);
+ end;
+
+ glVertexPointer(3, GL_FLOAT, sizeof(Tvector3_single), @blk.vertices[0].data[0]);
+ glNormalPointer(GL_FLOAT, sizeof(Tvector3_single), @blk.normals[0].data[0]);
+ glTexCoordPointer(2, GL_FLOAT, sizeof(Tvector2_single), @block_texcoords[0].data[0]);
+
+ if opts.points then
+ glDrawArrays(GL_POINTS, 0, 25)
+ else
+ glDrawElements(GL_TRIANGLES, 16*2*3, GL_UNSIGNED_BYTE, @block_face_indices);
end;
end;
diff --git a/terrain_viewer/terrain_viewer.lpi b/terrain_viewer/terrain_viewer.lpi
index d3029c3..f3e2adb 100644
--- a/terrain_viewer/terrain_viewer.lpi
+++ b/terrain_viewer/terrain_viewer.lpi
@@ -37,7 +37,7 @@
-
+
@@ -53,6 +53,11 @@
+
+
+
+
+
diff --git a/terrain_viewer/terrain_viewer.pas b/terrain_viewer/terrain_viewer.pas
index 59b7f81..49fcd43 100644
--- a/terrain_viewer/terrain_viewer.pas
+++ b/terrain_viewer/terrain_viewer.pas
@@ -22,7 +22,7 @@ program terrain_viewer;
uses
sysutils, math,
gl, glu, glext, sdl,
- terrain_mesh, rs_world;
+ terrain_mesh;
const
SCR_W_fscrn = 1024;
diff --git a/terrain_viewer/vector_util.pas b/terrain_viewer/vector_util.pas
new file mode 100644
index 0000000..7dff17e
--- /dev/null
+++ b/terrain_viewer/vector_util.pas
@@ -0,0 +1,33 @@
+unit vector_util;
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ matrix;
+
+function GetNormal(v0, v1, v2: Tvector3_single): Tvector3_single;
+
+implementation
+
+//cross product + normalize
+function GetNormal(v0, v1, v2: Tvector3_single): Tvector3_single;
+var
+ a, b: Tvector3_single;
+ len: single;
+begin
+ a := v0 - v1;
+ b := v1 - v2;
+
+ result.data[0] := (a.data[1] * b.data[2]) - (a.data[2] * b.data[1]);
+ result.data[1] := (a.data[2] * b.data[0]) - (a.data[0] * b.data[2]);
+ result.data[2] := (a.data[0] * b.data[1]) - (a.data[1] * b.data[0]);
+
+ len := result.length;
+ if len = 0 then len := 1;
+
+ result /= len;
+end;
+
+end.
+