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. +