GLS.ShadowHDS.pas 24 KB

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