fxRenderObject (Generic Render Object)
by Melv May · 09/11/2002 (1:02 pm) · 52 comments
Download Code File
Everyone,
I have been asked by quite a few people now about how you get an object into the scene and do some rendering.
Well here you will find my fxRenderObject which is an example object that can easily be modified to render whatever you like.
It contains examples on how to:-
- Create and inherit from the basic SceneObject building block.
- Add the object to the scene.
- Setup custom fields for the editor.
- Get data/custom fields from server to client.
- Send minimal data to client using masks.
- Load-up textures client-side only.
- Have texture loading done without having to exit the editor.
- Save/Restore canonical state.
- Draw at the objects position using object-space vertices.
- Animate object using game timebase for consistent, frame-independent animation.
Here is the rather large comment at the top of the implementation file that explains as much as I could bear to type...
[EDIT] 15/09/02
Amended error in description of the "packUpdate" function (thanks Tim Gift).
//------------------------------------------------------------------------------
//
// Okay, here's the wordy-wordy explanation of what's going on here.
//
// The object is inheriting from the core SceneObject. It is essential that
// you inherit from at least this object to be able to add your object into
// the SceneGraph.
//
// DECLARE_CONOBJECT(fxRenderObject);
// IMPLEMENT_CO_NETOBJECT_V1(fxRenderObject);
//
// The lines above are vitally important when creating these network objects.
// They register the object with the engine and you can't proceed without them.
// The DECLARE goes in your class definition and the IMPLEMENT goes into your
// implementation. Anyone who has used MFC should recognise this procedure.
//
//
// *** CONSTRUCTOR ***
//
// In the constructor you will notice that I set the mTypeMask. This is used
// as a standard way of identifying object 'types'. It's called a mask because
// you can merge multiple types. One of the most common uses for this is
// the collision detection system. The collision detection system allows you
// to include certain types when searching. It's these flags here that are
// used. You can create your own but be careful as there are only so many
// available. Normally you wouldn't do this as you should find one that fits
// into what you are doing but the option is always there.
//
// I also set the net flags. These are a bunch of flags that signify some
// important aspects of the object. You should really look at the networking
// documentation on the GG site for a basic explanation of ghosts and scoping
// as it's a bit much to explain here. The default settings should suffice
// unless you are doing something weird (like my fxShapeReplicator)!
//
// I'm also resetting the mLastRenderTime variable as I used this as a
// timestamp. Subtracting the current time from the last obviously gives
// me the elapsed time and it's this value that can be used to create
// frame-independent animation.
//
// I also reset most other stuff here to their defaults. It is vitally
// important to note that even on a single machine there are two objects
// created, one on the server and one on the client. The client could
// potentially be on another machine but Torque handles that for you.
//
// The constructor will be called for both the server and client objects and
// so this is a great place to initialise stuff. Try not to allocate memory
// in the constructor though, this goes for OOP everywhere though!
//
//
// *** DESTRUCTOR ***
//
// My destructor is empty because I don't need to do anything for shutdown.
// If I had textures allocated then the variable holding that texture
// reference will go out of scope and the resource manager will remove the
// reference and possibly destroy the texture (if no other objects have
// a reference to the same texture object).
//
//
// *** InitPersistFields ***
//
// It's here that we add our custom fields. Note that this gets called
// when the engine is starting up and *not* when the object is created.
// What you are essentially doing here is registering with the engine, the
// fields that objects of this 'class' will have. Don't put any
// object-instance specific code in here otherwise you will be disappointed!
//
// Use the addField function to define your fields but be careful not to
// use duplicate fieldnames or ones with spaces in them. You can happily
// use underscores though.
//
// If you've merged with the [HEAD] then you can used my new field-grouping
// which enables you to group blocks of fields that are related. They look
// much nicer in the editor when they are grouped and most things become
// easier to find. You can use addGroup("MyGroup1")/endGroup("MyGroup1")
// entries around you field-blocks. I have left an example in the code for
// you to look at (commented-out).
//
//
// *** OnAdd ***
//
// After the object is built (either from you creating one in the editor or
// upon mission start-up) this function is called by the engine to allow the
// object to add itself into the scene and setup scene specific attributes.
//
// First-up always try to call parent functions if they are there.
//
// Next I setup the object box. This is an object-space box that defines the
// region which the object occupies. It is important that you get this correct
// as the SceneTraversal routines uses this box to see if the object is in the
// view. Setting it too small results in the object not being rendered when
// in-fact it should be. Too big and the objects render function will get
// called when it's not on the screen which is just wasted processing. If the
// object dynamically changes shape during game-play then you can either create
// an object-box at start-up that completely encapsulates its' volume or you
// can dynamically change the object-box. Remember though, that if you must
// reset the world box and set the render transform after doing so and that
// you must do this client-side as well.
//
// After you've set the object-space box you *must always* reset the world-box.
// What this does is calculate, using the current objects position, the
// world-space coordinates of this box. It also calculates the world-sphere
// which is a sphere that encapsulates the specified box.
// Also, you must set the render transform. Failure to do this can result in
// weird and hard to track results in animations and editor manipulation.
//
// Finally we add the object to the scene with the simple call "addToScene()".
//
// Returning true here confirms that everything was okay. If you return false
// then the server will assume that there has been a problem and will
// disconnect the client informing them that this resource failed to load and
// that you may have incorrect resources or wrong engine version, so watch out!
//
//
// *** onRemove ***
//
// This one's dead easy. "removeFromScene()" does exactly that. Again, call
// the parent functions where needed.
//
//
// *** inspectPostApply ***
//
// This is an interesting function as it's called (Server-Side only) when the
// user hits the "Apply" button when editing the object. We respond by doing
// the nice thing and calling the parent and then signaling which bits of data
// need to get transferred to the client.
//
// We do this using "setMaskBits(fxRenderObjectMask)". Doing this will result
// in the "packUpdate(...)" (see below) function being called. Essentially,
// the mask you specify here is your customised mask you created in the header
// of the object.
//
// Quite often you will only have one and it will trigger all the object data
// being sent. This can be wasteful and if your object does this alot during
// game-play then you would want to send only the data that has changed or at
// least only relevant groups of data that has changed. I explain these masks
// more in the "packUpdate" section below.
//
//
// *** onEditorEnable / onEditorDisable ***
//
// I left these functions in as they can be extremely useful when you want your
// object to act differently when you know the editor is on. For instance, I
// used this in my fxShapeReplicator to signal that the editor was on. When it
// was I would additionally draw a sweeping arc defining the replication area.
//
//
// *** prepRenderImage ***
//
// Let me just say before I start an explanation here that this function should
// work without modification for most objects you will want to create and so if
// I loose you then just skip it. ;)
//
// Let me open-up the inner workings of the engine a little...
//
// After an object has been created client-side, it's "OnAdd" function is called
// (see above) and it adds itself to the scene. But what does this mean?
//
// Well the engine has lots of structures (believe me lots) one of which is a
// container. This, as it's name implies, can contain objects. You've heard
// me throw the term SceneGraph around but what does it do? Well, in Torque, all
// client-side objects are in a Container named "gClientContainer". Similarly,
// all server-side objects are in a Container named "gServerContainer" but let's
// focus on the client-side here as it's ultimately the client-side rendering we
// are interested in for this discussion.
//
// A container isn't just a big ol' list of objects, it's more than that. You may
// have seen various error messages like "Object is not out of the bins" etc?
//
// The Container splits up the world into cubes called bins. When an object is
// added to the scene, it's bounding-box is used to find which of these bins it
// belongs in. Basically it identifies a box in the world. If the object is too
// big then it gets put into a linear list called the overflow bin (where naughty
// objects go).
//
// When you do a CastRay using the engine it basically checks which of these
// bins it intersects. It then calls all the objects in turn that reside in these
// bins. The objects can then do their own, perhaps more detailed, collision check
// that may be polygon perfect or just the bounding-box, it's entirely up-to the
// object itself.
//
// Where is this going you say? Well, this system is also used to determine what
// objects are in the view. The viewing frustum defines a region that can be
// checked to see which bins it intersects (plus the naughty objects in the overflow
// bin). Using this, these objects "in-scope" have their "prepRenderImage" called
// to allow them to do exactly that, prepare a render image.
//
// Phew! Well what's a render image you say? Boy am I glad you asked that!
//
// A "SceneRenderImage" is simply a structure that defines the rendering attributes
// of the object that control the way the SceneTraversal routines handle it.
// Important factors such as is the object translucent (in which case it needs
// sorted back->front) can be specified here.
//
// In my example, you will see me dynamically allocating a "SceneRenderImage",
// populating relevant fields and then inserting it into the SceneState. You don't
// need to destroy it, the SceneGraph will do that after the frame is complete.
//
// There are lots of fields that I just haven't got the energy to go into here but
// two important ones I will. The "isTranslucent" field tells the SceneGraph that
// at least one portion of your object has transparency. If you didn't already
// know it, rendering transparent objects requires that objects be rendered from
// back to front (far to near) order to look correct. I won't go into why this is
// so because then I'm getting into imaging and then I'll really bore you. ;)
//
// The second important field is "sortType" which allows you to specify the way
// the object is handled by the scene. You can use the following ...
//
// Non-translucent objects ...
//
// Sky - Specialised for Sky object only.
// Terrain - Specialised for Terrain object only.
// Normal - Standard one to use for non-translucent objects.
//
// Translucent objects only ...
//
// Point - Object is a point defined by state->poly[0]
// Plane - Object is a plane defined by state->poly[0-3]
// EndSort - Object should appear after everything else has rendered
// BeginSort - Object should appear before everything else has rendered
//
// The "EndSort" is useful for rendering lens flares which need to be rendered
// after everything else has been rendered.
//
// The "BeginSort" is handy for rendering stuff behind the terrain but after
// the Sky like my fxSunLight object which renders a remote object and a local
// object.
//
// This brings me to an interesting point. You can insert more than one
// SceneRenderImage into the SceneState during the call to "prepRenderImage"
// which results in the "renderObject" being called more than once at the
// point defined by the "SceneRenderImage"s attributes. I do this with the
// fxSunLight but be sure you know what you are doing with this as you can
// significantly reduce your frame-rate by causing the same objects to be
// rendered multiple times.
//
// Why this complexity? Well, it's a very powerful method to use because you
// do all sorts of wizardry by traversing scene portals, clipping the frustum
// and iterating the new frustum up-to a specified depth to give you the
// ability to create mirrors, water reflections and other portal effects.
//
//
// *** renderObject ***
//
// Alrighty then! The SceneTraversal is stuffed with "SceneRenderImage"s, it
// then does all the sorting it needs and then proceeds to actually act on them.
//
// Because each "SceneRenderImage" has a reference to the object (notice the line
// that adds "state->obj = this", it then calls the "renderObject" for each object.
//
// Now we are nearly ready to render. When you enter the "renderObject" function
// we are in what is known as the Canonical state. It's just another way of
// saying a standard state or a consistent state that is guaranteed every-time
// we get to this position. This is handy for many reason but mostly because
// you reduce the possibility of other objects' state settings interacting with
// your functions. Imagine having to reset every possible state setting that could
// possibly affect you, nightmare!
//
// It's always good to check for this state with an AssertFatal at the beginning
// and end of this function.
//
// First-up, we get the current timestamp. We use "getVirtualMilliseconds" here
// that gives us the virtual game time-slice timestamp and we calculate the
// elapsed time since our last render. This is useful because we can use it by
// multiplying it with our changes to give us a frame-rate independent animation.
// This is also handy as you can normalise your object attributes in units/second.
//
// We then check the texture handle in the example, we can't render without it.
// This is specific to this example and can be ignored for you own object if you wish.
// Note though, that as soon as you have changed the rendering state in any way
// then you should restore the canonical state before returning otherwise you
// will get a fatal assertion further on down the line!
//
// I'm not going to explain the rendering pipeline here because there are many
// books which do the same. Drop me an email and I'll recommend one, I think
// I've personally got them all!!
//
// We *must* save the PROJECTION and MODELVIEW matrices plus the VIEWPORT so
// that we can restore them to our nice and friendly canonical state before exit.
//
// With everything saved, we multiply the MODELVIEW by our objects transform
// to effectively move our origin to the objects' position. All our graphical
// commands are then in object-space (or at-least relative to the objects origin).
//
// I won't explain the rest of the function because it's just OpenGL (possibly
// translated into DirectX) and I'm not here to teach you that!
//
// Before we exit we restore canonical state which is really important. As I
// stated above, I do an assertion to double-check this. You can never be to
// cautious with this and it goes away in the RELEASE build anyway.
//
//
// *** packUpdate ***
//
// Right, we are back to the networking. Remember above (inspectPostApply) when
// we made a call to "setMaskBits(...)"?
//
// After we did that, this function gets called. Remember that we are now
// server-side. This function effectively queues-up a bit-stream to be sent
// to the client (irrelevant of whether the client is on the same PC or half-way
// around the world).
//
// First-up we call the parent so that it can get any data it owns sent-out.
//
// We then do something a little clever. When the function was called, it was
// passed the mask that we sent using the "setMaskBits(...)" function.
//
// When designing your objects you can split the data up into groups that
// you can get sent using the appropriate mask(s).
//
// For each group you *always* write a flag signaling whether the group has
// data to send. The client will check this and know whether the following
// data is the rest of the group data or another group flag.
//
// In the example I have only one group controlled by the mask,
// "fxRenderObjectMask". Because I do a boolean "AND" against the passed-in
// mask I effectively send whether the group is being sent or not. The
// "writeFlag" function is also kind enough to return the result of this
// masking so that you can use it as a condition in an if(cond) statement as
// I do.
//
// Within this block you can call a multitude of functions from the BitStream
// class that allow you to write-out your data to the network.
//
// The return value here is important as it becomes the new state mask used by
// the networking code. If you manage to send all your data then you should
// return a zero. Returning any other mask value results in the "packUpdate"
// function being called again with the returned mask. Typically you would
// return the value from the "Parent::packUpdate" function" which which returns
// zero. If there are 'blocks' of data that you couldn't pack for any reason
// and you want to try again next time then you should return something like
// "mask & ~(state flags sent)". To cut a long story short though, returning
// zero indicates that you send all your data and the story ends there unless
// you are doing something a little more technical!
//
//
// *** unpackUpdate ***
//
// If you've understood the above then you shouldn't have any problems here.
//
// You should also know that we are now client-side.
//
// Eventually, after the "packUpdate" function was called (for each client),
// the data is sent out to respective clients. It is important to note that
// the "packUpdate" function is called once for each client connected and that
// each client maintains it's own state mask.
//
// First-up here is to do the friendly thing and call the parents "unpackUpdate"
// routine to allow it to load it's data from the stream.
//
// We then call "readFlag" which should correspond to the flag we wrote in the
// "packUpdate" function. If it's true then we know that the group data follows
// and we proceed to read it.
//
// It's using this "readFlag" then reading group data that we can control exactly
// what data elements get sent/received. You could do a mask per data item but that
// would be overkill. If you look deeper you find that the stream read/write
// commands also let you save bandwidth by allowing you to specify bit-lengths
// of data items but be sure you know what you are doing with these.
//
// Another interesting note is that after the data has been loaded I load-up
// any specified textures here. Most of the standard engine objects *don't* do
// this and only load them up in the "onAdd()" function (see above) which
// obviously only gets called once at startup. This accounts for why you need
// to restart the mission to see your changes. I recently modified the "Sky"
// object by adding the "loadDML()" call into the client-side "unpackUpdate"
// function.
//
// Also, note that you should never load texture resources on the server as the
// server doesn't actually do any rendering and so it's pointless.
//
// Also, because some functions are called server-side *and* client-side you need a
// method of knowing which side you are on and doing an appropriate action.
// You can do this with the calls "isClientObject()" and "isServerObject()".
//
// ******************
//
// Well, I hope you found this informative and I'm sorry if I didn't go into
// some areas in enough detail but I had to limit this somehow.
//
// If you have any specific questions then drop me an email to...
//
// melv.may@btinternet.com
//
// All the best everyone,
//
// - Melv.
//
//------------------------------------------------------------------------------
Everyone,
I have been asked by quite a few people now about how you get an object into the scene and do some rendering.
Well here you will find my fxRenderObject which is an example object that can easily be modified to render whatever you like.
It contains examples on how to:-
- Create and inherit from the basic SceneObject building block.
- Add the object to the scene.
- Setup custom fields for the editor.
- Get data/custom fields from server to client.
- Send minimal data to client using masks.
- Load-up textures client-side only.
- Have texture loading done without having to exit the editor.
- Save/Restore canonical state.
- Draw at the objects position using object-space vertices.
- Animate object using game timebase for consistent, frame-independent animation.
Here is the rather large comment at the top of the implementation file that explains as much as I could bear to type...
[EDIT] 15/09/02
Amended error in description of the "packUpdate" function (thanks Tim Gift).
//------------------------------------------------------------------------------
//
// Okay, here's the wordy-wordy explanation of what's going on here.
//
// The object is inheriting from the core SceneObject. It is essential that
// you inherit from at least this object to be able to add your object into
// the SceneGraph.
//
// DECLARE_CONOBJECT(fxRenderObject);
// IMPLEMENT_CO_NETOBJECT_V1(fxRenderObject);
//
// The lines above are vitally important when creating these network objects.
// They register the object with the engine and you can't proceed without them.
// The DECLARE goes in your class definition and the IMPLEMENT goes into your
// implementation. Anyone who has used MFC should recognise this procedure.
//
//
// *** CONSTRUCTOR ***
//
// In the constructor you will notice that I set the mTypeMask. This is used
// as a standard way of identifying object 'types'. It's called a mask because
// you can merge multiple types. One of the most common uses for this is
// the collision detection system. The collision detection system allows you
// to include certain types when searching. It's these flags here that are
// used. You can create your own but be careful as there are only so many
// available. Normally you wouldn't do this as you should find one that fits
// into what you are doing but the option is always there.
//
// I also set the net flags. These are a bunch of flags that signify some
// important aspects of the object. You should really look at the networking
// documentation on the GG site for a basic explanation of ghosts and scoping
// as it's a bit much to explain here. The default settings should suffice
// unless you are doing something weird (like my fxShapeReplicator)!
//
// I'm also resetting the mLastRenderTime variable as I used this as a
// timestamp. Subtracting the current time from the last obviously gives
// me the elapsed time and it's this value that can be used to create
// frame-independent animation.
//
// I also reset most other stuff here to their defaults. It is vitally
// important to note that even on a single machine there are two objects
// created, one on the server and one on the client. The client could
// potentially be on another machine but Torque handles that for you.
//
// The constructor will be called for both the server and client objects and
// so this is a great place to initialise stuff. Try not to allocate memory
// in the constructor though, this goes for OOP everywhere though!
//
//
// *** DESTRUCTOR ***
//
// My destructor is empty because I don't need to do anything for shutdown.
// If I had textures allocated then the variable holding that texture
// reference will go out of scope and the resource manager will remove the
// reference and possibly destroy the texture (if no other objects have
// a reference to the same texture object).
//
//
// *** InitPersistFields ***
//
// It's here that we add our custom fields. Note that this gets called
// when the engine is starting up and *not* when the object is created.
// What you are essentially doing here is registering with the engine, the
// fields that objects of this 'class' will have. Don't put any
// object-instance specific code in here otherwise you will be disappointed!
//
// Use the addField function to define your fields but be careful not to
// use duplicate fieldnames or ones with spaces in them. You can happily
// use underscores though.
//
// If you've merged with the [HEAD] then you can used my new field-grouping
// which enables you to group blocks of fields that are related. They look
// much nicer in the editor when they are grouped and most things become
// easier to find. You can use addGroup("MyGroup1")/endGroup("MyGroup1")
// entries around you field-blocks. I have left an example in the code for
// you to look at (commented-out).
//
//
// *** OnAdd ***
//
// After the object is built (either from you creating one in the editor or
// upon mission start-up) this function is called by the engine to allow the
// object to add itself into the scene and setup scene specific attributes.
//
// First-up always try to call parent functions if they are there.
//
// Next I setup the object box. This is an object-space box that defines the
// region which the object occupies. It is important that you get this correct
// as the SceneTraversal routines uses this box to see if the object is in the
// view. Setting it too small results in the object not being rendered when
// in-fact it should be. Too big and the objects render function will get
// called when it's not on the screen which is just wasted processing. If the
// object dynamically changes shape during game-play then you can either create
// an object-box at start-up that completely encapsulates its' volume or you
// can dynamically change the object-box. Remember though, that if you must
// reset the world box and set the render transform after doing so and that
// you must do this client-side as well.
//
// After you've set the object-space box you *must always* reset the world-box.
// What this does is calculate, using the current objects position, the
// world-space coordinates of this box. It also calculates the world-sphere
// which is a sphere that encapsulates the specified box.
// Also, you must set the render transform. Failure to do this can result in
// weird and hard to track results in animations and editor manipulation.
//
// Finally we add the object to the scene with the simple call "addToScene()".
//
// Returning true here confirms that everything was okay. If you return false
// then the server will assume that there has been a problem and will
// disconnect the client informing them that this resource failed to load and
// that you may have incorrect resources or wrong engine version, so watch out!
//
//
// *** onRemove ***
//
// This one's dead easy. "removeFromScene()" does exactly that. Again, call
// the parent functions where needed.
//
//
// *** inspectPostApply ***
//
// This is an interesting function as it's called (Server-Side only) when the
// user hits the "Apply" button when editing the object. We respond by doing
// the nice thing and calling the parent and then signaling which bits of data
// need to get transferred to the client.
//
// We do this using "setMaskBits(fxRenderObjectMask)". Doing this will result
// in the "packUpdate(...)" (see below) function being called. Essentially,
// the mask you specify here is your customised mask you created in the header
// of the object.
//
// Quite often you will only have one and it will trigger all the object data
// being sent. This can be wasteful and if your object does this alot during
// game-play then you would want to send only the data that has changed or at
// least only relevant groups of data that has changed. I explain these masks
// more in the "packUpdate" section below.
//
//
// *** onEditorEnable / onEditorDisable ***
//
// I left these functions in as they can be extremely useful when you want your
// object to act differently when you know the editor is on. For instance, I
// used this in my fxShapeReplicator to signal that the editor was on. When it
// was I would additionally draw a sweeping arc defining the replication area.
//
//
// *** prepRenderImage ***
//
// Let me just say before I start an explanation here that this function should
// work without modification for most objects you will want to create and so if
// I loose you then just skip it. ;)
//
// Let me open-up the inner workings of the engine a little...
//
// After an object has been created client-side, it's "OnAdd" function is called
// (see above) and it adds itself to the scene. But what does this mean?
//
// Well the engine has lots of structures (believe me lots) one of which is a
// container. This, as it's name implies, can contain objects. You've heard
// me throw the term SceneGraph around but what does it do? Well, in Torque, all
// client-side objects are in a Container named "gClientContainer". Similarly,
// all server-side objects are in a Container named "gServerContainer" but let's
// focus on the client-side here as it's ultimately the client-side rendering we
// are interested in for this discussion.
//
// A container isn't just a big ol' list of objects, it's more than that. You may
// have seen various error messages like "Object is not out of the bins" etc?
//
// The Container splits up the world into cubes called bins. When an object is
// added to the scene, it's bounding-box is used to find which of these bins it
// belongs in. Basically it identifies a box in the world. If the object is too
// big then it gets put into a linear list called the overflow bin (where naughty
// objects go).
//
// When you do a CastRay using the engine it basically checks which of these
// bins it intersects. It then calls all the objects in turn that reside in these
// bins. The objects can then do their own, perhaps more detailed, collision check
// that may be polygon perfect or just the bounding-box, it's entirely up-to the
// object itself.
//
// Where is this going you say? Well, this system is also used to determine what
// objects are in the view. The viewing frustum defines a region that can be
// checked to see which bins it intersects (plus the naughty objects in the overflow
// bin). Using this, these objects "in-scope" have their "prepRenderImage" called
// to allow them to do exactly that, prepare a render image.
//
// Phew! Well what's a render image you say? Boy am I glad you asked that!
//
// A "SceneRenderImage" is simply a structure that defines the rendering attributes
// of the object that control the way the SceneTraversal routines handle it.
// Important factors such as is the object translucent (in which case it needs
// sorted back->front) can be specified here.
//
// In my example, you will see me dynamically allocating a "SceneRenderImage",
// populating relevant fields and then inserting it into the SceneState. You don't
// need to destroy it, the SceneGraph will do that after the frame is complete.
//
// There are lots of fields that I just haven't got the energy to go into here but
// two important ones I will. The "isTranslucent" field tells the SceneGraph that
// at least one portion of your object has transparency. If you didn't already
// know it, rendering transparent objects requires that objects be rendered from
// back to front (far to near) order to look correct. I won't go into why this is
// so because then I'm getting into imaging and then I'll really bore you. ;)
//
// The second important field is "sortType" which allows you to specify the way
// the object is handled by the scene. You can use the following ...
//
// Non-translucent objects ...
//
// Sky - Specialised for Sky object only.
// Terrain - Specialised for Terrain object only.
// Normal - Standard one to use for non-translucent objects.
//
// Translucent objects only ...
//
// Point - Object is a point defined by state->poly[0]
// Plane - Object is a plane defined by state->poly[0-3]
// EndSort - Object should appear after everything else has rendered
// BeginSort - Object should appear before everything else has rendered
//
// The "EndSort" is useful for rendering lens flares which need to be rendered
// after everything else has been rendered.
//
// The "BeginSort" is handy for rendering stuff behind the terrain but after
// the Sky like my fxSunLight object which renders a remote object and a local
// object.
//
// This brings me to an interesting point. You can insert more than one
// SceneRenderImage into the SceneState during the call to "prepRenderImage"
// which results in the "renderObject" being called more than once at the
// point defined by the "SceneRenderImage"s attributes. I do this with the
// fxSunLight but be sure you know what you are doing with this as you can
// significantly reduce your frame-rate by causing the same objects to be
// rendered multiple times.
//
// Why this complexity? Well, it's a very powerful method to use because you
// do all sorts of wizardry by traversing scene portals, clipping the frustum
// and iterating the new frustum up-to a specified depth to give you the
// ability to create mirrors, water reflections and other portal effects.
//
//
// *** renderObject ***
//
// Alrighty then! The SceneTraversal is stuffed with "SceneRenderImage"s, it
// then does all the sorting it needs and then proceeds to actually act on them.
//
// Because each "SceneRenderImage" has a reference to the object (notice the line
// that adds "state->obj = this", it then calls the "renderObject" for each object.
//
// Now we are nearly ready to render. When you enter the "renderObject" function
// we are in what is known as the Canonical state. It's just another way of
// saying a standard state or a consistent state that is guaranteed every-time
// we get to this position. This is handy for many reason but mostly because
// you reduce the possibility of other objects' state settings interacting with
// your functions. Imagine having to reset every possible state setting that could
// possibly affect you, nightmare!
//
// It's always good to check for this state with an AssertFatal at the beginning
// and end of this function.
//
// First-up, we get the current timestamp. We use "getVirtualMilliseconds" here
// that gives us the virtual game time-slice timestamp and we calculate the
// elapsed time since our last render. This is useful because we can use it by
// multiplying it with our changes to give us a frame-rate independent animation.
// This is also handy as you can normalise your object attributes in units/second.
//
// We then check the texture handle in the example, we can't render without it.
// This is specific to this example and can be ignored for you own object if you wish.
// Note though, that as soon as you have changed the rendering state in any way
// then you should restore the canonical state before returning otherwise you
// will get a fatal assertion further on down the line!
//
// I'm not going to explain the rendering pipeline here because there are many
// books which do the same. Drop me an email and I'll recommend one, I think
// I've personally got them all!!
//
// We *must* save the PROJECTION and MODELVIEW matrices plus the VIEWPORT so
// that we can restore them to our nice and friendly canonical state before exit.
//
// With everything saved, we multiply the MODELVIEW by our objects transform
// to effectively move our origin to the objects' position. All our graphical
// commands are then in object-space (or at-least relative to the objects origin).
//
// I won't explain the rest of the function because it's just OpenGL (possibly
// translated into DirectX) and I'm not here to teach you that!
//
// Before we exit we restore canonical state which is really important. As I
// stated above, I do an assertion to double-check this. You can never be to
// cautious with this and it goes away in the RELEASE build anyway.
//
//
// *** packUpdate ***
//
// Right, we are back to the networking. Remember above (inspectPostApply) when
// we made a call to "setMaskBits(...)"?
//
// After we did that, this function gets called. Remember that we are now
// server-side. This function effectively queues-up a bit-stream to be sent
// to the client (irrelevant of whether the client is on the same PC or half-way
// around the world).
//
// First-up we call the parent so that it can get any data it owns sent-out.
//
// We then do something a little clever. When the function was called, it was
// passed the mask that we sent using the "setMaskBits(...)" function.
//
// When designing your objects you can split the data up into groups that
// you can get sent using the appropriate mask(s).
//
// For each group you *always* write a flag signaling whether the group has
// data to send. The client will check this and know whether the following
// data is the rest of the group data or another group flag.
//
// In the example I have only one group controlled by the mask,
// "fxRenderObjectMask". Because I do a boolean "AND" against the passed-in
// mask I effectively send whether the group is being sent or not. The
// "writeFlag" function is also kind enough to return the result of this
// masking so that you can use it as a condition in an if(cond) statement as
// I do.
//
// Within this block you can call a multitude of functions from the BitStream
// class that allow you to write-out your data to the network.
//
// The return value here is important as it becomes the new state mask used by
// the networking code. If you manage to send all your data then you should
// return a zero. Returning any other mask value results in the "packUpdate"
// function being called again with the returned mask. Typically you would
// return the value from the "Parent::packUpdate" function" which which returns
// zero. If there are 'blocks' of data that you couldn't pack for any reason
// and you want to try again next time then you should return something like
// "mask & ~(state flags sent)". To cut a long story short though, returning
// zero indicates that you send all your data and the story ends there unless
// you are doing something a little more technical!
//
//
// *** unpackUpdate ***
//
// If you've understood the above then you shouldn't have any problems here.
//
// You should also know that we are now client-side.
//
// Eventually, after the "packUpdate" function was called (for each client),
// the data is sent out to respective clients. It is important to note that
// the "packUpdate" function is called once for each client connected and that
// each client maintains it's own state mask.
//
// First-up here is to do the friendly thing and call the parents "unpackUpdate"
// routine to allow it to load it's data from the stream.
//
// We then call "readFlag" which should correspond to the flag we wrote in the
// "packUpdate" function. If it's true then we know that the group data follows
// and we proceed to read it.
//
// It's using this "readFlag" then reading group data that we can control exactly
// what data elements get sent/received. You could do a mask per data item but that
// would be overkill. If you look deeper you find that the stream read/write
// commands also let you save bandwidth by allowing you to specify bit-lengths
// of data items but be sure you know what you are doing with these.
//
// Another interesting note is that after the data has been loaded I load-up
// any specified textures here. Most of the standard engine objects *don't* do
// this and only load them up in the "onAdd()" function (see above) which
// obviously only gets called once at startup. This accounts for why you need
// to restart the mission to see your changes. I recently modified the "Sky"
// object by adding the "loadDML()" call into the client-side "unpackUpdate"
// function.
//
// Also, note that you should never load texture resources on the server as the
// server doesn't actually do any rendering and so it's pointless.
//
// Also, because some functions are called server-side *and* client-side you need a
// method of knowing which side you are on and doing an appropriate action.
// You can do this with the calls "isClientObject()" and "isServerObject()".
//
// ******************
//
// Well, I hope you found this informative and I'm sorry if I didn't go into
// some areas in enough detail but I had to limit this somehow.
//
// If you have any specific questions then drop me an email to...
//
// melv.may@btinternet.com
//
// All the best everyone,
//
// - Melv.
//
//------------------------------------------------------------------------------
About the author
#2
It's a do it yourself object. If you want your own C++ engine object that can do rendering or other stuff then this provides an excellent starting point. Currently there is nothing in the engine that provides a clean and simple example of how to proceed.
You can use it to create almost anything your imagination can think of, it's entirely up to you, you've now got a head-start though.
We're looking at getting this put into the engine and possibly others to start to provide examples for people to use.
I only wish I had something like this when I started because it would have explained loads and saved lots of headaches.
- Melv.
09/11/2002 (2:41 pm)
Eric,It's a do it yourself object. If you want your own C++ engine object that can do rendering or other stuff then this provides an excellent starting point. Currently there is nothing in the engine that provides a clean and simple example of how to proceed.
You can use it to create almost anything your imagination can think of, it's entirely up to you, you've now got a head-start though.
We're looking at getting this put into the engine and possibly others to start to provide examples for people to use.
I only wish I had something like this when I started because it would have explained loads and saved lots of headaches.
- Melv.
#3
You mention using this as the basis for things such as mirror or water reflection. Very intriguing possibilities.
I can see where it would have come in handy for your other fx items. The water textures in your sample picture made me wonder if things like moving textures aren't all too far off from becoming a Torque reality now?
BTW, if you ever need some textures or art content, let me know.
09/11/2002 (3:59 pm)
Thanks Melv.You mention using this as the basis for things such as mirror or water reflection. Very intriguing possibilities.
I can see where it would have come in handy for your other fx items. The water textures in your sample picture made me wonder if things like moving textures aren't all too far off from becoming a Torque reality now?
BTW, if you ever need some textures or art content, let me know.
#4
The elegance of Torque isn't always clear or best structured... clear docs and an overview of the design (in plain speak) is helpful for everybody...
I would say that a clean base is something the engine could really use... the lack of one is on the oddity list for people new to the code... I remember first looking at the player class and thinking ewwwwwwwwwwwwwwww... there's far too much crammed in there.
You always seem to pick something useful... This might be the most yet...
-J
09/11/2002 (5:51 pm)
Thanks Melv,The elegance of Torque isn't always clear or best structured... clear docs and an overview of the design (in plain speak) is helpful for everybody...
I would say that a clean base is something the engine could really use... the lack of one is on the oddity list for people new to the code... I remember first looking at the player class and thinking ewwwwwwwwwwwwwwww... there's far too much crammed in there.
You always seem to pick something useful... This might be the most yet...
-J
#5
09/12/2002 (1:35 pm)
This is excellent. Nice writeup in the comments and clears up quite a bit for our team. Thanks Melv.
#6
Now I can go back to my own creation and see what I may have missed. Plus I have some of the blanks filled in.
Just what was needed!
09/13/2002 (4:50 pm)
Thanks Melv.Now I can go back to my own creation and see what I may have missed. Plus I have some of the blanks filled in.
Just what was needed!
#7
Could it be used to create subclasses of classes other than SceneObject, like projectile?
Good work Melv :)
09/13/2002 (11:24 pm)
This sounds really interesting.Could it be used to create subclasses of classes other than SceneObject, like projectile?
Good work Melv :)
#9
Thanks Mel for the clarity. Now I can get back to trying to get my QuickTimeVR style billboard to work.
09/14/2002 (11:59 pm)
This is just the kind of "tutorial" enhancements GG needs to provide for all the budding indie game visionaries out there! I don't consider this "spoon fed hand holding" this is not easily infered from anything on the gg website or the source code itself. I figured alot of this out, but never could get it all to work so I gave up and moved on.Thanks Mel for the clarity. Now I can get back to trying to get my QuickTimeVR style billboard to work.
#10
I made a mistake in the description of the functionality of the "packUpdate" function and Tim Gift kindly got me back on track.
I guess we are all learning things here and I'm certainly no exception. That's the good thing about documents like this, they can put straight misconceptions regarding the engines' works.
Cheers guys.
- Melv.
09/15/2002 (10:46 am)
I just updated the above document and the download itself.I made a mistake in the description of the functionality of the "packUpdate" function and Tim Gift kindly got me back on track.
I guess we are all learning things here and I'm certainly no exception. That's the good thing about documents like this, they can put straight misconceptions regarding the engines' works.
Cheers guys.
- Melv.
#11
09/16/2002 (7:53 pm)
Melv do all the object rendered with this code, get added to the scene graph, and evaluated if they have alpha channels?
#13
10/28/2002 (2:39 pm)
#16
11/01/2002 (10:24 pm)
I can't seem to get this into the game... Is there some command I need to enter into the console?
#17
Excuse my clueless-ness, but would it be possible to use this to render something solid?
So the player couldn't move or shoot through it?
Thanks,
-- Adrian St. Onge
11/13/2002 (3:48 pm)
Hello,Excuse my clueless-ness, but would it be possible to use this to render something solid?
So the player couldn't move or shoot through it?
Thanks,
-- Adrian St. Onge
#18
11/22/2002 (8:32 pm)
Youve contributed alot Melv thankyou for picking up the slack of others. I was lost in how persistent objects could be created. Thanks for summerising the process.
#19
Melv you are my personal god for the day (I guess Carmack gets the day off). jk
11/23/2002 (6:19 pm)
I finally got around to messing with this today, it's awesome, so useful! Going to be very easy to add all of the neat little ogl effects I have been making in the past months.Melv you are my personal god for the day (I guess Carmack gets the day off). jk
#20
... keep em wordy... and keep em coming :)
--Mike
01/16/2003 (6:36 am)
Thanks Melv for your 'wordy' explanation... this is exactly what newbees like me need in order to get an idea of what's going on inside the engine...... keep em wordy... and keep em coming :)
--Mike

Torque 3D Owner Eric Forhan
BTW, that is one hella bit of documentation, Melv! lol wowwo