Files
kam-mobile/port-source/kam_remake/src/render/KM_RenderTerrain.pas
T
admin_d 6d3b8e6ac5
Sync changed files to Google Drive / sync (push) Successful in 2m2s
Complete BigTask
2026-10-08 11:59:40 +05:00

1377 lines
50 KiB
ObjectPascal

unit KM_RenderTerrain;
{$I KaM_Remake.inc}
interface
uses
dglOpenGL,
SysUtils, Math,
KromUtils,
KM_ResSprites, KM_ResTilesetTypes,
KM_CommonClasses, KM_CommonTypes, KM_Defaults, KM_Points,
KM_FogOfWar, KM_Pics,
KM_TerrainTypes;
type
TKMVBOArrayType = (vatNone, vatTile, vatTileLayer, vatAnimTile, vatFOW);
TKMUVRect = array [1 .. 4, 1 .. 2] of Single; // Texture UV coordinates
TKMTileVerticeExt = record
X, Y, Z, UTile, VTile, ULit, UShd: Single;
end;
TKMTileFowVertice = record
X, Y, Z, UFow: Single;
end;
TKMTileVertice = record
X, Y, Z, UAnim, VAnim: Single;
end;
TKMTileVerticeExtArray = array of TKMTileVerticeExt;
TKMTileFowVerticeArray = array of TKMTileFowVertice;
TKMTileVerticeArray = array of TKMTileVertice;
//Render terrain without sprites
TKMRenderTerrain = class
private
fClipRect: TKMRect;
fTextG: GLuint; //Shading gradient for lighting
fTextB: GLuint; //Contrast BW for FOW over color-coder
fUseVBO: Boolean; //Wherever to render terrain through VBO (faster but needs GL1.5) or DrawCalls (slower but needs only GL1.1)
fTilesVtx: TKMTileVerticeExtArray; //Vertice buffer for tiles
fTilesVtxCount: Integer;
fTilesInd: array of Integer; //Indexes for tiles array
fTilesIndCount: Integer;
fTilesLayersVtx: array of TKMTileVertice; //Vertice cache for layers
fTilesLayersInd: array of Integer; //Indexes for layers array
fAnimTilesVtx: TKMTileVerticeArray; //Vertice buffer for tiles animations (water/falls/swamp)
fAnimTilesVtxCount: Integer;
fAnimTilesInd: array of Integer; //Indexes for array tiles animation array
fAnimTilesIndCount: Integer;
fTilesFowVtx: TKMTileFowVerticeArray; //Vertice buffer for tiles
fTilesFowVtxCount: Integer;
fTilesFowInd: array of Integer; //Indexes for tiles array
fTilesFowIndCount: Integer;
fVtxTilesShd: GLUint;
fIndTilesShd: GLUint;
fVtxTilesLayersShd: GLUint;
fIndTilesLayersShd: GLUint;
fVtxAnimTilesShd: GLUint;
fIndAnimTilesShd: GLUint;
fVtxTilesFowShd: GLUint;
fIndTilesFowShd: GLUint;
fTileUVLookup: array [0..TILES_CNT-1, 0..3] of TKMUVRect;
fLastBindVBOArrayType: TKMVBOArrayType;
fVBONeedsFlush: array [TKMVBOArrayType] of Boolean;
fVBOLastClipRect: TKMRect;
fVBOLastGameTick: Cardinal;
fVBOLastFOW: TKMFogOfWarCommon;
procedure RenderQuadTexture(var TexC: TKMUVRect; tX,tY: Word); inline;
procedure RenderQuadTextureBlended(var TexC: TKMUVRect; tX,tY: Word; aCorners: TKMTileCorners; aBlendingLevel: Byte); inline;
function GetTileUV(Index: Word; Rot: Byte): TKMUVRect; inline;
procedure BindVBOArray(aVBOArrayType: TKMVBOArrayType); inline;
procedure UpdateVBO(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
procedure DoTiles(aFOW: TKMFogOfWarCommon);
procedure DoTilesLayers(aFOW: TKMFogOfWarCommon);
procedure DoOverlays(aFOW: TKMFogOfWarCommon);
procedure DoLighting(aFOW: TKMFogOfWarCommon);
procedure DoAnimations(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
procedure DoShadows(aFOW: TKMFogOfWarCommon);
function VBOSupported: Boolean;
procedure RenderFence(aFence: TKMFenceKind; Pos: TKMDirection; pX,pY: Integer);
procedure RenderMarkup(pX, pY: Word; aFieldType: TKMFieldType);
procedure DoRenderTile(aTerrainId: Word; pX,pY,Rot: Integer; aDoBindTexture: Boolean; aUseTileLookup: Boolean;
DoHighlight: Boolean = False; HighlightColor: Cardinal = 0; aBlendingLvl: Byte = 0); overload;
procedure DoRenderTile(aTerrainId: Word; pX,pY,Rot: Integer; aCorners: TKMTileCorners; aDoBindTexture: Boolean;
aUseTileLookup: Boolean; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0;
aBlendingLvl: Byte = 0); overload;
function DoUseVBO: Boolean;
public
constructor Create;
destructor Destroy; override;
property ClipRect: TKMRect read fClipRect write fClipRect;
procedure RenderBase(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
procedure RenderFences(aFOW: TKMFogOfWarCommon);
procedure RenderPlayerPlans(aFieldsList: TKMPointTagList; aHousePlansList: TKMPointDirList);
procedure RenderFOW(aFOW: TKMFogOfWarCommon; aUseContrast: Boolean = False);
procedure RenderTile(aTerrainId: Word; pX,pY,Rot: Integer; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0); overload;
procedure RenderTile(pX,pY: Integer; aTileBasic: TKMTerrainTileBasic; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0); overload;
procedure RenderTileOverlay(pX, pY: Integer; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0);
end;
implementation
uses
KM_Game,
KM_GameParams,
KM_Render, KM_RenderTypes,
KM_Resource, KM_ResTypes,
KM_DevPerfLog, KM_DevPerfLogTypes,
KM_Terrain;
const
TILE_LAYERS_USE_VBO = False;
MAX_RENDERABLE_TILES = (MAX_MAP_SIZE + 1) * (MAX_MAP_SIZE + 1);
MAX_RENDERABLE_VERTICIES = 4 * MAX_RENDERABLE_TILES;
MAX_RENDERABLE_INDEXES = 6 * MAX_RENDERABLE_TILES;
{ TKMRenderTerrain }
constructor TKMRenderTerrain.Create;
var
I, K: Integer;
pData: array [0..255] of Cardinal;
V: TKMVBOArrayType;
begin
inherited;
//Tiles UV lookup for faster access. Only base tileset for smaller size
for I := 0 to TILES_CNT - 1 do
for K := 0 to 3 do
fTileUVLookup[I, K] := GetTileUV(I, K);
//Generate gradient programmatically
//KaM uses [0..255] gradients
//We use slightly smoothed gradients [16..255] for Remake
//cos it shows much more of terrain on screen and it looks too contrast
for I := 0 to 255 do
pData[I] := EnsureRange(Round(I * 1.0625 - 16), 0, 255) * 65793 or $FF000000;
fTextG := TKMRender.GenTexture(256, 1, @pData[0], tfRGBA8, ftNearest, ftNearest);
//Sharp transition between black and white
pData[0] := $FF000000;
pData[1] := $00000000;
pData[2] := $00000000;
pData[3] := $00000000;
fTextB := TKMRender.GenTexture(4, 1, @pData[0], tfRGBA8, ftNearest, ftNearest);
fUseVBO := DoUseVBO;
if fUseVBO then
begin
glGenBuffers(1, @fVtxTilesShd);
glGenBuffers(1, @fIndTilesShd);
glGenBuffers(1, @fVtxTilesLayersShd);
glGenBuffers(1, @fIndTilesLayersShd);
glGenBuffers(1, @fVtxAnimTilesShd);
glGenBuffers(1, @fIndAnimTilesShd);
glGenBuffers(1, @fVtxTilesFowShd);
glGenBuffers(1, @fIndTilesFowShd);
//Allocate buffers large enough for the entire map
SetLength(fTilesVtx, MAX_RENDERABLE_VERTICIES);
SetLength(fTilesInd, MAX_RENDERABLE_INDEXES);
SetLength(fTilesFowVtx, MAX_RENDERABLE_VERTICIES);
SetLength(fTilesFowInd, MAX_RENDERABLE_INDEXES);
SetLength(fAnimTilesVtx, MAX_RENDERABLE_VERTICIES);
SetLength(fAnimTilesInd, MAX_RENDERABLE_INDEXES);
fTilesVtxCount := 0;
fTilesIndCount := 0;
fTilesFowVtxCount := 0;
fTilesFowIndCount := 0;
fAnimTilesVtxCount := 0;
fAnimTilesIndCount := 0;
end;
for V := Low(TKMVBOArrayType) to High(TKMVBOArrayType) do
fVBONeedsFlush[V] := True;
end;
destructor TKMRenderTerrain.Destroy;
begin
// fUseVBO := VBOSupported; //Could have been set to False if 3D rendering is enabled, so reset it
if fUseVBO then
begin
//Since RenderTerrain is created fresh everytime fGame is created, we should clear
//the buffers to avoid memory leaks.
glDeleteBuffers(1, @fVtxTilesShd);
glDeleteBuffers(1, @fIndTilesShd);
glDeleteBuffers(1, @fVtxTilesLayersShd);
glDeleteBuffers(1, @fIndTilesLayersShd);
glDeleteBuffers(1, @fVtxAnimTilesShd);
glDeleteBuffers(1, @fIndAnimTilesShd);
glDeleteBuffers(1, @fVtxTilesFowShd);
glDeleteBuffers(1, @fIndTilesFowShd);
end;
inherited;
end;
function TKMRenderTerrain.VBOSupported: Boolean;
begin
//Some GPUs don't comply with OpenGL 1.5 spec on VBOs, so check Assigned instead of GL_VERSION_1_5
Result := Assigned(glGenBuffers) and Assigned(glBindBuffer) and Assigned(glBufferData) and
Assigned(glEnableClientState) and Assigned(glVertexPointer) and Assigned(glClientActiveTexture) and
Assigned(glTexCoordPointer) and Assigned(glDrawElements) and Assigned(glDisableClientState) and
Assigned(glDeleteBuffers);
end;
function TKMRenderTerrain.DoUseVBO: Boolean;
begin
Result := VBOSupported;
end;
function TKMRenderTerrain.GetTileUV(Index: Word; Rot: Byte): TKMUVRect;
var
TexO: array [1 .. 4] of Byte; // order of UV coordinates, for rotations
A: Byte;
begin
TexO[1] := 1;
TexO[2] := 2;
TexO[3] := 3;
TexO[4] := 4;
// Rotate by 90 degrees: 4-1-2-3
if Rot and 1 = 1 then
begin
A := TexO[4];
TexO[4] := TexO[3];
TexO[3] := TexO[2];
TexO[2] := TexO[1];
TexO[1] := A;
end;
//Rotate by 180 degrees: 3-4-1-2
if Rot and 2 = 2 then
begin
SwapInt(TexO[1], TexO[3]);
SwapInt(TexO[2], TexO[4]);
end;
// Rotate by 270 degrees = 90 + 180
//Apply rotation
with gGFXData[rxTiles, Index+1] do
begin
Result[TexO[1], 1] := Tex.u1; Result[TexO[1], 2] := Tex.v1;
Result[TexO[2], 1] := Tex.u1; Result[TexO[2], 2] := Tex.v2;
Result[TexO[3], 1] := Tex.u2; Result[TexO[3], 2] := Tex.v2;
Result[TexO[4], 1] := Tex.u2; Result[TexO[4], 2] := Tex.v1;
end;
end;
function TileHasToBeRendered(IsFirst: Boolean; aTX,aTY: Word; aFOW: TKMFogOfWarCommon): Boolean; inline;
begin
// We have to render at least 1 tile (otherwise smth wrong with gl contex and all UI and other sprites are not rendered at all
// so lets take the 1st tile
Result := IsFirst or (aFOW.CheckVerticeRenderRev(aTX,aTY) > FOG_OF_WAR_MIN);
end;
procedure TKMRenderTerrain.UpdateVBO(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
var
fog: PKMByte2Array;
procedure SetTileVertexExt(out aVert: TKMTileVerticeExt; aTX, aTY: Word;
aIsBottomRow: Boolean; aUTile, aVTile: Single); inline;
begin
with gTerrain do
begin
aVert.X := aTX;
aVert.Y := aTY - LandExt^[aTY+1, aTX+1].RenderHeight / CELL_HEIGHT_DIV;
aVert.Z := aTY - Byte(aIsBottomRow);
aVert.UTile := aUTile;
aVert.VTile := aVTile;
aVert.ULit := LandExt[aTY+1, aTX+1].RenderLight;
aVert.UShd := -LandExt[aTY+1, aTX+1].RenderLight;
end;
end;
procedure SetTileFowVertex(out aVert: TKMTileFowVertice; Fog: PKMByte2Array; aTX, aTY: Word; aIsBottomRow: Boolean); inline;
begin
aVert.X := aTX;
aVert.Y := aTY - gTerrain.LandExt^[aTY+1, aTX+1].RenderHeight / CELL_HEIGHT_DIV;
aVert.Z := aTY - Byte(aIsBottomRow);
if Fog <> nil then
aVert.UFow := Fog^[aTY, aTX] / 256
else
aVert.UFow := 255;
end;
procedure SetTileVertex(out aVert: TKMTileVertice; aTX, aTY: Word; aIsBottomRow: Boolean; aUAnimTile, aVAnimTile: Single); inline;
begin
with gTerrain do
begin
aVert.X := aTX;
aVert.Y := aTY - LandExt^[aTY+1, aTX+1].RenderHeight / CELL_HEIGHT_DIV;
aVert.Z := aTY - Byte(aIsBottomRow);
aVert.UAnim := aUAnimTile;
aVert.VAnim := aVAnimTile;
end;
end;
function TryAddAnimTex(var aAnimCnt: Integer; aTX, aTY: Word; aAnimStep: Integer): Boolean;
function SetAnimTileVertex(aTerrain: Word; aRotation: Byte): Boolean;
var
I: Integer;
texAnimC: TKMUVRect;
vtxOffset, indOffset: Integer;
tile: TKMTileParams;
begin
tile := gRes.Tileset[aTerrain];
if not tile.HasAnim then Exit(False);
Result := False;
for I := Low(tile.Animation.Layers) to High(tile.Animation.Layers) do
begin
if not tile.Animation.Layers[I].HasAnim then Continue;
texAnimC := GetTileUV(tile.Animation.Layers[I].GetAnim(aAnimStep), aRotation mod 4);
vtxOffset := aAnimCnt * 4;
indOffset := aAnimCnt * 6;
SetTileVertex(fAnimTilesVtx[vtxOffset], aTX-1, aTY-1, False, texAnimC[1][1], texAnimC[1][2]);
SetTileVertex(fAnimTilesVtx[vtxOffset+1], aTX-1, aTY, True, texAnimC[2][1], texAnimC[2][2]);
SetTileVertex(fAnimTilesVtx[vtxOffset+2], aTX, aTY, True, texAnimC[3][1], texAnimC[3][2]);
SetTileVertex(fAnimTilesVtx[vtxOffset+3], aTX, aTY-1, False, texAnimC[4][1], texAnimC[4][2]);
fAnimTilesInd[indOffset+0] := vtxOffset;
fAnimTilesInd[indOffset+1] := vtxOffset + 1;
fAnimTilesInd[indOffset+2] := vtxOffset + 2;
fAnimTilesInd[indOffset+3] := vtxOffset;
fAnimTilesInd[indOffset+4] := vtxOffset + 3;
fAnimTilesInd[indOffset+5] := vtxOffset + 2;
Inc(aAnimCnt);
Result := True;
end;
end;
var
L: Integer;
begin
Result := SetAnimTileVertex(gTerrain.Land^[aTY,aTX].BaseLayer.Terrain, gTerrain.Land^[aTY,aTX].BaseLayer.Rotation);
for L := 0 to gTerrain.Land^[aTY,aTX].LayersCnt - 1 do
if not Result then
Result := SetAnimTileVertex(BASE_TERRAIN[gRes.Sprites.GetGenTerrainInfo(gTerrain.Land^[aTY,aTX].Layer[L].Terrain).TerKind],
gTerrain.Land^[aTY,aTX].Layer[L].Rotation)
else
Exit;
end;
var
I, J, tilesCnt, fowCnt, animCnt, vtxOffset, indOffset: Integer;
// P,L,TilesLayersCnt: Integer;
sizeX, sizeY: Word;
tX, tY: Word;
texTileC: TKMUVRect;
V: TKMVBOArrayType;
begin
if not fUseVBO then Exit;
//Skip updating VBOs if GameTick and ClipRect haven't changed
if not gGameParams.IsMapEditor
and (fClipRect = fVBOLastClipRect)
and (fVBOLastFOW = aFOW)
and (gGameParams.Tick = fVBOLastGameTick) then
Exit;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameUpdateVBO);
{$ENDIF}
fVBOLastClipRect := fClipRect;
fVBOLastGameTick := gGameParams.Tick;
fVBOLastFOW := aFOW;
fLastBindVBOArrayType := vatNone;
if aFOW is TKMFogOfWar then
fog := @TKMFogOfWar(aFOW).Revelation
else
fog := nil;
sizeX := Max(fClipRect.Right - fClipRect.Left, 0);
sizeY := Max(fClipRect.Bottom - fClipRect.Top, 0);
tilesCnt := 0;
fowCnt := 0;
animCnt := 0;
// P := 0;
// SetLength(fTilesLayersVtx, (SizeX + 1) * 4 * 3 * (SizeY + 1));
with gTerrain do
if (MapX > 0) and (MapY > 0) then
for I := 0 to sizeY do
for J := 0 to sizeX do
begin
tX := J + fClipRect.Left;
tY := I + fClipRect.Top;
if TileHasToBeRendered(I*J = 0,tX,tY,aFow) then // Do not render tiles fully covered by FOW
begin
texTileC := fTileUVLookup[Land^[tY, tX].BaseLayer.Terrain, Land^[tY, tX].BaseLayer.Rotation mod 4];
vtxOffset := tilesCnt * 4;
indOffset := tilesCnt * 6;
//Fill Tile vertices array
SetTileVertexExt(fTilesVtx[vtxOffset], tX-1, tY-1, False, texTileC[1][1], texTileC[1][2]);
SetTileVertexExt(fTilesVtx[vtxOffset+1], tX-1, tY, True, texTileC[2][1], texTileC[2][2]);
SetTileVertexExt(fTilesVtx[vtxOffset+2], tX, tY, True, texTileC[3][1], texTileC[3][2]);
SetTileVertexExt(fTilesVtx[vtxOffset+3], tX, tY-1, False, texTileC[4][1], texTileC[4][2]);
// Set Tile terrain indices
fTilesInd[indOffset+0] := vtxOffset;
fTilesInd[indOffset+1] := vtxOffset + 1;
fTilesInd[indOffset+2] := vtxOffset + 2;
fTilesInd[indOffset+3] := vtxOffset;
fTilesInd[indOffset+4] := vtxOffset + 3;
fTilesInd[indOffset+5] := vtxOffset + 2;
Inc(tilesCnt);
// if Land^[tY, tX].LayersCnt > 0 then
// for L := 0 to Land^[tY, tX].LayersCnt - 1 do
// begin
// TexTileC := GetTileUV(Land[tY,tX].Layer[L].Terrain, Land^[TY,TX].Layer[L].Rotation mod 4);
//
// //Fill Tile vertices array
// SetTileVertex(fTilesLayersVtx, P, tX-1, tY-1, False, TexTileC[1][1], TexTileC[1][2]);
// SetTileVertex(fTilesLayersVtx, P+1, tX-1, tY, True, TexTileC[2][1], TexTileC[2][2]);
// SetTileVertex(fTilesLayersVtx, P+2, tX, tY, True, TexTileC[3][1], TexTileC[3][2]);
// SetTileVertex(fTilesLayersVtx, P+3, tX, tY-1, False, TexTileC[4][1], TexTileC[4][2]);
// P := P + 4;
// end;
//
// if gTerrain.Land^[tY, tX].LayersCnt > 0 then
// // Set Tile layers terrain indices
// for L := 0 to gTerrain.Land^[tY, tX].LayersCnt - 1 do
// begin
// fTilesLayersInd[P+0] := KP shl 2; // shl 2 = *4
// fTilesLayersInd[P+1] := (KP shl 2) + 1;
// fTilesLayersInd[P+2] := (KP shl 2) + 2;
// fTilesLayersInd[P+3] := (KP shl 2);
// fTilesLayersInd[P+4] := (KP shl 2) + 3;
// fTilesLayersInd[P+5] := (KP shl 2) + 2;
// P := P + 6;
// Inc(KP);
// end;
end;
vtxOffset := fowCnt * 4;
indOffset := fowCnt * 6;
// Always set FOW
SetTileFowVertex(fTilesFowVtx[vtxOffset], fog, tX-1, tY-1, False);
SetTileFowVertex(fTilesFowVtx[vtxOffset+1], fog, tX-1, tY, True);
SetTileFowVertex(fTilesFowVtx[vtxOffset+2], fog, tX, tY, True);
SetTileFowVertex(fTilesFowVtx[vtxOffset+3], fog, tX, tY-1, False);
// Set FOW indices
fTilesFowInd[indOffset+0] := vtxOffset;
fTilesFowInd[indOffset+1] := vtxOffset + 1;
fTilesFowInd[indOffset+2] := vtxOffset + 2;
fTilesFowInd[indOffset+3] := vtxOffset;
fTilesFowInd[indOffset+4] := vtxOffset + 3;
fTilesFowInd[indOffset+5] := vtxOffset + 2;
Inc(fowCnt);
// Render animation only if tile is not covered by FOW
if (aFOW.CheckVerticeRenderRev(tX,tY) > FOG_OF_WAR_ACT) then
//every tile can have only 1 animation
TryAddAnimTex(animCnt, tX, tY, aAnimStep);
end;
//Update vertex/index counts
fTilesVtxCount := 4*tilesCnt;
fTilesIndCount := 6*tilesCnt;
fTilesFowVtxCount := 4*fowCnt;
fTilesFowIndCount := 6*fowCnt;
fAnimTilesVtxCount := 4*animCnt;
fAnimTilesIndCount := 6*animCnt;
// SetLength(fTilesLayersVtx, P);
// TilesLayersCnt := P div 4;
// SetLength(fTileslayersInd, TilesLayersCnt*6);
for V := Low(TKMVBOArrayType) to High(TKMVBOArrayType) do
fVBONeedsFlush[V] := True;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameUpdateVBO);
{$ENDIF}
end;
procedure TKMRenderTerrain.RenderQuadTexture(var TexC: TKMUVRect; tX,tY: Word);
begin
with gTerrain do
begin
glTexCoord2fv(@TexC[1]); glVertex3f(tX-1,tY-1-LandExt^[tY, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord2fv(@TexC[2]); glVertex3f(tX-1,tY -LandExt^[tY+1,tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord2fv(@TexC[3]); glVertex3f(tX ,tY -LandExt^[tY+1,tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord2fv(@TexC[4]); glVertex3f(tX ,tY-1-LandExt^[tY, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
end;
end;
procedure TKMRenderTerrain.RenderQuadTextureBlended(var TexC: TKMUVRect; tX,tY: Word; aCorners: TKMTileCorners; aBlendingLevel: Byte);
var
blendFactor: Single;
begin
blendFactor := 1 - Max(0, Min(1, aBlendingLevel / TERRAIN_MAX_BLENDING_LEVEL));
with gTerrain do
begin
if aCorners[0] then
glColor4f(1,1,1,1)
else
glColor4f(1,1,1,blendFactor);
glTexCoord2fv(@TexC[1]); glVertex3f(tX-1,tY-1-LandExt^[tY, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
if aCorners[3] then
glColor4f(1,1,1,1)
else
glColor4f(1,1,1,blendFactor);
glTexCoord2fv(@TexC[2]); glVertex3f(tX-1,tY -LandExt^[tY+1,tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
if aCorners[2] then
glColor4f(1,1,1,1)
else
glColor4f(1,1,1,blendFactor);
glTexCoord2fv(@TexC[3]); glVertex3f(tX ,tY -LandExt^[tY+1,tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
if aCorners[1] then
glColor4f(1,1,1,1)
else
glColor4f(1,1,1,blendFactor);
glTexCoord2fv(@TexC[4]); glVertex3f(tX ,tY-1-LandExt^[tY, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glColor4f(1,1,1,1);
end;
end;
procedure TKMRenderTerrain.DoTiles(aFOW: TKMFogOfWarCommon);
var
I, K: Integer;
texC: TKMUVRect;
sizeX, sizeY: Word;
tX, tY: Word;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameTiles);
{$ENDIF}
//First we render base layer, then we do animated layers for Water/Swamps/Waterfalls
//They all run at different speeds so we can't adjoin them in one layer
glColor4f(1,1,1,1);
//Draw with VBO only if all tiles are on the same texture
if fUseVBO and TKMResSprites.AllTilesOnOneAtlas then
begin
if fTilesVtxCount = 0 then Exit; //Nothing to render
BindVBOArray(vatTile);
//Bind to tiles texture. All tiles should be places in 1 atlas,
//so to get TexId we can use any of terrain tile Id (f.e. 1st)
TKMRender.BindTexture(gGFXData[rxTiles, 1].Tex.TexID);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(12));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, fTilesIndCount, GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
sizeX := Max(fClipRect.Right - fClipRect.Left, 0);
sizeY := Max(fClipRect.Bottom - fClipRect.Top, 0);
with gTerrain do
for I := 0 to sizeY do
for K := 0 to sizeX do
begin
tX := K + fClipRect.Left;
tY := I + fClipRect.Top;
if TileHasToBeRendered(I*K = 0, tX, tY, aFow) then // Do not render tiles fully covered by FOW
begin
with Land^[tY,tX] do
begin
TKMRender.BindTexture(gGFXData[rxTiles, BaseLayer.Terrain+1].Tex.TexID);
glBegin(GL_TRIANGLE_FAN);
texC := fTileUVLookup[BaseLayer.Terrain, BaseLayer.Rotation mod 4];
end;
RenderQuadTexture(texC, tX, tY);
glEnd;
end;
end;
end;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameTiles);
{$ENDIF}
end;
procedure TKMRenderTerrain.DoTilesLayers(aFOW: TKMFogOfWarCommon);
var
I, K, L: Integer;
texC: TKMUVRect;
sizeX, sizeY: Word;
tX, tY: Word;
terInfo: TKMGenTerrainInfo;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameTilesLayers);
{$ENDIF}
//First we render base layer, then we do animated layers for Water/Swamps/Waterfalls
//They all run at different speeds so we can't adjoin them in one layer
glColor4f(1,1,1,1);
//Draw with VBO only if all tiles are on the same texture
//DONT USE VBO FOR LAYERS FOR NOW, SINCE WE CAN USE BLENDING HERE
if TILE_LAYERS_USE_VBO
and fUseVBO
and TKMResSprites.AllTilesOnOneAtlas then
begin
if Length(fTilesLayersVtx) = 0 then Exit; //Nothing to render
BindVBOArray(vatTileLayer);
//Bind to tiles texture. All tiles should be places in 1 atlas,
//so to get TexId we can use any of terrain tile Id (f.e. 1st)
TKMRender.BindTexture(gGFXData[rxTiles, 1].Tex.TexID);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileVertice), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, SizeOf(TKMTileVertice), Pointer(12));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, Length(fTilesLayersInd), GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
sizeX := Max(fClipRect.Right - fClipRect.Left, 0);
sizeY := Max(fClipRect.Bottom - fClipRect.Top, 0);
with gTerrain do
for I := 0 to sizeY do
for K := 0 to sizeX do
begin
tX := K + fClipRect.Left;
tY := I + fClipRect.Top;
if TileHasToBeRendered(I*K = 0,tX,tY,aFow) then // Do not render tiles fully covered by FOW
for L := 0 to Land^[tY,tX].LayersCnt - 1 do
begin
with Land^[tY,tX] do
begin
TKMRender.BindTexture(gGFXData[rxTiles, Layer[L].Terrain+1].Tex.TexID);
glBegin(GL_TRIANGLE_FAN);
texC := GetTileUV(Layer[L].Terrain, Layer[L].Rotation);
terInfo := gRes.Sprites.GetGenTerrainInfo(Layer[L].Terrain);
if terInfo.TerKind = tkCustom then
Exit;
if BlendingLvl > 0 then
RenderQuadTextureBlended(texC, tX, tY, Layer[L].GetCorners, BlendingLvl)
else
RenderQuadTexture(texC, tX, tY);
glEnd;
end;
end;
end;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //Just in case...
end;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameTilesLayers);
{$ENDIF}
end;
procedure TKMRenderTerrain.DoAnimations(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
var
I, J, K: Integer;
texC: TKMUVRect;
animID: Word;
tile: TKMTileParams;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameWater);
{$ENDIF}
//First we render base layer, then we do animated layers for Water/Swamps/Waterfalls
//They all run at different speeds so we can't adjoin them in one layer
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if fUseVBO and TKMResSprites.AllTilesOnOneAtlas then
begin
if fAnimTilesVtxCount = 0 then Exit; //There is no animation on map
BindVBOArray(vatAnimTile);
//Bind to tiles texture. All tiles should be placed in 1 atlas,
//so to get TexId we can use any of terrain tile Id (f.e. 1st)
TKMRender.BindTexture(gGFXData[rxTiles, 1].Tex.TexID);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileVertice), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, SizeOf(TKMTileVertice), Pointer(12));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, fAnimTilesIndCount, GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
with gTerrain do
for I := fClipRect.Top to fClipRect.Bottom do
for K := fClipRect.Left to fClipRect.Right do
begin
tile := gRes.Tileset[Land^[I,K].BaseLayer.Terrain];
if tile.HasAnim
and (aFOW.CheckVerticeRenderRev(K,I) > FOG_OF_WAR_ACT) then //No animation in FOW
begin
for J := Low(tile.Animation.Layers) to High(tile.Animation.Layers) do
begin
if not tile.Animation.Layers[J].HasAnim then Continue;
animID := tile.Animation.Layers[J].GetAnim(aAnimStep);
TKMRender.BindTexture(gGFXData[rxTiles, animID + 1].Tex.TexID);
texC := GetTileUV(animID, Land^[I,K].BaseLayer.Rotation);
glBegin(GL_TRIANGLE_FAN);
glColor4f(1,1,1,1);
RenderQuadTexture(texC, K, I);
glEnd;
end;
end;
end;
end;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameWater);
{$ENDIF}
end;
//Render single tile overlay
procedure TKMRenderTerrain.RenderTileOverlay(pX, pY: Integer; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0);
// 1 //Select road tile and rotation
// 8*2 //depending on surrounding tiles
// 4 //Bitfield
const
RoadsConnectivity: array [0..15, 1..2] of Byte = (
(248,0), (248,0), (248,1), (250,3),
(248,0), (248,0), (250,0), (252,0),
(248,1), (250,2), (248,1), (252,3),
(250,1), (252,2), (252,1), (254,0));
var
road, ID, rot: Byte;
begin
//Fake tiles for MapEd fields
if gGame.MapEditor <> nil then
case gGame.MapEditor.LandMapEd^[pY, pX].CornOrWine of
1: RenderTile(gGame.MapEditor.LandMapEd^[pY, pX].CornOrWineTerrain, pX, pY, 0, DoHighlight, HighlightColor); // Corn
2: RenderTile(WINE_TERRAIN_ID, pX, pY, 0, DoHighlight, HighlightColor); //Wine
end;
if gTerrain.Land^[pY, pX].TileOverlay = toRoad then
begin
road := 0;
if (pY - 1 >= 1) then
road := road + Byte(gTerrain.Land^[pY - 1, pX].TileOverlay = toRoad) shl 0;
if (pX + 1 <= gTerrain.MapX - 1) then
road := road + Byte(gTerrain.Land^[pY, pX + 1].TileOverlay = toRoad) shl 1;
if (pY + 1 <= gTerrain.MapY - 1) then
road := road + Byte(gTerrain.Land^[pY + 1, pX].TileOverlay = toRoad) shl 2;
if (pX - 1 >= 1) then
road := road + Byte(gTerrain.Land^[pY, pX - 1].TileOverlay = toRoad) shl 3;
ID := RoadsConnectivity[road, 1];
rot := RoadsConnectivity[road, 2];
RenderTile(ID, pX, pY, rot, DoHighlight, HighlightColor);
end
else if gTerrain.Land^[pY, pX].TileOverlay <> toNone then
RenderTile(TILE_OVERLAY_IDS[gTerrain.Land^[pY, pX].TileOverlay], pX, pY, 0, DoHighlight, HighlightColor);
end;
procedure TKMRenderTerrain.DoOverlays(aFOW: TKMFogOfWarCommon);
var
I, K: Integer;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameOverlays);
{$ENDIF}
if not (mlOverlays in gGameParams.VisibleLayers) then
Exit;
for I := fClipRect.Top to fClipRect.Bottom do
for K := fClipRect.Left to fClipRect.Right do
if TileHasToBeRendered(False,K,I,aFow) then
RenderTileOverlay(K, I);
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameOverlays);
{$ENDIF}
end;
procedure TKMRenderTerrain.RenderFences(aFOW: TKMFogOfWarCommon);
var
I, K: Integer;
begin
if gGameParams.IsMapEditor and not (mlOverlays in gGameParams.VisibleLayers) then
Exit;
with gTerrain do
for I := fClipRect.Top to fClipRect.Bottom do
for K := fClipRect.Left to fClipRect.Right do
begin
if TileHasToBeRendered(False,K,I,aFow) then
begin
if Fences[I,K].Side and 1 = 1 then
RenderFence(Fences[I,K].Kind, dirN, K, I);
if Fences[I,K].Side and 2 = 2 then
RenderFence(Fences[I,K].Kind, dirE, K, I);
if Fences[I,K].Side and 4 = 4 then
RenderFence(Fences[I,K].Kind, dirW, K, I);
if Fences[I,K].Side and 8 = 8 then
RenderFence(Fences[I,K].Kind, dirS, K, I);
end;
end;
end;
//Player markings should be always clearly visible to the player (thats why we render them ontop FOW)
procedure TKMRenderTerrain.RenderPlayerPlans(aFieldsList: TKMPointTagList; aHousePlansList: TKMPointDirList);
var
I: Integer;
begin
//Rope field marks
for I := 0 to aFieldsList.Count - 1 do
RenderMarkup(aFieldsList[I].X, aFieldsList[I].Y, TKMFieldType(aFieldsList.Tag[I]));
//Rope outlines
for I := 0 to aHousePlansList.Count - 1 do
RenderFence(fncHousePlan, aHousePlansList[I].Dir, aHousePlansList[I].Loc.X, aHousePlansList[I].Loc.Y);
end;
procedure TKMRenderTerrain.DoLighting(aFOW: TKMFogOfWarCommon);
var
I, K: Integer;
sizeX, sizeY: Word;
tX, tY: Word;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameLighting);
{$ENDIF}
glColor4f(1, 1, 1, 1);
//Render highlights
glBlendFunc(GL_DST_COLOR, GL_ONE);
TKMRender.BindTexture(fTextG);
if fUseVBO then
begin
if fTilesVtxCount = 0 then Exit; //Nothing to render
BindVBOArray(vatTile);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(1, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(20));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, fTilesIndCount, GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
sizeX := Max(fClipRect.Right - fClipRect.Left, 0);
sizeY := Max(fClipRect.Bottom - fClipRect.Top, 0);
with gTerrain do
for I := 0 to sizeY do
for K := 0 to sizeX do
begin
tX := K + fClipRect.Left;
tY := I + fClipRect.Top;
if TileHasToBeRendered(I*K = 0,tX,tY,aFow) then // Do not render tiles fully covered by FOW
begin
glBegin(GL_TRIANGLE_FAN);
glTexCoord1f(LandExt[ tY, tX].RenderLight); glVertex3f(tX-1, tY-1 - LandExt^[ tY, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(LandExt[tY+1, tX].RenderLight); glVertex3f(tX-1, tY - LandExt^[tY+1, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(LandExt[tY+1, tX+1].RenderLight); glVertex3f( tX, tY - LandExt^[tY+1, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(LandExt[ tY, tX+1].RenderLight); glVertex3f( tX, tY-1 - LandExt^[ tY, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glEnd;
end;
end;
end;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameLighting);
{$ENDIF}
end;
//Render shadows and FOW at once
procedure TKMRenderTerrain.DoShadows(aFOW: TKMFogOfWarCommon);
var
I, K: Integer;
sizeX, sizeY: Word;
tX, tY: Word;
begin
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameShadows);
{$ENDIF}
glColor4f(1, 1, 1, 1);
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
TKMRender.BindTexture(fTextG);
if fUseVBO then
begin
if fTilesVtxCount = 0 then Exit; //Nothing to render
BindVBOArray(vatTile);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(1, GL_FLOAT, SizeOf(TKMTileVerticeExt), Pointer(24));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, fTilesIndCount, GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
sizeX := Max(fClipRect.Right - fClipRect.Left, 0);
sizeY := Max(fClipRect.Bottom - fClipRect.Top, 0);
with gTerrain do
for I := 0 to sizeY do
for K := 0 to sizeX do
begin
tX := K + fClipRect.Left;
tY := I + fClipRect.Top;
if TileHasToBeRendered(I*K = 0,tX,tY,aFow) then // Do not render tiles fully covered by FOW
begin
glBegin(GL_TRIANGLE_FAN);
glTexCoord1f(-LandExt[ tY, tX].RenderLight); glVertex3f(tX-1, tY-1 - LandExt^[ tY, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(-LandExt[tY+1, tX].RenderLight); glVertex3f(tX-1, tY - LandExt^[tY+1, tX].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(-LandExt[tY+1, tX+1].RenderLight); glVertex3f( tX, tY - LandExt^[tY+1, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glTexCoord1f(-LandExt[ tY, tX+1].RenderLight); glVertex3f( tX, tY-1 - LandExt^[ tY, tX+1].RenderHeight / CELL_HEIGHT_DIV, tY-1);
glEnd;
end;
end;
end;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
TKMRender.BindTexture(0);
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameShadows);
{$ENDIF}
end;
//Render FOW at once
procedure TKMRenderTerrain.RenderFOW(aFOW: TKMFogOfWarCommon; aUseContrast: Boolean = False);
var
I, K: Integer;
fog: PKMByte2Array;
begin
if aFOW is TKMFogOfWarOpen then Exit;
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameFOW);
{$ENDIF}
glColor4f(1, 1, 1, 1);
if aUseContrast then
begin
//Hide everything behind FOW with a sharp transition
glColor4f(0, 0, 0, 1);
TKMRender.BindTexture(fTextB);
end
else
begin
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
TKMRender.BindTexture(fTextG);
end;
fog := @TKMFogOfWar(aFOW).Revelation;
if fUseVBO then
begin
if fTilesFowVtxCount = 0 then Exit; //Nothing to render
BindVBOArray(vatFOW);
//Setup vertex and UV layout and offsets
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, SizeOf(TKMTileFowVertice), Pointer(0));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(1, GL_FLOAT, SizeOf(TKMTileFowVertice), Pointer(12));
//Here and above OGL requests Pointer, but in fact it's just a number (offset within Array)
glDrawElements(GL_TRIANGLES, fTilesFowIndCount, GL_UNSIGNED_INT, Pointer(0));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
end
else
begin
with gTerrain do
for I := fClipRect.Top to fClipRect.Bottom do
for K := fClipRect.Left to fClipRect.Right do
begin
glBegin(GL_TRIANGLE_FAN);
glTexCoord1f(fog^[I - 1, K - 1] / 255);
glVertex2f(K - 1, I - 1 - LandExt^[I, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[I, K - 1] / 255);
glVertex2f(K - 1, I - LandExt^[I + 1, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[I, K] / 255);
glVertex2f(K, I - LandExt^[I + 1, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[I - 1, K] / 255);
glVertex2f(K, I - 1 - LandExt^[I, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glEnd;
end;
end;
//Sprites (trees) can extend beyond the top edge of the map, so draw extra rows of fog to cover them
//@Krom: If you know a neater/faster way to solve this problem please feel free to change it.
//@Krom: Side note: Is it ok to not use VBOs in this case? (does mixing VBO with non-VBO code cause any problems?)
with gTerrain do
if fClipRect.Top <= 1 then
begin
//3 tiles is enough to cover the tallest tree with highest elevation on top row
for I := -2 to 0 do
for K := fClipRect.Left to fClipRect.Right do
begin
glBegin(GL_TRIANGLE_FAN);
glTexCoord1f(fog^[0, K - 1] / 255);
glVertex2f(K - 1, I - 1 - LandExt^[1, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[0, K - 1] / 255);
glVertex2f(K - 1, I - LandExt^[1, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[0, K] / 255);
glVertex2f(K, I - LandExt^[1, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[0, K] / 255);
glVertex2f(K, I - 1 - LandExt^[1, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glEnd;
end;
end;
//Similar thing for the bottom of the map (field borders can overhang)
with gTerrain do
if fClipRect.Bottom >= MapY-1 then
begin
//1 tile is enough to cover field borders
for K := fClipRect.Left to fClipRect.Right do
begin
glBegin(GL_TRIANGLE_FAN);
glTexCoord1f(fog^[MapY-1, K - 1] / 255);
glVertex2f(K - 1, MapY-1 - LandExt^[MapY, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[MapY-1, K - 1] / 255);
glVertex2f(K - 1, MapY - LandExt^[MapY, K].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[MapY-1, K] / 255);
glVertex2f(K, MapY-1 - LandExt^[MapY, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glTexCoord1f(fog^[MapY-1, K] / 255);
glVertex2f(K, MapY - LandExt^[MapY, K + 1].RenderHeight / CELL_HEIGHT_DIV);
glEnd;
end;
end;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
TKMRender.BindTexture(0);
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameFOW);
{$ENDIF}
end;
procedure TKMRenderTerrain.BindVBOArray(aVBOArrayType: TKMVBOArrayType);
begin
if fLastBindVBOArrayType = aVBOArrayType then Exit; // Do not to rebind for same tyle type
case aVBOArrayType of
vatTile: if fTilesVtxCount > 0 then
begin
glBindBuffer(GL_ARRAY_BUFFER, fVtxTilesShd);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndTilesShd);
if fVBONeedsFlush[aVBOArrayType] then
begin
glBufferData(GL_ARRAY_BUFFER, fTilesVtxCount * SizeOf(TKMTileVerticeExt), @fTilesVtx[0].X, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, fTilesIndCount * SizeOf(fTilesInd[0]), @fTilesInd[0], GL_STREAM_DRAW);
fVBONeedsFlush[aVBOArrayType] := False;
end;
end else Exit;
vatTileLayer: if Length(fTilesLayersVtx) > 0 then
begin
glBindBuffer(GL_ARRAY_BUFFER, fVtxTilesLayersShd);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndTilesLayersShd);
if fVBONeedsFlush[aVBOArrayType] then
begin
glBufferData(GL_ARRAY_BUFFER, Length(fTilesLayersVtx) * SizeOf(TKMTileVertice), @fTilesLayersVtx[0].X, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, Length(fTilesLayersInd) * SizeOf(fTilesLayersInd[0]), @fTilesLayersInd[0], GL_STREAM_DRAW);
fVBONeedsFlush[aVBOArrayType] := False;
end;
end else Exit;
vatAnimTile: if fAnimTilesVtxCount > 0 then
begin
glBindBuffer(GL_ARRAY_BUFFER, fVtxAnimTilesShd);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndAnimTilesShd);
if fVBONeedsFlush[aVBOArrayType] then
begin
glBufferData(GL_ARRAY_BUFFER, fAnimTilesVtxCount * SizeOf(TKMTileVertice), @fAnimTilesVtx[0].X, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, fAnimTilesIndCount * SizeOf(fAnimTilesInd[0]), @fAnimTilesInd[0], GL_STREAM_DRAW);
fVBONeedsFlush[aVBOArrayType] := False;
end;
end else Exit;
vatFOW: if fTilesFowVtxCount > 0 then
begin
glBindBuffer(GL_ARRAY_BUFFER, fVtxTilesFowShd);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndTilesFowShd);
if fVBONeedsFlush[aVBOArrayType] then
begin
glBufferData(GL_ARRAY_BUFFER, fTilesFowVtxCount * SizeOf(TKMTileFowVertice), @fTilesFowVtx[0].X, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, fTilesFowIndCount * SizeOf(fTilesFowInd[0]), @fTilesFowInd[0], GL_STREAM_DRAW);
fVBONeedsFlush[aVBOArrayType] := False;
end;
end else Exit;
end;
fLastBindVBOArrayType := aVBOArrayType;
end;
//AnimStep - animation step for terrain (water/etc)
//aFOW - whose players FOW to apply
procedure TKMRenderTerrain.RenderBase(aAnimStep: Integer; aFOW: TKMFogOfWarCommon);
begin
fUseVBO := DoUseVBO;
UpdateVBO(aAnimStep, aFOW);
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionEnter(psFrameTerrainBase);
{$ENDIF}
DoTiles(aFOW);
//It was 'unlit water goes above lit sand'
//But there is no big difference there, that is why, to make possible transitions with water,
//Animations was put before DoLighting
DoAnimations(aAnimStep, aFOW);
//TileLayers after water, as water with animation is always base layer
DoTilesLayers(aFOW);
DoOverlays(aFOW);
DoLighting(aFOW);
// DoAnimations(aAnimStep, aFOW);
DoShadows(aFOW);
{$IFDEF DBG_PERFLOG}
gPerfLogs.SectionLeave(psFrameTerrainBase);
{$ENDIF}
end;
procedure TKMRenderTerrain.DoRenderTile(aTerrainId: Word; pX,pY,Rot: Integer; aDoBindTexture: Boolean; aUseTileLookup: Boolean;
DoHighlight: Boolean = False; HighlightColor: Cardinal = 0; aBlendingLvl: Byte = 0);
const
NO_CORNERS: TKMTileCorners = (False, False, False, False);
begin
DoRenderTile(aTerrainId, pX,pY,Rot, NO_CORNERS, aDoBindTexture, aUseTileLookup, DoHighlight, HighlightColor, aBlendingLvl);
end;
procedure TKMRenderTerrain.DoRenderTile(aTerrainId: Word; pX,pY,Rot: Integer; aCorners: TKMTileCorners; aDoBindTexture: Boolean;
aUseTileLookup: Boolean; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0;
aBlendingLvl: Byte = 0);
var
texC: TKMUVRect; // Texture UV coordinates
begin
if not gTerrain.TileInMapCoords(pX,pY) then Exit;
if DoHighlight then
glColor4ub(HighlightColor and $FF, (HighlightColor shr 8) and $FF, (HighlightColor shr 16) and $FF, $FF)
else
glColor4f(1, 1, 1, 1);
if aDoBindTexture then
TKMRender.BindTexture(gGFXData[rxTiles, aTerrainId + 1].Tex.TexID);
if aUseTileLookup then
texC := fTileUVLookup[aTerrainId, Rot mod 4]
else
texC := GetTileUV(aTerrainId, Rot mod 4);
glBegin(GL_TRIANGLE_FAN);
//todo -cComplicated: DoHighlight and HighlightColor is not considered here, we use always glColor4f(1, 1, 1, 1);
if aBlendingLvl > 0 then
RenderQuadTextureBlended(texC, pX, pY, aCorners, aBlendingLvl)
else
RenderQuadTexture(texC, pX, pY);
glEnd;
end;
//Render single terrain cell
procedure TKMRenderTerrain.RenderTile(aTerrainId: Word; pX,pY,Rot: Integer; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0);
begin
DoRenderTile(aTerrainId, pX, pY, Rot, True, True, DoHighlight, HighlightColor);
end;
//Render single terrain cell
procedure TKMRenderTerrain.RenderTile(pX,pY: Integer; aTileBasic: TKMTerrainTileBasic; DoHighlight: Boolean = False; HighlightColor: Cardinal = 0);
var
L: Integer;
doBindTexture: Boolean;
begin
if not gTerrain.TileInMapCoords(pX,pY) then Exit;
doBindTexture := not TKMResSprites.AllTilesOnOneAtlas;
if not doBindTexture then
TKMRender.BindTexture(gGFXData[rxTiles, aTileBasic.BaseLayer.Terrain + 1].Tex.TexID);
// Render Base Layer
DoRenderTile(aTileBasic.BaseLayer.Terrain, pX, pY, aTileBasic.BaseLayer.Rotation, doBindTexture,
False, DoHighlight, HighlightColor);
// Render other Layers
for L := 0 to aTileBasic.LayersCnt - 1 do
DoRenderTile(aTileBasic.Layer[L].Terrain, pX, pY, aTileBasic.Layer[L].Rotation, aTileBasic.Layer[L].GetCorners,
doBindTexture, False, DoHighlight, HighlightColor, aTileBasic.BlendingLvl);
end;
procedure TKMRenderTerrain.RenderFence(aFence: TKMFenceKind; Pos: TKMDirection; pX,pY: Integer);
const
FO = 4; //Fence overlap
VO = -4; //Move fences a little down to avoid visible overlap when unit stands behind fence, but is rendered ontop of it, due to Z sorting algo we use
var
UVa, UVb: TKMPointF;
texID: Integer;
x1, y1, y2, fenceX, fenceY: Single;
heightInPx: Integer;
begin
case aFence of
fncHouseFence: if Pos in [dirN,dirS] then texID:=463 else texID:=467; //WIP (Wood planks)
fncHousePlan: if Pos in [dirN,dirS] then texID:=105 else texID:=117; //Plan (Ropes)
fncWine: if Pos in [dirN,dirS] then texID:=462 else texID:=466; //Fence (Wood)
fncCorn: if Pos in [dirN,dirS] then texID:=461 else texID:=465; //Fence (Stones)
else texID := 0;
end;
//With these directions render fences on next tile
if Pos = dirS then Inc(pY);
if Pos = dirW then Inc(pX);
if Pos in [dirN, dirS] then
begin //Horizontal
TKMRender.BindTexture(gGFXData[rxGui,texID].Tex.TexID);
UVa.X := gGFXData[rxGui, texID].Tex.u1;
UVa.Y := gGFXData[rxGui, texID].Tex.v1;
UVb.X := gGFXData[rxGui, texID].Tex.u2;
UVb.Y := gGFXData[rxGui, texID].Tex.v2;
y1 := pY - 1 - (gTerrain.LandExt^[pY, pX].RenderHeight + VO) / CELL_HEIGHT_DIV;
y2 := pY - 1 - (gTerrain.LandExt^[pY, pX + 1].RenderHeight + VO) / CELL_HEIGHT_DIV;
fenceY := gGFXData[rxGui,texID].PxWidth / CELL_SIZE_PX;
glBegin(GL_QUADS);
glTexCoord2f(UVb.x, UVa.y); glVertex2f(pX-1 -3/ CELL_SIZE_PX, y1);
glTexCoord2f(UVa.x, UVa.y); glVertex2f(pX-1 -3/ CELL_SIZE_PX, y1 - fenceY);
glTexCoord2f(UVa.x, UVb.y); glVertex2f(pX +3/ CELL_SIZE_PX, y2 - fenceY);
glTexCoord2f(UVb.x, UVb.y); glVertex2f(pX +3/ CELL_SIZE_PX, y2);
glEnd;
end
else
begin //Vertical
TKMRender.BindTexture(gGFXData[rxGui,texID].Tex.TexID);
heightInPx := Round(CELL_SIZE_PX * (1 + (gTerrain.LandExt^[pY,pX].RenderHeight - gTerrain.LandExt^[pY+1,pX].RenderHeight)/CELL_HEIGHT_DIV)+FO);
UVa.X := gGFXData[rxGui, texID].Tex.u1;
UVa.Y := gGFXData[rxGui, texID].Tex.v1;
UVb.X := gGFXData[rxGui, texID].Tex.u2;
UVb.Y := Mix(gGFXData[rxGui, texID].Tex.v2, gGFXData[rxGui, texID].Tex.v1, heightInPx / gGFXData[rxGui, texID].pxHeight);
y1 := pY - 1 - (gTerrain.LandExt^[pY, pX].RenderHeight + FO + VO) / CELL_HEIGHT_DIV;
y2 := pY - (gTerrain.LandExt^[pY + 1, pX].RenderHeight + VO) / CELL_HEIGHT_DIV;
fenceX := gGFXData[rxGui,texID].PxWidth / CELL_SIZE_PX;
case Pos of
dirW: x1 := pX - 1 - 3 / CELL_SIZE_PX;
dirE: x1 := pX - 1 + 3 / CELL_SIZE_PX - fenceX;
else x1 := pX - 1; //Should never happen
end;
glBegin(GL_QUADS);
glTexCoord2f(UVa.x, UVa.y); glVertex2f(x1, y1);
glTexCoord2f(UVb.x, UVa.y); glVertex2f(x1+ fenceX, y1);
glTexCoord2f(UVb.x, UVb.y); glVertex2f(x1+ fenceX, y2);
glTexCoord2f(UVa.x, UVb.y); glVertex2f(x1, y2);
glEnd;
end;
end;
procedure TKMRenderTerrain.RenderMarkup(pX, pY: Word; aFieldType: TKMFieldType);
const
MarkupTex: array [TKMFieldType] of Word = (0, 105, 107, 108, 0);
var
ID: Integer;
UVa,UVb: TKMPointF;
begin
ID := MarkupTex[aFieldType];
TKMRender.BindTexture(gGFXData[rxGui, ID].Tex.TexID);
UVa.X := gGFXData[rxGui, ID].Tex.u1;
UVa.Y := gGFXData[rxGui, ID].Tex.v1;
UVb.X := gGFXData[rxGui, ID].Tex.u2;
UVb.Y := gGFXData[rxGui, ID].Tex.v2;
glBegin(GL_QUADS);
glTexCoord2f(UVb.x, UVa.y); glVertex2f(pX-1, pY-1 - gTerrain.LandExt^[pY ,pX ].RenderHeight/CELL_HEIGHT_DIV+0.10);
glTexCoord2f(UVa.x, UVa.y); glVertex2f(pX-1, pY-1 - gTerrain.LandExt^[pY ,pX ].RenderHeight/CELL_HEIGHT_DIV-0.15);
glTexCoord2f(UVa.x, UVb.y); glVertex2f(pX , pY - gTerrain.LandExt^[pY+1,pX+1].RenderHeight/CELL_HEIGHT_DIV-0.25);
glTexCoord2f(UVb.x, UVb.y); glVertex2f(pX , pY - gTerrain.LandExt^[pY+1,pX+1].RenderHeight/CELL_HEIGHT_DIV);
glTexCoord2f(UVb.x, UVa.y); glVertex2f(pX-1, pY - gTerrain.LandExt^[pY+1,pX ].RenderHeight/CELL_HEIGHT_DIV);
glTexCoord2f(UVa.x, UVa.y); glVertex2f(pX-1, pY - gTerrain.LandExt^[pY+1,pX ].RenderHeight/CELL_HEIGHT_DIV-0.25);
glTexCoord2f(UVa.x, UVb.y); glVertex2f(pX , pY-1 - gTerrain.LandExt^[pY ,pX+1].RenderHeight/CELL_HEIGHT_DIV-0.15);
glTexCoord2f(UVb.x, UVb.y); glVertex2f(pX , pY-1 - gTerrain.LandExt^[pY ,pX+1].RenderHeight/CELL_HEIGHT_DIV+0.10);
glEnd;
end;
end.