Editor:Alex Mizrahi
Last update:29.11.2001
If you think something is missing here, mail me-
I'll make FAQ
Before reading this, you should read Overview
and Brief concepts
All declarations are in Delphi-style. I think C++ programmers will understand
them too. There are translated headers.
Ind3D standart strings are PChar( char*), calling convention is _stdcall,
indexes are in range 1..N.
If memory can be allocated in one module and released in another, it should
be done using GlobalAlloc
All Ind3D functions return NON-ADDREFED interfaces. It should be addrefed for
long storage(Delphi will do this automatically). Only QueryInterface addrefs
interface.
IIND3DObject
Indirect 3D object is an implementation of IIND3DObj interface.
Objects in Indirect3D are managed hierarchycally. Root object is Renderer.
Object may be linked statically or as DLL file.
IIND3DObj methods are:
procedure Render(state : PIND3DState);
Means that object should display itself and it's children(attached objects). In OpenGL it
means calling gl commands. Sometimes there is no need of rendering.
function ClassName :PChar;
Class name is used for object indentification. Like 'FFButton'.
Must be same with name in component factory - it is used in streaming.
function AddI(I:IIND3DObj):LongBool;
Should add an interface to object's children list. Object may reject adding(
typically if doesn't support objects or don't like Caps of object),
returning false as result. Should call AddNotify of added object and check if
added object rejects to be added.
If there is no rejection, both objects should call Addref of each other.
function ReleaseI(I:IIND3DObj):LongBool;
Should Release object and exclude it from children list. True if succesful.
Should call ReleaseNotify of released object.(and do Release)
function AddNotify(I:IIND3DObj; weak : LongBool ):LongBool;
Notification that is passed to object that is about to be added to another
object. If object doesn't like caps, it may reject, returning false.
Object should add I to it's parent list if all is correct.
Weak attach means attach to interpolator. Object can be unlinked(destroyed) when there are no non-weak attaches.
procedure ReleaseNotify(I:IIND3DObj);
Notification that is passed when object is Released using ReleaseI.
Object should exclude I from parentlist and check destruction conditions.
function GetCaps : PChar;
Capabilites separated by spaces, like " FF FFTexture ".
Visible FastFruit components check if renderer supports OpenGL.
procedure SetRot(pos : PPoint3D);
Rotation (rx,ry,rz) : rotation around X, Y, and Z axises on angles rx,
ry, rz in degrees respectively.
In FastFurit it's coresponding to OpenGL sequence:
glRotatef(rx,1,0,0);
glRotatef(ry,0,1,0);
glRotatef(rz,0,0,1);
function GetProperty(PrNum:integer) : Pointer;
Get pointer on property data. Sometimes (with negative PrNumS) is called for other purposes:
IND3D_PROP_GETDATA = -4; - used in streaming
IND3D_PROP_MULTIPASS = -5; - used in multipass materials. Should return nonzero value if new rendering pass reqiered.
IND3D_PROP_GETSTATE = -6; - renderer should give PIND3DState
procedure SetProperty(PrNum: integer;prop : Pointer);
Object should copy data to it's storage. Sometimes(with negative PrNum)
it's called as a procedure:
IND3D_PROP_SHOWMATERIAL=-1; -object calls material's SetProperty before rendering self(or childern)
IND3D_PROP_HIDEMATERIAL=-2; -Object calls material's SetProperty after rendering self (or childern)
IND3D_PROP_PROCESSMESSAGE=-3; - prop is pointer on message. NotYetImplemented.
IND3D_PROP_PICKCLICK=0;Used in SelectionBufferPicking in OpenGL.
Renderer calls this procedure of selected objects after processing all objects
IND3D_PROP_SETDATA = -4; -used in streaming
Indirect3D uses special event/trigger model for interobject and app-object interaction. Events are posted by objects(and not only by them) to State.newmsgs message list(See TIND3DState) in response to some internal factors - time expiration, mouse click or other. Each such Event has a unique for object name - like "OnClick". If object was clicked, it will post event with msgid associated with OnClick event(if it's 0 message willnt be posted).
Triggers represents actions that object may do in responce to event. They are also named(like Enable, Disable). During ProcessMessages objects scans event list of messages with same msgids as msgid associated with triggers. If it finds any, it executes trigger.
Additional 4-byte data can be passed in message in "data" field. It's message-dependent. Sometimes it's interface pointer of sender. If message needs to transfer more data, it should use MemoryBlock technique. Simply passing pointer is NOT recomended - otherwise event will not work properly in Distribution.
Structures and misc declarations
TIND3DMessage is used for state, events and memoryblocks lists.
TIND3DMessage = record
msgid : integer;
data : Integer;{or pointer}
next : PIND3DMessage;
end;
So they may form lists, which can be easily scanned(see standart
SeekMessages routine in FastFruit).
They sometimes may branch(in state), if it's assumed that data is PIND3DMessage.
MemBlocks are used to pass additional data with message. In this case
message's "data" is a pointer to MemBlock. It can also be used as a shared mem between several components.
Typically they're allocated with size larger than sizeof(TIND3DMessage), and
in this additional place data can be stored.
MemBlocks should be allocated/deallocated using GlobalAlloc.
If MemBlock's size(msgid) is valid, it can be used in Distribution -
if event is properly registered DistributionServer will send MemBlock with
event.
RefCounting can be used for MemBlocks. If RefCount==0 block is deallocated by
renderer in end of ProcessMessages.
TIND3DState record is main storage for different data.
It's keeped by renderer.
TIND3DState= record
x,y : Integer; window coords
vect : TPoint3D; in selection - upmost point which was collided
shift : TShiftState; set of (ssShift,ssAlt,ssCtrl,ssLeft,ssRight
,ssMiddle,ssDouble) that represents current shift status
event : clevent;
click event, that can be simple enumeration (cleMouseDown,cleMouseMove,cleMouseUp)
or use additional clevent flags(with button information)
upmostp : TPoint3D; upmost(nearest) clicked point
upmostevent : Integer; event id that should be posted for upmost clicked
object to know that it's upmost.(called by renderer in end of HandleClick.
state : PIND3DMessage;renderer's state list(branched) see below
msgs : PIND3DMessage; - message list, is emptied in end of ProcessMessages
newmsgs : PIND3DMessage; newly posted messages, objects should
post messages here, on the next message processing tact they will go to msgs
memoryblocks : PIND3DMessage;
renderer unallocates memory blocks in end
of processmessages, badly implemented now. See MemBlocks
RESERVED1,RESERVED2 : Integer;
State message list is used to keep there values and flags.
It should be as little as possible(it's better to use direct access).
It has following "tree" structure:
msgid=0 : renderer state - branches into new Message List
msgid=1 - Renderer(IIND3DObj)
msgid=2 - basequality : integer - number of faces in round objects
msgid=3 - default font : IIND3DObj - FFFont, if present
msgid=4 - control interface,ComObj(ActiveX) - IUnknown - used for Href.
msgid=1 ind3dTIME. Format: integer - milliseconds from the start of week = (DayOfWeek)*86400*1000+Hour * 3600000 + Min * 60000 + Sec * 1000 + MSec .
Time rollover - if (now-prevtime)<0 then prevtime := prevtime- 7*86400*1000 .(BadlyImplemented).
msgid=2 : custom data messages - branches into Message list - any component may create new msg in this list(typically for intercomponent talks)
msgid=3 : RENDERER'S STATE FLAG.
First bit means rendering - 1 - rendering mode; 0 - selection mode
msgid=4 : Error List, branches into new list where information about
errors are keeped.
Error information has following format:
TIND3DException = record
ID: LongWord; - 0 means "just a little exception"
flags : LongWord; - IEF_... constants
IEF_FATAL=1; - no more rendering!
IEF_SHOWTOUSER=2; - error should be displayed even in non-debug mode
sender : IIND3DObj;
descr : PCHar;
end;
It's a good style to place descr INSIDE of structure - it will be deallocated with this structure.
Structure should be allocated using GlobalAlloc, and
Renderer is responsible for dealocation and intf releasing.
(but not PChar deallocation, unless it's allocated inside of structure).
Colors:
TIND3DColorComponent = record
color : FLOATAR4; - RGBA value
used : LongBool; - if it's used
end;
TIND3DColor = record
ambient,diffuse,specular,emission : TIND3DColorComponent;
shine:integer; - in OpenGL should be mapped as
glMaterialf(GL_SHININESS,shine/32)
material : IIND3DObj;
Object calls material.SetProperty(-1,nil) on invoke (encolor, for example
textures do bind) and calls material.SetProperty(-2,nil) on hide
(textures do glDisable(texture). Object should release old material
and addref new when color(or color property) is set.
Object should also call material.GetProperty(-5,nil) to query multipass status and draw itself one more time if material return nonzero value.
end;
Property type:
TPropertyEditorProc = procedure(obj : IIND3DObj;propid : integer);
this procedure is called by EDITOR when user wants to change property,
it passes object and property id. If it's nil, default editor is called.
TIND3DPropertyType = record
name : PChar; name of property
size : integer; size of data
dynamicsize : LongBool;
if it's dynamic size, SetProprtySize is called before setting property. In fastfruit static properties are only aliases to variables, when dynamicsized ones are allocated dynamically
kind : integer; -type of property
kind is partially compatible with vt_ windows consts
(*) in varString PChar(char*) is passed to SetProperty, but GetProperty returns PPChar(char**), so special handling is needed
In varIND3D and varIND3DColor object should release old interface and addref new when property is set. In-place(w/o copying) editing of such properties may cause malfunction
varArray is a bit mask, for example $2004 is array of float. Not all types are suitable for arrays.
varVariant is not ole variant but simply custom type
modified : LongBool; flag streaming If it's modified, it should be stored
(and if not, it will not)
reserved : Pointer;
flags : LongWORD;
editor : TPropertyEditorProc;
end;
Property flags are
ipfStoreable = 1 - properties are streamed only when they are storeable
ipfMethod = 2 - methods should loaded _after_ all non-method properties. This is used, for example, in mesh operators - operator parameters are set first, and only then method-property Mesh is specified and mesh operator is executed.
ipfTagBit = 4; -this bit is for temporary use
Indirect3D can load DLL modules with components. Module should export IND3DComponentName, IND3DComponentCount and IND3DComponentCreate functions(w/o letter T and underscore!).
TIND3DComponentCountFunc= function (renderer:IIND3DObj):Integer;stdcall;
Returns count of components(may be renderer-dependant).
TIND3DComponentNameFunc= function (i : integer;renderer : IIND3DObj):PChar;stdcall;
Returns name of i component.
TIND3DComponentCreateFunc = function( i : integer;renderer : IIND3DObj) : pointer{as IIND3DObj};stdcall;
Creates new component. Returns non-addrefed pointer.
Services are dynamic extensions. Actually they are specific
interface implementations. Services are keeped in ServiceList object, that
is IIND3DServiceList interface implementation:
IIND3DServiceList = interface(IUnknown)
['{D5AE4CA0-5F2B-11D5-BD4C-D5586A2D4803}']
procedure AddService(Service : IUnknown);
procedure DeleteService(Service : IUnknown);
function QueryService(const IID: TGUID; out Obj): HResult;
end;
QueryService procedure is similar to QueryInterface. It returns interface in
Obj and returns S_OK=0 if successful.
Services are quered by GUID.
It's assumed that we reached current state of scene by some property
modifications. If we reproduce this modifications we can recieve similar
scene. This is not always true, but works in most cases(when it components are
validly implemented).
So,typically only modified properties are stored, and on read they are
marked as modified again.
Data about objects is stored into stream. It consists of chunks.
It's like XML, but binary.
All object references(in properties and materials) are substituted by
loader's internal ComponentIds. If component is not yet pushed when
property(or attach) is stored, it is stored in chunk before property chunk.
So both chunk writer and parser are linear(but recursive).
During reading there is "current" component, all components have their ID's and are stored in list.
If reader doesn't know chunk, it can simply skip it.
When storing object's properties streamer typically calls GetProperty
(IND3D_PROP_DATA). If object can be optimized to consume less memory(
decreasing unused array capacity) it should do this on this call.
If object wants store itself as own data format
it should form a data block with first 4-byte integer that indicates size.
In this case standart property storing can be bypassed using propery flags.
When reading we need to bind scene to renderer by assuming that
first component(with compID = 1 ) is a binding object, that was passed
to streamer and shouldn't be created.
ChunkData containes "useful" chunk information. Some chunks may not support
ChunkList.
This chunk structure is similar to 3DS.
Strings in ChunkData are null-terminated(ASCIIZ).As in 3ds format.
If p is pointer, pointer on next chunk is p+ChunkLength.
Dynamic-size data is typically length-prefixed, length is 4-byte integer.
It is lpData(like BLOB in OLE).
Name binding is (optionally) used to store names of properties, events and triggers in scene stream. It binds index that is used in this stream to real property(event,trigger)
that may have different index. This gives an ability of loading stream on different versions of Indirect3D, where
order of properties and their quantity may be different.
On test scene Name binding consumed about 13%. So it's only worth to use it when scene can be loaded on another version of Indirect3D.
Chunks language can be easily extended by introducing new chunk types(with new ChunkId) and providing "ChunkReactions" for them. ChunkReaction is a procedure that will be called if parser finds chunk with specific id.
pchunk is pointer to chunk beign parsed. stream is an OpenStream that parses current stream(can be used for resolving ObjId's and parsing parts of stream). pArg is an argument that is passed to parser procedure(it can be specified when adding chunk reaction). It can be used for calling object's method.
Following chunks are standart:
- FFFF Indirect3D root chunk, does nothing.
- 0001 LoadIndirect3DModule(lpString ModuleName)
- 0002 CreateComponent(lpString ComponentName, int ComponentId);
Makes component current("pushes" it), when chunk is ended sets current
component back("pops").
- 0003 LoadComponentData(lpData)
- 0004 LoadProperty(int PropertyId, lpData PropData)
- 0005 LoadComponent(int CompID) - makes component current
- 0006 AddI(int CompID)- adds component to current
- 0007 SetTrigger(int TrigId, int MsgId)
- 0008 SetEvent(int EventId, int MsgId)
- 0009 SetColor( TIND3DColor)
- 000A SetPos( TPoint3D )
- 000B SetRot( TPoint3D )
- 000C PostEvent(int MsgId, int Data) - NotYetImplemented
- 000D NameBinding(int Kind(nbkProp,nbkEvent,nbkTrig),int Id, lpString Name)
- 000E PreStaticChunk - renderer stores this chunk internally, so it's content
isnt changed when scene is stored and reloaded back. Any additional data
can be stored in it. No object can should be created
in it - otherwise they will be duplicated. It's always at start of stream.
- 000F PostStaticChunk - works like a PreStaticChunk, but it's added to
tail of the stream.
- 0XXX Reserved for Indirect3D
- 1XXX Reserved for FastFruit
- 2XXX Reserved for Nabla
- 3XXX Reserved for ALists
FastFruit has following additional chunks (they are ADVISED to be used)
- 1005 DownloadFile( lpString URL, lpString Name)
if URL is CAB, and name is empty, file is uncabbed using extrac32
FastFruit has following editor chunks:
- 1001 StartEditorInfo - all editor info is keeped here
- 1002 ComponentName(int CompID, lpString Name) - ffEditor may give
personal name to component.
- 1003 NamedEvent(lpString Name)- ffEditor can name events, like
"ButtonPressed=8"
IND3DArray
Arrays are managed in Ind3D as dynamicsize properties and can be easily
streamed(w/o additional handling in streamer). They consist of header and data
in one memory block. Each array has its controlling interface.
Data location may be changed, Control Interface cannot.
Sometimes arrays work like stacks.
In current FastFruit implementation arrays with obj references(varIND3D
and varIND3DColor) cannot be stored. However, object lists may be stored
as attached to an object like TFFContainer, and color without material
can be stored.
2D and 3D arrays are allowed for such things like 2D mesh. They should
specify dimensions and set iaf2D or iaf3D flag.
TIND3DArray=record
size : LongWord; size of memory block
headersize : LongWord; size of header
flags : LongWord;
iafDirectAccess=1; - elements can be accessed without interface
iafCanModify=2;
iafCanAdd=4;
iaf2D=8;
iaf3D=16;
iafCanRealloc=32;
intf : IIND3DArray; ControlInterface, intf should be reset on SetProperty
so no additional handling when streaming is needed.
kind : Integer; same as TIND3DPropertyType kind
elsize : Integer;size of element
elcount : Integer;//number of elements
capacity : Integer;
d1,d2,d3 : Integer; dimensions for 2D and 3D arrays
tag : LongWord;
here additional info may be placed
end;
IIND3DArray=interface(IUnknown)
['{07DD0142-43BA-11D5-BD4C-EC17B25ACC15}']
function elementcount : LongWord;
function getrecord : PIND3DArray; gives pointer on TIND3DArray,
nil if array is dead, but reference on interface still exists
function getflags : Integer;
function getelement(index : Integer) : Pointer;index is in range 1..n
procedure setelement(index : Integer;P : Pointer);copies data from pointer to
element
procedure addelement(P : Pointer);
procedure setcapacity(newcapacity:Integer);
procedure modifynotification; should be called when elements were changed by direct access
end;
OpenStream
OpenStream is a Indirect3D service{language-independent} that:
maintains constant obj_ids
gives a routine for standart stream parsing
maintains global list of ChunkReactions
provide interface for stream writting
It can be used in
DistributedInd3d: to transfer changes of hierarchy across net and maintain global object id's
In i3d scripts or script objects that will run scripts on events
There may be many OpenStream contexts.
IIND3DOpenStream = interface(IUnknown)
['{FFAB0D60-76C6-11D5-BD4C-C4B50E23A432}']
//procs used to maintain constant object ID list and use it:
function ObjToID(obj : IIND3DObj) : Integer;stdcall;
//Finds ID of obj. If it's not found returns 0.
function IDToObj(id : Integer) : Pointer{as IIND3DObj};stdcall;
//Finds obj by ID, if not found returns nil.
procedure AssignObj(obj : IIND3DObj;id : Integer);stdcall;
//Assigns ID to obj. If ID is assigned to nil, it's deleted.
function GetFreeID() : Integer;stdcall;
//Gives unused ID (for further assigning). Uses distribution if present.
procedure ReserveID(id : Integer);stdcall;
//Reserves ID so it cant be returned by GetFreeID again. For un-reserving
//use AssignObj(nil,id);
procedure ParseBuffer(bindto : IIND3DObj;merge : LongBool;
buffer : Pointer;sz : Integer);stdcall;
//see below
//Chunk reactions procs:
procedure AddChunkReaction(chunkid : word; proc : TChunkReactionProc;
pArg : Pointer);stdcall;
procedure RemoveChunkReaction(chunkid : word;proc : TChunkReactionProc);stdcall;
//new stream creation procs:
function CreateOpenStream(inheritIDs : LongBool;inheritNameBindings : LongBool;
inheritReactions : LongBool) : Pointer{as IIND3DOpenStream};stdcall;
//see below
function CreateWriteStream : Pointer{as IIND3DWriteStream};stdcall;
//see below
//Distribution status procs
procedure SetDistributionStatus(s : LongBool);stdcall;
function GetDistributionStatus : LongBool;stdcall;
//Static chunk control
function GetStaticChunk(pos :Integer):Pointer;stdcall;
procedure SetStaticChunk(pos : Integer;coalesce : LongBool;p : Pointer);Stdcall;
end;
ParseBuffer is a standart stream parsing routine. It's recomended to use because it uses all known chunk reactions. Params:
bindto - objects to bind scene in stream to. It's assumed that this object has ObjID=1 and it will be loaded but not created. If bindto=0 then bindto=renderer.
merge - in non-merge mode ObjID's are interpreted as they are, and if scene always has objects with such ID's, they will not be created again but loaded.
In merge mode buffer is assumed to be relating to another scene, and it's created just as if it were blank scene without any objects. After parsing OpenStream assigns new ID's to newly-created objects.
buffer - pointer on stream to parse
sz - size of stream. If sz=0 buffer only first chunk is parsed(if stream is wrapped by one object, as most i3d streams, it will be fully parsed).
AddChunkReactions overrides previous chunk reaction. To avoid removing new chunk reaction instead of old, proc parameter should be checked for equality with ChunkReaction's proc
CreateOpenStream creates new OpenStream contexts. It returns non-addrefed pointer, caller should addref it and release when it's no longer needed. New contexts can inherit features of it's parent conext - Object ID's, Name Bindings of ChunkReactions. If new stream inherits something, changes made to it in this context are also made to parent context.
OpenStream should know about distribution status at any time, so DistributionService should set it to true when it's created. Change will be automatically seen in all other contexts. GetDistributionStatus is fastest way to learn if distribution is present(searching in ServiceList isnt fast).
OpenStream can create write stream context. It will keep ObjID's of OpenStream in
scene file.
Static chunk procs allow to manipulated static chunk. pos param in this functions
defines position of chunk 0 for pre-chunk(at start) and 1 for post-chunk(at end).
Pointer returned by GetStaticChunk is not for a long storage - it can be changed
by a call to SetStaticChunk. Chunk returned by GetStaticChunk can be deallocated only internally in OpenStream.
SetStaticChunk can either replace static chunk or coalesce it. Caller should deallocate data
passed to SetStaticChunk because OpenStream will copy it.
IIND3DWriteStream = interface(IUnknown)
['{1F8D4520-86CC-11D5-BD4C-9C16A5E71A5C}']
procedure init();stdcall;//initializes new stream for writting
function finish(var sz : Integer): Pointer;stdcall;//finalizes
//writting to stream and produces buffer. Caller is responsible for
//deallocation of this buffer(using GlobalFree).
procedure usenamebindings(flag : LongBool);stdcall;
//Initializes or resets NameBindings
procedure resetobjects();stdcall;
//Resets ObjID's, so objects will be written again.
procedure set_flags(flags : LongBool);stdcall;//iws* consts
//iwsAssignNewObjects = 1;- WriteStream will assign ObjID's to objects being
//processed if they're unassigned. Otherwise it will only Reserve ID's.
procedure startchunk(chid : Word);stdcall;
//Starts chunk with specific chunk_id
procedure endchunk();stdcall;
//ends chunk and returns to upper level.
procedure pushint(i : Integer);stdcall;
//pushes int value to current chunk
procedure pushbinary(p : Pointer;sz:integer);stdcall;
//pushes binary value to current chunk
procedure pushstring(p : PChar);stdcall;
//pushes null-terminated string to current chunk
function pushobj(obj : IIND3DObj) : Integer;stdcall;
//pushes object with all properties and children
//Returns ObjID or 0(if failed)
function pushobjbinded(I : IIND3DObj):Integer;stdcall;
//pushes object that will be automatically binded to bindobj
procedure pushproperty(I : IIND3DObj;x : Integer;
pt : PIND3DPropertyType);stdcall;
//pushes x's property. pt can be nil, in this case it will read it.
function forceobject(I : IIND3DObj): integer;stdcall;
//pushes object if it's not yet pushed and returns ObjID anyway(0 for nil obj).
procedure pushproperties(I : IIND3DObj);stdcall;
//pushes all properties of object in correct order
procedure pushchunk(ch : POinter);stdcall;
//pushes ready chunk. used for storing static chunks
function getopenstream : Pointer{as IIND3DOpenStream};stdcall;
//Returns parent OpenStream context
end;
Indirect3D Standart caps
NOSAVE - objects don't want to be stored
Bounds - Object has "Bounds" property of TPoint3D, which contains
vertices of convex multihedron.
FastFruit introduces such caps as
FFPoints
FFMesh
FFTexture
They are _recomended_ to be supported.