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.
