GXS.ShadowHDS.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. //
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.ShadowHDS;
  5. (*
  6. Implements an HDS that automatically generates a terrain lightmap texture
  7. Issues:1:Ambient and Diffuse light properties can not be set to 0, to avoid what
  8. seems to be a Delphi bug: If a property of type 'Single' is set to 0,
  9. Delphi seems to skip the property's set method at startup, and just
  10. uses the default value instead. (Does anyone know a better workaround?)
  11. 2:Subsampling is not currently supported.
  12. 3:If the light vector's y component is not 0 then the shadow edges may be
  13. a bit jagged, due to the crude Bresenham line algorythm that was used.
  14. You can hide this by increasing SoftRange though.
  15. 5:At some light angles, rounding errors cause various artifacts:
  16. (Black tile edges / slight mis-alignments /etc.)
  17. 6:Applying materials ocasionally causes AV's
  18. PS. The RayCastShadowHeight function returns the height of the shadow at a point
  19. on the terrain. This, and the LightVector may come in handy for implementing shadow volumes?
  20. *)
  21. interface
  22. uses
  23. Winapi.OpenGL,
  24. System.Classes,
  25. System.SysUtils,
  26. System.Math,
  27. Stage.VectorTypes,
  28. GXS.VectorLists,
  29. GXS.HeightData,
  30. GXS.Graphics,
  31. Stage.VectorGeometry,
  32. GXS.Texture,
  33. GXS.Coordinates,
  34. GXS.Material;
  35. type
  36. TgxShadowHDS = class;
  37. TNewTilePreparedEvent = procedure(Sender: TgxShadowHDS;
  38. HeightData: TgxHeightData; ShadowMapMaterial: TgxLibMaterial) of object;
  39. TThreadBmp32 = procedure(Sender: TgxShadowHDS; HeightData: TgxHeightData;
  40. bmp32: TgxBitmap32) of object;
  41. (* An Height Data Source that generates terrain shadow maps automatically.
  42. The HDS must be connected to another HDS, which will provide the elevation
  43. data, and to a MaterialLibrary where shadowmaps will be placed. *)
  44. TgxShadowHDS = class(TgxHeightDataSourceFilter)
  45. private
  46. FTileSize: integer;
  47. FShadowmapLibrary: TgxMaterialLibrary;
  48. FLightVector: TgxCoordinates;
  49. FScale: TgxCoordinates;
  50. FScaleVec: TVector3f;
  51. FOnNewTilePrepared: TNewTilePreparedEvent;
  52. FOnThreadBmp32: TThreadBmp32;
  53. // FSubSampling : Integer;
  54. FMaxTextures: integer;
  55. Step: TVector3f;
  56. FScanDistance: integer;
  57. FSoftRange: cardinal;
  58. FDiffuse: single;
  59. FAmbient: single;
  60. OwnerHDS: TgxHeightDataSource; // The owner of the tile
  61. protected
  62. procedure SetShadowmapLibrary(const val: TgxMaterialLibrary);
  63. procedure SetScale(AValue: TgxCoordinates);
  64. procedure SetLightVector(AValue: TgxCoordinates);
  65. procedure SetSoftRange(AValue: cardinal);
  66. procedure SetDiffuse(AValue: single);
  67. procedure SetAmbient(AValue: single);
  68. // procedure SetSubSampling(const val : Integer);
  69. procedure Trim(MaxTextureCount: integer);
  70. function FindUnusedMaterial: TgxLibMaterial;
  71. function CalcStep: TAffineVector;
  72. function CalcScale: TAffineVector;
  73. (* Get the number of steps, before the current tile's edge is reached,
  74. in the direction of the step vector; *)
  75. function WrapDist(Lx, Ly: single): integer;
  76. // Converts local tile coordinates to world coordinages. Even if the coordinates are off the tile.
  77. procedure LocalToWorld(Lx, Ly: single; HD: TgxHeightData; var Wx: single;
  78. var Wy: single);
  79. // Takes World coordinates and returns the correct tile, and converted local coordinates
  80. procedure WorldToLocal(Wx, Wy: single; var HD: TgxHeightData;
  81. var Lx: single; var Ly: single);
  82. public
  83. // When true, only a blank ShadowMap is generated (FAST), but OnThreadBmp32 is still called in a subthread.
  84. SkipGenerate: boolean;
  85. constructor Create(AOwner: TComponent); override;
  86. destructor Destroy; override;
  87. // procedure Release(aHeightData : TgxHeightData); override;
  88. (* This will repeatedly delete the oldest unused texture from the TGLMaterialLibrary,
  89. until the texture count drops to MaxTextureCount.
  90. DONT use this if you used TGLHeightData.MaterialName to link your terrain textures.
  91. Either use with TGLHeightData.LibMaterial, or manually delete unused LightMap textures. *)
  92. procedure TrimTextureCache(MaxTextureCount: integer = 0);
  93. procedure Notification(AComponent: TComponent;
  94. Operation: TOperation); override;
  95. // Prepare a blank texture for this tile's lightmap, from the main thread
  96. procedure BeforePreparingData(HeightData: TgxHeightData); override;
  97. // Calculate the lightmap from the HD thread, using the attached blank texture
  98. procedure PreparingData(HeightData: TgxHeightData); override;
  99. procedure AfterPreparingData(HeightData: TgxHeightData); override;
  100. procedure GenerateShadowMap(HeightData: TgxHeightData;
  101. ShadowMap: TgxBitmap32; scale: single);
  102. (* This traces a ray from a point on the terrain surface, back to the Lightsource,
  103. while testing for any intersections with the terrain.
  104. It returns the height of the shadow. There is no shadow if the shadow height is equal to terrain height.
  105. This is slow, but only needs to be done for pixels along the tile edge, facing the light. *)
  106. function RayCastShadowHeight(HD: TgxHeightData; localX, localY: single)
  107. : single; overload;
  108. procedure RayCastLine(HeightData: TgxHeightData; Lx, Ly: single;
  109. ShadowMap: TgxBitmap32);
  110. (* Calculate the pixel brightness, using Direct Diffuse light and Ambient light.
  111. DirectLight = 1 if in direct sunlight (no shadows)
  112. 0 if in shadow. (Use "SoftRange" for soft shadow edges i.e. 1>Directlight>0 )
  113. AmbientLight = Relative to Angle between surface Normal and sky (Directly up)
  114. ie. Vertical walls are darker because they see less sky.
  115. DiffuseLight = Relative to Angle between surface Normal, and Sun vector. *)
  116. function Shade(HeightData: TgxHeightData; x, y: integer;
  117. ShadowHeight, TerrainHeight: single): byte;
  118. published
  119. property ShadowmapLibrary: TgxMaterialLibrary read FShadowmapLibrary
  120. write SetShadowmapLibrary;
  121. property OnThreadBmp32: TThreadBmp32 read FOnThreadBmp32
  122. write FOnThreadBmp32; // WARNING: This runs in a subthread
  123. property OnNewTilePrepared: TNewTilePreparedEvent read FOnNewTilePrepared
  124. write FOnNewTilePrepared;
  125. property LightVector: TgxCoordinates read FLightVector write SetLightVector;
  126. property scale: TgxCoordinates read FScale write FScale;
  127. property ScanDistance: integer read FScanDistance write FScanDistance;
  128. property SoftRange: cardinal read FSoftRange write SetSoftRange;
  129. // Shadow height above sufrace for max diffuse light
  130. property Diffuse: single read FDiffuse write SetDiffuse;
  131. property Ambient: single read FAmbient write SetAmbient;
  132. property MaxTextures: integer read FMaxTextures write FMaxTextures;
  133. property OnSourceDataFetched;
  134. end;
  135. // ------------------------------------------------------------------
  136. implementation
  137. // ------------------------------------------------------------------
  138. constructor TgxShadowHDS.Create(AOwner: TComponent);
  139. begin
  140. inherited Create(AOwner);
  141. FLightVector := TgxCoordinates.CreateInitialized(Self, VectorMake(1, 0, -1));
  142. FLightVector.Style := csVector; // csPoint;
  143. FScale := TgxCoordinates.CreateInitialized(Self, VectorMake(1, 1, 1));
  144. FScale.Style := csVector; // csPoint;
  145. FScanDistance := 64;
  146. FAmbient := 0.25;
  147. FDiffuse := 0.75;
  148. FSoftRange := 1;
  149. // FSubSampling:=1;
  150. OwnerHDS := Self;
  151. // Until told otherwise, assume that ShadowHDS IS the tile owner.
  152. SkipGenerate := false;
  153. // Set to true in "OnSourceDataFetched" to skip shadow generation.
  154. end;
  155. destructor TgxShadowHDS.Destroy;
  156. begin
  157. Self.Active := false;
  158. FreeAndNil(FLightVector);
  159. FreeAndNil(FScale);
  160. ShadowmapLibrary := nil;
  161. inherited Destroy;
  162. end;
  163. procedure TgxShadowHDS.Notification(AComponent: TComponent;
  164. Operation: TOperation);
  165. begin
  166. if Operation = opRemove then
  167. begin
  168. if AComponent = FShadowmapLibrary then
  169. ShadowmapLibrary := nil;
  170. end;
  171. inherited;
  172. end;
  173. (*
  174. procedure TgxShadowHDS.Release(aHeightData : TgxHeightData);
  175. var libMat : TgxLibMaterial;
  176. begin
  177. HeightDataSource.Data.LockList;
  178. libMat:=aHeightData.LibMaterial;
  179. aHeightData.MaterialName:='';
  180. if (FMaxTextures>0)and(assigned(LibMat))and(libMat.IsUsed=false)
  181. then LibMat.free;
  182. inherited;
  183. HeightDataSource.Data.UnlockList;
  184. end;
  185. *)
  186. procedure TgxShadowHDS.TrimTextureCache(MaxTextureCount: integer);
  187. // Thread-safe Version
  188. // Thread-safe Version
  189. begin
  190. If (not assigned(Self)) or (not assigned(OwnerHDS)) then
  191. exit;
  192. with OwnerHDS.Data.LockList do
  193. try
  194. Trim(MaxTextureCount);
  195. finally
  196. OwnerHDS.Data.UnlockList;
  197. end;
  198. end;
  199. procedure TgxShadowHDS.Trim(MaxTextureCount: integer); // internal use only
  200. var
  201. matLib: TgxMaterialLibrary;
  202. libMat: TgxLibMaterial;
  203. i: integer;
  204. cnt: integer;
  205. begin
  206. matLib := FShadowmapLibrary;
  207. if matLib <> nil then
  208. begin
  209. // ---------------------------------
  210. // --Trim unused textures, until MaxTextureCount is reached--
  211. cnt := matLib.Materials.Count;
  212. i := 0;
  213. while (i < cnt) and (cnt >= MaxTextureCount) do
  214. begin
  215. libMat := matLib.Materials[i];
  216. if libMat.IsUsed then
  217. inc(i)
  218. else
  219. begin
  220. libMat.Free;
  221. dec(cnt); // cnt:=matlib.Materials.Count;
  222. end;
  223. end;
  224. // ----------------------------------------------------------
  225. end;
  226. end;
  227. function TgxShadowHDS.FindUnusedMaterial: TgxLibMaterial;
  228. var
  229. matLib: TgxMaterialLibrary;
  230. i: integer;
  231. cnt: integer;
  232. begin
  233. result := nil;
  234. matLib := FShadowmapLibrary;
  235. if matLib <> nil then
  236. begin
  237. cnt := matLib.Materials.Count;
  238. i := 0;
  239. while (i < cnt) and (matLib.Materials[i].IsUsed) do
  240. inc(i);
  241. if (i < cnt) then
  242. result := matLib.Materials[i];
  243. end;
  244. end;
  245. procedure TgxShadowHDS.SetLightVector(AValue: TgxCoordinates);
  246. begin
  247. With OwnerHDS.Data.LockList do
  248. try
  249. FLightVector.Assign(AValue);
  250. CalcStep;
  251. // MarkDirty;
  252. finally
  253. OwnerHDS.Data.UnlockList;
  254. end;
  255. end;
  256. function TgxShadowHDS.CalcStep: TAffineVector;
  257. var
  258. L: single;
  259. v: TAffineVector;
  260. begin
  261. MakeVector(v, FLightVector.x / FScale.x, FLightVector.y / FScale.y,
  262. 256 * FLightVector.Z / FScale.Z);
  263. L := MaxFloat(abs(v.x), abs(v.y));
  264. Step := VectorScale(v, 1 / L);
  265. Step.x := trunc(Step.x * 16384) / 16384;
  266. // round down the fraction now, to prevent rounding errors later
  267. Step.y := trunc(Step.y * 16384) / 16384;
  268. // round down the fraction now, to prevent rounding errors later
  269. if ((FLightVector.x = 0) and (FLightVector.y = 0)) then
  270. begin
  271. Step.x := 1;
  272. Step.y := 0;
  273. Step.Z := -maxint;
  274. end;
  275. result := Step;
  276. end;
  277. function TgxShadowHDS.CalcScale: TAffineVector;
  278. begin
  279. FScaleVec.x := FScale.x * 256;
  280. FScaleVec.y := FScale.y * 256;
  281. FScaleVec.Z := FScale.Z;
  282. result := FScaleVec;
  283. end;
  284. procedure TgxShadowHDS.BeforePreparingData(HeightData: TgxHeightData);
  285. var
  286. HD: TgxHeightData;
  287. libMat: TgxLibMaterial;
  288. MatName: string;
  289. begin
  290. if not assigned(FShadowmapLibrary) then
  291. exit;
  292. HD := HeightData;
  293. OwnerHDS := HD.Owner;
  294. with OwnerHDS.Data.LockList do
  295. try
  296. Trim(FMaxTextures);
  297. MatName := 'ShadowHDS_x' + IntToStr(HD.XLeft) + 'y' + IntToStr(HD.YTop) +
  298. '.'; // name contains xy coordinates of the current tile
  299. libMat := FShadowmapLibrary.Materials.Add;
  300. // ---------Recycle Textures---------
  301. // libMat:=self.FindUnusedMaterial; //look for an unused texture, to recycle
  302. // if libMat=nil
  303. // then libMat:=FShadowmapLibrary.Materials.Add //if no free textures were found, get a new one
  304. // else libMat.Material.Texture.Enabled:=false; //recycle the unused texture
  305. // ----------------------------------
  306. libMat.Name := MatName;
  307. // HD.MaterialName:=LibMat.Name;
  308. HD.LibMaterial := libMat; // attach texture to current tile
  309. finally
  310. OwnerHDS.Data.UnlockList;
  311. end;
  312. end;
  313. procedure TgxShadowHDS.PreparingData(HeightData: TgxHeightData);
  314. var
  315. HD: TgxHeightData;
  316. libMat: TgxLibMaterial;
  317. bmp32: TgxBitmap32;
  318. begin
  319. HD := HeightData;
  320. libMat := HD.LibMaterial;
  321. Assert(assigned(HD));
  322. Assert(assigned(libMat));
  323. Assert(libMat.Material.Texture.Disabled);
  324. // With heightData.Owner.Data.LockList do try //lock out other threads
  325. // Transfer tile texture coordinates to generated texture
  326. libMat.TextureScale.x := HD.TextureCoordinatesScale.S;
  327. libMat.TextureScale.y := HD.TextureCoordinatesScale.T;
  328. libMat.TextureOffset.x := HD.TextureCoordinatesOffset.S;
  329. libMat.TextureOffset.y := HD.TextureCoordinatesOffset.T;
  330. // ------------------------------------------------------
  331. // --Set up new Lightmap texture for the current tile--
  332. libMat.Material.MaterialOptions := [moNoLighting];
  333. with libMat.Material.Texture do
  334. begin
  335. ImageClassName := TgxBlankImage.ClassName;
  336. MinFilter := miNearestMipmapNearest;
  337. // MinFilter:=miLinearMipmapLinear;
  338. // MagFilter:=maNearest;
  339. MagFilter := maLinear;
  340. TextureMode := tmReplace;
  341. TextureWrap := twNone;
  342. // TextureFormat:=tfLuminance;
  343. TextureFormat := tfRGB16;
  344. // TextureFormat:=tfRGBA;
  345. bmp32 := (Image as TgxBlankImage).GetBitmap32;
  346. if not SkipGenerate then
  347. GenerateShadowMap(HD, bmp32, 1);
  348. if assigned(FOnThreadBmp32) then
  349. FOnThreadBmp32(Self, HeightData, bmp32);
  350. // Enabled:=True;
  351. with HD.Owner.Data.LockList do
  352. try
  353. Enabled := True;
  354. finally
  355. HD.Owner.Data.UnlockList;
  356. end;
  357. end;
  358. // finally HD.Owner.Data.UnlockList; end;
  359. // ----------------------------------------------------
  360. end;
  361. procedure TgxShadowHDS.AfterPreparingData(HeightData: TgxHeightData);
  362. begin
  363. if assigned(FOnNewTilePrepared) then
  364. FOnNewTilePrepared(Self, HeightData, HeightData.LibMaterial);
  365. end;
  366. (*
  367. procedure TgxShadowHDS.PreparingData(heightData : TgxHeightData);
  368. var HD : TgxHeightData;
  369. libMat: TgxLibMaterial;
  370. bmp32 : TgxBitmap32;
  371. MatName:string;
  372. Hold:TgxUpdateAbleObject;
  373. lst:TList;
  374. begin
  375. if not assigned (FShadowmapLibrary) then exit;
  376. //--Generate Shadow Map for tile--
  377. lst:=HeightDataSource.Data.LockList; //lock out other threads
  378. //Uno.Acquire;
  379. HD:=HeightData;
  380. MatName:='ShadowHDS_x'+IntToStr(HD.XLeft)+'y'+IntToStr(HD.YTop)+'.'; //name contains xy coordinates of the current tile
  381. Hold:=TgxUpdateAbleObject.Create(self);
  382. LibMat:=FShadowmapLibrary.Materials.GetLibMaterialByName(MatName); //Check if Tile Texture already exists
  383. //if assigned(libmat) then LibMat.Name:='Dirty';
  384. //LibMat:=nil;
  385. if LibMat=nil then begin
  386. if (FMaxTextures>0)and(HD.Thread=nil) //Dont trim the cache from a sub-thread;
  387. then TrimTextureCache(FMaxTextures); //Trim unused textures from the material library
  388. //Generate new ShadowMap texture for this tile
  389. libMat:=FShadowmapLibrary.Materials.Add;
  390. libMat.RegisterUser(Hold); //hold onto the texture, so another thread doesnt delete it
  391. //Transfer tile texture coordinates to generated texture
  392. libMat.TextureScale.X :=HD.TextureCoordinatesScale.S;
  393. libMat.TextureScale.Y :=HD.TextureCoordinatesScale.T;
  394. libMat.TextureOffset.X:=HD.TextureCoordinatesOffset.S;
  395. libMat.TextureOffset.Y:=HD.TextureCoordinatesOffset.T;
  396. //------------------------------------------------------
  397. //--Set up new Lightmap texture for the current tile--
  398. libMat.Material.MaterialOptions:=[moNoLighting];
  399. with libMat.Material.Texture do begin
  400. ImageClassName:=TgxBlankImage.ClassName;
  401. Enabled:=True;
  402. MinFilter:=miNearestMipmapNearest;
  403. //MagFilter:=maNearest;
  404. MagFilter:=maLinear;
  405. TextureMode:=tmReplace;
  406. //TextureWrap:=twBoth;
  407. TextureWrap:=twNone;
  408. //TextureFormat:=tfRGB16;
  409. //TextureFormat:=tfRGBA16;
  410. TextureFormat:=tfLuminanceAlpha;
  411. bmp32:=(Image as TgxBlankImage).GetBitmap32(GL_TEXTURE_2D);
  412. GenerateShadowMap(HD , bmp32, 1);
  413. end;
  414. libMat.Name:=MatName;
  415. //----------------------------------------------------
  416. end;
  417. //HD.MaterialName:=LibMat.Name;
  418. HD.LibMaterial:=LibMat; //attach texture to current tile
  419. libMat.UnregisterUser(Hold);
  420. Hold.Free;
  421. //Uno.Release;
  422. HeightDataSource.Data.UnlockList;
  423. if Assigned(FOnNewTilePrepared) then FOnNewTilePrepared(Self,HD,libMat);
  424. end;
  425. *)
  426. procedure TgxShadowHDS.GenerateShadowMap(HeightData: TgxHeightData;
  427. ShadowMap: TgxBitmap32; scale: single);
  428. var
  429. HD: TgxHeightData;
  430. x, y: integer; // in local space
  431. sx, sy: single;
  432. begin
  433. HD := HeightData;
  434. FTileSize := (HD.Size - 1);
  435. ShadowMap.Height := FTileSize;
  436. ShadowMap.Width := FTileSize;
  437. CalcStep;
  438. CalcScale;
  439. sx := Step.x;
  440. sy := Step.y;
  441. if abs(sx) > abs(sy) then
  442. begin
  443. y := 0;
  444. if sx < 0 then
  445. x := FTileSize - 1 // right to left
  446. else
  447. x := 0; // left to right
  448. while (y < FTileSize) do
  449. begin
  450. RayCastLine(HD, x, y, ShadowMap); // cast a shadow line across the tile
  451. inc(y);
  452. end;
  453. end
  454. else
  455. begin
  456. x := 0;
  457. if sy < 0 then
  458. y := FTileSize - 1 // top to bottom
  459. else
  460. y := 0; // bottom to top
  461. while (x < FTileSize) do
  462. begin
  463. RayCastLine(HD, x, y, ShadowMap); // cast a shadow line across the tile
  464. inc(x);
  465. end;
  466. end;
  467. end;
  468. function TgxShadowHDS.RayCastShadowHeight(HD: TgxHeightData;
  469. localX, localY: single): single;
  470. var
  471. tmpHD: TgxHeightData;
  472. Wx, Wy: single;
  473. Lx, Ly: single;
  474. h: single;
  475. ctr: integer;
  476. rh: single;
  477. dif: single;
  478. ShadowDif: single;
  479. startH: single;
  480. jump: integer;
  481. begin
  482. Lx := ClampValue(localX, 0, FTileSize);
  483. Ly := ClampValue(localY, 0, FTileSize);
  484. startH := HD.InterpolatedHeight(Lx, Ly);
  485. tmpHD := HD;
  486. ctr := 0;
  487. ShadowDif := 0;
  488. rh := startH;
  489. jump := 1;
  490. while (ctr < FScanDistance) and (tmpHD.DataState <> hdsNone) do
  491. begin
  492. Lx := Lx - Step.x * jump;
  493. Ly := Ly - Step.y * jump;
  494. rh := rh - Step.Z * jump;
  495. // --jump to new tile--
  496. if (Lx < 0) or (Lx >= FTileSize) or (Ly < 0) or (Ly >= FTileSize) then
  497. begin
  498. LocalToWorld(Lx, Ly, tmpHD, Wx, Wy);
  499. // if our local coordinates are off the tile,
  500. WorldToLocal(Wx, Wy, tmpHD, Lx, Ly);
  501. // get the new tile, and local coordinates
  502. end
  503. else
  504. begin
  505. h := tmpHD.InterpolatedHeight(Lx, Ly);
  506. dif := h - rh;
  507. ShadowDif := MaxFloat(dif, ShadowDif);
  508. if ShadowDif > (-Step.Z) + FSoftRange
  509. // if ray is more than 1 steps above the surface
  510. then
  511. jump := 2 // then take 2 steps at a time
  512. else
  513. jump := 1;
  514. inc(ctr);
  515. end;
  516. end;
  517. result := startH + ShadowDif; // actual height of shadow
  518. end;
  519. procedure TgxShadowHDS.LocalToWorld(Lx, Ly: single; HD: TgxHeightData;
  520. var Wx: single; var Wy: single);
  521. var
  522. HDS: TgxHeightDataSource;
  523. begin
  524. HDS := Self.HeightDataSource;
  525. Wx := Lx + HD.XLeft;
  526. Wy := HDS.Height - HD.YTop - Ly;
  527. // wrap terrain //no longer needed?
  528. // if wx>=HDS.Width then wx:=wx-HDS.Width;
  529. // if wx<0 then wx:=wx+HDS.Width;
  530. // if wy>=HDS.Height then wy:=wy-HDS.Height;
  531. // if wy<0 then wy:=wy+HDS.Height;
  532. end;
  533. procedure TgxShadowHDS.WorldToLocal(Wx, Wy: single; var HD: TgxHeightData;
  534. var Lx: single; var Ly: single);
  535. var
  536. HDS: TgxHeightDataSource;
  537. XLeft, YTop: integer;
  538. Size: integer;
  539. begin
  540. // wrap terrain //no longer needed?
  541. // HDS:=self.HeightDataSource;
  542. // if wx>=HDS.Width then wx:=wx-HDS.Width;
  543. // if wx<0 then wx:=wx+HDS.Width;
  544. // if wy>=HDS.Height then wy:=wy-HDS.Height;
  545. // if wy<0 then wy:=wy+HDS.Height;
  546. HDS := Self.HeightDataSource;
  547. Size := FTileSize;
  548. XLeft := floor(Wx / Size) * Size;
  549. Lx := Wx - XLeft;
  550. YTop := floor((HDS.Height - Wy) / Size) * Size;
  551. Ly := (HDS.Height - YTop - Wy);
  552. HD := HDS.GetData(XLeft, YTop, Size + 1, hdtSmallInt);
  553. end;
  554. // ----------------------------------------------------------
  555. procedure TgxShadowHDS.RayCastLine(HeightData: TgxHeightData; Lx, Ly: single;
  556. ShadowMap: TgxBitmap32);
  557. var
  558. sh, h: single;
  559. HD: TgxHeightData;
  560. Size: integer;
  561. nmRow: PgxPixel32Array;
  562. ctr: integer;
  563. px, py: integer;
  564. lum: byte;
  565. wrapDst: integer;
  566. // pink:boolean;
  567. // PinkMax:integer;
  568. cx, cy: single;
  569. procedure LineStep; // draw the pixel, and increase counters
  570. begin
  571. cx := ClampValue(Lx, 0, Size - 1);
  572. cy := ClampValue(Ly, 0, Size - 1);
  573. px := trunc(cx);
  574. py := trunc(cy);
  575. h := HD.InterpolatedHeight(cx, cy);
  576. sh := MaxFloat(sh, h);
  577. lum := Shade(HD, px, py, sh, h);
  578. nmRow := ShadowMap.ScanLine[Size - 1 - py];
  579. nmRow[px].r := lum;
  580. nmRow[px].g := lum;
  581. nmRow[px].b := lum;
  582. nmRow[px].a := 255;
  583. // pinkMax:=MinInteger(Integer(lum+8),255);
  584. // if pink=true then nmRow[px].r:=pinkMax;
  585. Lx := Lx + Step.x;
  586. Ly := Ly + Step.y;
  587. sh := sh + Step.Z;
  588. inc(ctr);
  589. end;
  590. begin
  591. HD := HeightData;
  592. sh := RayCastShadowHeight(HD, Lx, Ly);
  593. Size := FTileSize;
  594. ctr := 0;
  595. wrapDst := WrapDist(Lx, Ly);
  596. // pink:=false;
  597. if wrapDst < Size then
  598. begin // check if this line will wrap before its end
  599. while ctr <= wrapDst do
  600. LineStep; // take one exta step, to prevent gaps due to rounding errors
  601. Lx := Lx - Step.x; //
  602. Ly := Ly - Step.y; // step back, to compensate for the extra step
  603. ctr := ctr - 1; //
  604. if abs(Step.x) > abs(Step.y) then
  605. begin // East or West
  606. if Step.y < 0 then
  607. Ly := Ly + Size; // ESE or WSW
  608. if Step.y > 0 then
  609. Ly := Ly - Size; // ENE or WNW
  610. end
  611. else
  612. begin // North or South
  613. if Step.x < 0 then
  614. Lx := Lx + Size; // NNW or SSW
  615. if Step.x > 0 then
  616. Lx := Lx - Size; // NNE or SSE
  617. end;
  618. cx := ClampValue(Lx, 0, Size - 1);
  619. cy := ClampValue(Ly, 0, Size - 1);
  620. sh := RayCastShadowHeight(HD, cx, cy);
  621. sh := sh + Step.Z * 0.4;
  622. // pink:=true;
  623. end;
  624. while ctr < Size do
  625. LineStep; // No wrapping
  626. end;
  627. // ----------------------------------------------------------
  628. function TgxShadowHDS.WrapDist(Lx, Ly: single): integer;
  629. var
  630. x, y: single;
  631. Size: integer;
  632. sx, sy: single;
  633. begin
  634. sx := Step.x;
  635. sy := Step.y;
  636. Size := FTileSize;
  637. x := Size;
  638. y := Size;
  639. if abs(sx) > abs(sy) then
  640. begin
  641. if sy > 0 then
  642. y := (Size - Ly) / sy;
  643. if sy < 0 then
  644. y := -Ly / sy;
  645. end
  646. else
  647. begin
  648. if sx > 0 then
  649. x := (Size - Lx) / sx;
  650. if sx < 0 then
  651. x := -Lx / sx;
  652. end;
  653. result := Ceil(minFloat(x, y));
  654. end;
  655. function TgxShadowHDS.Shade(HeightData: TgxHeightData; x, y: integer;
  656. ShadowHeight, TerrainHeight: single): byte;
  657. var
  658. HD: TgxHeightData;
  659. nv: TAffineVector;
  660. dot: single;
  661. sunVec: TAffineVector;
  662. directLight: single;
  663. // Range:0-1 (0 if in shadow) (<1 and >0 if near shadow edge)
  664. diffuseLight: single;
  665. ambientLight: single;
  666. Light: single;
  667. begin
  668. HD := HeightData;
  669. nv := HD.NormalAtNode(x, y, FScaleVec);
  670. // --Ambient Light from blue sky (directly up)--
  671. ambientLight := nv.Z;
  672. // --Shadows/Direct light/Soft shadow edges--
  673. directLight := ClampValue(1 - (ShadowHeight - TerrainHeight) /
  674. SoftRange, 0, 1);
  675. // --Diffuse light, when not in shadow--
  676. if directLight = 0 then
  677. diffuseLight := 0 // no direct light (shadow)
  678. else
  679. begin // diffused light ~ cos of normalVec and lightVec
  680. MakeVector(sunVec, LightVector.x, LightVector.y, -LightVector.Z);
  681. NormalizeVector(sunVec);
  682. dot := VectorDotProduct(nv, sunVec);
  683. // cos of the angle between the normal and light
  684. diffuseLight := MaxFloat(dot, 0);
  685. end;
  686. // -------------------------------------
  687. Light := (FDiffuse * diffuseLight * directLight) + (FAmbient * ambientLight);
  688. result := round(ClampValue(Light, 0, 1) * 255);
  689. end;
  690. procedure TgxShadowHDS.SetShadowmapLibrary(const val: TgxMaterialLibrary);
  691. begin
  692. if val <> FShadowmapLibrary then
  693. begin
  694. if assigned(FShadowmapLibrary) then
  695. FShadowmapLibrary.RemoveFreeNotification(Self);
  696. FShadowmapLibrary := val;
  697. if assigned(FShadowmapLibrary) then
  698. FShadowmapLibrary.FreeNotification(Self);
  699. MarkDirty;
  700. end;
  701. end;
  702. procedure TgxShadowHDS.SetScale(AValue: TgxCoordinates);
  703. begin
  704. with OwnerHDS.Data.LockList do
  705. try
  706. FScale.Assign(AValue);
  707. // CalcScale;
  708. // MarkDirty;
  709. finally
  710. OwnerHDS.Data.UnlockList;
  711. end;
  712. end;
  713. procedure TgxShadowHDS.SetSoftRange(AValue: cardinal);
  714. begin
  715. with OwnerHDS.Data.LockList do
  716. try
  717. FSoftRange := MaxInteger(AValue, 1);
  718. // MarkDirty;
  719. finally
  720. OwnerHDS.Data.UnlockList;
  721. end;
  722. end;
  723. procedure TgxShadowHDS.SetDiffuse(AValue: single);
  724. begin
  725. with OwnerHDS.Data.LockList do
  726. try
  727. FDiffuse := ClampValue(AValue, 0.001, 1);
  728. // MarkDirty;
  729. finally
  730. OwnerHDS.Data.UnlockList;
  731. end;
  732. end;
  733. procedure TgxShadowHDS.SetAmbient(AValue: single);
  734. begin
  735. with OwnerHDS.Data.LockList do
  736. try
  737. FAmbient := ClampValue(AValue, 0.001, 1);
  738. // MarkDirty;
  739. finally
  740. OwnerHDS.Data.UnlockList;
  741. end;
  742. end;
  743. // ------------------------------------------------------------------
  744. initialization
  745. // ------------------------------------------------------------------
  746. RegisterClass(TgxShadowHDS);
  747. end.