LWObjects.pas ============= This unit provides functions, constants and now classes for use in working with Lightwave3D Object files. Chunk ID constants are defined for all of the Chunk IDs listed in the Lightwave 7.5 sdk. It is important to note that this is a constant work-in-progress and as such there are omissions and may be errors. Feedback and suggestions would be appreciated. There are two ways of using this unit. The first uses user-defines callbacks to handle parsing lwo chunk data. The second way uses object orientation. Loading LWO chunk data via callbacks ==================================== A function is provided for loading a Lightwave object from a file. The Loader itself uses a callback mechanism for the loading of Lightwave object chunks. The callback is called for every chunk (with the exception of the FORM and LWOB or LWO2 chunks). The Chunk struct passed in the callback contains members for the chunk ID, chunk size and pointer to chunk data. This data is untouched internally so any parsing and numeric formatting is up to you. This provides maximum flexibility and allows you to handle the data that you need without loading the entire object into ram first. The chunk data memory is freed upon the return of the callback so do not keep a reference to the chunk data. Copy it to your own storage. function LoadLW0(const Filename: string; ReadProc: TLWOReadProc; UserData: Pointer): LongWord; cdecl; Filename: The fully qualified filename of the file to be loaded. ReadCallback: The address of a TLWOReadCallback procedure defined as: TLWOReadCallback = procedure(Chunk: TLWChunk; UserData: Pointer); cdecl; This procedure will be called for every chunk encountered in the Lightwave object file. The Chunk parameter is the chunk struct of the chunk being loaded. UserData is the pointer supplied in the original call to LoadLWO (see below). UserData: A pointer to user supplied data to be passed in the ReadCallback. A non-zero results indicates that the object file was parsed successfully. Loading LWO chunks via objects ============================== To load data from a lightwave object file, create an instance of TLWObjectFile and call its LoadFromFile method. The data can then be accessed with the Chunks array property and iterated in combination with the ChunkCount property. Chunk data is parsed and interfaced by descendents of the TLWChunk class. I have made handlers for the following chunk types: TLWLayr Modeler Layer chunk TLWPnts Points chunk TLWPols Polygons chunk TLWPTag Polygon tag mapping TLWSurf Surface subchunk container TLWTags Tags (Name tag strings for named items) TLWVMap Vertex Mapping The data for chunks without handlers can be gotten at with the Data and Size properties of the TLWChunk. Data is a pointer to the start of the chunk data. This data is unparsed. Data is nil for descendents. This should provide enough to move geometry into your favourite delphi-based 3d engine. Making chunk handler objects ============================ All chunk types are derived from TLWChunk in the following manner: TLWChunk ex: TLWPTag <- PTAG chunk type. polygon tag map. TLWParentChunk <- A base class for chunks that can contain other chunks. This is not necessarily how the data is stored in the file but more for ease of access to data. ex: TLWPnts <- PNTS chunk type (points) TLWLayr <- LAYR chunk type (modeler layer) TLWSurf <- SURF chunk type (constains surface attributes as sub chunks) TLWSubChunk <- A base class for chunks whose max data len is 65536 bytes. TLWDiff <- DIFF subchunk type (diffuse surface parameter) TLWSpec <- SPEC subchunk type (specularity surface parameter)... etc. Each descendent of TLWChunk or TLWSubChunk is required to override the GetID class function, the LoadData method and the Clear method to provide custom handling for chunktype data. ex: ... type TLWPnts = class (TLWParentChunk) private FPoints: TVEC12DynArray; function GetCount: LongWord; protected procedure Clear; override; procedure LoadData(AStream: TStream; DataStart, DataSize: LongWord); override; public class function GetID: TID4; override; function GetVMap(VMapID: TID4; out VMap: TLWVMap): boolean; property Count: LongWord read GetCount; property Points: TVEC12DynArray read FPoints; end; ... (* Return the the chunk id that is the target of this handler *) class function TLWPnts.GetID: TID4; begin result := ID_PNTS; end; (* Load the point data - the stream is already positioned at the start of the chunk data *) procedure TLWPnts.LoadData(AStream: TStream; DataStart, DataSize: LongWord); begin SetLength(FPoints,DataSize div 12); // allocate storage for DataSize div 12 points ReadMotorolaNumber(AStream,@FPoints[0],4,DataSize div 4); // read the point data end; (* Cleanup - Free any memory that you've allocated *) procedure TLWPnts.Clear; begin SetLength(FPoints,0); end; Utility Functions ================= A function is provided for converting an array of numbers between Motorola and Intel format (big endian <-> little endian). Converting only needs to be done for numeric types that are of 2 or 4 byte lengths. procedure ReverseByteOrder(ValueIn: Pointer; Size: integer; Count: integer = 1); ValueIn: The address of a number or array of numbers to have their bytes swapped. Size: The size in bytes of each numeric type. Count: The count of numbers in the numbers array. The default value is 1. Two routines are provided for reading and writing big endian (Motorola and misc other processor vendors ) numbers to and from a stream. These routines handle 2 and 4 byte numeric types and can also handle arrays. procedure ReadMotorolaNumber(Stream: TStream; Data: Pointer; ElementSize: integer; Count: integer = 1); function WriteMotorolaNumber(Stream: TStream; Data: Pointer; ElementSize: integer; Count: integer = 1): Integer; Each take a valid TStream descendent, a pointer to the numeric data, the element size of the data elements (either 2 or 4) and the array element count if sending an array. The default count is 1. Notes for improvement of this unit: - A version ID tag should be visible to all chunks in order to provide handling for Lightwave pre 6.0 object files. - Chunk type handlers should leave memory allocation to the base class (TLWChunk) and act more as an interface to the data pointed to by Data in TLWChunk. This would keep memory allocation very efficient and make implementing chunk handlers even easier. - A future Lightwave support unit could possibly benefit from the use of delphi's interface type.