program Gen2App;

uses
  Windows,
  Messages,
  Types,
  SysUtils,
  Gen2,
  G2SceneGraph,
  G2Particles3D,
  G2Math,
  DXTypes,
  Direct3D9,
  D3DX9,
  DirectInput;

{$R *.res}

type
  TBuilding = class;
  CBuilding = class of TBuilding;
  TImpactType = (itBlood, itSpark, itBang);

  TParticleBillboard = class (TG2Particle3DBillboard)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
  public
    var t: Single;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    constructor Create; override;
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleAxis = class (TG2Particle3DBillboardAxis)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
  public
    var t: Single;
    var Dir: TG2Vec3;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    constructor Create; override;
    procedure Update; override;
    procedure OnUpdate; virtual;
    procedure OnDie; virtual;
  end;

  TParticleFixed = class (TG2Particle3DFixed)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
    var m_Scale: Single;
  public
    var t: Single;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    property Scale: Single read m_Scale write m_Scale;
    constructor Create; override;
    procedure SetDir(const DirZ: TG2Vec3; const Ang: Single = 0);
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleRadialFlareFixed = class (TParticleFixed)
  public
    var Ang: Single;
    var FlareSize: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleRadialFlare = class (TParticleBillboard)
  public
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleMuzzleFlash = class (TParticleAxis)
  public
    var MuzzleFlashScale: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleBullet = class (TParticleAxis)
  strict protected
  public
    var BulletSrc: TG2Vec3;
    var BulletDst: TG2Vec3;
    var BulletScale: Single;
    var ImpactType: TImpactType;
    constructor Create; override;
    procedure OnUpdate; override;
    procedure OnDie; override;
    procedure AdjustDuration;
  end;

  TParticleBlood = class (TParticleBillboard)
  public
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleFlare = class (TParticleBillboard)
  public
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleFireball = class (TParticleBillboard)
  public
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TResources = record
  public
    var Transform: TG2Mat;
  end;
  PResources = ^TResources;

  TFaction = record
  public
    var ID: Integer;
    var Funds: Integer;
    var Color: TG2Vec3;
    var Units: TG2QuickList;
    var Buildings: TG2QuickList;
    procedure DoAI;
    procedure AIBuild(const DstBuild: CBuilding);
  end;

  PWaypoint = ^TWaypoint;
  TWaypoint = record
  public
    var Pos: TG2Vec3;
    var GridX: Integer;
    var GridY: Integer;
    var GridZ: Integer;
    var Links: TG2QuickList;
    var Traced: Boolean;
    var IsOccupied: Boolean;
    var FetchID: Int64;
    var StepPrev, StepNext: PWaypoint;
  end;

  TPath = record
  public
    var CurNode: Integer;
    var Nodes: array of PWaypoint;
    var NodeCount: Integer;
    procedure Allocate(const Amount: Integer);
  end;
  PPath = ^TPath;

  TCharState = (csIdle, csMove);

  TTargetType = (ttNone, ttUnit, ttBuilding);

  TChar = class (TG2SGChar)
  public
    var Faction: Integer;
    var State: TCharState;
    var Scale: Single;
    var Ang: Single;
    var Pos: TG2Vec3;
    var LastMoveDir: TG2Vec2;
    var MoveTarget: TG2Vec3;
    var MoveTargetReachTime: DWord;
    var AITime: DWord;
    var AttackTarget: TObject;
    var Path: TPath;
    var Speed: Single;
    var Range: Single;
    var Power: Integer;
    var ShootTime: DWord;
    var ShootInterval: DWord;
    var GridX, GridY, GridZ: Integer;
    var IsInGrid: Boolean;
    var PushCount: Integer;
    var Suspend: Boolean;
    var SuspendTime: DWord;
    var SuspendDur: DWord;
    var AnimBias: DWord;
    var Health: Integer;
    var HealthMax: Integer;
    var HealthBarHeight: Single;
    var Mass: Integer;
    var AttackerList: TG2QuickList;
    var Dead: Boolean;
    var DeadTime: DWord;
    var DieDur: DWord;
    var CanFight: Boolean;
    class function GetCost: Integer; virtual;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure Update; override;
    procedure OnUpdate; virtual;
    procedure AdjustTransform; virtual;
    procedure MoveTo(const NewPos: TG2Vec3);
    procedure UpdateGrid;
    procedure ResolvePushing(const co: TChar);
    procedure DoSuspend(const Duration: DWord);
    procedure SetFaction(const FactionID: Integer);
    procedure DoAI;
    procedure OnAI; virtual;
    procedure Die;
    procedure SetAttackTarget(const NewTarget: TObject);
    procedure Damage(const Amount: Integer);
    function CanShoot: Boolean;
    procedure OnDie; virtual;
  end;

  TSoldier = class (TChar)
  public
    var GunID: Integer;
    class function GetCost: Integer; override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure OnUpdate; override;
    procedure AdjustTransform; override;
    procedure Shoot(const Target: TObject);
  end;

  TVehicle = class (TChar)
  public
    var mx, my, mz: TG2Vec3;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure OnUpdate; override;
    procedure AdjustTransform; override;
    procedure OnDie; override;
  end;

  TTank = class (TVehicle)
  public
    var TurretID: Integer;
    var BarrelID: Integer;
    var GunID: Integer;
    class function GetCost: Integer; override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure OnUpdate; override;
    procedure Shoot(const Target: TObject);
  end;

  THarvester = class (TVehicle)
  public
    var Collecting: Boolean;
    var Fill: Integer;
    class function GetCost: Integer; override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure OnUpdate; override;
    procedure OnAI; override;
  end;

  TBuilding = class
  strict private
    var IsPlaced: Boolean;
    procedure SetPos(const Value: TG2Vec3);
    function GetPos: TG2Vec3;
    procedure SetScale(const Value: Single);
    function GetScale: Single;
  strict protected
    procedure AdjustScale;
  public
    class function GetSize: TPoint; virtual;
    class function GetCost: Integer; virtual;
    var Faction: Integer;
    var BoundsID: Integer;
    var Center: TG2Vec3;
    var Size: TPoint;
    var Transform: TG2Mat;
    var Mesh: TG2Mesh;
    var MeshInst: TG2MeshInst;
    var PlacePos: TPoint;
    var Height: Single;
    var OccupiedWaypoints: TG2QuickList;
    var Health: Integer;
    var HealthMax: Integer;
    var BuildExtend: Boolean;
    var BuildExtendRadius: Single;
    var AttackerList: TG2QuickList;
    property Pos: TG2Vec3 read GetPos write SetPos;
    property Scale: Single read GetScale write SetScale;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Render;
    procedure Update;
    procedure Place;
    procedure UnPlace;
    procedure CollideWithChars;
    procedure OnSelect; virtual;
    procedure SetFaction(const FactionID: Integer);
    procedure Damage(const Amount: Integer);
  end;

  TBConstruction = class (TBuilding)
  public
    class function GetSize: TPoint; override;
    class function GetCost: Integer; override;
    constructor Create; override;
    destructor Destroy; override;
    procedure OnSelect; override;
    procedure BtnBuildBConstruction;
    procedure BtnBuildBInfantry;
    procedure BtnBuildBFactoryHeavy;
    procedure BtnBuildBHarvester;
  end;

  TBInfantry = class (TBuilding)
  strict private
    var OutputID: Integer;
  public
    class function GetSize: TPoint; override;
    class function GetCost: Integer; override;
    constructor Create; override;
    destructor Destroy; override;
    procedure OnSelect; override;
    procedure BtnBuildSoldier;
  end;

  TBFactoryHeavy = class (TBuilding)
  strict private
    var OutputID: Integer;
  public
    class function GetSize: TPoint; override;
    class function GetCost: Integer; override;
    constructor Create; override;
    destructor Destroy; override;
    procedure OnSelect; override;
    procedure BtnBuildTank;
  end;

  TBHarvester = class (TBuilding)
  public
    var InputID: Integer;
    class function GetSize: TPoint; override;
    class function GetCost: Integer; override;
    constructor Create; override;
    destructor Destroy; override;
    procedure OnSelect; override;
    procedure BtnBuildHarvester;
  end;

  TWaypoints = record
  strict private
    var FetchList: TG2QuickSortList;
  public
    var Offset: TG2Vec3;
    var CellSize: Single;
    var Waypoints: TG2QuickList;
    var Grid: array of array of array of TG2QuickList;
    var FetchID: Int64;
    procedure Initialize;
    procedure Finalize;
    procedure ResetFetchID;
    function AddWaypoint(const Pos: TG2Vec3): PWaypoint;
    function GetGridPos(const Pos: TG2Vec3; var gx, gy, gz: Integer): Boolean;
    procedure Render(const FocusPoint: TG2Vec3);
    function GetWaypointAt(const Pos: TG2Vec3; const Threshold: Single; const Unoccupied: Boolean = False): PWaypoint;
    function FindPath(const w0, w1: PWaypoint; const Path: PPath): Boolean;
  end;

  TCharGrid = record
  public
    var Grid: array of array of array of TG2QuickList;
    var SizeX, SizeY, SizeZ: Integer;
    var CellSize: TG2Vec3;
    var Pos: TG2Vec3;
    procedure Initialize;
    procedure Finalzie;
  end;

  TProj = record
  strict private
    const ProjWidth = 2500;
    const ProjHeight = 2500;
    var x: Single;
    var y: Single;
  public
    var PixelWidth: Single;
    var PixelHeight: Single;
    var Tex: TG2Texture2DRT;
    var VPT: TG2Mat;
    procedure UpdateMatrices(const Pos: TG2Vec3);
    procedure Initialize;
    procedure Finalize;
    procedure RenderBegin;
    procedure RenderEnd;
    function Pos3DToProj(const Pos: TG2Vec3): TG2Vec2;
  end;

  TSelectionType = (stNone, stUnit, stBuilding);
  TSelectAction = (saIdle, saBuild);
  TMouseDownState = (mdsField, mdsActions);

  TPanelCell = record
  public
    var Image: TG2Texture2D;
    var Proc: TG2ProcObj;
    var Enabled: Boolean;
  end;
  PPanelCell = ^TPanelCell;

  TPanelActions = record
  public
    var r: TG2Rect;
    var sr: TG2Rect;
    var CellList: array of PPanelCell;
    var CellCount: Integer;
    var Cells: array of array of TPanelCell;
    var CellsX, CellsY: Integer;
    var CellSize: TG2Vec2;
    procedure Initialize;
    procedure Finalize;
    procedure Render;
    procedure Clear;
    procedure Select;
    procedure Add(const Image: TG2Texture2D; const Proc: TG2ProcObj);
    function PtInCell(const Pt: TPoint): Integer;
  end;

  TPanelRadar = record
  public
    var r: TG2Rect;
    var sr: TG2Rect;
    procedure Initialize;
    procedure Finalize;
    procedure Render;
  end;

  TPanelResources = record
  public
    var r: TG2Rect;
    var sr: TG2Rect;
    procedure Initialize;
    procedure Finalize;
    procedure Render;
  end;

  TBuildData = record
  public
    var BuildCheckNodes: array of array of Boolean;
    var BuildCheckX: Single;
    var BuildCheckZ: Single;
    var BuildChecked: Boolean;
    var BuildCheckResult: Boolean;
    var BuildingClass: CBuilding;
    var Size: TPoint;
    var Faction: Integer;
    var Cost: Integer;
    procedure Build;
    function CanBuild(const Pos: TG2Vec3): Boolean;
  end;

  TMenuButton = record
  public
    var Texture: TG2Texture2D;
    var Proc: TG2ProcObj;
  end;

  TMenu = record
  public
    var Buttons: array of TMenuButton;
    function McInButton(const Index: Integer): Boolean;
    procedure AddButton(const Texture: TG2Texture2D; const Proc: TG2ProcObj = nil);
    procedure Render;
    procedure Click;
  end;

  TGameState = (gsLoad, gsMenu, gsGame);

  TMyApp = class (TG2App)
  public
    var GameState: TGameState;
    var Font1: TG2Font;
    var MgrMeshes: TG2MeshMgr;
    var MgrTextures: TG2TextureMgr;
    var MgrSurfaces: TG2SurfaceMgr;
    var MgrEffects: TG2EffectMgr;
    var Scene: TG2SceneGraph;
    var QueryRender: PG2SGQuery;
    var QueryUpdate: PG2SGQuery;
    var Shaders: TG2Effect;
    var ShaderUtils: TG2Effect;
    var PostProcess: TG2PostProcess;
    var TexLogoIGDC: TG2Texture2D;
    var TexCursor: TG2Texture2D;
    var TexDepthRT: TG2Texture2DRT;
    var SurfDepthDS: TG2SurfaceDS;
    var TexPanelActions: TG2Texture2D;
    var TexPanelRadar: TG2Texture2D;
    var TexPanelCell: TG2Texture2D;
    var TexPanelResources: TG2Texture2D;
    var TexIconBConstruction: TG2Texture2D;
    var TexIconBInfantry: TG2Texture2D;
    var TexIconBFactoryHeavy: TG2Texture2D;
    var TexIconBHarvester: TG2Texture2D;
    var TexIconSoldier: TG2Texture2D;
    var TexIconTank01: TG2Texture2D;
    var TexIconHarvester: TG2Texture2D;
    var TexBlack: TG2Texture2D;
    var TexMuzzleFlash: TG2Texture2D;
    var TexBlood: TG2Texture2D;
    var TexFireball: TG2Texture2D;
    var TexFlare: TG2Texture2D;
    var TexRadialFlare: TG2Texture2D;
    var TexMenuNewGame: TG2Texture2D;
    var TexMenuQuit: TG2Texture2D;
    var MeshResources: TG2Mesh;
    var MeshInstResources: TG2MeshInst;
    var LevelMin, LevelMax: TG2Vec3;
    var GroundGeoms: TG2QuickList;
    var Waypoints: TWaypoints;
    var CharGrid: TCharGrid;
    var Selection: TG2QuickList;
    var MDPos: TPoint;
    var MDState: TMouseDownState;
    var DrawSelection: Boolean;
    var Buildings: TG2QuickList;
    var SelectionType: TSelectionType;
    var SelectAction: TSelectAction;
    var Proj: TProj;
    var PanelActions: TPanelActions;
    var PanelRadar: TPanelRadar;
    var PanelResources: TPanelResources;
    var BuildData: TBuildData;
    var Factions: array of TFaction;
    var FactionCount: Integer;
    var PlayerFaction: Integer;
    var Particles: TG2Particles3D;
    var Resources: TG2QuickList;
    var Menu: TMenu;
    var AIInterval: DWord;
    var AITime: DWord;
    var AIPosArr: array of TPoint;
    var AIPosList: TG2QuickList;
    var LevelStarted: Boolean;
    procedure OnRender; override;
    procedure OnUpdate; override;
    procedure OnMouseDown(const Button: Byte); override;
    procedure OnMouseUp(const Button: Byte); override;
    procedure OnKeyDown(const Key: Byte); override;
    procedure Initialize; override;
    procedure Finalize; override;
    procedure RenderLoading;
    procedure RenderMenu;
    procedure UpdateMenu;
    procedure RenderGame;
    procedure UpdateGame;
    procedure GameOnMouseDown(const Button: Byte);
    procedure GameOnMouseUp(const Button: Byte);
    procedure GameOnKeyDown(const Key: Byte);
    procedure RenderProj;
    procedure RenderScene(const Query: PG2SGQuery);
    procedure MakeSelection(const Pt0, Pt1: TPoint);
    procedure CheckCollideChars;
    procedure CollideChars(const Char0, Char1: TChar);
    procedure Build(const Pos: TG2Vec3; const Building: CBuilding; const FactionID: Integer);
    procedure AddResources(const Pos: TG2Vec3);
    procedure LoadTextures(const MeshInst: TG2MeshInst);
    function VerticalTrace(const Pos: TG2Vec3; var Res: TG2Vec3): Boolean;
    procedure PrintInRect(const r: TG2Rect; const Text: AnsiString);
    procedure LevelStart;
    procedure LevelStop;
    procedure MenuNewGame;
    procedure MenuQuit;
  end;

var
  App: TMyApp;

const
  FactionColors: array[0..7] of TG2Vec3 = (
    (x: 1; y: 0; z: 0), (x: 0; y: 1; z: 0), (x: 0; y: 0; z: 1), (x: 1; y: 1; z: 0),
    (x: 1; y: 0; z: 1), (x: 0; y: 1; z: 1), (x: 1; y: 1; z: 1), (x: 0; y: 0; z: 0)
  );

//TParticle BEGIN
constructor TParticleBillboard.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
end;

procedure TParticleBillboard.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then Die
  else
  OnUpdate;
end;

procedure TParticleBillboard.OnUpdate;
begin

end;
//TParticle END

//TParticleAxis BEGIN
constructor TParticleAxis.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
end;

procedure TParticleAxis.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then
  begin
    Die;
    OnDie;
  end
  else
  OnUpdate;
end;

procedure TParticleAxis.OnUpdate;
begin

end;

procedure TParticleAxis.OnDie;
begin

end;
//TParticleAxis END

//TParticleFixed BEGIN
constructor TParticleFixed.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
  m_Scale := 1;
  DirX.SetValue(1, 0, 0);
  DirY.SetValue(0, 0, 1);
end;

procedure TParticleFixed.SetDir(const DirZ: TG2Vec3; const Ang: Single = 0);
  var dx, dy: TG2Vec3;
  var m: TG2Mat;
begin
  dy.SetValue(0, 1, 0);
  if Abs(DirZ.Dot(dy)) > 0.95 then
  dy.SetValue(0, 0, 1);
  dx := DirZ.Cross(dy); dx.Normalize;
  dy := -DirZ.Cross(dx); dy.Normalize;
  m.SetRotation(DirZ, Ang);
  dx := dx.Transform4x3(m);
  dy := dy.Transform4x3(m);
  DirX := dx * m_Scale;
  DirY := dy * m_Scale;
end;

procedure TParticleFixed.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then Die
  else
  OnUpdate;
end;

procedure TParticleFixed.OnUpdate;
begin

end;
//TParticleFixed END

//TParticleRadialFlareFixed BEGIN
constructor TParticleRadialFlareFixed.Create;
begin
  inherited Create;
  Texture := App.TexRadialFlare;
  Ang := G2Random2Pi;
  FlareSize := 200;
  LifeLength := 150;
end;

procedure TParticleRadialFlareFixed.OnUpdate;
begin
  Scale := FlareSize * t;
  SetDir(G2Vec3(0, 1, 0), Ang);
end;
//TParticleRadialFlareFixed END

//TParticleRadialFlare BEGIN
constructor TParticleRadialFlare.Create;
begin
  inherited Create;
  Texture := App.TexRadialFlare;
  Rotation := G2Random2Pi;
  LifeLength := 150;
  BlendMode.SetAdd;
end;

procedure TParticleRadialFlare.OnUpdate;
  //var c: Byte;
begin
  Scale := t * 200;
  //c := Trunc(Sin((1 - t) * HalfPi) * 255);
  //Color := G2Color(c, c, c);
end;
//TParticleRadialFlare END

//TParticleMuzzleFlash BEGIN
constructor TParticleMuzzleFlash.Create;
begin
  inherited Create;
  Texture := App.TexMuzzleFlash;
  LifeLength := 200;
  BlendMode.SetAdd;
  MuzzleFlashScale := 1;
end;

procedure TParticleMuzzleFlash.OnUpdate;
  var g: Single;
begin
  g := (1 - t);
  PosDst := PosSrc + Dir * (g * 20 * MuzzleFlashScale);
  Size := g * 10 * MuzzleFlashScale;
end;
//TParticleMuzzleFlash END

//TParticleBullet BEGIN
constructor TParticleBullet.Create;
begin
  inherited Create;
  Texture := App.TexMuzzleFlash;
  BlendMode.SetAdd;
  BulletScale := 1;
  ImpactType := itBlood;
end;

procedure TParticleBullet.OnUpdate;
begin
  Texture := App.TexMuzzleFlash;
  PosDst := G2LerpVec3(BulletSrc, BulletDst, t);
  PosSrc := PosDst + Dir * (30 * BulletScale);
  Size := 5 * BulletScale;
end;

procedure TParticleBullet.OnDie;
  var p0: TParticleBlood;
  var p1: TParticleFlare;
  var p2: TParticleFireball;
begin
  case ImpactType of
    itBlood:
    begin
      p0 := TParticleBlood.Create;
      p0.Pos := PosSrc;
      App.Particles.AddParticle(p0);
    end;
    itSpark:
    begin
      p1 := TParticleFlare.Create;
      p1.Pos := PosSrc;
      App.Particles.AddParticle(p1);
    end;
    itBang:
    begin
      p2 := TParticleFireball.Create;
      p2.Pos := PosSrc;
      App.Particles.AddParticle(p2);
    end;
  end;
end;

procedure TParticleBullet.AdjustDuration;
begin
  LifeLength := Round((BulletSrc - BulletDst).Len);
end;
//TParticleBullet END

//TParticleBlood BEGIN
constructor TParticleBlood.Create;
begin
  inherited Create;
  Texture := App.TexBlood;
  Rotation := G2Random2Pi;
  LifeLength := 400;
  BlendMode.SetNormal;
end;

procedure TParticleBlood.OnUpdate;
  var c: TG2Color;
begin
  Scale := (0.1 + t * 0.9) * 40;
  c := Color;
  c.a := Trunc(Sin((1 - t) * HalfPi) * 255);
  Color := c;
end;
//TParticleBlood END

//TParticleFlare BEGIN
constructor TParticleFlare.Create;
begin
  inherited Create;
  Texture := App.TexFlare;
  Rotation := G2Random2Pi;
  LifeLength := 100;
  BlendMode.SetAdd;
end;

procedure TParticleFlare.OnUpdate;
  var c: Byte;
begin
  Scale := (0.1 + t * 0.9) * 50;
  c := Trunc(Sin((1 - t) * HalfPi) * 255);
  Color := G2Color(c, c, c);
end;
//TParticleFlare END

//TParticleFireball BEGIN
constructor TParticleFireball.Create;
begin
  inherited Create;
  Texture := App.TexFireball;
  Rotation := G2Random2Pi;
  LifeLength := 100;
  BlendMode.SetAdd;
end;

procedure TParticleFireball.OnUpdate;
  var c: Byte;
begin
  Scale := (0.1 + t * 0.9) * 100;
  c := Trunc(Sin((1 - t) * HalfPi) * 255);
  Color := G2Color(c, c, c);
end;
//TParticleFireball END

//TFaction BEGIN
procedure TFaction.DoAI;
  var i: Integer;
  var IDBConstruction: Integer;
  var IDBHarvester: Integer;
  var IDBFactoryHeavy: Integer;
  var IDBInfantry: Integer;
  var HarvesterCount: Integer;
  var SoldierCount: Integer;
  var TankCount: Integer;
begin
  IDBConstruction := -1;
  IDBHarvester := -1;
  IDBFactoryHeavy := -1;
  IDBInfantry := -1;
  HarvesterCount := 0;
  for i := 0 to Buildings.Count - 1 do
  begin
    if TBuilding(Buildings[i]) is TBConstruction then
    IDBConstruction := i
    else if TBuilding(Buildings[i]) is TBHarvester then
    IDBHarvester := i
    else if TBuilding(Buildings[i]) is TBFactoryHeavy then
    IDBFactoryHeavy := i
    else if TBuilding(Buildings[i]) is TBInfantry then
    IDBInfantry := i;
  end;
  for i := 0 to Units.Count - 1 do
  begin
    if TChar(Units[i]) is THarvester then
    Inc(HarvesterCount)
    else if TChar(Units[i]) is TSoldier then
    Inc(SoldierCount)
    else if TChar(Units[i]) is TTank then
    Inc(TankCount);
  end;
  if (IDBConstruction > -1) then
  begin
    if (IDBHarvester = -1) then
    begin
      if Funds >= TBHarvester.GetCost then
      AIBuild(TBHarvester);
      Exit;
    end;
    if (HarvesterCount < 3) then
    begin
      if (Funds >= THarvester.GetCost) then
      TBHarvester(Buildings[IDBHarvester]).BtnBuildHarvester;
      Exit;
    end;
    if (IDBInfantry = -1) then
    begin
      if (Funds >= TBInfantry.GetCost) then
      AIBuild(TBInfantry);
      Exit;
    end;
    if SoldierCount < 6 then
    begin
      if Funds >= TSoldier.GetCost then
      TBInfantry(Buildings[IDBInfantry]).BtnBuildSoldier;
      Exit;
    end;
    if (HarvesterCount < 6) then
    begin
      if (Funds >= THarvester.GetCost) then
      TBHarvester(Buildings[IDBHarvester]).BtnBuildHarvester;
      Exit;
    end;
    if (IDBFactoryHeavy = -1) then
    begin
      if (Funds >= TBFactoryHeavy.GetCost) then
      AIBuild(TBFactoryHeavy);
      Exit;
    end;
    if TankCount < 4 then
    begin
      if Funds >= TTank.GetCost then
      TBFactoryHeavy(Buildings[IDBFactoryHeavy]).BtnBuildTank;
      Exit;
    end;
    if SoldierCount < 20 then
    begin
      if Funds >= TSoldier.GetCost then
      TBInfantry(Buildings[IDBInfantry]).BtnBuildSoldier;
      Exit;
    end;
    if TankCount < 16 then
    begin
      if Funds >= TTank.GetCost then
      TBFactoryHeavy(Buildings[IDBFactoryHeavy]).BtnBuildTank;
      Exit;
    end;
    if SoldierCount < 60 then
    begin
      if Funds >= TSoldier.GetCost then
      TBInfantry(Buildings[IDBInfantry]).BtnBuildSoldier;
      Exit;
    end;
  end;
end;

procedure TFaction.AIBuild(const DstBuild: CBuilding);
  var i, j, n, p, MinX, MinZ, MaxX, MaxZ: Integer;
  var v3, Origin: TG2Vec3;
  var b: TBuilding;
  var bd: TBuildData;
  var pt: PPoint;
begin
  bd.BuildingClass := DstBuild;
  bd.Size := DstBuild.GetSize;
  bd.Faction := ID;
  bd.Cost := DstBuild.GetCost;
  bd.BuildChecked := False;
  p := 0;
  for n := 0 to Buildings.Count - 1 do
  if TBuilding(Buildings[n]).BuildExtend then
  begin
    b := TBuilding(Buildings[n]);
    Origin := b.Center + G2Vec3(0, 5, 0);
    MinX := Trunc((Origin.x - b.BuildExtendRadius) / App.Waypoints.CellSize);
    MinZ := Trunc((Origin.z - b.BuildExtendRadius) / App.Waypoints.CellSize);
    MaxX := Trunc((Origin.x + b.BuildExtendRadius) / App.Waypoints.CellSize);
    MaxZ := Trunc((Origin.z + b.BuildExtendRadius) / App.Waypoints.CellSize);
    for i := MinX to MaxX do
    for j := MinZ to MaxZ do
    begin
      if p > High(App.AIPosArr) then
      SetLength(App.AIPosArr, Length(App.AIPosArr) + 100);
      App.AIPosArr[p] := Point(i, j);
      Inc(p);
    end;
  end;
  App.AIPosList.Clear;
  for i := 0 to p - 1 do
  App.AIPosList.Add(@App.AIPosArr[i]);
  while App.AIPosList.Count > 0 do
  begin
    n := Random(App.AIPosList.Count);
    pt := PPoint(App.AIPosList[n]);
    App.AIPosList.Delete(n);
    v3 := G2Vec3(pt^.X * App.Waypoints.CellSize, Origin.y, pt^.Y * App.Waypoints.CellSize);
    if App.VerticalTrace(v3, v3) then
    begin
      if bd.CanBuild(v3) then
      begin
        App.Build(v3, bd.BuildingClass, ID);
        Funds := Funds - bd.Cost;
        Exit;
      end;
    end;
  end;
end;
//TFaction END

//TPath BEGIN
procedure TPath.Allocate(const Amount: Integer);
begin
  if Length(Nodes) < Amount then
  SetLength(Nodes, Amount);
end;
//TPath END

//TChar BEGIN
class function TChar.GetCost: Integer;
begin
  Result := 0;
end;

constructor TChar.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Faction := -1;
  Collide := True;
  Render := True;
  Radius := 12;
  Step.SetValue(0, -Radius * 1.5, 0);
  Scale := 0.2;
  HealthBarHeight := Radius * 2;
  Ang := 0;
  Path.NodeCount := 0;
  State := csIdle;
  Speed := 2;
  Range := 300;
  Power := 5;
  GridX := -1;
  GridY := -1;
  GridZ := -1;
  IsInGrid := False;
  PushCount := 0;
  Suspend := False;
  HealthMax := 100;
  Health := HealthMax;
  AnimBias := Random(10000);
  Mass := 1;
  AITime := App.AppTime;
  ShootTime := App.AppTime;
  ShootInterval := 1000;
  DieDur := 1000;
  CanFight := False;
  AttackerList.Clear;
end;

destructor TChar.Destroy;
  var i: Integer;
begin
  if Faction > -1 then
  App.Factions[Faction].Units.Remove(Self);
  App.Selection.Remove(Self);
  if IsInGrid then
  App.CharGrid.Grid[GridX, GridY, GridZ].Remove(Self);
  for i := 0 to AttackerList.Count - 1 do
  TChar(AttackerList[i]).AttackTarget := nil;
  AttackerList.Clear;
  inherited Destroy;
end;

procedure TChar.Update;
  var n: TG2Vec2;
begin
  if Health <= 0 then Die;
  Pos := Transform.GetTranslation - Step;
  if not Dead then
  begin
    if Suspend and (App.AppTime - SuspendTime > SuspendDur) then
    begin
      Suspend := False;
      MoveTargetReachTime := App.AppTime;
    end;
    if App.AppTime - AITime > 1000 then
    begin
      AITime := App.AppTime;
      DoAI;
    end;
  end;
  OnUpdate;
  Pos := Pos + G2Vec3(0, -4, 0);
  AdjustTransform;
  UpdateGrid;
  inherited Update;
  if Dead and (App.AppTime - DeadTime > DieDur) then
  begin
    OnDie;
    Free;
  end;
end;

procedure TChar.OnUpdate;
begin

end;

procedure TChar.AdjustTransform;
begin
  Transform.SetTranslation(Pos + Step);
end;

procedure TChar.MoveTo(const NewPos: TG2Vec3);
  var w0, w1: PWaypoint;
  var i: Integer;
  var p: TG2Vec3;
begin
  SetAttackTarget(nil);
  w0 := App.Waypoints.GetWaypointAt(Pos, 80, True);
  w1 := App.Waypoints.GetWaypointAt(NewPos, 80, True);
  if w1 = nil then
  begin
    State := csIdle;
    Exit;
  end;
  if ((NewPos - Pos).Len > App.Waypoints.CellSize)
  and (w0 <> nil) and (w1 <> nil) and (w0 <> w1)
  and App.Waypoints.FindPath(w0, w1, @Path) then
  begin
    p := Pos + Step;
    for i := 0 to Path.NodeCount - 1 do
    if (p - PWaypoint(Path.Nodes[i])^.Pos).Len < App.Waypoints.CellSize then
    Inc(Path.CurNode)
    else
    Break;
  end
  else
  begin
    Path.CurNode := 0;
    Path.NodeCount := 0;
  end;
  MoveTarget := NewPos;
  State := csMove;
  MoveTargetReachTime := App.AppTime;
  PushCount := 0;
end;

procedure TChar.UpdateGrid;
  var NewX, NewY, NewZ: Integer;
begin
  NewX := Trunc((Pos.x - App.CharGrid.Pos.x) / App.CharGrid.CellSize.x);
  NewY := Trunc((Pos.y - App.CharGrid.Pos.y) / App.CharGrid.CellSize.y);
  NewZ := Trunc((Pos.z - App.CharGrid.Pos.z) / App.CharGrid.CellSize.z);
  if (NewX <> GridX) or (NewY <> GridY) or (NewZ <> GridZ) then
  begin
    if IsInGrid then
    App.CharGrid.Grid[GridX, GridY, GridZ].Remove(Self);
    GridX := NewX; GridY := NewY; GridZ := NewZ;
    IsInGrid := (
      (GridX >= 0) and (GridX < App.CharGrid.SizeX)
      and (GridY >= 0) and (GridY < App.CharGrid.SizeY)
      and (GridZ >= 0) and (GridZ < App.CharGrid.SizeZ)
    );
    if IsInGrid then
    App.CharGrid.Grid[GridX, GridY, GridZ].Add(Self);
  end;
end;

procedure TChar.ResolvePushing(const co: TChar);
begin
  if (State = csMove) then
  begin
    if ((Transform.GetTranslation - MoveTarget).Len < 50) then
    begin
      if PushCount > 10 then
      begin
        PushCount := 0;
        State := csIdle;
      end
      else
      Inc(PushCount);
    end
    else if (not co.Suspend)
    and not (co.State = csIdle)
    and (Mass <= co.Mass)
    and (LastMoveDir.Dot(co.LastMoveDir) < 0) then
    begin
      if PushCount > 10 then
      begin
        PushCount := 0;
        DoSuspend(400 + Random(600));
      end
      else
      Inc(PushCount);
    end;
  end;
end;

procedure TChar.DoSuspend(const Duration: DWord);
begin
  Suspend := True;
  SuspendTime := App.AppTime;
  SuspendDur := Duration;
end;

procedure TChar.SetFaction(const FactionID: Integer);
begin
  if Faction = FactionID then Exit;
  if Faction > -1 then
  App.Factions[Faction].Units.Remove(Self);
  Faction := FactionID;
  App.Factions[Faction].Units.Add(Self);
end;

procedure TChar.DoAI;
  var i, x, y, z, MinX, MinY, MinZ, MaxX, MaxY, MaxZ, r: Integer;
  var c: TChar;
  var b: TBuilding;
  var md, d: Single;
  var NewAttackTarget: TObject;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d0: Single;
  var Ray: TG2Ray;
begin
  if (State = csIdle) and CanFight then
  begin
    if (AttackTarget <> nil) and ((Pos - TChar(AttackTarget).Pos).Len > Range) then AttackTarget := nil;
    NewAttackTarget := AttackTarget;
    if  (NewAttackTarget = nil) then
    begin
      r := Round(Range + 100);
      MinX := Trunc((Pos.x - App.CharGrid.Pos.x - r) / App.CharGrid.CellSize.x); if MinX < 0 then MinX := 0;
      MinY := Trunc((Pos.y - App.CharGrid.Pos.y - r) / App.CharGrid.CellSize.y); if MinY < 0 then MinY := 0;
      MinZ := Trunc((Pos.z - App.CharGrid.Pos.z - r) / App.CharGrid.CellSize.z); if MinZ < 0 then MinZ := 0;
      MaxX := Trunc((Pos.x - App.CharGrid.Pos.x + r) / App.CharGrid.CellSize.x); if MaxX > App.CharGrid.SizeX - 1 then MaxX := App.CharGrid.SizeX - 1;
      MaxY := Trunc((Pos.y - App.CharGrid.Pos.y + r) / App.CharGrid.CellSize.y); if MaxY > App.CharGrid.SizeY - 1 then MaxY := App.CharGrid.SizeY - 1;
      MaxZ := Trunc((Pos.z - App.CharGrid.Pos.z + r) / App.CharGrid.CellSize.z); if MaxZ > App.CharGrid.SizeZ - 1 then MaxZ := App.CharGrid.SizeZ - 1;
      for x := MinX to MaxX do
      for y := MinY to MaxY do
      for z := MinZ to MaxZ do
      for i := 0 to App.CharGrid.Grid[x, y, z].Count - 1 do
      if TChar(App.CharGrid.Grid[x, y, z][i]).Faction <> Faction then
      begin
        c := TChar(App.CharGrid.Grid[x, y, z][i]);
        d := (c.Pos - Pos).Len;
        if d <= Range then
        begin
          Ray.Origin := Pos;
          Ray.Dir := (c.Pos - Pos).Normalized;
          if not App.Scene.CollideRay(Ray, GeomID, FaceID, u, v, d0) then d0 := Range + 1;
          if (d0 > d)
          and (
            (NewAttackTarget = nil)
            or (d < md)
          ) then
          begin
            md := d;
            NewAttackTarget := c;
          end;
        end;
      end;
      if NewAttackTarget = nil then
      begin
        for i := 0 to App.Buildings.Count - 1 do
        begin
          b := TBuilding(App.Buildings[i]);
          if b.Faction <> Faction then
          begin
            d := (b.Center - Pos).Len;
            if (d <= Range)
            and (
              (NewAttackTarget = nil)
              or (d < md)
            ) then
            begin
              md := d;
              NewAttackTarget := b;
            end;
          end;
        end;
      end;
      SetAttackTarget(NewAttackTarget);
    end;
  end;
  OnAI;
end;

procedure TChar.OnAI;
begin

end;

procedure TChar.Die;
begin
  if Dead then Exit;
  Dead := True;
  DeadTime := App.AppTime;
end;

procedure TChar.SetAttackTarget(const NewTarget: TObject);
begin
  if AttackTarget = NewTarget then Exit;
  if AttackTarget <> nil then
  begin
    if AttackTarget is TChar then
    TChar(AttackTarget).AttackerList.Remove(Self)
    else if AttackTarget is TBuilding then
    TBuilding(AttackTarget).AttackerList.Remove(Self);
  end;
  AttackTarget := NewTarget;
  if AttackTarget <> nil then
  begin
    if AttackTarget is TChar then
    TChar(AttackTarget).AttackerList.Add(Self)
    else if AttackTarget is TBuilding then
    TBuilding(AttackTarget).AttackerList.Add(Self);
  end;
end;

procedure TChar.Damage(const Amount: Integer);
begin
  Health := Health - Amount;
  if Health < 0 then Health := 0;
end;

function TChar.CanShoot: Boolean;
begin
  Result := App.AppTime - ShootTime > ShootInterval;
end;

procedure TChar.OnDie;
begin

end;
//TChar END

//TSoldier BEGIN
class function TSoldier.GetCost: Integer;
begin
  Result := 50;
end;

constructor TSoldier.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Scale := 0.2;
  Radius := 12;
  Step.SetValue(0, -Radius - 4, 0);
  HealthBarHeight := Radius * 2 + 8;
  Mass := 1;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Soldier01');
  GunID := Mesh.NodeIndex('Gun01');
  DieDur := 700;
  CanFight := True;
  HealthMax := 80; Health := HealthMax;
  Power := 7;
end;

destructor TSoldier.Destroy;
begin
  inherited Destroy;
end;

procedure TSoldier.OnUpdate;
  var n: TG2Vec2;
  var t: Single;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d, l: Single;
  var r: TG2Ray;
begin
  if Dead then
  begin
    t := (App.AppTime - DeadTime) / DieDur;
    if t > 1 then t := 1;
    MeshInst.FrameSet('Die', t * (Mesh.Anims[Mesh.AnimIndex('Die')].NodeCount - 1));
  end
  else if not Suspend then
  case State of
    csIdle:
    begin
      if (AttackTarget <> nil) then
      begin
        if (AttackTarget is TChar) then
        begin
          l := (TChar(AttackTarget).Pos - Pos).Len;
          if (l < Range) then
          begin
            r.Origin := Pos;
            r.Dir := (TChar(AttackTarget).Pos - Pos).Normalized;
            if not App.Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
            d := Range + 1;
            if d > l then
            begin
              Ang := -G2Vec2AngleOX(G2Vec2(Pos.x, Pos.z), G2Vec2(TChar(AttackTarget).Pos.x, TChar(AttackTarget).Pos.z)) - HalfPi;
              if CanShoot then
              begin
                TChar(AttackTarget).Damage(Power);
                Shoot(AttackTarget);
              end;
            end
            else
            SetAttackTarget(nil);
          end
          else
          SetAttackTarget(nil);
        end
        else if (AttackTarget is TBuilding) then
        begin
          l := (TBuilding(AttackTarget).Center - Pos).Len;
          if (l < Range) then
          begin
            Ang := -G2Vec2AngleOX(G2Vec2(Pos.x, Pos.z), G2Vec2(TBuilding(AttackTarget).Center.x, TBuilding(AttackTarget).Center.z)) - HalfPi;
            if CanShoot then
            begin
              TBuilding(AttackTarget).Damage(Power);
              Shoot(AttackTarget);
            end;
          end
          else
          SetAttackTarget(nil);
        end;
        MeshInst.FrameSet('Aim', 0);
      end
      else
      MeshInst.FrameSet('Idle', G2TimeInterval(5000, App.AppTime + AnimBias) * Mesh.Anims[Mesh.AnimIndex('Idle')].FrameCount);
    end;
    csMove:
    begin
      if ((MoveTarget - Step) - Pos).Len < App.Waypoints.CellSize then
      Path.NodeCount := 0;
      if Path.CurNode >= Path.NodeCount then
      begin
        n := G2Vec2(MoveTarget.x, MoveTarget.z) - G2Vec2(Pos.x, Pos.z);
        if n.Len < Speed then
        begin
          Pos.x := MoveTarget.x;
          Pos.z := MoveTarget.z;
          State := csIdle;
          MoveTargetReachTime := App.AppTime;
        end
        else
        begin
          n.Normalize;
          LastMoveDir := n;
          n := n * Speed;
          Pos.x := Pos.x + n.x;
          Pos.z := Pos.z + n.y;
          Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
        end;
        if (App.AppTime - MoveTargetReachTime) > 2000 then
        State := csIdle;
      end
      else
      begin
        n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        if (Path.CurNode < Path.NodeCount - 2)
        and (n.Len > (G2Vec2(Path.Nodes[Path.CurNode + 1].Pos.x, Path.Nodes[Path.CurNode + 1].Pos.z) - G2Vec2(Pos.x, Pos.z)).Len) then
        begin
          Inc(Path.CurNode);
          n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        end;
        if n.Len < Speed * 10 then
        begin
          Inc(Path.CurNode);
          MoveTargetReachTime := App.AppTime;
        end;
        n.Normalize;
        LastMoveDir := n;
        n := n * Speed;
        Pos.x := Pos.x + n.x;
        Pos.z := Pos.z + n.y;
        Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
      end;
      if (App.AppTime - MoveTargetReachTime) > 3000 then
      MoveTo(MoveTarget);
      MeshInst.FrameSet('Move', G2TimeInterval(1000, App.AppTime + AnimBias) * Mesh.Anims[Mesh.AnimIndex('Move')].FrameCount);
    end;
  end
  else
  begin
    MeshInst.FrameSet('Idle', G2TimeInterval(5000, App.AppTime + AnimBias) * Mesh.Anims[Mesh.AnimIndex('Idle')].FrameCount);
  end;
end;

procedure TSoldier.AdjustTransform;
begin
  Transform.SetScaling(Scale);
  Transform.RotateY(Ang);
  Transform.Translate(Pos + Step);
end;

procedure TSoldier.Shoot(const Target: TObject);
  var p: TParticleMuzzleFlash;
  var p1: TParticleBullet;
  var ShootPos, GunPos, n: TG2Vec3;
  var c: TChar;
  var b: TBuilding;
begin
  if Target is TChar then
  begin
    c := TChar(Target);
    ShootPos := c.Pos;
    n := (ShootPos - Pos).Normalized;
    GunPos := (MeshInst.NodeTransforms[GunID].TransformRen * Transform).GetTranslation;
    p := TParticleMuzzleFlash.Create;
    p.PosSrc := GunPos;
    p.Dir := n;
    p.OnUpdate;
    p1 := TParticleBullet.Create;
    p1.BulletSrc := GunPos;
    p1.BulletDst := ShootPos + G2RandomSpherePoint * 10 - n * 30;
    p1.Dir := n;
    if c is TSoldier then
    p1.ImpactType := itBlood
    else if c is TVehicle then
    p1.ImpactType := itSpark;
    p1.AdjustDuration;
    p1.OnUpdate;
    App.Particles.AddParticle(p);
    App.Particles.AddParticle(p1);
  end
  else if Target is TBuilding then
  begin
    b := TBuilding(Target);
    ShootPos := b.Center;
    n := (ShootPos - Pos).Normalized;
    GunPos := (MeshInst.NodeTransforms[GunID].TransformRen * Transform).GetTranslation;
    p := TParticleMuzzleFlash.Create;
    p.PosSrc := GunPos;
    p.Dir := n;
    p.OnUpdate;
    p1 := TParticleBullet.Create;
    p1.BulletSrc := GunPos;
    p1.BulletDst := ShootPos + G2RandomSpherePoint * 10 - n * 30;
    p1.Dir := n;
    p1.ImpactType := itSpark;
    p1.AdjustDuration;
    p1.OnUpdate;
    App.Particles.AddParticle(p);
    App.Particles.AddParticle(p1);
  end;
  ShootTime := App.AppTime;
end;
//TSoldier END

//TVehicle BEGIN
constructor TVehicle.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Radius := 22;
  Scale := 0.6;
  Step.SetValue(0, -Radius - 4, 0);
  HealthBarHeight := Radius + 8;
  Mass := 10;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Tank01');
  DieDur := 100;
  mx.SetValue(1, 0, 0);
  my.SetValue(0, 1, 0);
  mz.SetValue(0, 0, 1);
end;

destructor TVehicle.Destroy;
begin
  inherited Destroy;
end;

procedure TVehicle.OnUpdate;
  var n: TG2Vec2;
  var GeomID: Integer;
  var FaceID: Word;
  var l, u, v, d: Single;
  var r: TG2Ray;
  var AimDirX, AimDirY, AimDirZ, v3: TG2Vec3;
  var pm, m, mt: TG2Mat;
begin
  if not Dead and not Suspend then
  case State of
    csIdle:
    begin
      
    end;
    csMove:
    begin
      if ((MoveTarget - Step) - Pos).Len < App.Waypoints.CellSize then
      Path.NodeCount := 0;
      if Path.CurNode >= Path.NodeCount then
      begin
        n := G2Vec2(MoveTarget.x, MoveTarget.z) - G2Vec2(Pos.x, Pos.z);
        if n.Len < Speed then
        begin
          Pos.x := MoveTarget.x;
          Pos.z := MoveTarget.z;
          State := csIdle;
          MoveTargetReachTime := App.AppTime;
        end
        else
        begin
          n.Normalize;
          LastMoveDir := n;
          n := n * Speed;
          Pos.x := Pos.x + n.x;
          Pos.z := Pos.z + n.y;
          Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
        end;
        if (App.AppTime - MoveTargetReachTime) > 2000 then
        State := csIdle;
      end
      else
      begin
        n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        if (Path.CurNode < Path.NodeCount - 2)
        and (n.Len > (G2Vec2(Path.Nodes[Path.CurNode + 1].Pos.x, Path.Nodes[Path.CurNode + 1].Pos.z) - G2Vec2(Pos.x, Pos.z)).Len) then
        begin
          Inc(Path.CurNode);
          n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        end;
        if n.Len < Speed * 10 then
        begin
          Inc(Path.CurNode);
          MoveTargetReachTime := App.AppTime;
        end;
        n.Normalize;
        LastMoveDir := n;
        n := n * Speed;
        Pos.x := Pos.x + n.x;
        Pos.z := Pos.z + n.y;
        Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
      end;
      if (App.AppTime - MoveTargetReachTime) > 3000 then
      MoveTo(MoveTarget);
    end;
  end;
end;

procedure TVehicle.AdjustTransform;
  var vc, vx0, vx1, vz: TG2Vec3;
  var v2, v2p: TG2Vec2;
  var r: Single;
  var m: TG2Mat;
begin
  vc := Pos + G2Vec3(0, 4, 0);
  G2SinCos(Ang, v2.x, v2.y); v2p := v2.Perp;
  r := Radius * 0.4;
  vz := vc + G2Vec3(v2.x * r, 0, v2.y * r);
  r := Radius * 0.2;
  vx0 := vc + (G2Vec3(-v2.x + v2p.x, 0, -v2.y + v2p.y) * r);
  vx1 := vc + (G2Vec3(-v2.x - v2p.x, 0, -v2.y - v2p.y) * r);
  if App.VerticalTrace(vc, vc)
  and App.VerticalTrace(vz, vz)
  and App.VerticalTrace(vx0, vx0)
  and (App.VerticalTrace(vx1, vx1)) then
  begin
    r := vz.y - vc.y; if Abs(r) > 4 then vz.y := G2Sign(r) * 4 + vc.y;
    r := vx0.y - vc.y; if Abs(r) > 4 then vx0.y := G2Sign(r) * 4 + vc.y;
    r := vx1.y - vc.y; if Abs(r) > 4 then vx1.y := G2Sign(r) * 4 + vc.y;
    vc := vx1 - vx0;
    mx := vc.Normalized;
    vc := vx0 + vc * 0.5;
    mz := (vz - vc).Normalized;
    my := mz.Cross(mx).Normalized;
    m.SetValue(mx, my, mz, Pos + Step);
    Transform.SetScaling(Scale);
    Transform := Transform * m;
  end
  else
  begin
    Transform.SetRotationY(Ang);
    Transform.Translate(Pos + Step);
    Transform.GetSpaceVectors(@mx, @my, @mz, @vc);
    Transform.PreScale(Scale, Scale, Scale);
  end;
end;

procedure TVehicle.OnDie;
  var p0: TParticleRadialFlareFixed;
begin
  p0 := TParticleRadialFlareFixed.Create;
  p0.Pos := Pos;
  p0.OnUpdate;
  App.Particles.AddParticle(p0);
end;
//TVehicle END

//TTank BEGIN
class function TTank.GetCost: Integer;
begin
  Result := 300;
end;

constructor TTank.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Radius := 22;
  Scale := 0.6;
  Step.SetValue(0, -Radius - 4, 0);
  HealthBarHeight := Radius + 8;
  Mass := 10;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Tank01');
  GunID := Mesh.NodeIndex('Gun01');
  BarrelID := Mesh.NodeIndex('Barrel01');
  TurretID := Mesh.NodeIndex('Turret01');
  DieDur := 100;
  CanFight := True;
  HealthMax := 400; Health := HealthMax;
  Power := 50;
end;

destructor TTank.Destroy;
begin
  inherited Destroy;
end;

procedure TTank.OnUpdate;
  var n: TG2Vec2;
  var GeomID: Integer;
  var FaceID: Word;
  var l, u, v, d: Single;
  var r: TG2Ray;
  var AimDirX, AimDirY, AimDirZ, v3: TG2Vec3;
  var pm, m, mt: TG2Mat;
begin
  if not Dead and not Suspend then
  case State of
    csIdle:
    begin
      if (AttackTarget <> nil) then
      begin
        if AttackTarget is TChar then
        begin
          l := (TChar(AttackTarget).Pos - Pos).Len;
          if (l < Range) then
          begin
            r.Origin := Pos;
            r.Dir := (TChar(AttackTarget).Pos - Pos).Normalized;
            if not App.Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
            d := Range + 1;
            if d > l then
            begin
              mt.SetValue(mx, my, mz, G2Vec3(0, 0, 0));
              mt := mt.Inverse;
              pm := MeshInst.NodeTransforms[Mesh.Nodes[TurretID].OwnerID].TransformDef;
              AimDirY := my;
              AimDirZ := r.Dir;
              AimDirX := AimDirZ.Cross(AimDirY).Normalized;
              AimDirZ := AimDirX.Cross(AimDirY).Normalized;
              v3 := (MeshInst.NodeTransforms[TurretID].TransformDef * pm).GetTranslation;
              m.SetValue(AimDirX, AimDirY, AimDirZ, G2Vec3(0, 0, 0));
              m := m * mt;
              m.Translate(v3);
              MeshInst.NodeTransforms[TurretID].TransformRen := m;
              AimDirY := r.Dir;
              AimDirZ := AimDirX.Cross(AimDirY).Normalized;
              AimDirY := AimDirZ.Cross(AimDirX).Normalized;
              v3 := (MeshInst.NodeTransforms[BarrelID].TransformDef * MeshInst.NodeTransforms[TurretID].TransformRen).GetTranslation;
              m.SetValue(AimDirX, AimDirY, AimDirZ, G2Vec3(0, 0, 0));
              m := m * mt;
              m.Translate(v3);
              m.PreRotateY(Pi);
              MeshInst.NodeTransforms[BarrelID].TransformRen := m;
              MeshInst.NodeTransforms[GunID].TransformRen := MeshInst.NodeTransforms[GunID].TransformDef * MeshInst.NodeTransforms[BarrelID].TransformRen;
              if CanShoot then
              begin
                TChar(AttackTarget).Damage(Power);
                Shoot(AttackTarget);
              end;
            end
            else
            SetAttackTarget(nil);
          end
          else
          SetAttackTarget(nil);
        end
        else if AttackTarget is TBuilding then
        begin
          l := (TBuilding(AttackTarget).Center - Pos).Len;
          if (l < Range) then
          begin
            r.Dir := (TBuilding(AttackTarget).Center - Pos).Normalized;
            mt.SetValue(mx, my, mz, G2Vec3(0, 0, 0));
            mt := mt.Inverse;
            pm := MeshInst.NodeTransforms[Mesh.Nodes[TurretID].OwnerID].TransformDef;
            AimDirY := my;
            AimDirZ := r.Dir;
            AimDirX := AimDirZ.Cross(AimDirY).Normalized;
            AimDirZ := AimDirX.Cross(AimDirY).Normalized;
            v3 := (MeshInst.NodeTransforms[TurretID].TransformDef * pm).GetTranslation;
            m.SetValue(AimDirX, AimDirY, AimDirZ, G2Vec3(0, 0, 0));
            m := m * mt;
            m.Translate(v3);
            MeshInst.NodeTransforms[TurretID].TransformRen := m;
            AimDirY := r.Dir;
            AimDirZ := AimDirX.Cross(AimDirY).Normalized;
            AimDirY := AimDirZ.Cross(AimDirX).Normalized;
            v3 := (MeshInst.NodeTransforms[BarrelID].TransformDef * MeshInst.NodeTransforms[TurretID].TransformRen).GetTranslation;
            m.SetValue(AimDirX, AimDirY, AimDirZ, G2Vec3(0, 0, 0));
            m := m * mt;
            m.Translate(v3);
            m.PreRotateY(Pi);
            MeshInst.NodeTransforms[BarrelID].TransformRen := m;
            MeshInst.NodeTransforms[GunID].TransformRen := MeshInst.NodeTransforms[GunID].TransformDef * MeshInst.NodeTransforms[BarrelID].TransformRen;
            if CanShoot then
            begin
              TBuilding(AttackTarget).Damage(Power);
              Shoot(AttackTarget);
            end;
          end
          else
          SetAttackTarget(nil);
        end;
      end;
    end;
    csMove:
    begin
      if ((MoveTarget - Step) - Pos).Len < App.Waypoints.CellSize then
      Path.NodeCount := 0;
      if Path.CurNode >= Path.NodeCount then
      begin
        n := G2Vec2(MoveTarget.x, MoveTarget.z) - G2Vec2(Pos.x, Pos.z);
        if n.Len < Speed then
        begin
          Pos.x := MoveTarget.x;
          Pos.z := MoveTarget.z;
          State := csIdle;
          MoveTargetReachTime := App.AppTime;
        end
        else
        begin
          n.Normalize;
          LastMoveDir := n;
          n := n * Speed;
          Pos.x := Pos.x + n.x;
          Pos.z := Pos.z + n.y;
          Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
        end;
        if (App.AppTime - MoveTargetReachTime) > 2000 then
        State := csIdle;
      end
      else
      begin
        n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        if (Path.CurNode < Path.NodeCount - 2)
        and (n.Len > (G2Vec2(Path.Nodes[Path.CurNode + 1].Pos.x, Path.Nodes[Path.CurNode + 1].Pos.z) - G2Vec2(Pos.x, Pos.z)).Len) then
        begin
          Inc(Path.CurNode);
          n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        end;
        if n.Len < Speed * 10 then
        begin
          Inc(Path.CurNode);
          MoveTargetReachTime := App.AppTime;
        end;
        n.Normalize;
        LastMoveDir := n;
        n := n * Speed;
        Pos.x := Pos.x + n.x;
        Pos.z := Pos.z + n.y;
        Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
      end;
      if (App.AppTime - MoveTargetReachTime) > 3000 then
      MoveTo(MoveTarget);
    end;
  end;
end;

procedure TTank.Shoot(const Target: TObject);
  var p: TParticleMuzzleFlash;
  var p1: TParticleBullet;
  var ShootPos, GunPos, n: TG2Vec3;
  var c: TChar;
  var b: TBuilding;
begin
  if Target is TChar then
  begin
    c := TChar(Target);
    ShootPos := c.Pos;
    n := (ShootPos - Pos).Normalized;
    GunPos := (MeshInst.NodeTransforms[GunID].TransformRen * Transform).GetTranslation;
    p := TParticleMuzzleFlash.Create;
    p.PosSrc := GunPos;
    p.Dir := n;
    p.MuzzleFlashScale := 3;
    p.OnUpdate;
    p1 := TParticleBullet.Create;
    p1.BulletSrc := GunPos;
    p1.BulletDst := ShootPos + G2RandomSpherePoint * 10 - n * 60;
    p1.Dir := n;
    p1.BulletScale := 2;
    p1.ImpactType := itBang;
    p1.AdjustDuration;
    p1.OnUpdate;
    App.Particles.AddParticle(p);
    App.Particles.AddParticle(p1);
  end
  else if Target is TBuilding then
  begin
    b := TBuilding(Target);
    ShootPos := b.Center;
    n := (ShootPos - Pos).Normalized;
    GunPos := (MeshInst.NodeTransforms[GunID].TransformRen * Transform).GetTranslation;
    p := TParticleMuzzleFlash.Create;
    p.PosSrc := GunPos;
    p.Dir := n;
    p.MuzzleFlashScale := 3;
    p.OnUpdate;
    p1 := TParticleBullet.Create;
    p1.BulletSrc := GunPos;
    p1.BulletDst := ShootPos + G2RandomSpherePoint * 10 - n * 60;
    p1.Dir := n;
    p1.BulletScale := 2;
    p1.ImpactType := itBang;
    p1.AdjustDuration;
    p1.OnUpdate;
    App.Particles.AddParticle(p);
    App.Particles.AddParticle(p1);
  end;
  ShootTime := App.AppTime;
end;
//TTank END

//THarvester BEGIN
class function THarvester.GetCost: Integer;
begin
  Result := 500;
end;

constructor THarvester.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Radius := 22;
  Scale := 1.2;
  Step.SetValue(0, -Radius - 4, 0);
  HealthBarHeight := Radius + 8;
  Mass := 10;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Harvester');
  DieDur := 100;
  Collecting := False;
  Fill := 0;
  HealthMax := 500; Health := HealthMax;
end;

destructor THarvester.Destroy;
begin
  inherited Destroy;
end;

procedure THarvester.OnUpdate;
  var i: Integer;
  var n: TG2Vec2;
  var GeomID: Integer;
  var FaceID: Word;
  var l, u, v, d: Single;
  var r: TG2Ray;
  var AimDirX, AimDirY, AimDirZ, v3: TG2Vec3;
  var pm, m, mt: TG2Mat;
  var h: TBHarvester;
begin
  if not Dead and not Suspend then
  case State of
    csIdle:
    begin
      for i := 0 to App.Buildings.Count - 1 do
      if (TBuilding(App.Buildings[i]) is TBHarvester)
      and (TBuilding(App.Buildings[i]).Faction = Faction) then
      begin
        h := TBHarvester(App.Buildings[i]);
        v3 := (h.MeshInst.NodeTransforms[h.InputID].TransformRen * h.Transform).GetTranslation;
        if (v3 - Pos).Len < 50 then
        begin
          App.Factions[Faction].Funds := App.Factions[Faction].Funds + Fill div 5;
          Fill := 0;
        end;
      end;
      Collecting := False;
      if Fill < 1000 then
      for i := 0 to App.Resources.Count - 1 do
      if (PResources(App.Resources[i])^.Transform.GetTranslation - Pos).Len < 50 then
      begin
        Collecting := True;
        Inc(Fill);
      end;
    end;
    csMove:
    begin
      if ((MoveTarget - Step) - Pos).Len < App.Waypoints.CellSize then
      Path.NodeCount := 0;
      if Path.CurNode >= Path.NodeCount then
      begin
        n := G2Vec2(MoveTarget.x, MoveTarget.z) - G2Vec2(Pos.x, Pos.z);
        if n.Len < Speed then
        begin
          Pos.x := MoveTarget.x;
          Pos.z := MoveTarget.z;
          State := csIdle;
          MoveTargetReachTime := App.AppTime;
        end
        else
        begin
          n.Normalize;
          LastMoveDir := n;
          n := n * Speed;
          Pos.x := Pos.x + n.x;
          Pos.z := Pos.z + n.y;
          Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
        end;
        if (App.AppTime - MoveTargetReachTime) > 2000 then
        State := csIdle;
      end
      else
      begin
        n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        if (Path.CurNode < Path.NodeCount - 2)
        and (n.Len > (G2Vec2(Path.Nodes[Path.CurNode + 1].Pos.x, Path.Nodes[Path.CurNode + 1].Pos.z) - G2Vec2(Pos.x, Pos.z)).Len) then
        begin
          Inc(Path.CurNode);
          n := G2Vec2(Path.Nodes[Path.CurNode].Pos.x, Path.Nodes[Path.CurNode].Pos.z) - G2Vec2(Pos.x, Pos.z);
        end;
        if n.Len < Speed * 10 then
        begin
          Inc(Path.CurNode);
          MoveTargetReachTime := App.AppTime;
        end;
        n.Normalize;
        LastMoveDir := n;
        n := n * Speed;
        Pos.x := Pos.x + n.x;
        Pos.z := Pos.z + n.y;
        Ang := -G2Vec2AngleOX(G2Vec2(0, 0), n.Normalized) - HalfPi;
      end;
      if (App.AppTime - MoveTargetReachTime) > 3000 then
      MoveTo(MoveTarget);
    end;
  end;
end;

procedure THarvester.OnAI;
  var i: Integer;
  var d, md: Single;
  var ft: Boolean;
  var Target, v3: TG2Vec3;
begin
  if (State = csIdle) then
  begin
    if not Collecting
    and (Fill < 1000) then
    begin
      ft := True;
      for i := 0 to App.Resources.Count - 1 do
      begin
        v3 := PResources(App.Resources[i])^.Transform.GetTranslation;
        d := (v3 - Pos).Len;
        if ft or (d < md) then
        begin
          ft := False;
          md := d;
          Target := v3;
        end;
      end;
      if not ft then
      begin
        MoveTo(Target);
      end;
    end;
    if Fill >= 1000 then
    begin
      ft := True;
      for i := 0 to App.Buildings.Count - 1 do
      if (TBuilding(App.Buildings[i]).Faction = Faction)
      and (TBuilding(App.Buildings[i]) is TBHarvester) then
      begin
        v3 := (
          TBHarvester(App.Buildings[i]).MeshInst.NodeTransforms[TBHarvester(App.Buildings[i]).InputID].TransformRen * TBHarvester(App.Buildings[i]).Transform
        ).GetTranslation;
        d := (v3 - Pos).Len;
        if ft or (d < md) then
        begin
          ft := False;
          md := d;
          Target := v3;
        end;
      end;
      if not ft then
      begin
        MoveTo(Target);
      end;
    end;
  end;
end;
//THarvester END

//TBuilding BEGIN
procedure TBuilding.SetPos(const Value: TG2Vec3);
begin
  PG2Vec3(@Transform.e30)^ := Value;
end;

function TBuilding.GetPos: TG2Vec3;
begin
  Result := PG2Vec3(@Transform.e30)^;
end;

procedure TBuilding.SetScale(const Value: Single);
begin
  Transform.e00 := Value;
  Transform.e11 := Value;
  Transform.e22 := Value;
end;

function TBuilding.GetScale: Single;
begin
  Result := Transform.e00;
end;

procedure TBuilding.AdjustScale;
  var AABox: TG2AABox;
  var sx, sz: Single;
begin
  BoundsID := Mesh.GeomIndex('Bounds01');
  if BoundsID > -1 then
  begin
    MeshInst.GeomProperties[BoundsID].Visible := False;
    AABox := Mesh.Geoms[BoundsID].BBox.AABox;
    sx := (Size.X * App.Waypoints.CellSize) / (AABox.MaxV.x - AABox.MinV.x);
    sz := (Size.Y * App.Waypoints.CellSize) / (AABox.MaxV.z - AABox.MinV.z);
    if sx < sz then Scale := sx else Scale := sz;
    Height := (AABox.MaxV.y - AABox.MinV.y) * Scale;
  end;
end;

class function TBuilding.GetSize: TPoint;
begin
  Result := Point(1, 1);
end;

class function TBuilding.GetCost: Integer;
begin
  Result := 0;
end;

constructor TBuilding.Create;
begin
  inherited Create;
  Faction := -1;
  Size := GetSize;
  Transform.SetIdentity;
  App.Buildings.Add(Self);
  Height := 40;
  IsPlaced := False;
  OccupiedWaypoints.Clear;
  HealthMax := 100;
  Health := HealthMax;
  BuildExtend := False;
  BuildExtendRadius := 200;
  AttackerList.Clear;
end;

destructor TBuilding.Destroy;
  var i: Integer;
begin
  for i := 0 to AttackerList.Count - 1 do
  TChar(AttackerList[i]).AttackTarget := nil;
  if (App.SelectionType = stBuilding)
  and (TBuilding(App.Selection[0]) = Self) then
  begin
    App.Selection.Clear;
    App.SelectionType := stNone;
  end;
  UnPlace;
  App.Factions[Faction].Buildings.Remove(Self);
  App.Buildings.Remove(Self);
  if MeshInst <> nil then
  MeshInst.Free;
  inherited Destroy;
end;

procedure TBuilding.Render;
begin
  if App.Gfx.Transforms.Frustum.BoxInFrustum((MeshInst.BBox * Transform).AABox) then
  begin
    App.Gfx.Transforms.PushW;
    App.Gfx.Transforms.W[0] := Transform;
    App.Gfx.Transforms.V := App.Cam.View;
    App.Gfx.Transforms.P := App.Cam.Proj;
    MeshInst.Render;
    App.Gfx.Transforms.PopW;
  end;
end;

procedure TBuilding.Update;
  var p0: TParticleRadialFlareFixed;
begin
  CollideWithChars;
  if Health <= 0 then
  begin
    p0 := TParticleRadialFlareFixed.Create;
    p0.Pos := Center;
    p0.FlareSize := 300;
    p0.OnUpdate;
    App.Particles.AddParticle(p0);
    Free;
  end;
end;

procedure TBuilding.Place;
  var p, tr, tr1: TG2Vec3;
  var i, j, x, y, z: Integer;
  var ry: Single;
  var MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Integer;
  const Border = 20;
begin
  if IsPlaced then UnPlace;
  p := Pos;
  PlacePos.X := Round((p.x - App.LevelMin.x) / App.Waypoints.CellSize);
  if (PlacePos.X < 0) or (PlacePos.X + Size.X > Length(App.Waypoints.Grid)) then Exit;
  PlacePos.Y := Round((p.z - App.LevelMin.z) / App.Waypoints.CellSize);
  if (PlacePos.Y - Size.Y <= 0) or (PlacePos.Y >= Length(App.Waypoints.Grid[0][0])) then Exit;
  p.SetValue(PlacePos.X * App.Waypoints.CellSize + App.LevelMin.x, p.y + Height * 0.5, PlacePos.Y * App.Waypoints.CellSize + App.LevelMin.z);
  ry := 0;
  for i := 0 to Size.X - 1 do
  for j := 0 to Size.Y - 1 do
  begin
    tr.SetValue(p.x + App.Waypoints.CellSize * (0.5 + i), p.y, p.z - App.Waypoints.CellSize * (0.5 + j));
    if not App.VerticalTrace(tr, tr1) then tr1 := tr;
    ry := ry + tr1.y;
  end;
  ry := ry / (Size.X * Size.Y);
  p := G2Vec3(p.x, ry, p.z);
  App.Waypoints.GetGridPos(p - Border + G2Vec3(0, 0, -App.Waypoints.CellSize * Size.Y), MinX, MinY, MinZ);
  App.Waypoints.GetGridPos(p + Border + G2Vec3(App.Waypoints.CellSize * Size.X, Height, 0), MaxX, MaxY, MaxZ);
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for i := 0 to App.Waypoints.Grid[x, y, z].Count - 1 do
  if (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.x > p.x - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.y > p.y - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.z > p.z - App.Waypoints.CellSize * Size.Y - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.x < p.x + App.Waypoints.CellSize * Size.X + Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.y < p.y + Height + Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.z < p.z + Border) then
  begin
    PWaypoint(App.Waypoints.Grid[x, y, z][i])^.IsOccupied := True;
    OccupiedWaypoints.Add(App.Waypoints.Grid[x, y, z][i]);
  end;
  Pos := p;
  Center := G2Vec3(p.x + Size.X * App.Waypoints.CellSize * 0.5, p.y, p.z - Size.Y * App.Waypoints.CellSize * 0.5);
  IsPlaced := True;
end;

procedure TBuilding.UnPlace;
  var i: Integer;
begin
  if not IsPlaced then Exit;
  for i := 0 to OccupiedWaypoints.Count - 1 do
  PWaypoint(OccupiedWaypoints[i])^.IsOccupied := False;
  OccupiedWaypoints.Clear;
  IsPlaced := False;
end;

procedure TBuilding.CollideWithChars;
  var i, j, k, x, y, z, MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Integer;
  var p, cp: TG2Vec3;
  var c: TChar;
  var cyl, cyh, d, d0, d1, d2, d3: Single;
  var bpl, bph, cp2, n: TG2Vec2;
  var dh, dv: Boolean;
begin
  p := Pos;
  MinX := Trunc((p.x - App.CharGrid.Pos.x - 100) / App.CharGrid.CellSize.x); if MinX < 0 then MinX := 0;
  MinY := Trunc((p.y - App.CharGrid.Pos.y - 100) / App.CharGrid.CellSize.y); if MinY < 0 then MinY := 0;
  MinZ := Trunc((p.z - Size.Y * App.Waypoints.CellSize - App.CharGrid.Pos.z - 100) / App.CharGrid.CellSize.z); if MinZ < 0 then MinZ := 0;
  MaxX := Trunc((p.x + Size.X * App.Waypoints.CellSize - App.CharGrid.Pos.x + 100) / App.CharGrid.CellSize.x); if MaxX > App.CharGrid.SizeX - 1 then MaxX := App.CharGrid.SizeX - 1;
  MaxY := Trunc((p.y + Height - App.CharGrid.Pos.y + 100) / App.CharGrid.CellSize.y); if MaxY > App.CharGrid.SizeY - 1 then MaxY := App.CharGrid.SizeY - 1;
  MaxZ := Trunc((p.z - App.CharGrid.Pos.z + 100) / App.CharGrid.CellSize.z); if MaxZ > App.CharGrid.SizeZ - 1 then MaxZ := App.CharGrid.SizeZ - 1;
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for k := 0 to App.CharGrid.Grid[x, y, z].Count - 1 do
  if (TG2SGChar(App.CharGrid.Grid[x, y, z][k]) is TChar) then
  begin
    c := TChar(App.CharGrid.Grid[x, y, z][k]);
    cp := c.Transform.GetTranslation;
    cyl := cp.y;
    cyh := cp.y - c.Step.y + c.Radius;
    if (cyl > p.y + Height) or (cyh < p.y) then Continue;
    cp2.SetValue(cp.x, cp.z);
    for i := 0 to 1 do
    for j := 0 to 1 do
    begin
      bpl.SetValue(p.x + i * Size.X * App.Waypoints.CellSize, p.z - j * Size.Y * App.Waypoints.CellSize);
      n := cp2 - bpl;
      d := n.Len; if d < c.Radius then
      begin
        n.Normalize;
        cp2 := cp2 + n * (c.Radius - d);
      end;
    end;
    bpl.SetValue(p.x, p.z - Size.Y * App.Waypoints.CellSize);
    bph.SetValue(p.x + Size.X * App.Waypoints.CellSize, p.z);
    if (cp2.y > bpl.y)
    and (cp2.y < bph.y) then
    begin
      d0 := bpl.x - (cp2.x + c.Radius);
      d1 := (cp2.x - c.Radius) - bph.x;
    end
    else
    begin
      d0 := 1; d1 := 1;
    end;
    if (cp2.x > bpl.x)
    and (cp2.x < bph.x) then
    begin
      d2 := bpl.y - (cp2.y + c.Radius);
      d3 := (cp2.y - c.Radius) - bph.y;
    end
    else
    begin
      d2 := 1; d3 := 1;
    end;
    dh := (d0 < 0) and (d1 < 0);
    dv := (d2 < 0) and (d3 < 0);
    if dh and dv then
    begin
      if d0 > d1 then
      begin
        if d0 > d2 then
        begin
          if d0 > d3 then
          cp2.x := bpl.x - c.Radius
          else
          cp2.y := bph.y + c.Radius
        end
        else
        begin
          if d2 > d3 then
          cp2.y := bpl.y - c.Radius
          else
          cp2.y := bph.y + c.Radius
        end;
      end
      else
      begin
        if d1 > d2 then
        begin
          if d1 > d3 then
          cp2.x := bph.x + c.Radius
          else
          cp2.y := bph.y + c.Radius
        end
        else
        begin
          if d2 > d3 then
          cp2.y := bpl.y - c.Radius
          else
          cp2.y := bph.y + c.Radius
        end;
      end;
    end
    else if dh then
    begin
      if d0 > d1 then
      cp2.x := bpl.x - c.Radius
      else
      cp2.x := bph.x + c.Radius;
    end
    else if dv then
    begin
      if d2 > d3 then
      cp2.y := bpl.y - c.Radius
      else
      cp2.y := bph.y + c.Radius;
    end;
    cp.x := cp2.x;
    cp.z := cp2.y;
    PG2Vec3(@c.Transform.e30)^ := cp;
  end;
end;

procedure TBuilding.OnSelect;
begin

end;

procedure TBuilding.SetFaction(const FactionID: Integer);
begin
  if Faction = FactionID then Exit;
  if Faction > -1 then
  App.Factions[Faction].Buildings.Remove(Self);
  Faction := FactionID;
  App.Factions[Faction].Buildings.Add(Self);
end;

procedure TBuilding.Damage(const Amount: Integer);
begin
  Health := Health - Amount;
  if Health < 0 then Health := 0;
end;
//TBuilding END

//TBConstruction BEGIN
class function TBConstruction.GetSize: TPoint;
begin
  Result := Point(3, 3);
end;

class function TBConstruction.GetCost: Integer;
begin
  Result := 3000;
end;

constructor TBConstruction.Create;
begin
  inherited Create;
  BuildExtend := True;
  BuildExtendRadius := 400;
  Mesh := App.MgrMeshes.FindMesh('BConstruction');
  MeshInst := Mesh.InstanceCreate;
  App.LoadTextures(MeshInst);
  AdjustScale;
  HealthMax := 1000; Health := HealthMax;
end;

destructor TBConstruction.Destroy;
begin
  inherited Destroy;
end;

procedure TBConstruction.OnSelect;
begin
  App.PanelActions.Clear;
  App.PanelActions.Add(App.TexIconBConstruction, BtnBuildBConstruction);
  App.PanelActions.Add(App.TexIconBHarvester, BtnBuildBHarvester);
  App.PanelActions.Add(App.TexIconBInfantry, BtnBuildBInfantry);
  App.PanelActions.Add(App.TexIconBFactoryHeavy, BtnBuildBFactoryHeavy);
end;

procedure TBConstruction.BtnBuildBConstruction;
begin
  App.SelectAction := saBuild;
  App.BuildData.BuildingClass := TBConstruction;
  App.BuildData.Size := App.BuildData.BuildingClass.GetSize;
  App.BuildData.Faction := Faction;
  App.BuildData.Cost := App.BuildData.BuildingClass.GetCost;
  App.BuildData.BuildChecked := False;
end;

procedure TBConstruction.BtnBuildBInfantry;
begin
  App.SelectAction := saBuild;
  App.BuildData.BuildingClass := TBInfantry;
  App.BuildData.Size := App.BuildData.BuildingClass.GetSize;
  App.BuildData.Faction := Faction;
  App.BuildData.Cost := App.BuildData.BuildingClass.GetCost;
  App.BuildData.BuildChecked := False;
end;

procedure TBConstruction.BtnBuildBFactoryHeavy;
begin
  App.SelectAction := saBuild;
  App.BuildData.BuildingClass := TBFactoryHeavy;
  App.BuildData.Size := App.BuildData.BuildingClass.GetSize;
  App.BuildData.Faction := Faction;
  App.BuildData.Cost := App.BuildData.BuildingClass.GetCost;
  App.BuildData.BuildChecked := False;
end;

procedure TBConstruction.BtnBuildBHarvester;
begin
  App.SelectAction := saBuild;
  App.BuildData.BuildingClass := TBHarvester;
  App.BuildData.Size := App.BuildData.BuildingClass.GetSize;
  App.BuildData.Faction := Faction;
  App.BuildData.Cost := App.BuildData.BuildingClass.GetCost;
  App.BuildData.BuildChecked := False;
end;
//TBConstruction END

//TBInfantry BEGIN
class function TBInfantry.GetSize: TPoint;
begin
  Result := Point(3, 3);
end;

class function TBInfantry.GetCost: Integer;
begin
  Result := 1000;
end;

constructor TBInfantry.Create;
begin
  inherited Create;
  Size := GetSize;
  BuildExtend := True;
  BuildExtendRadius := 280;
  Mesh := App.MgrMeshes.FindMesh('BInfantry');
  MeshInst := Mesh.InstanceCreate;
  OutputID := Mesh.NodeIndex('Output01');
  App.LoadTextures(MeshInst);
  AdjustScale;
  HealthMax := 300; Health := HealthMax;
end;

destructor TBInfantry.Destroy;
begin
  inherited Destroy;
end;

procedure TBInfantry.OnSelect;
begin
  App.PanelActions.Clear;
  App.PanelActions.Add(App.TexIconSoldier, BtnBuildSoldier);
end;

procedure TBInfantry.BtnBuildSoldier;
  var c: TChar;
  var v3: TG2Vec3;
  var v2: TG2Vec2;
begin
  if App.Factions[Faction].Funds >= TSoldier.GetCost then
  begin
    c := TSoldier.Create(App.Scene);
    c.SetFaction(Faction);
    v3 := (MeshInst.NodeTransforms[OutputID].TransformRen * Transform).GetTranslation;
    v2 := G2RandomCirclePoint * ((10 + Random(91)) * 0.1);
    v3.x := v3.x + v2.x; v3.z := v3.z + v2.y;
    c.Transform.SetTranslation(v3);
    App.Factions[Faction].Funds := App.Factions[Faction].Funds - TSoldier.GetCost;
  end;
end;
//TBInfantry END

//TBFactoryHeavy BEGIN
class function TBFactoryHeavy.GetSize: TPoint;
begin
  Result := Point(3, 3);
end;

class function TBFactoryHeavy.GetCost: Integer;
begin
  Result := 1000;
end;

constructor TBFactoryHeavy.Create;
begin
  inherited Create;
  Size := GetSize;
  BuildExtend := True;
  BuildExtendRadius := 280;
  Mesh := App.MgrMeshes.FindMesh('BFactoryHeavy');
  MeshInst := Mesh.InstanceCreate;
  OutputID := Mesh.NodeIndex('Output01');
  App.LoadTextures(MeshInst);
  AdjustScale;
  HealthMax := 500; Health := HealthMax;
end;

destructor TBFactoryHeavy.Destroy;
begin
  inherited Destroy;
end;

procedure TBFactoryHeavy.OnSelect;
begin
  App.PanelActions.Clear;
  App.PanelActions.Add(App.TexIconTank01, BtnBuildTank);
end;

procedure TBFactoryHeavy.BtnBuildTank;
  var c: TChar;
  var v3: TG2Vec3;
  var v2: TG2Vec2;
begin
  if App.Factions[Faction].Funds >= TTank.GetCost then
  begin
    c := TTank.Create(App.Scene);
    c.SetFaction(Faction);
    v3 := (MeshInst.NodeTransforms[OutputID].TransformRen * Transform).GetTranslation;
    v2 := G2RandomCirclePoint * ((10 + Random(91)) * 0.1);
    v3.x := v3.x + v2.x; v3.z := v3.z + v2.y;
    c.Transform.SetTranslation(v3);
    App.Factions[Faction].Funds := App.Factions[Faction].Funds - TTank.GetCost;
  end;
end;
//TBFactoryHeavy END

//TBHarvester BEGIN
class function TBHarvester.GetSize: TPoint;
begin
  Result := Point(3, 3);
end;

class function TBHarvester.GetCost: Integer;
begin
  Result := 1000;
end;

constructor TBHarvester.Create;
begin
  inherited Create;
  Size := GetSize;
  BuildExtend := True;
  BuildExtendRadius := 280;
  Mesh := App.MgrMeshes.FindMesh('BHarvester');
  MeshInst := Mesh.InstanceCreate;
  InputID := Mesh.NodeIndex('Input01');
  App.LoadTextures(MeshInst);
  AdjustScale;
  HealthMax := 800; Health := HealthMax;
end;

destructor TBHarvester.Destroy;
begin
  inherited Destroy;
end;

procedure TBHarvester.OnSelect;
begin
  App.PanelActions.Clear;
  App.PanelActions.Add(App.TexIconHarvester, BtnBuildHarvester);
end;

procedure TBHarvester.BtnBuildHarvester;
  var c: TChar;
  var v3: TG2Vec3;
  var v2: TG2Vec2;
begin
  if App.Factions[Faction].Funds >= THarvester.GetCost then
  begin
    c := THarvester.Create(App.Scene);
    c.SetFaction(Faction);
    v3 := (MeshInst.NodeTransforms[InputID].TransformRen * Transform).GetTranslation;
    v2 := G2RandomCirclePoint * ((10 + Random(91)) * 0.1);
    v3.x := v3.x + v2.x; v3.z := v3.z + v2.y;
    c.Transform.SetTranslation(v3);
    App.Factions[Faction].Funds := App.Factions[Faction].Funds - THarvester.GetCost;
  end;
end;
//TBHarvester END

//TWaypoints BEGIN
procedure TWaypoints.Initialize;
  const Dirs: array[0..3] of TG2Vec3 = (
    (x: -1; y: 0; z: 0), (x: 0; y: 0; z: 1), (x: 1; y: 0; z: 0), (x: 0; y: 0; z: -1)
  );
  var c: TG2SGChar;
  var g: TG2SGGeom;
  var r: TG2Ray;
  var TraceWaypoints: TG2QuickList;
  var FaceID: Word;
  var u, v, d: Single;
  var w: PWaypoint;
  var p, WTarget, vel: TG2Vec3;
  var Origin: PWaypoint;
  procedure AdjustPoint(var Pt: TG2Vec3);
    var n: Integer;
  begin
    c.Transform.SetTranslation(Pt);
    c.Transform.SetTranslation(c.Transform.GetTranslation + c.Step * 0.1);
    c.Grounded := False;
    n := 0;
    while not c.Grounded and (n < 20) do
    begin
      App.Scene.CollideChar(c, App.QueryUpdate);
      c.Transform.SetTranslation(c.Transform.GetTranslation + c.Step * 0.1);
      Inc(n);
    end;
    Pt := c.Transform.GetTranslation;
  end;
  function WaypointsLinked(const w0, w1: PWaypoint): Boolean;
    var i: Integer;
  begin
    for i := 0 to w0^.Links.Count - 1 do
    if PWaypoint(w0^.Links[i]) = w1 then
    begin
      Result := True;
      Exit;
    end;
    Result := False;
  end;
  function Trace(const Pt0, Pt1: TG2Vec3): Boolean;
    var p, vel: TG2Vec3;
    var i: Integer;
  begin
    p := Pt0;
    vel := (Pt1 - Pt0); vel.y := 0; vel := vel.Normalized * CellSize * 0.1;
    for i := 0 to 20 do
    begin
      p := p + vel;
      AdjustPoint(p);
      if (p - Pt1).Len < CellSize * 0.1 then
      begin
        p := Pt1;
        Break;
      end
      else
      begin
        vel := (Pt1 - Pt0); vel.y := 0; vel := vel.Normalized * CellSize * 0.1;
      end;
    end;
    if (p - Pt1).Len > CellSize * 0.1 then
    begin
      Result := False;
      Exit;
    end;
    p := Pt1;
    vel := (Pt0 - Pt1); vel.y := 0; vel := vel.Normalized * CellSize * 0.1;
    for i := 0 to 30 do
    begin
      p := p + vel;
      AdjustPoint(p);
      if (p - Pt0).Len < CellSize * 0.1 then
      begin
        p := Pt0;
        Break;
      end
      else
      begin
        vel := (Pt0 - Pt1); vel.y := 0; vel := vel.Normalized * CellSize * 0.1;
      end;
    end;
    if (p - Pt0).Len > CellSize * 0.1 then
    begin
      Result := False;
      Exit;
    end;
    Result := True;
  end;
  var i, j, t, gx, gy, gz, lx, ly, lz, hx, hy, hz: Integer;
begin
  Waypoints.Clear;
  Origin := nil;
  CellSize := 50;
  gx := Trunc((App.LevelMax.x - App.LevelMin.x) / CellSize);
  gy := Trunc((App.LevelMax.y - App.LevelMin.y) / CellSize);
  gz := Trunc((App.LevelMax.z - App.LevelMin.z) / CellSize);
  SetLength(Grid, gx, gy, gz);
  c := TG2SGChar.Create(App.Scene);
  c.Transform.SetIdentity;
  c.Radius := CellSize * 0.4;
  c.Step.SetValue(0, -c.Radius * 1.2, 0);
  TraceWaypoints.Clear;
  r.Origin := App.Scene.FindNode('Trace01').Transform.GetTranslation;
  r.Origin.y := App.LevelMax.y;
  r.Dir.SetValue(0, -1, 0);
  for i := 0 to App.GroundGeoms.Count - 1 do
  begin
    g := TG2SGGeom(App.GroundGeoms[i]);
    if g.IntersectRay(r, FaceID, u, v, d) then
    begin
      p := r.Origin + r.Dir * d;
      AdjustPoint(p);
      AddWaypoint(p);
      if Waypoints.Count > 0 then
      begin
        Origin := PWaypoint(Waypoints[0]);
        Break;
      end;
    end;
  end;
  Offset := (r.Origin - App.LevelMin);
  Offset.x := Offset.x - Trunc(Offset.x / CellSize) * CellSize;
  Offset.y := Offset.y - Trunc(Offset.y / CellSize) * CellSize;
  Offset.z := Offset.z - Trunc(Offset.z / CellSize) * CellSize;
  for gx := 0 to Trunc((App.LevelMax.x - App.LevelMin.x) / CellSize) do
  for gz := 0 to Trunc((App.LevelMax.z - App.LevelMin.z) / CellSize) do
  begin
   r.Origin.x := App.LevelMin.x + gx * CellSize + Offset.x;
   r.Origin.z := App.LevelMin.z + gz * CellSize + Offset.z;
   for i := 0 to App.GroundGeoms.Count - 1 do
    begin
      g := TG2SGGeom(App.GroundGeoms[i]);
      if g.IntersectRay(r, FaceID, u, v, d) then
      begin
        p := r.Origin + r.Dir * d;
        if g.Collider.Faces[FaceID].Plane.N.Dot(G2Vec3(0, 1, 0)) > 0.85 then
        begin
          AdjustPoint(p);
          AddWaypoint(p);
        end;
      end;
    end;
  end;
  if Origin = nil then
  Origin := PWaypoint(Waypoints[Waypoints.Count div 2]);
  Origin^.Traced := True;
  TraceWaypoints.Add(Origin);
  while TraceWaypoints.Count > 0 do
  begin
    w := PWaypoint(TraceWaypoints.Pop);
    lx := Trunc((w^.Pos.x - App.LevelMin.x - CellSize * 2) / CellSize); if lx < 0 then lx := 0;
    ly := Trunc((w^.Pos.y - App.LevelMin.y - CellSize * 2) / CellSize); if ly < 0 then ly := 0;
    lz := Trunc((w^.Pos.z - App.LevelMin.z - CellSize * 2) / CellSize); if lz < 0 then lz := 0;
    hx := Trunc((w^.Pos.x - App.LevelMin.x + CellSize * 2) / CellSize); if hx > High(Grid) then hx := High(Grid);
    hy := Trunc((w^.Pos.y - App.LevelMin.y + CellSize * 2) / CellSize); if hy > High(Grid[0]) then hy := High(Grid[0]);
    hz := Trunc((w^.Pos.z - App.LevelMin.z + CellSize * 2) / CellSize); if hz > High(Grid[0][0]) then hz := High(Grid[0][0]);
    for gx := lx to hx do
    for gy := ly to hy do
    for gz := lz to hz do
    for i := 0 to Grid[gx, gy, gz].Count - 1 do
    if not WaypointsLinked(w, PWaypoint(Grid[gx, gy, gz][i]))
    and ((w^.Pos - PWaypoint(Grid[gx, gy, gz][i])^.Pos).Len < CellSize * 2)
    and Trace(w^.Pos, PWaypoint(Grid[gx, gy, gz][i])^.Pos) then
    begin
      w^.Links.Add(PWaypoint(Grid[gx, gy, gz][i]));
      PWaypoint(Grid[gx, gy, gz][i])^.Links.Add(w);
      if not PWaypoint(Grid[gx, gy, gz][i])^.Traced then
      begin
        PWaypoint(Grid[gx, gy, gz][i])^.Traced := True;
        TraceWaypoints.Add(PWaypoint(Grid[gx, gy, gz][i]));
      end;
    end;
  end;
  for i := 0 to Waypoints.Count - 1 do
  if PWaypoint(Waypoints[i])^.Links.Count = 0 then
  TraceWaypoints.Add(PWaypoint(Waypoints[i]));
  for i := 0 to TraceWaypoints.Count - 1 do
  begin
    w := PWaypoint(TraceWaypoints[i]);
    Grid[w^.GridX, w^.GridY, w^.GridZ].Remove(w);
    Waypoints.Remove(w);
    Dispose(w);
  end;
  TraceWaypoints.Clear;
  ResetFetchID;
  c.Free;
end;

procedure TWaypoints.Finalize;
  var i, x, y, z: Integer;
begin
  for i := 0 to Waypoints.Count - 1 do
  Dispose(PWaypoint(Waypoints[i]));
  Waypoints.Clear;
  for x := 0 to High(Grid) do
  for y := 0 to High(Grid[0]) do
  for z := 0 to High(Grid[0][0]) do
  Grid[x, y, z].Clear;
  SetLength(Grid, 0, 0, 0);
end;

procedure TWaypoints.ResetFetchID;
  var i: Integer;
begin
  FetchID := 0;
  for i := 0 to Waypoints.Count - 1 do
  PWaypoint(Waypoints[i])^.FetchID := 0;
end;

function TWaypoints.AddWaypoint(const Pos: TG2Vec3): PWaypoint;
  var i, x, y, z, gx, gy, gz, MinGX, MinGY, MinGZ, MaxGX, MaxGY, MaxGZ: Integer;
begin
  if GetGridPos(Pos, gx, gy, gz) then
  begin
    MinGX := gx - 1; if MinGX < 0 then MinGX := 0;
    MinGY := gy - 1; if MinGY < 0 then MinGY := 0;
    MinGZ := gz - 1; if MinGZ < 0 then MinGZ := 0;
    MaxGX := gx + 1; if MaxGX > High(Grid) then MaxGX := High(Grid);
    MaxGY := gy + 1; if MaxGY > High(Grid[0]) then MaxGY := High(Grid[0]);
    MaxGZ := gz + 1; if MaxGZ > High(Grid[0][0]) then MaxGZ := High(Grid[0][0]);
    for x := MinGX to MaxGX do
    for y := MinGY to MaxGY do
    for z := MinGZ to MaxGZ do
    for i := 0 to Grid[x, y, z].Count - 1 do
    if (PWaypoint(Grid[x, y, z][i])^.Pos - Pos).Len <= CellSize * 0.8 then
    begin
      Result := nil;
      Exit;
    end;
    New(Result);
    Result^.Pos := Pos;
    Result^.GridX := gx;
    Result^.GridY := gy;
    Result^.GridZ := gz;
    Result^.Links.Clear;
    Result^.Traced := False;
    Result^.IsOccupied := False;
    Waypoints.Add(Result);
    Grid[gx, gy, gz].Add(Result);
  end;
end;

function TWaypoints.GetGridPos(const Pos: TG2Vec3; var gx, gy, gz: Integer): Boolean;
begin
  gx := Trunc((Pos.x - App.LevelMin.x) / CellSize); if gx < 0 then gx := 0; if gx > High(Grid) then gx := High(Grid);
  gy := Trunc((Pos.y - App.LevelMin.y) / CellSize); if gy < 0 then gy := 0; if gy > High(Grid[0]) then gy := High(Grid[0]);
  gz := Trunc((Pos.z - App.LevelMin.z) / CellSize); if gz < 0 then gz := 0; if gz > High(Grid[0][0]) then gz := High(Grid[0][0]);
  Result := (
    (gx >= 0) and (gy >= 0) and (gz >= 0)
    and (gx <= High(Grid)) and (gy <= High(Grid[0])) and (gz <= High(Grid[0][0]))
  );
end;

procedure TWaypoints.Render(const FocusPoint: TG2Vec3);
  var MinX, MinY, MinZ: Integer;
  var MaxX, MaxY, MaxZ: Integer;
  var i, j, x, y, z: Integer;
  var GridPt: TG2Vec3;
  var w: PWaypoint;
  const RenderArea = 500;
begin
  App.Render.TextureClear();
  GridPt := FocusPoint - App.LevelMin;
  MinX := Trunc((GridPt.x - RenderArea * 0.5) / CellSize); if MinX < 0 then MinX := 0;
  MinY := Trunc((GridPt.y - RenderArea * 0.5) / CellSize); if MinY < 0 then MinY := 0;
  MinZ := Trunc((GridPt.z - RenderArea * 0.5) / CellSize); if MinZ < 0 then MinZ := 0;
  MaxX := Trunc((GridPt.x + RenderArea * 0.5) / CellSize); if MaxX > High(Grid) then MaxX := High(Grid);
  MaxY := Trunc((GridPt.y + RenderArea * 0.5) / CellSize); if MaxY > High(Grid[0]) then MaxY := High(Grid[0]);
  MaxZ := Trunc((GridPt.z + RenderArea * 0.5) / CellSize); if MaxZ > High(Grid[0][0]) then MaxZ := High(Grid[0][0]);
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for i := 0 to Grid[x, y, z].Count - 1 do
  begin
    w := PWaypoint(Grid[x, y, z][i]);
    if w^.IsOccupied then
    App.Prim3D.DrawSphere(w^.Pos.x, w^.Pos.y + CellSize * 0.05, w^.Pos.z, CellSize * 0.25, $80808000, 4, 4)
    else
    App.Prim3D.DrawSphere(w^.Pos.x, w^.Pos.y + CellSize * 0.05, w^.Pos.z, CellSize * 0.25, $80ff0000, 4, 4);
  end;
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for i := 0 to Grid[x, y, z].Count - 1 do
  begin
    w := PWaypoint(Grid[x, y, z][i]);
    for j := 0 to w^.Links.Count - 1 do
    App.Prim3D.DrawLine(w^.Pos + G2Vec3(0, CellSize * 0.05, 0), PWaypoint(w^.Links[j])^.Pos + G2Vec3(0, CellSize * 0.05, 0), $ff0000ff);
  end;
end;

function TWaypoints.GetWaypointAt(const Pos: TG2Vec3; const Threshold: Single; const Unoccupied: Boolean = False): PWaypoint;
  var MinX, MinY, MinZ: Integer;
  var MaxX, MaxY, MaxZ: Integer;
  var GridPt: TG2Vec3;
  var i, x, y, z: Integer;
  var w: PWaypoint;
  var md, d: Single;
begin
  Result := nil;
  GridPt := Pos - App.LevelMin;
  GetGridPos(Pos - Threshold, MinX, MinY, MinZ);
  GetGridPos(Pos + Threshold, MaxX, MaxY, MaxZ);
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for i := 0 to Grid[x, y, z].Count - 1 do
  begin
    w := PWaypoint(Grid[x, y, z][i]);
    if w^.IsOccupied and Unoccupied then Continue;
    d := (Pos - w^.Pos).Len;
    if d < Threshold then
    begin
      if (Result = nil)
      or (d < md) then
      begin
        md := d;
        Result := w;
      end;
    end;
  end;
end;

function TWaypoints.FindPath(const w0, w1: PWaypoint; const Path: PPath): Boolean;
  var w: PWaypoint;
  var i, c: Integer;
begin
  if FetchID >= High(Int64) then ResetFetchID;
  Inc(FetchID);
  FetchList.Clear;
  FetchList.Add(w0, -(w0^.Pos - w1^.Pos).LenSq);
  w0^.FetchID := FetchID;
  while FetchList.Count > 0 do
  begin
    w := PWaypoint(FetchList.Pop);
    if w = w1 then
    begin
      Result := True;
      c := 1;
      while w <> w0 do
      begin
        w^.StepPrev^.StepNext := w;
        w := w^.StepPrev;
        Inc(c);
      end;
      Path.Allocate(c);
      Path.NodeCount := c;
      Path.CurNode := 0;
      for i := 0 to c - 1 do
      begin
        Path.Nodes[i] := w;
        w := w^.StepNext;
      end;
      Exit;
    end
    else
    begin
      for i := 0 to w^.Links.Count - 1 do
      if not PWaypoint(w^.Links[i])^.IsOccupied
      and (PWaypoint(w^.Links[i])^.FetchID < FetchID) then
      begin
        PWaypoint(w^.Links[i])^.FetchID := FetchID;
        PWaypoint(w^.Links[i])^.StepPrev := w;
        FetchList.Add(w^.Links[i], -(PWaypoint(w^.Links[i])^.Pos - w1^.Pos).LenSq);
      end;
    end;
  end;
  Result := False;
end;
//TWaypoints END

//TCharGrid BEGIN
procedure TCharGrid.Initialize;
  const CharSize = 50;
  var x, y, z: Integer;
begin
  SizeX := Round((App.LevelMax.x - App.LevelMin.x) / CharSize);
  SizeY := Round((App.LevelMax.y - App.LevelMin.y) / CharSize);
  SizeZ := Round((App.LevelMax.z - App.LevelMin.z) / CharSize);
  CellSize.x := (App.LevelMax.x - App.LevelMin.x) / SizeX;
  CellSize.y := (App.LevelMax.y - App.LevelMin.y) / SizeY;
  CellSize.z := (App.LevelMax.z - App.LevelMin.z) / SizeZ;
  Pos := App.LevelMin;
  SetLength(Grid, SizeX, SizeY, SizeZ);
  for x := 0 to SizeX - 1 do
  for y := 0 to SizeY - 1 do
  for z := 0 to SizeZ - 1 do
  Grid[x, y, z].Clear;
end;

procedure TCharGrid.Finalzie;
begin

end;
//TCharGrid END

//TProj BEGIN
procedure TProj.UpdateMatrices(const Pos: TG2Vec3);
  var ProjV, ProjP: TG2Mat;
begin
  x := Trunc(Pos.x / PixelWidth) * PixelWidth;
  y := Trunc(Pos.z / PixelHeight) * PixelHeight;
  ProjV.SetView(G2Vec3(x, 1000, y), G2Vec3(x, 0, y), G2Vec3(0, 0, 1));
  ProjP.SetOrthogonal(ProjWidth, ProjHeight, 1, 10000);
  VPT := ProjV * ProjP * Tex.ProjMatrix;
end;

procedure TProj.Initialize;
begin
  Tex := App.MgrTextures.CreateTexture2DRT('Proj', 1024, 1024, D3DFMT_A8R8G8B8);
  x := 0;
  y := 0;
  PixelWidth := ProjWidth / Tex.RealWidth;
  PixelHeight := ProjHeight / Tex.RealHeight;
end;

procedure TProj.Finalize;
begin
  Tex.Free;
end;

procedure TProj.RenderBegin;
begin
  App.Gfx.SetRenderTargetTexture2D(Tex);
  App.Gfx.SetDepthStencilSurface(nil);
  App.Render.Clear(False, False, True, 0);
end;

procedure TProj.RenderEnd;
begin
  App.Gfx.SetRenderTargetDefault;
  App.Gfx.SetDepthStencilDefault;
end;

function TProj.Pos3DToProj(const Pos: TG2Vec3): TG2Vec2;
begin
  Result.x := (Pos.x - x + ProjWidth * 0.5) / PixelWidth;
  Result.y := Tex.RealHeight - (Pos.z - y + ProjWidth * 0.5) / PixelHeight;
end;
//TProj END

//TPanelActions BEGIN
procedure TPanelActions.Initialize;
  var s, sx, sy: Single;
  var i, j, n: Integer;
begin
  sx := App.Gfx.Params.Width * 0.25;
  sy := App.Gfx.Params.Height * 0.3;
  if sx < sy then s := sx / App.TexPanelActions.Width
  else s := sy / App.TexPanelActions.Height;
  sx := App.TexPanelActions.Width * s;
  sy := App.TexPanelActions.Height * s;
  r.l := App.Gfx.Params.Width - sx;
  r.t := App.Gfx.Params.Height - sy;
  r.r := App.Gfx.Params.Width;
  r.b := App.Gfx.Params.Height;
  sr.l := r.l + 45 * s;
  sr.t := r.t + 56 * s;
  sr.r := r.l + 239 * s;
  sr.b := r.t + 240 * s;
  CellsX := 3;
  CellsY := 3;
  SetLength(Cells, CellsX, CellsY);
  SetLength(CellList, CellsX * CellsY);
  n := 0;
  for j := 0 to CellsY - 1 do
  for i := 0 to CellsX - 1 do
  begin
    CellList[n] := @Cells[i, j];
    Inc(n);
  end;
  CellSize.SetValue(sr.w / CellsX, sr.h / CellsY);
  Clear;
end;

procedure TPanelActions.Finalize;
begin

end;

procedure TPanelActions.Render;
  var i, j, n, Ind: Integer;
begin
  App.RenderModes.FilteringLinear();
  App.Render2D.DrawRect(
    r.x, r.y, r.w, r.h, $ffffffff,
    App.TexPanelActions
  );
  Ind := PtInCell(App.PlugInput.MousePos);
  n := 0;
  for j := 0 to CellsY - 1 do
  for i := 0 to CellsX - 1 do
  if Cells[i, j].Enabled then
  begin
    App.Render2D.DrawRect(
      sr.x + i * CellSize.x, sr.y + j * CellSize.y, CellSize.x, CellSize.y,
      $ffffffff,
      Cells[i, j].Image
    );
    if n = Ind then
    begin
      App.Render.TextureClear();
      if App.PlugInput.MouseDown[0] then
      begin
        if (App.MDState = mdsActions)
        and (PtInCell(App.MDPos) = Ind) then
        begin
          App.Prim2D.DrawRect(
            sr.x + i * CellSize.x, sr.y + j * CellSize.y,
            CellSize.x, CellSize.y,
            $88ffffff
          );
        end;
      end
      else
      begin
        App.Prim2D.DrawRect(
          sr.x + i * CellSize.x, sr.y + j * CellSize.y,
          CellSize.x, CellSize.y,
          $4400ff00
        );
      end;
    end;
    Inc(n);
  end;
  App.Render.TextureClear();
  App.Prim2D.DrawRect(
    sr.x, sr.y, sr.w, sr.h, $1100ff00
  );
  App.RenderModes.BlendModeAddColor;
  for i := 0 to CellsX - 1 do
  for j := 0 to CellsY - 1 do
  begin
    App.Render2D.DrawRect(
      sr.x + i * CellSize.x, sr.y + j * CellSize.y, CellSize.x, CellSize.y, $ff888888,
      App.TexPanelCell
    );
  end;
  App.RenderModes.BlendModeNormal;
  App.RenderModes.FilteringPoint();
end;

procedure TPanelActions.Clear;
  var i: Integer;
begin
  for i := 0 to High(CellList) do
  CellList[i]^.Enabled := False;
  CellCount := 0;
end;

procedure TPanelActions.Select;
  var Ind0, Ind1: Integer;
begin
  Ind0 := PtInCell(App.MDPos);
  Ind1 := PtInCell(App.PlugInput.MousePos);
  if (Ind0 > -1)
  and (Ind0 = Ind1) then
  begin
    if CellList[Ind0]^.Enabled
    and (Assigned(CellList[Ind0]^.Proc)) then
    CellList[Ind0]^.Proc;
  end;
end;

procedure TPanelActions.Add(const Image: TG2Texture2D; const Proc: TG2ProcObj);
begin
  if CellCount >= CellsX * CellsY then Exit;
  CellList[CellCount]^.Image := Image;
  CellList[CellCount]^.Proc := Proc;
  CellList[CellCount]^.Enabled := True;
  Inc(CellCount);
end;

function TPanelActions.PtInCell(const Pt: TPoint): Integer;
  var i, j: Integer;
begin
  Result := 0;
  for j := 0 to CellsY - 1 do
  for i := 0 to CellsX - 1 do
  begin
    if PtInRect(
      Rect(
        Round(sr.x + i * CellSize.x),
        Round(sr.y + j * CellSize.y),
        Round(sr.x + (i + 1) * CellSize.x),
        Round(sr.y + (j + 1) * CellSize.y)
      ),
      Pt
    ) then
    Exit;
    Inc(Result);
  end;
  Result := -1;
end;
//TPanelActions END

//TPanelRadar BEGIN
procedure TPanelRadar.Initialize;
  var s, sx, sy: Single;
begin
  sx := App.Gfx.Params.Width * 0.3;
  sy := App.Gfx.Params.Height * 0.35;
  if sx < sy then s := sx / App.TexPanelRadar.Width
  else s := sy / App.TexPanelRadar.Height;
  sx := App.TexPanelRadar.Width * s;
  sy := App.TexPanelRadar.Height * s;
  r.x := 0;
  r.y := App.Gfx.Params.Height - sy;
  r.w := sx;
  r.h := sy;
end;

procedure TPanelRadar.Finalize;
begin

end;

procedure TPanelRadar.Render;
begin
  App.RenderModes.FilteringLinear();
  App.Render2D.DrawRect(
    r.x, r.y, r.w, r.h, $ffffffff,
    App.TexPanelRadar
  );
  App.RenderModes.FilteringPoint();
end;
//TPanelRadar END

//TPanelResources BEGIN
procedure TPanelResources.Initialize;
  var s, sx, sy: Single;
begin
  sx := App.Gfx.Params.Width * 0.35;
  sy := App.Gfx.Params.Height * 0.08;
  if sx < sy then s := sx / App.TexPanelResources.Width
  else s := sy / App.TexPanelResources.Height;
  sx := App.TexPanelResources.Width * s;
  sy := App.TexPanelResources.Height * s;
  r.x := App.Gfx.Params.Width - sx + 1;
  r.y := -1;
  r.w := sx;
  r.h := sy;
  sr.l := r.x + 121 * s;
  sr.t := r.y + 6 * s;
  sr.r := r.x + 236 * s;
  sr.b := r.y + 27 * s;
end;

procedure TPanelResources.Finalize;
begin

end;

procedure TPanelResources.Render;
begin
  App.RenderModes.FilteringLinear();
  App.Render2D.DrawRect(
    r.x, r.y, r.w, r.h, $ffffffff,
    App.TexPanelResources
  );
  App.PrintInRect(sr, IntToStr(App.Factions[App.PlayerFaction].Funds));
  App.RenderModes.FilteringPoint();
end;
//TPanelResources END

//TBuildData BEGIN
procedure TBuildData.Build;
  var r: TG2Ray;
  var i: Integer;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
  var v3: TG2Vec3;
begin
  if (App.Factions[Faction].Funds >= Cost)
  and BuildChecked and BuildCheckResult then
  begin
    r := App.Gfx.PointToRay(App.PlugInput.MousePos);
    if App.Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
    begin
      v3 := r.Origin + r.Dir * d;
      App.Build(v3, BuildingClass, Faction);
      App.Factions[Faction].Funds := App.Factions[Faction].Funds - Cost;
    end;
  end;
end;

function TBuildData.CanBuild(const Pos: TG2Vec3): Boolean;
  var p: TG2Vec3;
  var PlacePos: TPoint;
  var x, y, z, i, j, n: Integer;
  var tr, tr1: TG2Vec3;
  var ry, s: Single;
  var MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Integer;
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
  const Border = 20;
begin
  if (Length(BuildCheckNodes) < Size.X)
  or (Length(BuildCheckNodes[0]) < Size.Y) then
  SetLength(BuildCheckNodes, Size.X, Size.Y);
  Result := False;
  BuildCheckResult := False;
  for i := 0 to Size.X - 1 do
  for j := 0 to Size.Y - 1 do
  BuildCheckNodes[i, j] := False;
  p := Pos + G2Vec3(-Size.X * App.Waypoints.CellSize * 0.5, 0, Size.Y * App.Waypoints.CellSize * 0.5);
  PlacePos.X := Round((p.x - App.LevelMin.x) / App.Waypoints.CellSize);
  if (PlacePos.X < 0) or (PlacePos.X + Size.X > Length(App.Waypoints.Grid)) then Exit;
  PlacePos.Y := Round((p.z - App.LevelMin.z) / App.Waypoints.CellSize);
  if (PlacePos.Y - Size.Y <= 0) or (PlacePos.Y >= Length(App.Waypoints.Grid[0][0])) then Exit;
  BuildCheckX := PlacePos.X * App.Waypoints.CellSize + App.LevelMin.x;
  BuildCheckZ := PlacePos.Y * App.Waypoints.CellSize + App.LevelMin.z;
  p.SetValue(BuildCheckX, p.y + 50, BuildCheckZ);
  ry := 0;
  for i := 0 to Size.X - 1 do
  for j := 0 to Size.Y - 1 do
  begin
    tr.SetValue(p.x + App.Waypoints.CellSize * (0.5 + i), p.y, p.z - App.Waypoints.CellSize * (0.5 + j));
    r.Origin := tr;
    r.Dir.SetValue(0, -1, 0);
    if App.Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
    begin
      tr1 := r.Origin + r.Dir * d;
      if TG2SGGeom(App.Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N.Dot(G2Vec3(0, 1, 0)) < 0.95 then
      Continue;
    end
    else
    begin
      tr1 := tr;
      Continue;
    end;
    ry := ry + tr1.y;
    s := p.y - tr1.y;
    if (s > 75) or (s < 25) then Continue;
    for n := 0 to App.Buildings.Count - 1 do
    if TBuilding(App.Buildings[n]).BuildExtend and
    ((G2Vec2(tr1.x, tr1.z) - G2Vec2(TBuilding(App.Buildings[n]).Center.x, TBuilding(App.Buildings[n]).Center.z)).Len <= TBuilding(App.Buildings[n]).BuildExtendRadius) then
    begin
      BuildCheckNodes[i, j] := True;
      Break;
    end;
  end;
  ry := ry / (Size.X * Size.Y);
  p := G2Vec3(p.x, ry, p.z);
  App.Waypoints.GetGridPos(p - Border + G2Vec3(0, 0, -App.Waypoints.CellSize * Size.Y), MinX, MinY, MinZ);
  App.Waypoints.GetGridPos(p + Border + G2Vec3(App.Waypoints.CellSize * Size.X, 50, 0), MaxX, MaxY, MaxZ);
  for x := MinX to MaxX do
  for y := MinY to MaxY do
  for z := MinZ to MaxZ do
  for i := 0 to App.Waypoints.Grid[x, y, z].Count - 1 do
  if (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.x > p.x - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.y > p.y - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.z > p.z - App.Waypoints.CellSize * Size.Y - Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.x < p.x + App.Waypoints.CellSize * Size.X + Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.y < p.y + 50 + Border)
  and (PWaypoint(App.Waypoints.Grid[x, y, z][i])^.Pos.z < p.z + Border) then
  if PWaypoint(App.Waypoints.Grid[x, y, z][i])^.IsOccupied then
  begin
    for n := 0 to Size.X - 1 do
    for j := 0 to Size.Y - 1 do
    BuildCheckNodes[n, j] := False;
    Exit;
  end;
  BuildChecked := True;
  BuildCheckResult := True;
  for n := 0 to Size.X - 1 do
  for j := 0 to Size.Y - 1 do
  BuildCheckResult := BuildCheckResult and BuildCheckNodes[n, j];
  Result := BuildCheckResult;
end;
//TBuildData END

//TMenu BEGIN
//TMenu BEGIN
function TMenu.McInButton(const Index: Integer): Boolean;
  var x, y: Integer;
begin
  x := (App.Gfx.Params.Width - 256) div 2;
  y := (App.Gfx.Params.Height - 128 * Length(Buttons)) div 2;
  Result := PtInRect(
    Rect(x, y + 128 * Index, x + 256, y + 128 * Index + 128),
    App.PlugInput.MousePos
  );
end;

procedure TMenu.AddButton(const Texture: TG2Texture2D; const Proc: TG2ProcObj = nil);
begin
  SetLength(Buttons, Length(Buttons) + 1);
  Buttons[High(Buttons)].Texture := Texture;
  Buttons[High(Buttons)].Proc := Proc;
end;

procedure TMenu.Render;
  var i, x, y: Integer;
begin
  x := (App.Gfx.Params.Width - 256) div 2;
  y := (App.Gfx.Params.Height - 128 * Length(Buttons)) div 2;
  for i := 0 to High(Buttons) do
  begin
    App.Render2D.DrawRect(
      x, y + i * 128,
      Buttons[i].Texture
    );
    if McInButton(i) then
    begin
      App.RenderModes.BlendModeAdd;
      App.Render2D.DrawRect(
        x, y + i * 128,
        Buttons[i].Texture
      );
      App.RenderModes.BlendModeNormal;
    end;
  end;
end;

procedure TMenu.Click;
  var i: Integer;
begin
  for i := 0 to High(Buttons) do
  if McInButton(i) then
  begin
    if Assigned(Buttons[i].Proc) then
    Buttons[i].Proc();
    Break;
  end;
end;
//TMenu END
//TMenu END

//TMyApp BEGIN
procedure TMyApp.OnRender;
  var Pos, v0: TG2Vec3;
  var mc, p0, p1: TG2Vec2;
  var i: Integer;
  var g: Single;
  var CamX, CamY, CamZ: TG2Vec3;
  var bw, bh: Single;
  var Col: TG2Color;
begin
  mc := PlugInput.MousePos;
  Render.Clear(False, True, True, $ff808080);
  Render.RenderStart;

  case GameState of
    gsMenu: RenderMenu;
    gsGame: RenderGame;
  end;

  if PlugInput.MouseDown[0] then
  Col := $ff0088ff
  else if PlugInput.MouseDown[1] then
  Col := $ff00ff00
  else
  Col := $ffffffff;
  Render2D.DrawRect(mc.x, mc.y, TexCursor.Width, TexCursor.Height, Col, TexCursor);
  Font1.Print(10, 10, $ff000000, 'FPS: ' + IntToStr(Tmr.FPS));
  if Gfx.Recording then
  Font1.Print(Gfx.Params.Width - 10 - Font1.GetTextWidth('* Recording *'), 10, $ffff0000, '* Recording *');

  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.OnUpdate;
begin
  case GameState of
    gsMenu: UpdateMenu;
    gsGame: UpdateGame;
  end;
end;

procedure TMyApp.OnMouseDown(const Button: Byte);
begin
  case GameState of
    gsMenu:
    begin
      Menu.Click;
    end;
    gsGame:
    begin
      GameOnMouseDown(Button);
    end;
  end;
end;

procedure TMyApp.OnMouseUp(const Button: Byte);
begin
  case GameState of
    gsGame: GameOnMouseUp(Button);
  end;
end;

procedure TMyApp.OnKeyDown(const Key: Byte);
begin
  case GameState of
    gsGame: GameOnKeyDown(Key);
  end;
  if Key = DIK_ESCAPE then Running := False;
  if Key = DIK_F12 then Gfx.SaveScreenshot('Screen.jpg');
  if Key = DIK_F11 then
  begin
    if Gfx.Recording then
    Gfx.RecStop
    else
    Gfx.RecStart('Recording.avi', 400, Round(400 * (Gfx.Params.Height / Gfx.Params.Width)), 10000);
  end;
end;

procedure TMyApp.Initialize;
  var i, j: Integer;
begin
  if Gfx.Specs.CompatiableResolutionCount > 0 then
  begin
    j := Gfx.Specs.CompatiableResolutionCount - 1;
    for i := 0 to Gfx.Specs.CompatiableResolutionCount - 1 do
    if Gfx.Specs.CompatiableResolutions[i].Width >= 1024 then
    begin
      j := i;
      Break;
    end;
    Gfx.InitParams.Width := Gfx.Specs.CompatiableResolutions[j].Width;
    Gfx.InitParams.Height := Gfx.Specs.CompatiableResolutions[j].Height;
  end
  else
  begin
    Gfx.InitParams.Width := 800;
    Gfx.InitParams.Height := 600;
  end;
  Gfx.InitParams.FullScreen := False;
  Gfx.InitParams.FormatBackBuffer := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceRT := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceDS := D3DFMT_D24S8;
  Gfx.InitParams.FormatTexture2D := D3DFMT_DXT5;
  g2.Handle := App.WindowCreate(0, 0, 'Gen2');
  Tmr.Mode := tmManual;
  Tmr.MaxFPS := 0;
  Gfx.InitParams.FullScreen := True;
  Sfx.Powered := False;
  inherited Initialize;
  ShowCursor(False);
  g2.PackLinker.LinkPack('Data.g2pk', 'Gen2 Key');
  g2.RequestMod(TG2MeshMgr, @MgrMeshes);
  g2.RequestMod(TG2TextureMgr, @MgrTextures);
  g2.RequestMod(TG2SurfaceMgr, @MgrSurfaces);
  g2.RequestMod(TG2EffectMgr, @MgrEffects);
  g2.RequestMod(TG2Particles3D, @Particles);
  Font1 := Gfx.Shared.RequestFont('Times New Roman', 12);
  TexLogoIGDC := MgrTextures.CreateTexture2DFromPack('LogoIGDC', 'Gfx', 'LogoIGDC', 1);
  RenderLoading;
  GameState := gsLoad;
  MgrMeshes.CreateMeshFromPack('BConstruction', 'Mesh', 'BConstruction');
  MgrMeshes.CreateMeshFromPack('BInfantry', 'Mesh', 'BInfantry');
  MgrMeshes.CreateMeshFromPack('BFactoryHeavy', 'Mesh', 'BFactoryHeavy');
  MgrMeshes.CreateMeshFromPack('BHarvester', 'Mesh', 'BHarvester');
  TexCursor := MgrTextures.CreateTexture2DFromPack('Cursor', 'Gfx', 'Cursor', 1);
  TexPanelActions := MgrTextures.CreateTexture2DFromPack('PanelActions', 'Gfx', 'PanelActions', 1);
  TexPanelRadar := MgrTextures.CreateTexture2DFromPack('PanelRadar', 'Gfx', 'PanelRadar', 1);
  TexPanelCell := MgrTextures.CreateTexture2DFromPack('PanelCell', 'Gfx', 'PanelCell', 1);
  TexPanelResources := MgrTextures.CreateTexture2DFromPack('PanelResources', 'Gfx', 'PanelResources', 1);
  TexIconBConstruction := MgrTextures.CreateTexture2DFromPack('IconBConstruction', 'Gfx', 'IconBConstruction', 1);
  TexIconBInfantry := MgrTextures.CreateTexture2DFromPack('IconBInfantry', 'Gfx', 'IconBInfantry', 1);
  TexIconBFactoryHeavy := MgrTextures.CreateTexture2DFromPack('IconBFactoryHeavy', 'Gfx', 'IconBFactoryHeavy', 1);
  TexIconBHarvester := MgrTextures.CreateTexture2DFromPack('IconBHarvester', 'Gfx', 'IconBHarvester', 1);
  TexIconSoldier := MgrTextures.CreateTexture2DFromPack('IconSoldier', 'Gfx', 'IconSoldier', 1);
  TexIconTank01 := MgrTextures.CreateTexture2DFromPack('IconTank01', 'Gfx', 'IconTank01', 1);
  TexIconHarvester := MgrTextures.CreateTexture2DFromPack('IconHarvester', 'Gfx', 'IconHarvester', 1);
  TexMuzzleFlash := MgrTextures.CreateTexture2DFromPack('MuzzleFlash', 'Gfx', 'MuzzleFlash', 8);
  TexBlood := MgrTextures.CreateTexture2DFromPack('Blood', 'Gfx', 'Blood', 8);
  TexFireball := MgrTextures.CreateTexture2DFromPack('Fireball', 'Gfx', 'Fireball', 8);
  TexFlare := MgrTextures.CreateTexture2DFromPack('Flare', 'Gfx', 'Flare', 8);
  TexRadialFlare := MgrTextures.CreateTexture2DFromPack('RadialFlare', 'Gfx', 'RadialFlare', 8);
  TexMenuNewGame := MgrTextures.CreateTexture2DFromPack('MenuNewGame', 'Gfx', 'MenuNewGame', 1);
  TexMenuQuit := MgrTextures.CreateTexture2DFromPack('MenuQuit', 'Gfx', 'MenuQuit', 1);
  TexBlack := MgrTextures.CreateTexture2D('Black', 4, 4, 1, 0, D3DFMT_A8R8G8B8);
  TexBlack.Surfaces[0].Lock();
  for j := 0 to TexBlack.Surfaces[0].Height - 1 do
  for i := 0 to TexBlack.Surfaces[0].Width - 1 do
  TexBlack.Surfaces[0].Pixels[i, j] := $ff000000;
  TexBlack.Surfaces[0].UnLock;
  TexDepthRT := MgrTextures.CreateTexture2DRT('DepthRT', 1024, 1024, D3DFMT_R32F);
  SurfDepthDS := MgrSurfaces.CreateDepthStencilSurface('DepthDS', TexDepthRT.RealWidth, TexDepthRT.RealHeight);
  MeshResources := MgrMeshes.CreateMeshFromPack('Resources', 'Mesh', 'Resources');
  MeshInstResources := MeshResources.InstanceCreate;
  LoadTextures(MeshInstResources);
  Shaders := MgrEffects.CreateEffectFromPack('Shaders', 'Shaders', 'Shaders');
  ShaderUtils := Gfx.ShaderLib.RequestEffect('fx_Misc');
  PostProcess := TG2PostProcess.Create(g2);
  Proj.Initialize;
  PanelActions.Initialize;
  PanelRadar.Initialize;
  PanelResources.Initialize;
  Menu.AddButton(TexMenuNewGame, MenuNewGame);
  Menu.AddButton(TexMenuQuit, MenuQuit);
  GameState := gsMenu;
  LevelStarted := False;
  //LevelStart;
  Tmr.Enabled := True;
end;

procedure TMyApp.Finalize;
begin
  if LevelStarted then
  LevelStop;
  MeshInstResources.Free;
  PanelResources.Finalize;
  PanelRadar.Finalize;
  PanelActions.Finalize;
  Proj.Finalize;
  PostProcess.Free;
  g2.ReleaseMod(@MgrEffects);
  g2.ReleaseMod(@MgrSurfaces);
  g2.ReleaseMod(@MgrTextures);
  inherited Finalize;
end;

procedure TMyApp.RenderLoading;
  var Str: AnsiString;
begin
  Render.RenderStart;
  Render.Clear(False, False, True, $ff000000);
  Render2D.DrawRect(
    (Gfx.Params.Width - TexLogoIGDC.Width) * 0.5,
    (Gfx.Params.Height - TexLogoIGDC.Height) * 0.5,
    TexLogoIGDC
  );
  Str := 'Loading...';
  Font1.Print(Gfx.Params.Width - Font1.GetTextWidth(Str), Gfx.Params.Height - Font1.GetTextHeight(Str), $ffffffff, Str);
  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.RenderMenu;
begin
  Render.Clear(False, False, True, $ff000000);
  Menu.Render;
end;

procedure TMyApp.UpdateMenu;
begin

end;

procedure TMyApp.RenderGame;
var Pos, v0: TG2Vec3;
  var mc, p0, p1: TG2Vec2;
  var i: Integer;
  var g: Single;
  var CamX, CamY, CamZ: TG2Vec3;
  var bw, bh: Single;
  var Col: TG2Color;
begin
  mc := PlugInput.MousePos;

  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyV;
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.ApplyP;

  Proj.UpdateMatrices(Cam.Target^);
  RenderProj;
  RenderScene(QueryRender);
  Particles.Render;
//  for i := 0 to Buildings.Count - 1 do
//  TBuilding(Buildings[i]).Render;
  Render.TextureClear();

  Gfx.RenderStates.ZEnable := True;
//  Pos := Ch.Transform.GetTranslation - Ch.Step;
//  Prim3D.DrawSphere(
//    Pos.x, Pos.y, Pos.z, Ch.Radius, $4400ff00
//  );
//  Waypoints.Render(Cam.Target^);
  Gfx.RenderStates.ZEnable := False;

  if DrawSelection then
  begin
    if mc.X < MDPos.X then begin p0.X := mc.X; p1.X := MDPos.X; end else begin p0.X := MDPos.X; p1.X := mc.X; end;
    if mc.Y < MDPos.Y then begin p0.Y := mc.Y; p1.Y := MDPos.Y; end else begin p0.Y := MDPos.Y; p1.Y := mc.Y; end;
    Prim2D.DrawQuad4Col(
      G2Vec2(p0.x - 4, p0.y - 4), G2Vec2(p1.x + 4, p0.y - 4),
      G2Vec2(p0.x, p0.y), G2Vec2(p1.x, p0.y),
      $ff8080ff, $228080ff,
      $ff8080ff, $228080ff
    );
    Prim2D.DrawQuad4Col(
      G2Vec2(p1.x, p0.y), G2Vec2(p1.x + 4, p0.y - 4),
      G2Vec2(p1.x, p1.y), G2Vec2(p1.x + 4, p1.y + 4),
      $228080ff, $228080ff,
      $ff8080ff, $ff8080ff
    );
    Prim2D.DrawQuad4Col(
      G2Vec2(p0.x, p1.y), G2Vec2(p1.x, p1.y),
      G2Vec2(p0.x - 4, p1.y + 4), G2Vec2(p1.x + 4, p1.y + 4),
      $228080ff, $ff8080ff,
      $228080ff, $ff8080ff
    );
    Prim2D.DrawQuad4Col(
      G2Vec2(p0.x - 4, p0.y - 4), G2Vec2(p0.x, p0.y),
      G2Vec2(p0.x - 4, p1.y + 4), G2Vec2(p0.x, p1.y + 4),
      $ff8080ff, $ff8080ff,
      $228080ff, $228080ff
    );
  end;

  if Selection.Count > 0 then
  begin
    Cam.GetViewSpaceVectors(CamX, CamY, CamZ);
    case SelectionType of
      stUnit:
      begin
        for i := 0 to Selection.Count - 1 do
        begin
          Pos := TChar(Selection[i]).Transform.GetTranslation + G2Vec3(0, TChar(Selection[i]).HealthBarHeight, 0);
          v0 := Pos + CamX * 20;
          Pos := Gfx.PointTo2D(Pos);
          v0 := Gfx.PointTo2D(v0);
          bw := (G2Vec2(Pos.x, Pos.y) - G2Vec2(v0.x, v0.y)).Len;
          bh := bw * 0.1;
          g := TChar(Selection[i]).Health / TChar(Selection[i]).HealthMax;
          Prim2D.DrawRect(Pos.x - bw * 0.5, Pos.y - bh * 0.5, bw * g, bh, $ff00ff00);
          Prim2D.DrawRect(Pos.x - bw * 0.5 + bw * g, Pos.y - bh * 0.5, bw * (1 - g), bh, $ff222222);
        end;
      end;
      stBuilding:
      begin
        Pos := (
          TBuilding(Selection[0]).Transform.GetTranslation +
          G2Vec3(
            TBuilding(Selection[0]).Size.X * Waypoints.CellSize * 0.5,
            TBuilding(Selection[0]).Height + 20,
            -TBuilding(Selection[0]).Size.Y * Waypoints.CellSize * 0.5
          )
        );
        v0 := Pos + CamX * 20;
        Pos := Gfx.PointTo2D(Pos);
        v0 := Gfx.PointTo2D(v0);
        bw := (G2Vec2(Pos.x, Pos.y) - G2Vec2(v0.x, v0.y)).Len;
        bh := bw * 0.1;
        g := TBuilding(Selection[0]).Health / TBuilding(Selection[0]).HealthMax;
        Prim2D.DrawRect(Pos.x - bw * 0.5, Pos.y - bh * 0.5, bw * g, bh, $ff00ff00);
        Prim2D.DrawRect(Pos.x - bw * 0.5 + bw * g, Pos.y - bh * 0.5, bw * (1 - g), bh, $ff222222);
      end;
    end;
  end;

  PanelRadar.Render;
  PanelActions.Render;
  PanelResources.Render;
end;

procedure TMyApp.UpdateGame;
  var mc: TPoint;
  var CamShift: TG2Vec3;
  var CamX, CamY, CamZ: TG2Vec3;
  var i: Integer;
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
  const ScreenPitch = 200;
begin
  if App.AppTime - AITime > AIInterval then
  begin
    AITime := App.AppTime;
    for i := 0 to FactionCount - 1 do
    if i <> PlayerFaction then
    Factions[i].DoAI;
  end;
  mc := PlugInput.MousePos;
  if (MDState = mdsField)
  and PlugInput.MouseDown[0] then
  begin
    if (Abs(mc.X - MDPos.X) > 4)
    and (Abs(mc.Y - MDPos.Y) > 4) then
    DrawSelection := True;
  end;
  Cam.UpdateDir := PlugInput.MouseDown[2];
  Cam.Update;
  CamShift := Cam.Target^;
  Cam.GetViewSpaceVectors(CamX, CamY, CamZ);
  CamZ := CamX.Cross(G2Vec3(0, 1, 0)); CamZ.Normalize;
  if not (PlugInput.MouseDown[0] or PlugInput.MouseDown[2]) then
  begin
    if mc.X <= 0 then CamShift := CamShift + CamX * Cam.Speed;
    if mc.X >= Gfx.Params.Width - 1 then CamShift := CamShift - CamX * Cam.Speed;
    if mc.Y <= 0 then CamShift := CamShift - CamZ * Cam.Speed;
    if mc.Y >= Gfx.Params.Height - 1 then CamShift := CamShift + CamZ * Cam.Speed;
  end;
  if CamShift.x < LevelMin.x + ScreenPitch then CamShift.x := LevelMin.x + ScreenPitch
  else
  if CamShift.x > LevelMax.x - ScreenPitch then CamShift.x := LevelMax.x - ScreenPitch;
  if CamShift.z < LevelMin.z + ScreenPitch then CamShift.z := LevelMin.z + ScreenPitch
  else
  if CamShift.z > LevelMax.z - ScreenPitch then CamShift.z := LevelMax.z - ScreenPitch;
  CamShift := CamShift - Cam.Target^;
  Cam.MoveTo(Cam.Pos^ + CamShift);
  Scene.Update(QueryUpdate);
  Particles.Update;
  CheckCollideChars;
  for i := 0 to Buildings.Count - 1 do
  TBuilding(Buildings[i]).Update;
  if (SelectionType = stBuilding)
  and (SelectAction = saBuild) then
  begin
    r := Gfx.PointToRay(mc);
    if Scene.CollideRay(r, GeomId, FaceID, u, v, d) then
    BuildData.CanBuild(r.Origin + r.Dir * d);
  end;
end;

procedure TMyApp.GameOnMouseDown(const Button: Byte);
  var r: TG2Ray;
  var i, GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
  var Dest: TG2Vec3;
  var ClipRect: TRect;
  var mc: TPoint;
begin
  case Button of
    0:
    begin
      MDPos := PlugInput.MousePos;
      if PanelActions.PtInCell(MDPos) > -1 then
      MDState := mdsActions
      else
      MDState := mdsField;
      case SelectAction of
        saIdle:
        begin

        end;
        saBuild:
        begin

        end;
      end;
    end;
    1:
    begin
      case SelectionType of
        stUnit:
        begin
          r := Gfx.PointToRay(PlugInput.MousePos);
          if Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
          begin
            if TG2SGGeom(Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N.Dot(G2Vec3(0, 1, 0)) > 0.85 then
            begin
              Dest := r.Origin + r.Dir * d;
              for i := 0 to Selection.Count - 1 do
              TChar(Selection[i]).MoveTo(Dest);
            end;
          end;
        end;
        stBuilding:
        begin
          SelectAction := saIdle;
        end;
      end;
    end;
    2:
    begin
      GetCursorPos(mc);
      ClipRect.Left := mc.X;
      ClipRect.Top := mc.Y;
      ClipRect.Right := mc.X + 1;
      ClipRect.Bottom := mc.Y + 1;
      ClipCursor(@ClipRect);
    end;
  end;
end;

procedure TMyApp.GameOnMouseUp(const Button: Byte);
begin
  case Button of
    0:
    begin
      case SelectAction of
        saIdle:
        begin
          case MDState of
            mdsField:
            begin
              MakeSelection(MDPos, PlugInput.MousePos);
              DrawSelection := False;
            end;
            mdsActions:
            begin
              PanelActions.Select;
            end;
          end;
        end;
        saBuild:
        begin
         case MDState of
            mdsField:
            begin
              BuildData.Build;
            end;
            mdsActions:
            begin
              PanelActions.Select;
            end;
          end;
        end;
      end;
    end;
    1:
    begin

    end;
    2:
    begin
      ClipCursor(nil);
    end;
  end;
end;

procedure TMyApp.GameOnKeyDown(const Key: Byte);
  var i: Integer;
begin
  if Key = DIK_DELETE then
  begin
    if SelectionType = stUnit then
    begin
      TChar(Selection[0]).Health := 0;
    end
    else if SelectionType = stBuilding then
    begin
      TBuilding(Selection[0]).Health := 0;
    end;
  end;
end;

procedure TMyApp.RenderProj;
  var Pos, Size: TG2Vec2;
  var BPos0, BPos1, BPos2, BPos3: TG2Vec2;
  var PrevSrcCol, PrevSrcAlpha, PrevDstCol, PrevDstAlpha: DWord;
  var i, x, y: Integer;
  var b: TBuilding;
begin
  PrevSrcCol := Gfx.RenderStates.SrcBlend;
  PrevSrcAlpha := Gfx.RenderStates.SrcBlendAlpha;
  PrevDstCol := Gfx.RenderStates.DestBlend;
  PrevDstAlpha := Gfx.RenderStates.DestBlendAlpha;
  Proj.RenderBegin;
  Render.TextureClear();
  Gfx.RenderStates.SeparateAlphaBlendEnable := True;
  Gfx.RenderStates.DestBlend := D3DBLEND_ZERO;
  Gfx.RenderStates.DestBlendAlpha := D3DBLEND_ZERO;
  Gfx.RenderStates.SrcBlend := D3DBLEND_ONE;
  Gfx.RenderStates.SrcBlendAlpha := D3DBLEND_ONE;
  if (SelectionType = stBuilding)
  and (SelectAction = saBuild) then
  begin
    for i := 0 to Buildings.Count - 1 do
    if TBuilding(Buildings[i]).BuildExtend then
    begin
      b := TBuilding(Buildings[i]);
      Size.x := (b.BuildExtendRadius + 20) / Proj.PixelWidth;
      Pos := Proj.Pos3DToProj(b.Center);
      Prim2D.DrawCircle(Pos, Size.x, $88004400);
    end;
    for i := 0 to Buildings.Count - 1 do
    if TBuilding(Buildings[i]).BuildExtend then
    begin
      b := TBuilding(Buildings[i]);
      Size.x := b.BuildExtendRadius / Proj.PixelWidth;
      Pos := Proj.Pos3DToProj(b.Center);
      Prim2D.DrawCircle(Pos, Size.x, $2200ff00);
    end;
    if BuildData.BuildChecked then
    begin
      for x := 0 to BuildData.Size.X - 1 do
      for y := 0 to BuildData.Size.Y - 1 do
      begin
        Pos := Proj.Pos3DToProj(G2Vec3(BuildData.BuildCheckX + Waypoints.CellSize * x, 0, BuildData.BuildCheckZ - Waypoints.CellSize * y));
        Size.x := Waypoints.CellSize / Proj.PixelWidth;
        Size.y := Waypoints.CellSize / Proj.PixelHeight;
        if BuildData.BuildCheckNodes[x, y] then
        Prim2D.DrawRect(Pos.x, Pos.y, Size.x, Size.y, $ff00ff00)
        else
        Prim2D.DrawRect(Pos.x, Pos.y, Size.x, Size.y, $ffff0000);
      end;
    end;
  end;
  BPos0 := Proj.Pos3DToProj(LevelMin);
  BPos1 := Proj.Pos3DToProj(LevelMin + G2Vec3(20, 0, 20));
  BPos2 := Proj.Pos3DToProj(LevelMax - G2Vec3(20, 0, 20));
  BPos3 := Proj.Pos3DToProj(LevelMax);
  Prim2D.DrawQuad4Col(
    G2Vec2(BPos0.x, BPos3.y), G2Vec2(BPos1.x, BPos2.y),
    G2Vec2(BPos0.x, BPos0.y), G2Vec2(BPos1.x, BPos1.y),
    $ff00ff00, $0000ff00,
    $ff00ff00, $0000ff00
  );
  Prim2D.DrawQuad4Col(
    G2Vec2(BPos0.x, BPos3.y), G2Vec2(BPos3.x, BPos3.y),
    G2Vec2(BPos1.x, BPos2.y), G2Vec2(BPos2.x, BPos2.y),
    $ff00ff00, $ff00ff00,
    $0000ff00, $0000ff00
  );
  Prim2D.DrawQuad4Col(
    G2Vec2(BPos2.x, BPos2.y), G2Vec2(BPos3.x, BPos3.y),
    G2Vec2(BPos2.x, BPos1.y), G2Vec2(BPos3.x, BPos0.y),
    $0000ff00, $ff00ff00,
    $0000ff00, $ff00ff00
  );
  Prim2D.DrawQuad4Col(
    G2Vec2(BPos1.x, BPos1.y), G2Vec2(BPos2.x, BPos1.y),
    G2Vec2(BPos0.x, BPos0.y), G2Vec2(BPos3.x, BPos0.y),
    $0000ff00, $0000ff00,
    $ff00ff00, $ff00ff00
  );
  Gfx.RenderStates.SeparateAlphaBlendEnable := False;
  Proj.RenderEnd;
  Gfx.RenderStates.SrcBlend := PrevSrcCol;
  Gfx.RenderStates.SrcBlendAlpha := PrevSrcAlpha;
  Gfx.RenderStates.DestBlend := PrevDstCol;
  Gfx.RenderStates.DestBlendAlpha := PrevDstAlpha;
end;

procedure TMyApp.RenderScene(const Query: PG2SGQuery);
  type TVarDepthLinear = packed record
    W: TFloat4x3;
    WVP: TFloat4x4;
    DepthDirW: TG2Vec4;
    DepthMaxRcp: Single;
  end;
  var VarDepthLinear: TVarDepthLinear;
  var ShadowPos: TG2Vec3;
  var ShadowDir: TG2Vec3;
  var ShadowV, ShadowP, ShadowVP: TG2Mat;
  var ProjWVPT: TG2Mat;
  var Frustum: TG2Frustum;
  var m, W, VP, WVP: TG2Mat;
  var i, j, g, PSID: Integer;
  var Geom: TG2SGGeom;
  var Cr: TG2SGChar;
  var b: TBuilding;
  var r: PResources;
begin
  Gfx.RenderStates.ZEnable := True;
  ShadowDir.SetValue(-1, -1.2, -1); ShadowDir.Normalize;
  ShadowPos := Cam.Target^ - ShadowDir * 3000;
  ShadowV.SetView(ShadowPos, ShadowPos + ShadowDir, G2Vec3(0, 1, 0));
  ShadowP.SetOrthogonal(2500, 2500, 1, 20000);
  ShadowVP := ShadowV * ShadowP;
  Frustum.RefV := @ShadowV;
  Frustum.RefP := @ShadowP;
  Frustum.Update;
  Gfx.SetRenderTargetTexture2D(TexDepthRT);
  Gfx.SetDepthStencilSurface(SurfDepthDS);
  Render.Clear(False, True, True, $ffffffff);
  Scene.QueryFetch(Frustum, Query);
  Gfx.Device.SetVertexDeclaration(Scene.DataFormats.DeclGeom);
  ShaderUtils.Technique := 'DepthLinear';
  ShaderUtils.BeginEffect(nil);
  ShaderUtils.SetInt('VS_Index', 0);
  ShaderUtils.BeginPass(0);
  VarDepthLinear.DepthMaxRcp := 1 / 20001;
  VarDepthLinear.DepthDirW.SetValue(ShadowDir, ShadowDir.Dot(ShadowPos));
  for i := 0 to Query.GeomCount - 1 do
  begin
    Geom := Query.Geoms[i];
    WVP := Geom.Transform * ShadowVP;
    VarDepthLinear.W := Geom.Transform;
    VarDepthLinear.WVP := WVP;
    ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
    ShaderUtils.SetFloat('g_Bias', 0);
    ShaderUtils.CommitChanges;
    Gfx.Device.SetStreamSource(0, Geom.VB, 0, Scene.DataFormats.VertexStrideGeom);
    Gfx.Device.SetIndices(Geom.IB);
    Gfx.Device.DrawIndexedPrimitive(
      D3DPT_TRIANGLELIST, 0,
      0, Geom.VCount,
      0, Geom.FCount
    );
  end;
  for i := 0 to Buildings.Count - 1 do
  begin
    b := TBuilding(Buildings[i]);
    if Frustum.BoxInFrustum((b.MeshInst.BBox * b.Transform).AABox) then
    begin
      for j := 0 to b.Mesh.GeomCount - 1 do
      if b.MeshInst.GeomProperties[j].Visible then
      begin
        if b.Mesh.Geoms[j].Skinned then
        begin
          ShaderUtils.SetMatrixArray('g_SkinPallete', PD3DXMatrix(b.MeshInst.SkinTransforms[j]), b.Mesh.Geoms[j].BoneCount);
          VarDepthLinear.W := b.Transform;
        end
        else
        VarDepthLinear.W := b.MeshInst.NodeTransforms[b.Mesh.Geoms[j].NodeID].TransformRen * b.Transform;
        VarDepthLinear.WVP := VarDepthLinear.W * ShadowVP;
        ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
        ShaderUtils.SetInt('VS_Index', b.Mesh.Geoms[j].MaxWeights);
        ShaderUtils.CommitChanges;
        for g := 0 to b.Mesh.Geoms[j].MaterialCount - 1 do
        b.Mesh.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  for i := 0 to Resources.Count - 1 do
  begin
    r := PResources(Resources[i]);
    if Frustum.BoxInFrustum((MeshInstResources.BBox * r.Transform).AABox) then
    begin
      for j := 0 to MeshResources.GeomCount - 1 do
      if MeshInstResources.GeomProperties[j].Visible then
      begin
        if MeshResources.Geoms[j].Skinned then
        begin
          ShaderUtils.SetMatrixArray('g_SkinPallete', PD3DXMatrix(MeshInstResources.SkinTransforms[j]), MeshResources.Geoms[j].BoneCount);
          VarDepthLinear.W := r.Transform;
        end
        else
        VarDepthLinear.W := MeshInstResources.NodeTransforms[MeshResources.Geoms[j].NodeID].TransformRen * r.Transform;
        VarDepthLinear.WVP := VarDepthLinear.W * ShadowVP;
        ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
        ShaderUtils.SetInt('VS_Index', MeshResources.Geoms[j].MaxWeights);
        ShaderUtils.CommitChanges;
        for g := 0 to MeshResources.Geoms[j].MaterialCount - 1 do
        MeshResources.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  for i := 0 to Query.CharCount - 1 do
  if (TG2SGChar(Query.Chars[i]).Mesh <> nil) then
  begin
    Cr := Query.Chars[i];
    for j := 0 to Cr.Mesh.GeomCount - 1 do
    if Cr.MeshInst.GeomProperties[j].Visible then
    begin
      if Cr.Mesh.Geoms[j].Skinned then
      begin
        ShaderUtils.SetMatrixArray('g_SkinPallete', PD3DXMatrix(Cr.MeshInst.SkinTransforms[j]), Cr.Mesh.Geoms[j].BoneCount);
        VarDepthLinear.W := Cr.Transform;
      end
      else
      VarDepthLinear.W := Cr.MeshInst.NodeTransforms[Cr.Mesh.Geoms[j].NodeID].TransformRen * Cr.Transform;
      VarDepthLinear.WVP := VarDepthLinear.W * ShadowVP;
      ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
      ShaderUtils.SetInt('VS_Index', Cr.Mesh.Geoms[j].MaxWeights);
      ShaderUtils.CommitChanges;
      for g := 0 to Cr.Mesh.Geoms[j].MaterialCount - 1 do
      Cr.Mesh.Geoms[j].Mesh.DrawSubset(g);
    end;
  end;
  ShaderUtils.EndPass;
  ShaderUtils.EndEffect;
  Gfx.SetRenderTargetDefault;
  Gfx.SetDepthStencilDefault;

  PostProcess.EffectBlur(TexDepthRT, TexDepthRT, 3);

  Scene.QueryFetch(Gfx.Transforms.Frustum, Query);
  VP := Gfx.Transforms.VP;
  Gfx.Device.SetVertexDeclaration(Scene.DataFormats.DeclGeom);
  Shaders.Technique := 'General';
  Shaders.BeginEffect(nil);
  Shaders.SetInt('VS_Index', 0);
  Shaders.SetVec2('g_DepthRes', G2Vec2(TexDepthRT.Width, TexDepthRT.Height));
  Shaders.SetVec2('g_DepthResRcp', G2Vec2(1 / TexDepthRT.Width, 1 / TexDepthRT.Height));
  Shaders.SetVector('g_DepthDir', VarDepthLinear.DepthDirW);
  Shaders.SetFloat('g_DepthMaxRcp', VarDepthLinear.DepthMaxRcp);
  Shaders.SetFloat('g_DepthBias', -0.0002);
  Shaders.SetTexture('TexDepth', TexDepthRT.Texture);
  Shaders.SetTexture('TexProj', Proj.Tex.Texture);
  Shaders.BeginPass(0);
  for i := 0 to Query.GeomCount - 1 do
  begin
    Geom := Query.Geoms[i];
    WVP := Geom.Transform * VP;
    ProjWVPT := Geom.Transform * Proj.VPT;
    Shaders.SetMatrix('g_W', Geom.Transform);
    Shaders.SetMatrix('g_WVP', WVP);
    Shaders.SetMatrix('g_WVPT', Geom.Transform * ShadowVP * TexDepthRT.ProjMatrix);
    Shaders.SetMatrix('g_ProjWVPT', ProjWVPT);
    Gfx.Device.SetStreamSource(0, Geom.VB, 0, Scene.DataFormats.VertexStrideGeom);
    Gfx.Device.SetIndices(Geom.IB);
    for g := 0 to Geom.GCount - 1 do
    if Geom.Groups[g].Material^.ChannelCount > 0 then
    begin
      case Geom.Groups[g].Material^.ChannelCount of
        1:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          PSID := 0;
        end;
        2:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          PSID := 1;
        end;
        3:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse2', Geom.Groups[g].Material^.Channels[2].TexDiffuse.Texture);
          PSID := 2;
        end;
        4:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse2', Geom.Groups[g].Material^.Channels[2].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse3', Geom.Groups[g].Material^.Channels[3].TexDiffuse.Texture);
          PSID := 3;
        end
        else
        PSID := 0;
      end;
      if Geom.Groups[g].Material^.Channels[0].TwoSided then
      Gfx.RenderStates.CullMode := D3DCULL_NONE
      else
      Gfx.RenderStates.CullMode := D3DCULL_CCW;
      Shaders.SetInt('PS_Index', PSID);
      Shaders.CommitChanges;
      Gfx.Device.DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0,
        Geom.Groups[g].VStart, Geom.Groups[g].VCount,
        Geom.Groups[g].FStart * 3, Geom.Groups[g].FCount
      );
    end;
  end;
  Gfx.RenderStates.CullMode := D3DCULL_CCW;
  Shaders.SetInt('PS_Index', 4);
  Shaders.SetFloat('g_DepthBias', -0.0002);
  for i := 0 to Buildings.Count - 1 do
  begin
    b := TBuilding(Buildings[i]);
    Shaders.SetVec3('g_FactionColor', Factions[b.Faction].Color);
    if Gfx.Transforms.Frustum.BoxInFrustum((b.MeshInst.BBox * b.Transform).AABox) then
    begin
      for j := 0 to b.Mesh.GeomCount - 1 do
      if b.MeshInst.GeomProperties[j].Visible then
      begin
        if b.Mesh.Geoms[j].Skinned then
        begin
          Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(b.MeshInst.SkinTransforms[j]), b.Mesh.Geoms[j].BoneCount);
          W := b.Transform;
        end
        else
        W := b.MeshInst.NodeTransforms[b.Mesh.Geoms[j].NodeID].TransformRen * b.Transform;
        WVP := W * VP;
        Shaders.SetMatrix('g_W', W);
        Shaders.SetMatrix('g_WVP', WVP);
        Shaders.SetMatrix('g_WVPT', W * ShadowVP * TexDepthRT.ProjMatrix);
        Shaders.SetInt('VS_Index', b.Mesh.Geoms[j].MaxWeights + 1);
        for g := 0 to b.Mesh.Geoms[j].MaterialCount - 1 do
        begin
          Shaders.SetTexture('TexDiffuse0', b.MeshInst.Materials[b.Mesh.Geoms[j].Materials[g]].MapDiffuse.Texture);
          if b.MeshInst.Materials[b.Mesh.Geoms[j].Materials[g]].MapSpecular <> nil then
          Shaders.SetTexture('TexMask0', b.MeshInst.Materials[b.Mesh.Geoms[j].Materials[g]].MapSpecular.Texture)
          else
          Shaders.SetTexture('TexMask0', TexBlack.Texture);
          Shaders.CommitChanges;
          b.Mesh.Geoms[j].Mesh.DrawSubset(g);
        end;
      end;
    end;
  end;
  Shaders.SetVec3('g_FactionColor', G2Vec3(0, 0, 0));
  Shaders.SetTexture('TexMask0', TexBlack.Texture);
  for i := 0 to Resources.Count - 1 do
  begin
    r := PResources(Resources[i]);
    if Gfx.Transforms.Frustum.BoxInFrustum((MeshInstResources.BBox * r.Transform).AABox) then
    begin
      for j := 0 to MeshResources.GeomCount - 1 do
      if MeshInstResources.GeomProperties[j].Visible then
      begin
        if MeshResources.Geoms[j].Skinned then
        begin
          Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(MeshInstResources.SkinTransforms[j]), MeshResources.Geoms[j].BoneCount);
          W := r.Transform;
        end
        else
        W := MeshInstResources.NodeTransforms[MeshResources.Geoms[j].NodeID].TransformRen * r.Transform;
        WVP := W * VP;
        Shaders.SetMatrix('g_W', W);
        Shaders.SetMatrix('g_WVP', WVP);
        Shaders.SetMatrix('g_WVPT', W * ShadowVP * TexDepthRT.ProjMatrix);
        Shaders.SetInt('VS_Index', MeshResources.Geoms[j].MaxWeights + 1);
        for g := 0 to MeshResources.Geoms[j].MaterialCount - 1 do
        begin
          Shaders.SetTexture('TexDiffuse0', MeshInstResources.Materials[MeshResources.Geoms[j].Materials[g]].MapDiffuse.Texture);
          Shaders.CommitChanges;
          MeshResources.Geoms[j].Mesh.DrawSubset(g);
        end;
      end;
    end;
  end;
  for i := 0 to Query.CharCount - 1 do
  if (TG2SGChar(Query.Chars[i]).Mesh <> nil) then
  begin
    Cr := Query.Chars[i];
    if Cr is TChar then
    Shaders.SetVec3('g_FactionColor', Factions[TChar(Cr).Faction].Color);
    for j := 0 to Cr.Mesh.GeomCount - 1 do
    if Cr.MeshInst.GeomProperties[j].Visible then
    begin
      if Cr.Mesh.Geoms[j].Skinned then
      begin
        Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(Cr.MeshInst.SkinTransforms[j]), Cr.Mesh.Geoms[j].BoneCount);
        W := Cr.Transform;
      end
      else
      W := Cr.MeshInst.NodeTransforms[Cr.Mesh.Geoms[j].NodeID].TransformRen * Cr.Transform;
      WVP := W * VP;
      Shaders.SetMatrix('g_W', W);
      Shaders.SetMatrix('g_WVP', WVP);
      Shaders.SetMatrix('g_WVPT', W * ShadowVP * TexDepthRT.ProjMatrix);
      Shaders.SetInt('VS_Index', Cr.Mesh.Geoms[j].MaxWeights + 1);
      for g := 0 to Cr.Mesh.Geoms[j].MaterialCount - 1 do
      begin
        Shaders.SetTexture('TexDiffuse0', Cr.MeshInst.Materials[Cr.Mesh.Geoms[j].Materials[g]].MapDiffuse.Texture);
        if Cr.MeshInst.Materials[Cr.Mesh.Geoms[j].Materials[g]].MapSpecular <> nil then
        Shaders.SetTexture('TexMask0', Cr.MeshInst.Materials[Cr.Mesh.Geoms[j].Materials[g]].MapSpecular.Texture)
        else
        Shaders.SetTexture('TexMask0', TexBlack.Texture);
        Shaders.CommitChanges;
        Cr.Mesh.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  Shaders.EndPass;
  Shaders.EndEffect;
  Gfx.RenderStates.ZEnable := False;

  //Render2D.DrawRect(0, 0, 256, 256, $ffffffff, TexDepthRT);
end;

procedure TMyApp.MakeSelection(const Pt0, Pt1: TPoint);
  var i: Integer;
  var c: TChar;
  var b: TBuilding;
  var CamX, CamY, CamZ: TG2Vec3;
  var v0, v1: TG2Vec3;
  var p0, p1: TPoint;
  var r, d, md: Single;
  var OOBox: TG2Box;
  var AABox: TG2AABox;
  var Ray: TG2Ray;
begin
  if Pt0.X < Pt1.X then begin p0.X := Pt0.X; p1.X := Pt1.X; end else begin p0.X := Pt1.X; p1.X := Pt0.X; end;
  if Pt0.Y < Pt1.Y then begin p0.Y := Pt0.Y; p1.Y := Pt1.Y; end else begin p0.Y := Pt1.Y; p1.Y := Pt0.Y; end;
  Selection.Clear;
  PanelActions.Clear;
  SelectAction := saIdle;
  Cam.GetViewSpaceVectors(CamX, CamY, CamZ);
  for i := 0 to Scene.Chars.Count - 1 do
  if (TG2SGChar(Scene.Chars[i]) is TChar)
  and (TChar(Scene.Chars[i]).Faction = PlayerFaction) then
  begin
    c := TChar(Scene.Chars[i]);
    v0 := c.Pos; v1 := v0 + CamX * c.Radius;
    v0 := Gfx.PointTo2D(v0); v1 := Gfx.PointTo2D(v1);
    r := (v1 - v0).Len;
    if ((v0.AsVec2 - G2Vec2(p0.X, p0.Y)).Len < r)
    or ((v0.AsVec2 - G2Vec2(p1.X, p1.Y)).Len < r)
    or PtInRect(Rect(p0.X, p0.Y, p1.X, p1.Y), Point(Round(v0.x), Round(v0.y))) then
    Selection.Add(c);
  end;
  if Selection.Count > 0 then
  begin
    SelectionType := stUnit;
    Exit;
  end;
  if not DrawSelection then
  for i := 0 to Factions[PlayerFaction].Buildings.Count - 1 do
  begin
    b := TBuilding(Factions[PlayerFaction].Buildings[i]);
    if b.BoundsID > -1 then
    begin
      OOBox := (b.MeshInst.GeomBBox[b.BoundsID] * b.Transform);
      AABox := OOBox.AABox;
      if Gfx.Transforms.Frustum.BoxInFrustum(AABox) then
      begin
        Ray := Gfx.PointToRay(Pt1);
        if Ray.IntersectAABox(AABox, v0) then
        begin
          d := (v0 - Ray.Origin).Len;
          if (Selection.Count = 0)
          or (d < md) then
          begin
            md := d;
            Selection.Clear;
            Selection.Add(b);
          end;
        end;
      end;
    end;
  end;
  if Selection.Count > 0 then
  begin
    SelectionType := stBuilding;
    TBuilding(Selection[0]).OnSelect;
    Exit;
  end;
  SelectionType := stNone;
end;

procedure TMyApp.CheckCollideChars;
  var i, j: Integer;
  var c: TChar;
  var x, y, z, MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Integer;
begin
  if Scene.Chars.Count < 30 then
  begin
    for i := 0 to Scene.Chars.Count - 1 do
    if TG2SGChar(Scene.Chars[i]) is TChar then
    begin
      for j := i + 1 to Scene.Chars.Count - 1 do
      if TG2SGChar(Scene.Chars[j]) is TChar then
      CollideChars(TChar(Scene.Chars[i]), TChar(Scene.Chars[j]));
    end;
  end
  else
  begin
    for i := 0 to Scene.Chars.Count - 1 do
    if TG2SGChar(Scene.Chars[i]) is TChar then
    begin
      c := TChar(Scene.Chars[i]);
      MinX := Trunc((c.Pos.x - App.CharGrid.Pos.x - 100) / App.CharGrid.CellSize.x); if MinX < 0 then MinX := 0;
      MinY := Trunc((c.Pos.y - App.CharGrid.Pos.y - 100) / App.CharGrid.CellSize.y); if MinY < 0 then MinY := 0;
      MinZ := Trunc((c.Pos.z - App.CharGrid.Pos.z - 100) / App.CharGrid.CellSize.z); if MinZ < 0 then MinZ := 0;
      MaxX := Trunc((c.Pos.x - App.CharGrid.Pos.x + 100) / App.CharGrid.CellSize.x); if MaxX > App.CharGrid.SizeX - 1 then MaxX := App.CharGrid.SizeX - 1;
      MaxY := Trunc((c.Pos.y - App.CharGrid.Pos.y + 100) / App.CharGrid.CellSize.y); if MaxY > App.CharGrid.SizeY - 1 then MaxY := App.CharGrid.SizeY - 1;
      MaxZ := Trunc((c.Pos.z - App.CharGrid.Pos.z + 100) / App.CharGrid.CellSize.z); if MaxZ > App.CharGrid.SizeZ - 1 then MaxZ := App.CharGrid.SizeZ - 1;
      for x := MinX to MaxX do
      for y := MinY to MaxY do
      for z := MinZ to MaxZ do
      for j := 0 to App.CharGrid.Grid[x, y, z].Count - 1 do
      if (TG2SGChar(App.CharGrid.Grid[x, y, z][j]) is TChar)
      and (TG2SGChar(App.CharGrid.Grid[x, y, z][j]) <> c) then
      CollideChars(c, TChar(App.CharGrid.Grid[x, y, z][j]));
    end;
  end;
end;

procedure TMyApp.CollideChars(const Char0, Char1: TChar);
  var Char0L, Char0H, Char1L, Char1H, L: Single;
  var Char0Pos, Char1Pos, Dir: TG2Vec2;
  var mt, mg0, mg1: Single;
begin
  Char0L := Char0.Transform.e31;
  Char1H := Char1.Transform.e31 + Char1.Step.Len + Char1.Radius;
  if Char0L > Char1H then Exit;
  Char1L := Char1.Transform.e31;
  Char0H := Char0.Transform.e31 + Char0.Step.Len + Char0.Radius;
  if Char1L > Char0H then Exit;
  Char0Pos.SetValue(Char0.Transform.e30, Char0.Transform.e32);
  Char1Pos.SetValue(Char1.Transform.e30, Char1.Transform.e32);
  Dir := Char1Pos - Char0Pos;
  L := Dir.Len;
  if L > Char0.Radius + Char1.Radius then Exit;
  Dir.Normalize;
  L := (Char0.Radius + Char1.Radius - L);
  mt := 1 / (Char0.Mass + Char1.Mass);
  mg0 := Char1.Mass * mt * L;
  mg1 := Char0.Mass * mt * L;
  Char0.Transform.e30 := Char0Pos.x - Dir.x * mg0;
  Char0.Transform.e32 := Char0Pos.y - Dir.y * mg0;
  Char1.Transform.e30 := Char1Pos.x + Dir.x * mg1;
  Char1.Transform.e32 := Char1Pos.y + Dir.y * mg1;
  Char0.ResolvePushing(Char1);
  Char1.ResolvePushing(Char0);
end;

procedure TMyApp.Build(const Pos: TG2Vec3; const Building: CBuilding; const FactionID: Integer);
  var b: TBuilding;
begin
  b := Building.Create;
  b.SetFaction(FactionID);
  b.Pos := Pos + G2Vec3(-b.Size.X * Waypoints.CellSize * 0.5, 0, b.Size.Y * Waypoints.CellSize * 0.5);
  b.Place;
end;

procedure TMyApp.AddResources(const Pos: TG2Vec3);
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
  var r: TG2Ray;
  var Res: PResources;
  var q: TG2Quat;
begin
  r.Origin := Pos;
  r.Dir.SetValue(0, -1, 0);
  if Scene.CollideRay(r, GeomID, FaceID, u, v, d) then
  begin
    New(Res);
    Res^.Transform.SetScaling(0.5);
    Res^.Transform.RotateY(G2Random2Pi);
    q := G2Vec3Rotation(G2Vec3(0, 1, 0), TG2SGGeom(Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N);
    Res^.Transform.Rotate(q);
    Res^.Transform.Translate(r.Origin + r.Dir * d);
    Resources.Add(Res);
  end;
end;

procedure TMyApp.LoadTextures(const MeshInst: TG2MeshInst);
  var i: Integer;
  var Ext, Str: WideString;
begin
  for i := 0 to MeshInst.Mesh.MaterialCount - 1 do
  begin
    MeshInst.Materials[i].MapDiffuse := nil;
    MeshInst.Materials[i].MapSpecular := nil;
    MeshInst.Materials[i].MapDiffuse := TG2Texture2D(MgrTextures.FindTexture(MeshInst.Mesh.Materials[i].DiffuseMap));
    Ext := ExtractFileExt(MeshInst.Mesh.Materials[i].DiffuseMap);
    Str := G2StrParam(MeshInst.Mesh.Materials[i].DiffuseMap, Ext, 0);
    if MeshInst.Materials[i].MapDiffuse = nil then
    MeshInst.Materials[i].MapDiffuse := MgrTextures.CreateTexture2DFromPack(Str, 'Gfx', Str);
    Str := G2StrParam(MeshInst.Mesh.Materials[i].DiffuseMap, Ext, 0) + '_F';
    MeshInst.Materials[i].MapSpecular := TG2Texture2D(MgrTextures.FindTexture(Str));
    if MeshInst.Materials[i].MapSpecular = nil then
    MeshInst.Materials[i].MapSpecular := MgrTextures.CreateTexture2DFromPack(Str, 'Gfx', Str);
  end;
end;

function TMyApp.VerticalTrace(const Pos: TG2Vec3; var Res: TG2Vec3): Boolean;
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var u, v, d: Single;
begin
  r.Origin := Pos;
  r.Dir.SetValue(0, -1, 0);
  Result := Scene.CollideRay(r, GeomID, FaceID, u, v, d);
  if Result then
  Res := r.Origin + r.Dir * d;
end;

procedure TMyApp.PrintInRect(const r: TG2Rect; const Text: AnsiString);
  var fw, fh, sx, sy, s: Single;
begin
  if Length(Text) <= 0 then Exit;
  fw := Font1.GetTextWidth(Text);
  fh := Font1.GetTextHeight(Text);
  sx := r.w / fw;
  sy := r.h / fh;
  if sx < sy then s := sx else s := sy;
  fw := fw * s; fh := fh * s;
  Font1.Print(r.x + (r.w - fw) * 0.5, r.y + (r.h - fh) * 0.5, s, s, $ffffffff, Text);
end;

procedure TMyApp.LevelStart;
  var i, j: Integer;
  var ParamArr: TG2StrArrA;
  var c: TChar;
  var v3: TG2Vec3;
  var n: TG2SGNode;
  var LevelBuffer: Pointer;
  var LevelSize: DWord;
begin
  AIInterval := 1000;
  AITime := App.AppTime;
  SelectAction := saIdle;
  Scene := TG2SceneGraph.Create(g2);
  Scene.SearchPathAdd('Mesh');
  Scene.SearchPathAdd('Gfx');
  g2.PackLinker.GetFileData('Mesh', 'Level01', LevelBuffer, LevelSize);
  Scene.LoadG2M(LevelBuffer, LevelSize);
  QueryRender := Scene.QueryCreate;
  QueryUpdate := Scene.QueryCreate;
  QueryRender^.OcclusionCull := [];
  QueryRender^.DistanceSortEnable := False;
  QueryUpdate^.OcclusionCull := [];
  QueryUpdate^.DistanceSortEnable := False;
  GroundGeoms.Clear;
  Resources.Clear;
  for i := 0 to Scene.Geoms.Count - 1 do
  begin
    ParamArr := G2StrExplode(TG2SGGeom(Scene.Geoms[i]).Name, '_');
    for j := 0 to High(ParamArr) do
    begin
      if ParamArr[j] = '[r]' then
      TG2SGGeom(Scene.Geoms[i]).Render := True
      else if ParamArr[j] = '[c]' then
      TG2SGGeom(Scene.Geoms[i]).Collide := True
      else if ParamArr[j] = '[g]' then
      GroundGeoms.Add(Scene.Geoms[i]);
    end;
  end;
  for i := 0 to Scene.Nodes.Count - 1 do
  begin
    if TG2SGNode(Scene.Nodes[i]).Name = 'Limits01' then
    LevelMin := TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation
    else
    if TG2SGNode(Scene.Nodes[i]).Name = 'Limits02' then
    LevelMax := TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation
    else
    begin
      ParamArr := G2StrExplode(TG2SGNode(Scene.Nodes[i]).Name, '_');
      for j := 0 to High(ParamArr) do
      begin
        if ParamArr[j] = 'Res' then
        AddResources(TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation);
      end;
    end;
  end;
  Scene.MgrCharMeshes.CreateMeshFromPack('Soldier01', 'Mesh', 'Soldier01');
  Scene.MgrCharMeshes.CreateMeshFromPack('Tank01', 'Mesh', 'Tank01');
  Scene.MgrCharMeshes.CreateMeshFromPack('Harvester', 'Mesh', 'Harvester');
  Waypoints.Initialize;
  CharGrid.Initialize;
  Selection.Clear;
  DrawSelection := False;
  Buildings.Clear;
  SelectionType := stNone;
  PlayerFaction := 0;
  FactionCount := 0;
  repeat
    n := Scene.FindNode('Faction' + IntToStr(FactionCount));
    if n <> nil then
    begin
      SetLength(Factions, FactionCount + 1);
      Factions[FactionCount].ID := FactionCount;
      Factions[FactionCount].Color := FactionColors[FactionCount];
      Factions[FactionCount].Units.Clear;
      Factions[FactionCount].Buildings.Clear;
      Factions[FactionCount].Funds := 3000;
      if VerticalTrace(n.Transform.GetTranslation, v3) then
      Build(v3, TBConstruction, FactionCount);
      Inc(FactionCount);
    end;
  until n = nil;
  Cam.SetPerspective(HalfPi, Gfx.Params.Width / Gfx.Params.Height, 1, 10000);
  n := Scene.FindNode('Faction0');
  if n <> nil then
  begin
    v3 := n.Transform.GetTranslation;
    Cam.SetView(v3.x, 200, v3.z - 100, v3.x, 0, v3.z, 0, 1, 0);
  end
  else
  Cam.SetView((LevelMin.x + LevelMax.x) * 0.5, 200, (LevelMin.z + LevelMax.z) * 0.5, (LevelMin.x + LevelMax.x) * 0.5, 0, (LevelMin.z + LevelMax.z) * 0.5 + 100, 0, 1, 0);
  Cam.UpdatePos := True;
  Cam.Mode := cmHorizontal;
  Cam.DragMode := cdDragPos;
  Cam.Speed := 10;
  Cam.Sensitivity := 0.3;
  Cam.MaxAngV := -Pi * 0.36;
  Cam.MinAngV := -Pi * 0.49;
  LevelStarted := True;
  GameState := gsGame;
end;

procedure TMyApp.LevelStop;
  var i: Integer;
begin
  Selection.Clear;
  for i := 0 to Resources.Count - 1 do
  Dispose(PResources(Resources[i]));
  Resources.Clear;
  while Buildings.Count > 0 do
  TBuilding(Buildings[0]).Free;
  CharGrid.Finalzie;
  Waypoints.Finalize;
  GroundGeoms.Clear;
  Scene.QueryDestroy(QueryUpdate);
  Scene.QueryDestroy(QueryRender);
  Scene.Free;
  SetLength(Factions, 0);
  FactionCount := 0;
  LevelStarted := False;
end;

procedure TMyApp.MenuNewGame;
begin
  RenderLoading;
  LevelStart;
end;

procedure TMyApp.MenuQuit;
begin
  Running := False;
end;
//TMyApp END

begin
  App := TMyApp.Create;       
  App.Initialize;
  if App.g2.Initialized then
  try
    App.Loop;
  finally
    App.Free;
  end;
  ExitCode := 0;
end.
