1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.HeightData;
- (*
- Classes for height data access.
- The components and classes in the unit are the core data providers for
- height-based objects (terrain rendering mainly), they are independant
- from the rendering stage.
- In short: access to raw height data is performed by a TgxHeightDataSource
- subclass, that must take care of performing all necessary data access,
- cacheing and manipulation to provide TgxHeightData objects. A TgxHeightData
- is basicly a square, power of two dimensionned raster heightfield, and
- holds the data a renderer needs.
- *)
- interface
- {$I Stage.Defines.inc}
- uses
- Winapi.Windows, // for CreateMonochromeBitmap
- System.Classes,
- System.SysUtils,
- FMX.Objects,
- FMX.Graphics,
- Stage.VectorGeometry,
- Stage.Utils,
- GXS.BaseClasses,
- GXS.ApplicationFileIO,
- GXS.ImageUtils,
- GXS.Material;
- type
- TByteArray = array [0 .. MaxInt div (2 * SizeOf(Byte))] of Byte;
- TByteRaster = array [0 .. MaxInt div (2 * SizeOf(Pointer))] of PByteArray;
- PByteRaster = ^TByteRaster;
- TSmallintArray = array [0 .. MaxInt div (2 * SizeOf(SmallInt))] of SmallInt;
- PSmallIntArray = ^TSmallintArray;
- TSmallIntRaster = array [0 .. MaxInt div (2 * SizeOf(Pointer))
- ] of PSmallIntArray;
- PSmallIntRaster = ^TSmallIntRaster;
- TSingleRaster = array [0 .. MaxInt div (2 * SizeOf(Pointer))] of PSingleArray;
- PSingleRaster = ^TSingleRaster;
- TgxHeightData = class;
- TgxHeightDataClass = class of TgxHeightData;
- (* Determines the type of data stored in a TgxHeightData.
- There are 3 data types (8 bits unsigned, signed 16 bits and 32 bits).
- Conversions: (128*(ByteValue-128)) = SmallIntValue = Round(SingleValue).
- The 'hdtDefault' type is used for request only, and specifies that the
- default type for the source should be used. *)
- TgxHeightDataType = (hdtByte, hdtSmallInt, hdtSingle, hdtDefault);
- (* Base class for height datasources.
- This class is abstract and presents the standard interfaces for height
- data retrieval (TgxHeightData objects). The class offers the following
- features (that a subclass may decide to implement or not, what follow
- is the complete feature set, check subclass doc to see what is actually
- supported):
- Pooling / Cacheing (return a TgxHeightData with its "Release" method)
- Pre-loading : specify a list of TgxHeightData you want to preload
- Multi-threaded preload/queueing : specified list can be loaded in
- a background task *)
- TgxHeightDataSource = class(TComponent)
- private
- FData: TThreadList; // stores all TgxHeightData, whatever their state/type
- FDataHash: array [0 .. 255] of TList; // X/Y hash references for HeightDatas
- FThread: TThread; // queue manager
- FMaxThreads: Integer;
- FMaxPoolSize: Integer;
- FHeightDataClass: TgxHeightDataClass;
- // FReleaseLatency : TDateTime; //Not used anymore???
- FDefaultHeight: Single;
- protected
- procedure SetMaxThreads(const Val: Integer);
- function HashKey(XLeft, YTop: Integer): Integer;
- // Adjust this property in you subclasses.
- property HeightDataClass: TgxHeightDataClass read FHeightDataClass
- write FHeightDataClass;
- (* Looks up the list and returns the matching TgxHeightData, if any. *)
- function FindMatchInList(XLeft, YTop, size: Integer;
- DataType: TgxHeightDataType): TgxHeightData;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- (* Access to currently pooled TgxHeightData objects, and Thread locking *)
- property Data: TThreadList read FData;
- (* Empties the Data list, terminating thread if necessary.
- If some TgxHeightData are hdsInUse, triggers an exception and does
- nothing. *)
- procedure Clear;
- (* Removes less used TDataHeight objects from the pool.
- Only removes objects whose state is hdsReady and UseCounter is zero,
- starting from the end of the list until total data size gets below
- MaxPoolSize (or nothing can be removed). *)
- procedure CleanUp;
- (* Base TgxHeightData requester method.
- Returns (by rebuilding it or from the cache) a TgxHeightData
- corresponding to the given area. Size must be a power of two.
- Subclasses may choose to publish it or just publish datasource-
- specific requester method using specific parameters. *)
- function GetData(XLeft, YTop, size: Integer; DataType: TgxHeightDataType)
- : TgxHeightData; virtual;
- // Preloading request. See GetData for details.
- function PreLoad(XLeft, YTop, size: Integer; DataType: TgxHeightDataType)
- : TgxHeightData; virtual;
- // Replacing dirty tiles.
- procedure PreloadReplacement(aHeightData: TgxHeightData);
- (* Notification that the data is no longer used by the renderer.
- Default behaviour is just to change DataState to hdsReady (ie. return
- the data to the pool) *)
- procedure Release(aHeightData: TgxHeightData); virtual;
- (* Marks the given area as "dirty" (ie source data changed).
- All loaded and in-cache tiles overlapping the area are flushed. *)
- procedure MarkDirty(const Area: TRect); overload; virtual;
- procedure MarkDirty(XLeft, YTop, xRight, yBottom: Integer); overload;
- procedure MarkDirty; overload;
- (* Maximum number of background threads.
- If 0 (zero), multithreading is disabled and StartPreparingData
- will be called from the mainthread, and all preload requirements
- (queued TgxHeightData objects) will be loaded in sequence from
- the main thread.
- If 1, basic multithreading and queueing gets enabled,
- ie. StartPreparingData will be called from a thread, but from one
- thread only (ie. there is no need to implement a TgxHeightDataThread,
- just make sure StartPreparingData code is thread-safe).
- Other values (2 and more) are relevant only if you implement
- a TgxHeightDataThread subclass and fire it in StartPreparingData. *)
- property MaxThreads: Integer read FMaxThreads write SetMaxThreads;
- (* Maximum Size of TDataHeight pool in bytes.
- The pool (cache) can actually get larger if more data than the pool
- can accomodate is used, but as soon as data gets released and returns
- to the pool, TDataHeight will be freed until total pool Size gets
- below this figure.
- The pool manager frees TDataHeight objects who haven't been requested
- for the longest time first.
- The default value of zero effectively disables pooling. *)
- property MaxPoolSize: Integer read FMaxPoolSize write FMaxPoolSize;
- // Height to return for undefined tiles.
- property DefaultHeight: Single read FDefaultHeight write FDefaultHeight;
- // Interpolates height for the given point.
- function InterpolatedHeight(x, y: Single; tileSize: Integer)
- : Single; virtual;
- function Width: Integer; virtual; abstract;
- function Height: Integer; virtual; abstract;
- procedure ThreadIsIdle; virtual;
- (* This is called BEFORE StartPreparing Data, but always from the main thread. *)
- procedure BeforePreparingData(HeightData: TgxHeightData); virtual;
- (* Request to start preparing data.
- If your subclass is thread-enabled, this is here that you'll create
- your thread and fire it (don't forget the requirements), if not,
- that'll be here you'll be doing your work.
- Either way, you are responsible for adjusting the DataState to
- hdsReady when you're done (DataState will be hdsPreparing when this
- method will be invoked). *)
- procedure StartPreparingData(HeightData: TgxHeightData); virtual;
- (* This is called After "StartPreparingData", but always from the main thread. *)
- procedure AfterPreparingData(HeightData: TgxHeightData); virtual;
- procedure TextureCoordinates(HeightData: TgxHeightData;
- Stretch: boolean = false);
- end;
- THDTextureCoordinatesMode = (tcmWorld, tcmLocal);
- (* Possible states for a TgxHeightData.
- hdsQueued : the data has been queued for loading
- hdsPreparing : the data is currently loading or being prepared for use
- hdsReady : the data is fully loaded and ready for use
- hdsNone : the height data does not exist for this tile *)
- TgxHeightDataState = (hdsQueued, hdsPreparing, hdsReady, hdsNone);
- TgxHeightDataThread = class;
- TOnHeightDataDirtyEvent = procedure(sender: TgxHeightData) of object;
- TgxHeightDataUser = record
- user: TObject;
- event: TOnHeightDataDirtyEvent;
- end;
- (* Base class for height data, stores a height-field raster.
- The raster is a square, whose Size must be a power of two. Data can be
- accessed through a base pointer ("ByteData[n]" f.i.), or through pointer
- indirections ("ByteRaster[y][x]" f.i.), this are the fastest way to access
- height data (and the most unsecure).
- Secure (with range checking) data access is provided by specialized
- methods (f.i. "ByteHeight"), in which coordinates (x & y) are always
- considered relative (like in raster access).
- The class offers conversion facility between the types (as a whole data
- conversion), but in any case, the TgxHeightData should be directly requested
- from the TgxHeightDataSource with the appropriate format.
- Though this class can be instantiated, you will usually prefer to subclass
- it in real-world cases, f.i. to add texturing data. *)
- /// TgxHeightData = class (TObject)
- TgxHeightData = class(TgxUpdateAbleObject)
- private
- FUsers: array of TgxHeightDataUser;
- FOwner: TgxHeightDataSource;
- FDataState: TgxHeightDataState;
- FSize: Integer;
- FXLeft, FYTop: Integer;
- FUseCounter: Integer;
- FDataType: TgxHeightDataType;
- FDataSize: Integer;
- FByteData: PByteArray;
- FByteRaster: PByteRaster;
- FSmallIntData: PSmallIntArray;
- FSmallIntRaster: PSmallIntRaster;
- FSingleData: PSingleArray;
- FSingleRaster: PSingleRaster;
- FTextureCoordinatesMode: THDTextureCoordinatesMode;
- FTCOffset, FTCScale: TTexPoint;
- FMaterialName: String; // Unsafe. Use FLibMaterial instead
- FLibMaterial: TgxLibMaterial;
- FObjectTag: TObject;
- FTag, FTag2: Integer;
- FOnDestroy: TNotifyEvent;
- FDirty: boolean;
- FHeightMin, FHeightMax: Single;
- procedure BuildByteRaster;
- procedure BuildSmallIntRaster;
- procedure BuildSingleRaster;
- procedure ConvertByteToSmallInt;
- procedure ConvertByteToSingle;
- procedure ConvertSmallIntToByte;
- procedure ConvertSmallIntToSingle;
- procedure ConvertSingleToByte;
- procedure ConvertSingleToSmallInt;
- protected
- FThread: TgxHeightDataThread;
- // thread used for multi-threaded processing (if any)
- procedure SetDataType(const Val: TgxHeightDataType);
- procedure SetMaterialName(const MaterialName: string);
- procedure SetLibMaterial(LibMaterial: TgxLibMaterial);
- function GetHeightMin: Single;
- function GetHeightMax: Single;
- public
- OldVersion: TgxHeightData; // previous version of this tile
- NewVersion: TgxHeightData; // the replacement tile
- DontUse: boolean; // Tells TerrainRenderer which version to use
- // constructor Create(AOwner : TComponent); override;
- constructor Create(AOwner: TgxHeightDataSource; aXLeft, aYTop, aSize: Integer;
- aDataType: TgxHeightDataType); reintroduce; virtual;
- destructor Destroy; override;
- { The component who created and maintains this data. }
- property Owner: TgxHeightDataSource read FOwner;
- { Fired when the object is destroyed. }
- property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
- { Counter for use registration.
- A TgxHeightData is not returned to the pool until this counter reaches
- a value of zero. }
- property UseCounter: Integer read FUseCounter;
- { Increments UseCounter.
- User objects should implement a method that will be notified when
- the data becomes dirty, when invoked they should release the heightdata
- immediately after performing their own cleanups. }
- procedure RegisterUse;
- { Allocate memory and prepare lookup tables for current datatype.
- Fails if already allocated. Made Dynamic to allow descendants }
- procedure Allocate(const Val: TgxHeightDataType); virtual;
- { Decrements UseCounter.
- When the counter reaches zero, notifies the Owner TgxHeightDataSource
- that the data is no longer used.
- The renderer should call Release when it no longer needs a THeighData,
- and never free/destroy the object directly. }
- procedure Release;
- { Marks the tile as dirty.
- The immediate effect is currently the destruction of the tile. }
- procedure MarkDirty;
- { World X coordinate of top left point. }
- property XLeft: Integer read FXLeft;
- { World Y coordinate of top left point. }
- property YTop: Integer read FYTop;
- { Type of the data.
- Assigning a new datatype will result in the data being converted. }
- property DataType: TgxHeightDataType read FDataType write SetDataType;
- { Current state of the data. }
- property DataState: TgxHeightDataState read FDataState write FDataState;
- { Size of the data square, in data units. }
- property Size: Integer read FSize;
- { True if the data is dirty (ie. no longer up-to-date). }
- property Dirty: boolean read FDirty write FDirty;
- { Memory Size of the raw data in bytes. }
- property DataSize: Integer read FDataSize;
- { Access to data as a byte array (n = y*Size+x).
- If TgxHeightData is not of type hdtByte, this value is nil. }
- property ByteData: PByteArray read FByteData;
- { Access to data as a byte raster (y, x).
- If TgxHeightData is not of type hdtByte, this value is nil. }
- property ByteRaster: PByteRaster read FByteRaster;
- { Access to data as a SmallInt array (n = y*Size+x).
- If TgxHeightData is not of type hdtSmallInt, this value is nil. }
- property SmallIntData: PSmallIntArray read FSmallIntData;
- { Access to data as a SmallInt raster (y, x).
- If TgxHeightData is not of type hdtSmallInt, this value is nil. }
- property SmallIntRaster: PSmallIntRaster read FSmallIntRaster;
- { Access to data as a Single array (n = y*Size+x).
- If TgxHeightData is not of type hdtSingle, this value is nil. }
- property SingleData: PSingleArray read FSingleData;
- { Access to data as a Single raster (y, x).
- If TgxHeightData is not of type hdtSingle, this value is nil. }
- property SingleRaster: PSingleRaster read FSingleRaster;
- { Name of material for the tile (if terrain uses multiple materials). }
- // property MaterialName : String read FMaterialName write FMaterialName;
- // (WARNING: Unsafe when deleting textures! If possible, rather use LibMaterial.)
- property MaterialName: String read FMaterialName write SetMaterialName;
- // property LibMaterial : Links directly to the tile's TgxLibMaterial.
- // Unlike 'MaterialName', this property also registers the tile as
- // a user of the texture.
- // This prevents TgxLibMaterials.DeleteUnusedTextures from deleting the
- // used texture by mistake and causing Access Violations.
- // Use this instead of the old MaterialName property, to prevent AV's.
- property LibMaterial: TgxLibMaterial read FLibMaterial write SetLibMaterial;
- { Texture coordinates generation mode.
- Default is tcmWorld coordinates. }
- property TextureCoordinatesMode: THDTextureCoordinatesMode
- read FTextureCoordinatesMode write FTextureCoordinatesMode;
- property TextureCoordinatesOffset: TTexPoint read FTCOffset write FTCOffset;
- property TextureCoordinatesScale: TTexPoint read FTCScale write FTCScale;
- { Height of point x, y as a Byte. }
- function ByteHeight(x, y: Integer): Byte;
- { Height of point x, y as a SmallInt. }
- function SmallIntHeight(x, y: Integer): SmallInt;
- { Height of point x, y as a Single. }
- function SingleHeight(x, y: Integer): Single;
- { Interopolated height of point x, y as a Single. }
- function InterpolatedHeight(x, y: Single): Single;
- { Minimum height in the tile.
- DataSources may assign a value to prevent automatic computation
- if they have a faster/already computed value. }
- property HeightMin: Single read GetHeightMin write FHeightMin;
- { Maximum height in the tile.
- DataSources may assign a value to prevent automatic computation
- if they have a faster/already computed value. }
- property HeightMax: Single read GetHeightMax write FHeightMax;
- { Returns the height as a single, whatever the DataType (slow). }
- function Height(x, y: Integer): Single;
- { Calculates and returns the normal for vertex point x, y.
- Sub classes may provide normal cacheing, the default implementation
- being rather blunt. }
- function Normal(x, y: Integer; const scale: TAffineVector)
- : TAffineVector; virtual;
- { Calculates and returns the normal for cell x, y.(between vertexes) }
- function NormalAtNode(x, y: Integer; const scale: TAffineVector)
- : TAffineVector; virtual;
- { Returns True if the data tile overlaps the area. }
- function OverlapsArea(const Area: TRect): boolean;
- { Reserved for renderer use. }
- property ObjectTag: TObject read FObjectTag write FObjectTag;
- { Reserved for renderer use. }
- property Tag: Integer read FTag write FTag;
- { Reserved for renderer use. }
- property Tag2: Integer read FTag2 write FTag2;
- { Used by perlin HDS. }
- property Thread: TgxHeightDataThread read FThread write FThread;
- end;
- { A thread specialized for processing TgxHeightData in background.
- Requirements:
- must have FreeOnTerminate set to true,
- must check and honour Terminated swiftly }
- TgxHeightDataThread = class(TThread)
- protected
- FHeightData: TgxHeightData;
- public
- destructor Destroy; override;
- { The Height Data the thread is to prepare. }
- property HeightData: TgxHeightData read FHeightData write FHeightData;
- end;
- { Bitmap-based Height Data Source.
- The image is automatically wrapped if requested data is out of picture Size,
- or if requested data is larger than the picture.
- The internal format is an 8 bit bitmap whose dimensions are a power of two,
- if the original image does not comply, it is StretchDraw'ed on a monochrome
- (gray) bitmap. }
- TgxBitmapHDS = class(TgxHeightDataSource)
- private
- FScanLineCache: array of PByteArray;
- FBitmap: TBitmap;
- FPicture: TImage;
- FInfiniteWrap: boolean;
- FInverted: boolean;
- protected
- procedure SetPicture(const Val: TImage);
- procedure OnPictureChanged(sender: TObject);
- procedure SetInfiniteWrap(Val: boolean);
- procedure SetInverted(Val: boolean);
- procedure CreateMonochromeBitmap(size: Integer);
- procedure FreeMonochromeBitmap;
- function GetScanLine(y: Integer): PByteArray;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure StartPreparingData(HeightData: TgxHeightData); override;
- procedure MarkDirty(const Area: TRect); override;
- function Width: Integer; override;
- function Height: Integer; override;
- published
- { The picture serving as Height field data reference.
- The picture is (if not already) internally converted to a 8 bit
- bitmap (grayscale). For better performance and to save memory,
- feed it this format! }
- property Picture: TImage read FPicture write SetPicture;
- { If true the height field is wrapped indefinetely. }
- property InfiniteWrap: boolean read FInfiniteWrap write SetInfiniteWrap
- default True;
- { If true, the rendered terrain is a mirror image of the input data. }
- property Inverted: boolean read FInverted write SetInverted default True;
- property MaxPoolSize;
- end;
- TStartPreparingDataEvent = procedure(HeightData: TgxHeightData) of object;
- TMarkDirtyEvent = procedure(const Area: TRect) of object;
- // TTexturedHeightDataSource = class (TgxTexturedHeightDataSource)
- { An Height Data Source for custom use.
- Provides event handlers for the various requests to be implemented
- application-side (for application-specific needs). }
- TgxCustomHDS = class(TgxHeightDataSource)
- private
- FOnStartPreparingData: TStartPreparingDataEvent;
- FOnMarkDirty: TMarkDirtyEvent;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure StartPreparingData(HeightData: TgxHeightData); override;
- procedure MarkDirty(const Area: TRect); override;
- published
- property MaxPoolSize;
- property OnStartPreparingData: TStartPreparingDataEvent
- read FOnStartPreparingData write FOnStartPreparingData;
- property OnMarkDirtyEvent: TMarkDirtyEvent read FOnMarkDirty
- write FOnMarkDirty;
- end;
- { TerrainBase-based Height Data Source.
- This component takes its data from the TerrainBase Gobal Terrain Model.
- Though it can be used directly, the resolution of the TerrainBase dataset
- isn't high enough for accurate short-range representation and the data
- should rather be used as basis for further (fractal) refinement.
- TerrainBase is freely available from the National Geophysical Data Center
- and World Data Center web site (http://ngdc.noaa.com).
- (this component expects to find "tbase.bin" in the current directory). }
- TgxTerrainBaseHDS = class(TgxHeightDataSource)
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure StartPreparingData(HeightData: TgxHeightData); override;
- published
- property MaxPoolSize;
- end;
- TgxHeightDataSourceFilter = Class;
- TSourceDataFetchedEvent = procedure(sender: TgxHeightDataSourceFilter;
- HeightData: TgxHeightData) of object;
- { Height Data Source Filter.
- This component sits between the TgxTerrainRenderer, and a real TgxHeightDataSource.
- i.e. TgxTerrainRenderer links to this. This links to the real TgxHeightDataSource.
- Use the 'HeightDataSource' property, to link to a source HDS.
- The 'OnSourceDataFetched' event then gives you the opportunity to make any changes,
- or link in a texture to the TgxHeightData object, BEFORE it is cached.
- It bypasses the cache of the source HDS, by calling the source's StartPreparingData procedure directly.
- The TgxHeightData objects are then cached by THIS component, AFTER you have made your changes.
- This eliminates the need to copy and release the TgxHeightData object from the Source HDS's cache,
- before linking your texture. See the new version of TgxBumpmapHDS for an example. (LIN)
- To create your own HDSFilters, Derive from this component, and override the PreparingData procedure. }
- TgxHeightDataSourceFilter = Class(TgxHeightDataSource)
- private
- FHDS: TgxHeightDataSource;
- FOnSourceDataFetched: TSourceDataFetchedEvent;
- FActive: boolean;
- protected
- { PreparingData:
- Override this function in your filter subclasses, to make any
- updates/changes to HeightData, before it goes into the cache.
- Make sure any code in this function is thread-safe, in case TAsyncHDS was used. }
- procedure PreparingData(HeightData: TgxHeightData); virtual; abstract;
- { SetHDS - Set HeightDataSource property }
- procedure SetHDS(Val: TgxHeightDataSource);
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Release(aHeightData: TgxHeightData); override;
- procedure StartPreparingData(HeightData: TgxHeightData); override;
- procedure Notification(AComponent: TComponent;
- Operation: TOperation); override;
- function Width: Integer; override;
- function Height: Integer; override;
- property OnSourceDataFetched: TSourceDataFetchedEvent
- read FOnSourceDataFetched write FOnSourceDataFetched;
- published
- property MaxPoolSize;
- property HeightDataSource: TgxHeightDataSource read FHDS write SetHDS;
- property Active: boolean read FActive write FActive;
- // If Active=False, height data passes through unchanged
- end;
- // ------------------------------------------------------------------
- implementation
- // ------------------------------------------------------------------
- // ------------------
- // ------------------ TgxHeightDataSourceThread ------------------
- // ------------------
- type
- TgxHeightDataSourceThread = class(TThread)
- FOwner: TgxHeightDataSource;
- procedure Execute; override;
- function WaitForTile(HD: TgxHeightData; seconds: Integer): boolean;
- procedure HDSIdle;
- end;
- // Execute
- //
- procedure TgxHeightDataSourceThread.Execute;
- var
- i: Integer;
- lst: TList;
- HD: TgxHeightData;
- max: Integer;
- TdCtr: Integer;
- begin
- while not Terminated do
- begin
- max := FOwner.MaxThreads;
- lst := FOwner.FData.LockList;
- // --count active threads--
- i := 0;
- TdCtr := 0;
- while (i < lst.Count) and (TdCtr < max) do
- begin
- if TgxHeightData(lst.Items[i]).FThread <> nil then
- Inc(TdCtr);
- Inc(i);
- end;
- // ------------------------
- // --Find the queued tiles, and Start preparing them--
- i := 0;
- While ((i < lst.Count) and (TdCtr < max)) do
- begin
- HD := TgxHeightData(lst.Items[i]);
- if HD.DataState = hdsQueued then
- begin
- FOwner.StartPreparingData(HD); // prepare
- Inc(TdCtr);
- end;
- Inc(i);
- end;
- // ---------------------------------------------------
- FOwner.FData.UnlockList;
- if (TdCtr = 0) then
- synchronize(HDSIdle);
- if (TdCtr = 0) then
- sleep(10)
- else
- sleep(0); // sleep longer if no Queued tiles were found
- end;
- end;
- // WaitForTile
- //
- // When Threading, wait a specified time, for the tile to finish preparing
- function TgxHeightDataSourceThread.WaitForTile(HD: TgxHeightData;
- seconds: Integer): boolean;
- var
- // i:integer;
- eTime: TDateTime;
- begin
- eTime := now + (1000 * seconds);
- while (HD.FThread <> nil) and (now < eTime) do
- begin
- sleep(0);
- end;
- Result := (HD.FThread = nil); // true if the thread has finished
- end;
- // HDSIdle
- //
- // When using threads, HDSIdle is called in the main thread,
- // whenever all HDS threads have finished, AND no queued tiles were found.
- // (GXS.AsyncHDS uses this for the OnIdle event.)
- procedure TgxHeightDataSourceThread.HDSIdle;
- begin
- self.FOwner.ThreadIsIdle;
- end;
- // ------------------
- // ------------------ TgxHeightDataSource ------------------
- // ------------------
- // Create
- //
- constructor TgxHeightDataSource.Create(AOwner: TComponent);
- var
- i: Integer;
- begin
- inherited Create(AOwner);
- FHeightDataClass := TgxHeightData;
- FData := TThreadList.Create;
- for i := 0 to High(FDataHash) do
- FDataHash[i] := TList.Create;
- // FReleaseLatency:=15/(3600*24);
- FThread := TgxHeightDataSourceThread.Create(True);
- FThread.FreeOnTerminate := false;
- TgxHeightDataSourceThread(FThread).FOwner := self;
- if self.MaxThreads > 0 then
- FThread.Start;
- end;
- // Destroy
- //
- destructor TgxHeightDataSource.Destroy;
- var
- i: Integer;
- begin
- inherited Destroy;
- if Assigned(FThread) then
- begin
- FThread.Terminate;
- FThread.Start;
- FThread.WaitFor;
- FThread.Free;
- end;
- Clear;
- FData.Free;
- for i := 0 to High(FDataHash) do
- FDataHash[i].Free;
- end;
- // Clear
- //
- procedure TgxHeightDataSource.Clear;
- var
- i: Integer;
- begin
- with FData.LockList do
- begin
- try
- for i := 0 to Count - 1 do
- if TgxHeightData(Items[i]).UseCounter > 0 then
- if not(csDestroying in ComponentState) then
- raise Exception.Create('ERR: HeightData still in use');
- for i := 0 to Count - 1 do
- begin
- TgxHeightData(Items[i]).FOwner := nil;
- TgxHeightData(Items[i]).Free;
- end;
- for i := 0 to High(FDataHash) do
- FDataHash[i].Clear;
- Clear;
- finally
- FData.UnlockList;
- end;
- end;
- end;
- // HashKey
- //
- function TgxHeightDataSource.HashKey(XLeft, YTop: Integer): Integer;
- begin
- Result := (XLeft + (XLeft shr 8) + (YTop shl 1) + (YTop shr 7)) and
- High(FDataHash);
- end;
- // FindMatchInList
- //
- function TgxHeightDataSource.FindMatchInList(XLeft, YTop, size: Integer;
- DataType: TgxHeightDataType): TgxHeightData;
- var
- i: Integer;
- HD: TgxHeightData;
- begin
- Result := nil;
- FData.LockList;
- try
- with FDataHash[HashKey(XLeft, YTop)] do
- for i := 0 to Count - 1 do
- begin
- HD := TgxHeightData(Items[i]);
- // if (not hd.Dirty) and (hd.XLeft=xLeft) and (hd.YTop=YTop) and (hd.Size=Size) and (hd.DataType=DataType) then begin
- if (HD.XLeft = XLeft) and (HD.YTop = YTop) and (HD.size = size) and
- (HD.DataType = DataType) and (HD.DontUse = false) then
- begin
- Result := HD;
- Break;
- end;
- end;
- finally
- FData.UnlockList;
- end;
- end;
- // GetData
- //
- function TgxHeightDataSource.GetData(XLeft, YTop, size: Integer;
- DataType: TgxHeightDataType): TgxHeightData;
- begin
- Result := FindMatchInList(XLeft, YTop, size, DataType);
- if not Assigned(Result) then
- Result := PreLoad(XLeft, YTop, size, DataType)
- else
- with FData.LockList do
- begin
- try
- Move(IndexOf(Result), 0); // Moves item to the beginning of the list.
- finally
- FData.UnlockList;
- end;
- end;
- // got one... can be used ?
- // while not (Result.DataState in [hdsReady, hdsNone]) do Sleep(0);
- end;
- // PreLoad
- //
- function TgxHeightDataSource.PreLoad(XLeft, YTop, size: Integer;
- DataType: TgxHeightDataType): TgxHeightData;
- begin
- Result := HeightDataClass.Create(self, XLeft, YTop, size, DataType);
- with FData.LockList do
- try
- Add(Result);
- BeforePreparingData(Result);
- FDataHash[HashKey(XLeft, YTop)].Add(Result);
- finally
- FData.UnlockList;
- end;
- // -- When NOT using Threads, fully prepare the tile immediately--
- if MaxThreads = 0 then
- begin
- StartPreparingData(Result);
- AfterPreparingData(Result);
- end;
- // ---------------------------------------------------------------
- end;
- // When Multi-threading, this queues a replacement for a dirty tile
- // The Terrain renderer will continue to use the dirty tile, until the replacement is complete
- procedure TgxHeightDataSource.PreloadReplacement(aHeightData: TgxHeightData);
- var
- HD: TgxHeightData;
- NewHD: TgxHeightData;
- begin
- Assert(MaxThreads > 0);
- HD := aHeightData;
- NewHD := HeightDataClass.Create(self, HD.XLeft, HD.YTop, HD.size,
- HD.DataType);
- with FData.LockList do
- try
- Add(NewHD);
- NewHD.OldVersion := HD; // link
- HD.NewVersion := NewHD; // link
- NewHD.DontUse := True;
- BeforePreparingData(NewHD);
- FDataHash[HashKey(HD.XLeft, HD.YTop)].Add(NewHD);
- finally
- FData.UnlockList;
- end;
- end;
- procedure TgxHeightDataSource.Release(aHeightData: TgxHeightData);
- begin
- // nothing, yet
- end;
- procedure TgxHeightDataSource.MarkDirty(const Area: TRect);
- var
- i: Integer;
- HD: TgxHeightData;
- begin
- with FData.LockList do
- begin
- try
- for i := Count - 1 downto 0 do
- begin
- HD := TgxHeightData(Items[i]);
- if HD.OverlapsArea(Area) then
- HD.MarkDirty;
- end;
- finally
- FData.UnlockList;
- end;
- end;
- end;
- procedure TgxHeightDataSource.MarkDirty(XLeft, YTop, xRight, yBottom: Integer);
- var
- r: TRect;
- begin
- r.Left := XLeft;
- r.Top := YTop;
- r.Right := xRight;
- r.Bottom := yBottom;
- MarkDirty(r);
- end;
- procedure TgxHeightDataSource.MarkDirty;
- const
- m = MaxInt - 1;
- begin
- MarkDirty(-m, -m, m, m);
- end;
- // CleanUp
- //
- procedure TgxHeightDataSource.CleanUp;
- var
- packList: boolean;
- i, k: Integer;
- usedMemory: Integer;
- HD: TgxHeightData;
- ReleaseThis: boolean;
- begin
- with FData.LockList do
- begin
- try
- usedMemory := 0;
- packList := false;
- // Cleanup dirty tiles and compute used memory
- for i := Count - 1 downto 0 do
- begin
- HD := TgxHeightData(Items[i]);
- if HD <> nil then
- with HD do
- begin
- // --Release criteria for dirty tiles--
- ReleaseThis := false;
- if HD.Dirty then
- begin // Only release dirty tiles
- if (MaxThreads = 0) then
- ReleaseThis := True
- // when not threading, delete ALL dirty tiles
- else if (HD.DataState <> hdsPreparing) then
- begin // Dont release Preparing tiles
- if (HD.UseCounter = 0) then
- ReleaseThis := True; // This tile is unused
- if (HD.NewVersion = nil) then
- ReleaseThis := True
- // This tile has no queued replacement to wait for
- else if (HD.DontUse) then
- ReleaseThis := True; // ??This tile has already been replaced.
- end;
- end;
- // ------------------------------------
- // if Dirty then ReleaseThis:=true;
- if ReleaseThis then
- begin
- FDataHash[HashKey(HD.XLeft, HD.YTop)].Remove(HD);
- Items[i] := nil;
- FOwner := nil;
- Free;
- packList := True;
- end
- else
- usedMemory := usedMemory + HD.DataSize;
- end;
- end;
- // If MaxPoolSize exceeded, release all that may be, and pack the list
- k := 0;
- if usedMemory > MaxPoolSize then
- begin
- for i := 0 to Count - 1 do
- begin
- HD := TgxHeightData(Items[i]);
- if HD <> nil then
- with HD do
- begin
- if (DataState <> hdsPreparing) and (UseCounter = 0) and
- (OldVersion = nil)
- // if (DataState=hdsReady)and(UseCounter=0)and(OldVersion=nil)
- then
- begin
- FDataHash[HashKey(HD.XLeft, HD.YTop)].Remove(HD);
- Items[i] := nil;
- FOwner := nil;
- Free;
- // packList:=True;
- end
- else
- begin
- Items[k] := HD;
- Inc(k);
- end;
- end;
- end;
- Count := k;
- end
- else if packList then
- begin
- for i := 0 to Count - 1 do
- if Items[i] <> nil then
- begin
- Items[k] := Items[i];
- Inc(k);
- end;
- Count := k;
- end;
- finally
- FData.UnlockList;
- end;
- end;
- end;
- // SetMaxThreads
- //
- procedure TgxHeightDataSource.SetMaxThreads(const Val: Integer);
- begin
- if (Val <= 0) then
- FMaxThreads := 0
- else
- begin
- // If we didn't do threading, but will now
- // resume our thread
- if (FMaxThreads <= 0) then
- FThread.Start;
- FMaxThreads := Val;
- end;
- end;
- // BeforePreparingData
- // Called BEFORE StartPreparingData, but always from the MAIN thread.
- // Override this in subclasses, to prepare for Threading.
- //
- procedure TgxHeightDataSource.BeforePreparingData(HeightData: TgxHeightData);
- begin
- //
- end;
- // StartPreparingData
- // When Threads are used, this runs from the sub-thread, so this MUST be thread-safe.
- // Any Non-thread-safe code should be placed in "BeforePreparingData"
- //
- procedure TgxHeightDataSource.StartPreparingData(HeightData: TgxHeightData);
- begin
- // Only the tile Owner may set the preparing tile to ready
- if (HeightData.Owner = self) and (HeightData.DataState = hdsPreparing) then
- HeightData.FDataState := hdsReady;
- end;
- // AfterPreparingData
- // Called AFTER StartPreparingData, but always from the MAIN thread.
- // Override this in subclasses, if needed.
- //
- procedure TgxHeightDataSource.AfterPreparingData(HeightData: TgxHeightData);
- begin
- //
- end;
- // ThreadIsIdle
- //
- procedure TgxHeightDataSource.ThreadIsIdle;
- begin
- // TgxAsyncHDS overrides this
- end;
- // TextureCoordinates
- // Calculates texture World texture coordinates for the current tile.
- // Use Stretch for OpenGL1.1, to hide the seams when using linear filtering.
- procedure TgxHeightDataSource.TextureCoordinates(HeightData: TgxHeightData;
- Stretch: boolean = false);
- var
- w, h, size: Integer;
- scaleS, scaleT: Single;
- offsetS, offsetT: Single;
- HD: TgxHeightData;
- halfpixel: Single;
- begin
- HD := HeightData;
- w := self.Width;
- h := self.Height;
- size := HD.FSize;
- // if GL_VERSION_1_2 then begin //OpenGL1.2 supports texture clamping, so seams dont show.
- if Stretch = false then
- begin // These are the real Texture coordinates
- scaleS := w / (size - 1);
- scaleT := h / (size - 1);
- offsetS := -((HD.XLeft / w) * scaleS);
- offsetT := -(h - (HD.YTop + size - 1)) / (size - 1);
- end
- else
- begin // --Texture coordinates: Stretched by 1 pixel, to hide seams on OpenGL-1.1(no Clamping)--
- scaleS := w / size;
- scaleT := h / size;
- halfpixel := 1 / (size shr 1);
- offsetS := -((HD.XLeft / w) * scaleS) + halfpixel;
- offsetT := -(h - (HD.YTop + size)) / size - halfpixel;
- end;
- HD.FTCScale.S := scaleS;
- HD.FTCScale.T := scaleT;
- HD.FTCOffset.S := offsetS;
- HD.FTCOffset.T := offsetT;
- end;
- // InterpolatedHeight
- //
- function TgxHeightDataSource.InterpolatedHeight(x, y: Single;
- tileSize: Integer): Single;
- var
- i: Integer;
- HD, foundHd: TgxHeightData;
- begin
- with FData.LockList do
- begin
- try
- // first, lookup data list to find if aHeightData contains our point
- foundHd := nil;
- for i := 0 to Count - 1 do
- begin
- HD := TgxHeightData(Items[i]);
- if (HD.XLeft <= x) and (HD.YTop <= y) and (HD.XLeft + HD.size - 1 > x)
- and (HD.YTop + HD.size - 1 > y) then
- begin
- foundHd := HD;
- Break;
- end;
- end;
- finally
- FData.UnlockList;
- end;
- end;
- if (foundHd = nil) or foundHd.Dirty then
- begin
- // not found, request one... slowest mode (should be avoided)
- if tileSize > 1 then
- foundHd := GetData(Round(x / (tileSize - 1) - 0.5) * (tileSize - 1),
- Round(y / (tileSize - 1) - 0.5) * (tileSize - 1), tileSize, hdtDefault)
- else
- begin
- Result := DefaultHeight;
- Exit;
- end;
- end
- else
- begin
- // request it using "standard" way (takes care of threads)
- foundHd := GetData(foundHd.XLeft, foundHd.YTop, foundHd.size,
- foundHd.DataType);
- end;
- if foundHd.DataState = hdsNone then
- Result := DefaultHeight
- else
- Result := foundHd.InterpolatedHeight(x - foundHd.XLeft, y - foundHd.YTop);
- end;
- // ------------------
- // ------------------ TgxHeightData ------------------
- // ------------------
- // Create
- //
- constructor TgxHeightData.Create(AOwner: TgxHeightDataSource;
- aXLeft, aYTop, aSize: Integer; aDataType: TgxHeightDataType);
- begin
- inherited Create(AOwner);
- SetLength(FUsers, 0);
- FOwner := AOwner;
- FXLeft := aXLeft;
- FYTop := aYTop;
- FSize := aSize;
- FTextureCoordinatesMode := tcmWorld;
- FTCScale := XYTexPoint;
- FDataType := aDataType;
- FDataState := hdsQueued;
- FHeightMin := 1E30;
- FHeightMax := 1E30;
- OldVersion := nil;
- NewVersion := nil;
- DontUse := false;
- end;
- // Destroy
- //
- destructor TgxHeightData.Destroy;
- begin
- Assert(Length(FUsers) = 0,
- 'You should *not* free a TgxHeightData, use "Release" instead');
- Assert(not Assigned(FOwner),
- 'You should *not* free a TgxHeightData, use "Release" instead');
- if Assigned(FThread) then
- begin
- FThread.Terminate;
- if FThread.Suspended then
- FThread.Start;
- FThread.WaitFor;
- end;
- if Assigned(FOnDestroy) then
- FOnDestroy(self);
- case DataType of
- hdtByte:
- begin
- FreeMem(FByteData);
- FreeMem(FByteRaster);
- end;
- hdtSmallInt:
- begin
- FreeMem(FSmallIntData);
- FreeMem(FSmallIntRaster);
- end;
- hdtSingle:
- begin
- FreeMem(FSingleData);
- FreeMem(FSingleRaster);
- end;
- hdtDefault:
- ; // nothing
- else
- Assert(false);
- end;
- // ----------------------
- self.LibMaterial := nil; // release a used material
- // --Break any link with a new/old version of this tile--
- if Assigned(self.OldVersion) then
- begin
- self.OldVersion.NewVersion := nil;
- self.OldVersion := nil;
- end;
- if Assigned(self.NewVersion) then
- begin
- self.NewVersion.OldVersion := nil;
- self.NewVersion := nil;
- end;
- // ------------------------------------------------------
- // ----------------------
- inherited Destroy;
- end;
- // RegisterUse
- //
- procedure TgxHeightData.RegisterUse;
- begin
- Inc(FUseCounter);
- end;
- // Release
- //
- procedure TgxHeightData.Release;
- begin
- if FUseCounter > 0 then
- Dec(FUseCounter);
- if FUseCounter = 0 then
- begin
- Owner.Release(self); // ???
- end;
- end;
- // MarkDirty
- //
- // Release Dirty tiles, unless threading, and the tile is being used.
- // In that case, start building a replacement tile instead.
- procedure TgxHeightData.MarkDirty;
- begin
- with Owner.Data.LockList do
- try
- if (not Dirty) and (DataState <> hdsQueued) then
- begin // dont mark queued tiles as dirty
- FDirty := True;
- if (Owner.MaxThreads > 0) and (FUseCounter > 0) then
- Owner.PreloadReplacement(self)
- else
- begin
- FUseCounter := 0;
- Owner.Release(self);
- end;
- end;
- finally
- Owner.Data.UnlockList;
- end;
- end;
- // Allocate
- //
- procedure TgxHeightData.Allocate(const Val: TgxHeightDataType);
- begin
- Assert(FDataSize = 0);
- case Val of
- hdtByte:
- begin
- FDataSize := size * size * SizeOf(Byte);
- GetMem(FByteData, FDataSize);
- BuildByteRaster;
- end;
- hdtSmallInt:
- begin
- FDataSize := size * size * SizeOf(SmallInt);
- GetMem(FSmallIntData, FDataSize);
- BuildSmallIntRaster;
- end;
- hdtSingle:
- begin
- FDataSize := size * size * SizeOf(Single);
- GetMem(FSingleData, FDataSize);
- BuildSingleRaster;
- end;
- else
- Assert(false);
- end;
- FDataType := Val;
- end;
- // WARNING: SetMaterialName does NOT register the tile as a user of this texture.
- // So, TgxLibMaterials.DeleteUnusedMaterials may see this material as unused, and delete it.
- // This may lead to AV's the next time this tile is rendered.
- // To be safe, rather assign the new TgxHeightData.LibMaterial property
- procedure TgxHeightData.SetMaterialName(const MaterialName: string);
- begin
- SetLibMaterial(nil);
- FMaterialName := MaterialName;
- end;
- procedure TgxHeightData.SetLibMaterial(LibMaterial: TgxLibMaterial);
- begin
- if Assigned(FLibMaterial) then
- FLibMaterial.UnregisterUser(self); // detach from old texture
- FLibMaterial := LibMaterial; // Attach new Material
- if Assigned(LibMaterial) then
- begin
- LibMaterial.RegisterUser(self); // Mark new Material as 'used'
- FMaterialName := LibMaterial.Name; // sync up MaterialName property
- end
- else
- FMaterialName := '';
- end;
- // SetDataType
- //
- procedure TgxHeightData.SetDataType(const Val: TgxHeightDataType);
- begin
- if (Val <> FDataType) and (Val <> hdtDefault) then
- begin
- if DataState <> hdsNone then
- begin
- case FDataType of
- hdtByte:
- case Val of
- hdtSmallInt:
- ConvertByteToSmallInt;
- hdtSingle:
- ConvertByteToSingle;
- else
- Assert(false);
- end;
- hdtSmallInt:
- case Val of
- hdtByte:
- ConvertSmallIntToByte;
- hdtSingle:
- ConvertSmallIntToSingle;
- else
- Assert(false);
- end;
- hdtSingle:
- case Val of
- hdtByte:
- ConvertSingleToByte;
- hdtSmallInt:
- ConvertSingleToSmallInt;
- else
- Assert(false);
- end;
- hdtDefault:
- ; // nothing, assume StartPreparingData knows what it's doing
- else
- Assert(false);
- end;
- end;
- FDataType := Val;
- end;
- end;
- // BuildByteRaster
- //
- procedure TgxHeightData.BuildByteRaster;
- var
- i: Integer;
- begin
- GetMem(FByteRaster, size * SizeOf(PByteArray));
- for i := 0 to size - 1 do
- FByteRaster^[i] := @FByteData[i * size]
- end;
- // BuildSmallIntRaster
- //
- procedure TgxHeightData.BuildSmallIntRaster;
- var
- i: Integer;
- begin
- GetMem(FSmallIntRaster, size * SizeOf(PSmallIntArray));
- for i := 0 to size - 1 do
- FSmallIntRaster^[i] := @FSmallIntData[i * size]
- end;
- // BuildSingleRaster
- //
- procedure TgxHeightData.BuildSingleRaster;
- var
- i: Integer;
- begin
- GetMem(FSingleRaster, size * SizeOf(PSingleArray));
- for i := 0 to size - 1 do
- FSingleRaster^[i] := @FSingleData[i * size]
- end;
- // ConvertByteToSmallInt
- //
- procedure TgxHeightData.ConvertByteToSmallInt;
- var
- i: Integer;
- begin
- FreeMem(FByteRaster);
- FByteRaster := nil;
- FDataSize := size * size * SizeOf(SmallInt);
- GetMem(FSmallIntData, FDataSize);
- for i := 0 to size * size - 1 do
- FSmallIntData^[i] := (FByteData^[i] - 128) shl 7;
- FreeMem(FByteData);
- FByteData := nil;
- BuildSmallIntRaster;
- end;
- // ConvertByteToSingle
- //
- procedure TgxHeightData.ConvertByteToSingle;
- var
- i: Integer;
- begin
- FreeMem(FByteRaster);
- FByteRaster := nil;
- FDataSize := size * size * SizeOf(Single);
- GetMem(FSingleData, FDataSize);
- for i := 0 to size * size - 1 do
- FSingleData^[i] := (FByteData^[i] - 128) shl 7;
- FreeMem(FByteData);
- FByteData := nil;
- BuildSingleRaster;
- end;
- // ConvertSmallIntToByte
- //
- procedure TgxHeightData.ConvertSmallIntToByte;
- var
- i: Integer;
- begin
- FreeMem(FSmallIntRaster);
- FSmallIntRaster := nil;
- FByteData := Pointer(FSmallIntData);
- for i := 0 to size * size - 1 do
- FByteData^[i] := (FSmallIntData^[i] div 128) + 128;
- FDataSize := size * size * SizeOf(Byte);
- ReallocMem(FByteData, FDataSize);
- FSmallIntData := nil;
- BuildByteRaster;
- end;
- // ConvertSmallIntToSingle
- //
- procedure TgxHeightData.ConvertSmallIntToSingle;
- var
- i: Integer;
- begin
- FreeMem(FSmallIntRaster);
- FSmallIntRaster := nil;
- FDataSize := size * size * SizeOf(Single);
- GetMem(FSingleData, FDataSize);
- for i := 0 to size * size - 1 do
- FSingleData^[i] := FSmallIntData^[i];
- FreeMem(FSmallIntData);
- FSmallIntData := nil;
- BuildSingleRaster;
- end;
- // ConvertSingleToByte
- //
- procedure TgxHeightData.ConvertSingleToByte;
- var
- i: Integer;
- begin
- FreeMem(FSingleRaster);
- FSingleRaster := nil;
- FByteData := Pointer(FSingleData);
- for i := 0 to size * size - 1 do
- FByteData^[i] := (Round(FSingleData^[i]) div 128) + 128;
- FDataSize := size * size * SizeOf(Byte);
- ReallocMem(FByteData, FDataSize);
- FSingleData := nil;
- BuildByteRaster;
- end;
- // ConvertSingleToSmallInt
- //
- procedure TgxHeightData.ConvertSingleToSmallInt;
- var
- i: Integer;
- begin
- FreeMem(FSingleRaster);
- FSingleRaster := nil;
- FSmallIntData := Pointer(FSingleData);
- for i := 0 to size * size - 1 do
- FSmallIntData^[i] := Round(FSingleData^[i]);
- FDataSize := size * size * SizeOf(SmallInt);
- ReallocMem(FSmallIntData, FDataSize);
- FSingleData := nil;
- BuildSmallIntRaster;
- end;
- // ByteHeight
- //
- function TgxHeightData.ByteHeight(x, y: Integer): Byte;
- begin
- Assert((Cardinal(x) < Cardinal(size)) and (Cardinal(y) < Cardinal(size)));
- Result := ByteRaster^[y]^[x];
- end;
- // SmallIntHeight
- //
- function TgxHeightData.SmallIntHeight(x, y: Integer): SmallInt;
- begin
- Assert((Cardinal(x) < Cardinal(size)) and (Cardinal(y) < Cardinal(size)));
- Result := SmallIntRaster^[y]^[x];
- end;
- // SingleHeight
- //
- function TgxHeightData.SingleHeight(x, y: Integer): Single;
- begin
- Assert((Cardinal(x) < Cardinal(size)) and (Cardinal(y) < Cardinal(size)));
- Result := SingleRaster^[y]^[x];
- end;
- // InterpolatedHeight
- //
- function TgxHeightData.InterpolatedHeight(x, y: Single): Single;
- var
- ix, iy, ixn, iyn: Integer;
- h1, h2, h3: Single;
- begin
- if FDataState = hdsNone then
- Result := 0
- else
- begin
- ix := Trunc(x);
- x := Frac(x);
- iy := Trunc(y);
- y := Frac(y);
- ixn := ix + 1;
- if ixn >= size then
- ixn := ix;
- iyn := iy + 1;
- if iyn >= size then
- iyn := iy;
- if x > y then
- begin
- // top-right triangle
- h1 := Height(ixn, iy);
- h2 := Height(ix, iy);
- h3 := Height(ixn, iyn);
- Result := h1 + (h2 - h1) * (1 - x) + (h3 - h1) * y;
- end
- else
- begin
- // bottom-left triangle
- h1 := Height(ix, iyn);
- h2 := Height(ixn, iyn);
- h3 := Height(ix, iy);
- Result := h1 + (h2 - h1) * (x) + (h3 - h1) * (1 - y);
- end;
- end;
- end;
- // Height
- //
- function TgxHeightData.Height(x, y: Integer): Single;
- begin
- case DataType of
- hdtByte:
- Result := (ByteHeight(x, y) - 128) shl 7;
- hdtSmallInt:
- Result := SmallIntHeight(x, y);
- hdtSingle:
- Result := SingleHeight(x, y);
- else
- Result := 0;
- Assert(false);
- end;
- end;
- // GetHeightMin
- //
- function TgxHeightData.GetHeightMin: Single;
- var
- i: Integer;
- b: Byte;
- sm: SmallInt;
- si: Single;
- begin
- if FHeightMin = 1E30 then
- begin
- if DataState = hdsReady then
- begin
- case DataType of
- hdtByte:
- begin
- b := FByteData^[0];
- for i := 1 to size * size - 1 do
- if FByteData^[i] < b then
- b := FByteData^[i];
- FHeightMin := ((Integer(b) - 128) shl 7);
- end;
- hdtSmallInt:
- begin
- sm := FSmallIntData^[0];
- for i := 1 to size * size - 1 do
- if FSmallIntData^[i] < sm then
- sm := FSmallIntData^[i];
- FHeightMin := sm;
- end;
- hdtSingle:
- begin
- si := FSingleData^[0];
- for i := 1 to size * size - 1 do
- if FSingleData^[i] < si then
- si := FSingleData^[i];
- FHeightMin := si;
- end;
- else
- FHeightMin := 0;
- end;
- end
- else
- FHeightMin := 0;
- end;
- Result := FHeightMin;
- end;
- // GetHeightMax
- //
- function TgxHeightData.GetHeightMax: Single;
- var
- i: Integer;
- b: Byte;
- sm: SmallInt;
- si: Single;
- begin
- if FHeightMax = 1E30 then
- begin
- if DataState = hdsReady then
- begin
- case DataType of
- hdtByte:
- begin
- b := FByteData^[0];
- for i := 1 to size * size - 1 do
- if FByteData^[i] > b then
- b := FByteData^[i];
- FHeightMax := ((Integer(b) - 128) shl 7);
- end;
- hdtSmallInt:
- begin
- sm := FSmallIntData^[0];
- for i := 1 to size * size - 1 do
- if FSmallIntData^[i] > sm then
- sm := FSmallIntData^[i];
- FHeightMax := sm;
- end;
- hdtSingle:
- begin
- si := FSingleData^[0];
- for i := 1 to size * size - 1 do
- if FSingleData^[i] > si then
- si := FSingleData^[i];
- FHeightMax := si;
- end;
- else
- FHeightMax := 0;
- end;
- end
- else
- FHeightMax := 0;
- end;
- Result := FHeightMax;
- end;
- // Normal
- //
- // Calculates the normal at a vertex
- function TgxHeightData.Normal(x, y: Integer; const scale: TAffineVector)
- : TAffineVector;
- var
- dx, dy: Single;
- begin
- if x > 0 then
- if x < size - 1 then
- dx := (Height(x + 1, y) - Height(x - 1, y))
- else
- dx := (Height(x, y) - Height(x - 1, y))
- else
- dx := (Height(x + 1, y) - Height(x, y));
- if y > 0 then
- if y < size - 1 then
- dy := (Height(x, y + 1) - Height(x, y - 1))
- else
- dy := (Height(x, y) - Height(x, y - 1))
- else
- dy := (Height(x, y + 1) - Height(x, y));
- Result.X := dx * scale.Y * scale.Z;
- Result.Y := dy * scale.X * scale.Z;
- Result.Z := scale.X * scale.Y;
- NormalizeVector(Result);
- end;
- // Calculates the normal at a surface cell (Between vertexes)
- function TgxHeightData.NormalAtNode(x, y: Integer; const scale: TAffineVector)
- : TAffineVector;
- var
- dx, dy, Hxy: Single;
- begin
- MinInteger(MaxInteger(x, 0), size - 2); // clamp x to 0 -> Size-2
- MinInteger(MaxInteger(y, 0), size - 2); // clamp x to 0 -> Size-2
- Hxy := Height(x, y);
- dx := Height(x + 1, y) - Hxy;
- dy := Height(x, y + 1) - Hxy;
- Result.X := dx * scale.Y * scale.Z; // Result[0]:=dx/scale[0];
- Result.Y := dy * scale.X * scale.Z; // Result[1]:=dy/scale[1];
- Result.Z := 1 * scale.X * scale.Y; // Result[2]:=1 /scale[2];
- NormalizeVector(Result);
- end;
- function TgxHeightData.OverlapsArea(const Area: TRect): boolean;
- begin
- Result := (XLeft <= Area.Right) and (YTop <= Area.Bottom) and
- (XLeft + size > Area.Left) and (YTop + size > Area.Top);
- end;
- // ------------------
- // ------------------ TgxHeightDataThread ------------------
- // ------------------
- destructor TgxHeightDataThread.Destroy;
- begin
- if Assigned(FHeightData) then
- FHeightData.FThread := nil;
- inherited;
- end;
- // ------------------
- // ------------------ TgxBitmapHDS ------------------
- // ------------------
- constructor TgxBitmapHDS.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- FPicture := TImage.Create(AOwner);
- FPicture.OnDblClick := OnPictureChanged;
- FInfiniteWrap := True;
- FInverted := True;
- end;
- destructor TgxBitmapHDS.Destroy;
- begin
- inherited Destroy;
- FreeMonochromeBitmap;
- FPicture.Free;
- end;
- procedure TgxBitmapHDS.SetPicture(const Val: TImage);
- begin
- FPicture.Assign(Val);
- end;
- procedure TgxBitmapHDS.OnPictureChanged(sender: TObject);
- var
- oldPoolSize, size: Integer;
- begin
- // cleanup pool
- oldPoolSize := MaxPoolSize;
- MaxPoolSize := 0;
- CleanUp;
- MaxPoolSize := oldPoolSize;
- // prepare MonoChromeBitmap
- FreeMonochromeBitmap;
- size := Round(Picture.Width);
- if size > 0 then
- CreateMonochromeBitmap(size);
- end;
- procedure TgxBitmapHDS.SetInfiniteWrap(Val: boolean);
- begin
- if FInfiniteWrap <> Val then
- begin
- FInfiniteWrap := Val;
- MarkDirty;
- end;
- end;
- procedure TgxBitmapHDS.SetInverted(Val: boolean);
- begin
- if FInverted = Val then
- Exit;
- FInverted := Val;
- MarkDirty;
- end;
- procedure TgxBitmapHDS.MarkDirty(const Area: TRect);
- begin
- inherited;
- FreeMonochromeBitmap;
- if Picture.Width > 0 then
- CreateMonochromeBitmap(Round(Picture.Width));
- end;
- procedure TgxBitmapHDS.CreateMonochromeBitmap(size: Integer);
- type
- TPaletteEntryArray = array [0 .. 255] of TPaletteEntry;
- PPaletteEntryArray = ^TPaletteEntryArray;
- TLogPal = record
- lpal: TLogPalette;
- pe: TPaletteEntryArray;
- end;
- var
- x: Integer;
- logpal: TLogPal;
- hPal: HPalette;
- begin
- size := RoundUpToPowerOf2(size);
- FBitmap := TBitmap.Create;
- { TODO -oPW : E2129 Cannot assign to a read-only property }
- (*FBitmap.PixelFormat := glpf8bit;*)
- FBitmap.Width := size;
- FBitmap.Height := size;
- for x := 0 to 255 do
- with PPaletteEntryArray(@logpal.lpal.palPalEntry[0])[x] do
- begin
- peRed := x;
- peGreen := x;
- peBlue := x;
- peFlags := 0;
- end;
- with logpal.lpal do
- begin
- palVersion := $300;
- palNumEntries := 256;
- end;
- hPal := CreatePalette(logpal.lpal);
- Assert(hPal <> 0);
- { TODO -oPW : E2003 Undeclared identifier: 'Palette' }
- (*FBitmap.Palette := hPal;*)
- // some picture formats trigger a "change" when drawed
- Picture.Bitmap.OnChange := nil;
- try
- { TODO -oPW : E2003 Undeclared identifier: 'StretchDraw' }
- (*FBitmap.StretchDraw(Rect(0, 0, size, size), Picture.Bitmap);*)
- finally
- Picture.Bitmap.OnChange := OnPictureChanged;
- end;
- SetLength(FScanLineCache, 0); // clear the cache
- SetLength(FScanLineCache, size);
- end;
- procedure TgxBitmapHDS.FreeMonochromeBitmap;
- begin
- SetLength(FScanLineCache, 0);
- FBitmap.Free;
- FBitmap := nil;
- end;
- function TgxBitmapHDS.GetScanLine(y: Integer): PByteArray;
- begin
- Result := FScanLineCache[y];
- if not Assigned(Result) then
- begin
- Result := BitmapScanLine(FBitmap, y); // FBitmap.ScanLine[y];
- FScanLineCache[y] := Result;
- end;
- end;
- procedure TgxBitmapHDS.StartPreparingData(HeightData: TgxHeightData);
- var
- y, x: Integer;
- bmpSize, wrapMask: Integer;
- bitmapLine, rasterLine: PByteArray;
- oldType: TgxHeightDataType;
- b: Byte;
- YPos: Integer;
- begin
- if FBitmap = nil then
- Exit;
- HeightData.FDataState := hdsPreparing;
- bmpSize := FBitmap.Width;
- wrapMask := bmpSize - 1;
- // retrieve data
- with HeightData do
- begin
- if (not InfiniteWrap) and ((XLeft >= bmpSize) or (XLeft < 0) or
- (YTop >= bmpSize) or (YTop < 0)) then
- begin
- HeightData.FDataState := hdsNone;
- Exit;
- end;
- oldType := DataType;
- Allocate(hdtByte);
- if Inverted then
- YPos := YTop
- else
- YPos := 1 - size - YTop;
- for y := 0 to size - 1 do
- begin
- bitmapLine := GetScanLine((y + YPos) and wrapMask);
- if Inverted then
- rasterLine := ByteRaster^[y]
- else
- rasterLine := ByteRaster^[size - 1 - y];
- // *BIG CAUTION HERE* : Don't remove the intermediate variable here!!!
- // or Delphi compiler will "optimize" to 32 bits access with clamping
- // Resulting in possible reads of stuff beyon bitmapLine length!!!!
- for x := XLeft to XLeft + size - 1 do
- begin
- b := bitmapLine^[x and wrapMask];
- rasterLine^[x - XLeft] := b;
- end;
- end;
- if (oldType <> hdtByte) and (oldType <> hdtDefault) then
- DataType := oldType;
- end;
- TextureCoordinates(HeightData);
- inherited;
- end;
- function TgxBitmapHDS.Width: Integer;
- begin
- if Assigned(self.FBitmap) then
- Result := self.FBitmap.Width
- else
- Result := 0;
- end;
- function TgxBitmapHDS.Height: Integer;
- begin
- if Assigned(self.FBitmap) then
- Result := self.FBitmap.Height
- else
- Result := 0;
- end;
- // ------------------
- // ------------------ TgxCustomHDS ------------------
- // ------------------
- constructor TgxCustomHDS.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- end;
- destructor TgxCustomHDS.Destroy;
- begin
- inherited Destroy;
- end;
- procedure TgxCustomHDS.MarkDirty(const Area: TRect);
- begin
- inherited;
- if Assigned(FOnMarkDirty) then
- FOnMarkDirty(Area);
- end;
- procedure TgxCustomHDS.StartPreparingData(HeightData: TgxHeightData);
- begin
- if Assigned(FOnStartPreparingData) then
- FOnStartPreparingData(HeightData);
- if HeightData.DataState <> hdsNone then
- HeightData.DataState := hdsReady;
- end;
- // ------------------
- // ------------------ TgxTerrainBaseHDS ------------------
- // ------------------
- constructor TgxTerrainBaseHDS.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- end;
- destructor TgxTerrainBaseHDS.Destroy;
- begin
- inherited Destroy;
- end;
- procedure TgxTerrainBaseHDS.StartPreparingData(HeightData: TgxHeightData);
- const
- cTBWidth: Integer = 4320;
- cTBHeight: Integer = 2160;
- var
- y, x, offset: Integer;
- rasterLine: PSmallIntArray;
- oldType: TgxHeightDataType;
- b: SmallInt;
- fs: TStream;
- begin
- if not FileExists('tbase.bin') then
- Exit;
- fs := TFileStream.Create('tbase.bin', fmOpenRead + fmShareDenyNone);
- try
- // retrieve data
- with HeightData do
- begin
- oldType := DataType;
- Allocate(hdtSmallInt);
- for y := YTop to YTop + size - 1 do
- begin
- offset := (y mod cTBHeight) * (cTBWidth * 2);
- rasterLine := SmallIntRaster^[y - YTop];
- for x := XLeft to XLeft + size - 1 do
- begin
- fs.Seek(offset + (x mod cTBWidth) * 2, soFromBeginning);
- fs.Read(b, 2);
- if b < 0 then
- b := 0;
- rasterLine^[x - XLeft] := SmallInt(b);
- end;
- end;
- if oldType <> hdtSmallInt then
- DataType := oldType;
- end;
- inherited;
- finally
- fs.Free;
- end;
- end;
- // ------------------
- // ------------------ TgxHeightDataSourceFilter ------------------
- // ------------------
- constructor TgxHeightDataSourceFilter.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- FActive := True;
- end;
- destructor TgxHeightDataSourceFilter.Destroy;
- begin
- HeightDataSource := nil;
- inherited Destroy;
- end;
- procedure TgxHeightDataSourceFilter.Release(aHeightData: TgxHeightData);
- begin
- if Assigned(HeightDataSource) then
- HeightDataSource.Release(aHeightData);
- end;
- procedure TgxHeightDataSourceFilter.Notification(AComponent: TComponent;
- Operation: TOperation);
- begin
- if Operation = opRemove then
- begin
- if AComponent = FHDS then
- HeightDataSource := nil
- end;
- inherited;
- end;
- procedure TgxHeightDataSourceFilter.SetHDS(Val: TgxHeightDataSource);
- begin
- if Val = self then
- Val := nil; // prevent self-referencing
- if Val <> FHDS then
- begin
- if Assigned(FHDS) then
- FHDS.RemoveFreeNotification(self);
- FHDS := Val;
- if Assigned(FHDS) then
- FHDS.FreeNotification(self);
- // MarkDirty;
- self.Clear; // when removing the HDS, also remove all tiles from the cache
- end;
- end;
- function TgxHeightDataSourceFilter.Width: Integer;
- begin
- if Assigned(FHDS) then
- Result := FHDS.Width
- else
- Result := 0;
- end;
- function TgxHeightDataSourceFilter.Height: Integer;
- begin
- if Assigned(FHDS) then
- Result := FHDS.Height
- else
- Result := 0;
- end;
- procedure TgxHeightDataSourceFilter.StartPreparingData(HeightData: TgxHeightData);
- begin
- // ---if there is no linked HDS then return an empty tile--
- if not Assigned(FHDS) then
- begin
- HeightData.Owner.Data.LockList;
- HeightData.DataState := hdsNone;
- HeightData.Owner.Data.UnlockList;
- Exit;
- end;
- // ---Use linked HeightDataSource to prepare height data--
- if HeightData.DataState = hdsQueued then
- begin
- HeightData.Owner.Data.LockList;
- HeightData.DataState := hdsPreparing;
- HeightData.Owner.Data.UnlockList;
- end;
- FHDS.StartPreparingData(HeightData);
- if Assigned(FOnSourceDataFetched) then
- FOnSourceDataFetched(self, HeightData);
- if HeightData.DataState = hdsNone then
- Exit;
- if FActive then
- PreparingData(HeightData);
- inherited; // HeightData.DataState:=hdsReady;
- end;
- // ------------------------------------------------------------------
- initialization
- // ------------------------------------------------------------------
- RegisterClasses([TgxBitmapHDS, TgxCustomHDS, TgxHeightDataSourceFilter]);
- end.
|