750 lines
30 KiB
C++
Executable File
750 lines
30 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
//
|
|
// Written by Melvyn May, Started on 9th September 2002.
|
|
//
|
|
// "My code is written for the Torque community, so do your worst with it,
|
|
// just don't rip-it-off and call it your own without even thanking me".
|
|
//
|
|
// - Melv.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//$foo = new fxRenderObject() { position = LocalClientConnection.player.getPosition(); Texture = "starter.fps/data/water/water.png"; };
|
|
|
|
#include "dgl/dgl.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "core/bitStream.h"
|
|
#include "math/mathIO.h"
|
|
#include "game/gameConnection.h"
|
|
#include "console/simBase.h"
|
|
#include "sceneGraph/sceneGraph.h"
|
|
#include "sceneGraph/sgUtil.h"
|
|
#include "fxRenderObject.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// 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 identifing 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-independant 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 gameplay 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 signalling 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
|
|
// gameplay 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 upto 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 framerate 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 upto 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 "renderObjec" 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 everytime
|
|
// we get to this position. This is handy for many reason but mostly because
|
|
// you reduce the possibilty 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 timeslice 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 independant 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 signalling 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.
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put the function in /example/common/editor/ObjectBuilderGui.gui [around line 458] ...
|
|
//
|
|
// function ObjectBuilderGui::buildfxRenderObject(%this)
|
|
// {
|
|
// %this.className = "fxRenderObject";
|
|
// %this.process();
|
|
// }
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Put this in /example/common/editor/EditorGui.cs in [function Creator::init( %this )]
|
|
//
|
|
// %Environment_Item[ next free entry ] = "fxRenderObject"; <-- ADD THIS.
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
extern bool gEditingMission;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(fxRenderObject);
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Class: fxRenderObject
|
|
//------------------------------------------------------------------------------
|
|
|
|
fxRenderObject::fxRenderObject()
|
|
{
|
|
// Setup NetObject.
|
|
mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType;
|
|
mNetFlags.set(Ghostable);
|
|
|
|
// Reset Last Render Time.
|
|
mLastRenderTime = 0;
|
|
|
|
// Texture Handle.
|
|
mTextureHandle = NULL;
|
|
// Flare Texture Name.
|
|
mTextureName = StringTable->insert("");
|
|
|
|
// Reset Quad Size.
|
|
mQuadSize = 5.0f;
|
|
// Reset Quad Rotate Speed.
|
|
mQuadRotateSpeed = 90.0f;
|
|
|
|
// Reset Current Angle.
|
|
mCurrentAngle = 0.0f;
|
|
|
|
mObjColor = ColorI(255, 0, 0);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
fxRenderObject::~fxRenderObject()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::initPersistFields()
|
|
{
|
|
// Initialise parents' persistent fields.
|
|
Parent::initPersistFields();
|
|
|
|
// Add out own persistent fields.
|
|
//addGroup( "MyFirstGroup1" );
|
|
addField( "QuadSize", TypeF32, Offset( mQuadSize, fxRenderObject ) );
|
|
addField( "QuadRotateSpeed",TypeF32, Offset( mQuadRotateSpeed, fxRenderObject ) );
|
|
addField( "Texture", TypeFilename, Offset( mTextureName, fxRenderObject ) );
|
|
addField( "ObjColor", TypeColorI, Offset( mObjColor, fxRenderObject ) );
|
|
//enddGroup( "MyFirstGroup1" );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool fxRenderObject::onAdd()
|
|
{
|
|
if(!Parent::onAdd()) return(false);
|
|
|
|
// Calculate Quad Radius.
|
|
F32 QuadHalfSize = mQuadSize / 2.0f;
|
|
|
|
// Set initial bounding box.
|
|
//
|
|
// NOTE:- Set this box to completely encapsulate your object.
|
|
// You must reset the world box and set the render transform
|
|
// after changing this.
|
|
mObjBox.min.set( -QuadHalfSize, -0.5f, -QuadHalfSize );
|
|
mObjBox.max.set( QuadHalfSize, +0.5f, QuadHalfSize );
|
|
// Reset the World Box.
|
|
resetWorldBox();
|
|
// Set the Render Transform.
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
// Add to Scene.
|
|
addToScene();
|
|
|
|
// Return OK.
|
|
return(true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::onRemove()
|
|
{
|
|
// Remove from Scene.
|
|
removeFromScene();
|
|
|
|
// Do Parent.
|
|
Parent::onRemove();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::inspectPostApply()
|
|
{
|
|
// Set Parent.
|
|
Parent::inspectPostApply();
|
|
|
|
// Set fxPortal Mask.
|
|
setMaskBits(fxRenderObjectMask);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::onEditorEnable()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::onEditorDisable()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool fxRenderObject::prepRenderImage( SceneState* state, const U32 stateKey, const U32 startZone,
|
|
const bool modifyBaseZoneState)
|
|
{
|
|
// Return if last state.
|
|
if (isLastState(state, stateKey)) return false;
|
|
// Set Last State.
|
|
setLastState(state, stateKey);
|
|
|
|
// Is Object Rendered?
|
|
if (state->isObjectRendered(this))
|
|
{
|
|
// Yes, so get a SceneRenderImage.
|
|
SceneRenderImage* image = new SceneRenderImage;
|
|
// Populate it.
|
|
image->obj = this;
|
|
image->isTranslucent = false;
|
|
image->sortType = SceneRenderImage::Normal;
|
|
|
|
// Insert it into the scene images.
|
|
state->insertRenderImage(image);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::renderObject(SceneState* state, SceneRenderImage*)
|
|
{
|
|
// Check we are in Canonical State.
|
|
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
|
|
|
|
// Calculate Elapsed Time and take new Timestamp.
|
|
S32 Time = Platform::getVirtualMilliseconds();
|
|
F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f;
|
|
mLastRenderTime = Time;
|
|
|
|
// Return if we don't have a texture.
|
|
if (!mTextureHandle) return;
|
|
|
|
// Save state.
|
|
RectI viewport;
|
|
|
|
// Save Projection Matrix so we can restore Canonical state at exit.
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
|
|
// Save Viewport so we can restore Canonical state at exit.
|
|
dglGetViewport(&viewport);
|
|
|
|
// Setup the projection to the current frustum.
|
|
//
|
|
// NOTE:- You should let the SceneGraph drive the frustum as it
|
|
// determines portal clipping etc.
|
|
// It also leaves us with the MODELVIEW current.
|
|
//
|
|
state->setupBaseProjection();
|
|
|
|
// Save ModelView Matrix so we can restore Canonical state at exit.
|
|
glPushMatrix();
|
|
|
|
// Transform by the objects' transform e.g move it.
|
|
dglMultMatrix(&getTransform());
|
|
|
|
// Rotate by Rotate Speed.
|
|
//
|
|
// NOTE:- We use the elapsed time as a coeficient,
|
|
// that way we get consistent rotational speed
|
|
// independant of frame-rate.
|
|
//
|
|
mCurrentAngle += mFmod(mQuadRotateSpeed * ElapsedTime, 360);
|
|
|
|
// Rotate Quad by current roation.
|
|
glRotatef(mCurrentAngle, 0,0,1);
|
|
|
|
// Setup our rendering state (alpha blending).
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
// Enable Texturing.
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
// Select the objects' texture.
|
|
glBindTexture(GL_TEXTURE_2D, mTextureHandle.getGLName());
|
|
|
|
// Set Colour/Alpha.
|
|
glColor4f(1,1,1,1);
|
|
|
|
// Calculate Quad Radius.
|
|
F32 QuadHalfSize = mQuadSize / 2.0f;
|
|
|
|
// Draw a Quad.
|
|
//
|
|
// NOTE:- We draw in object space here and *not* world-space.
|
|
// Notice that Z is UP in this system and XY lie on the terrain plane.
|
|
//
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0,0);
|
|
glVertex3f(-QuadHalfSize,0,+QuadHalfSize);
|
|
glTexCoord2f(1,0);
|
|
glVertex3f(+QuadHalfSize,0,+QuadHalfSize);
|
|
glTexCoord2f(1,1);
|
|
glVertex3f(+QuadHalfSize,0,-QuadHalfSize);
|
|
glTexCoord2f(0,1);
|
|
glVertex3f(-QuadHalfSize,0,-QuadHalfSize);
|
|
glEnd();
|
|
|
|
// Restore our canonical rendering state.
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_TEXTURE_2D);
|
|
// Restore our canonical matrix state.
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
// Restore our canonical viewport state.
|
|
dglSetViewport(viewport);
|
|
|
|
// Check we have restored Canonical State.
|
|
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
U32 fxRenderObject::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
|
|
{
|
|
// Pack Parent.
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
// Write fxPortal Mask Flag.
|
|
if (stream->writeFlag(mask & fxRenderObjectMask))
|
|
{
|
|
// Write Object Transform.
|
|
stream->writeAffineTransform(mObjToWorld);
|
|
// Write Texture Name.
|
|
stream->writeString(mTextureName);
|
|
// Write Quad Size.
|
|
stream->write(mQuadSize);
|
|
// Write Quad Rotate Speed.
|
|
stream->write(mQuadRotateSpeed);
|
|
// Write Object Color
|
|
stream->write( mObjColor );
|
|
}
|
|
|
|
// Were done ...
|
|
return(retMask);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void fxRenderObject::unpackUpdate(NetConnection * con, BitStream * stream)
|
|
{
|
|
// Unpack Parent.
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
// Read fxPortal Mask Flag.
|
|
if(stream->readFlag())
|
|
{
|
|
MatrixF ObjectMatrix;
|
|
|
|
// Read Object Transform.
|
|
stream->readAffineTransform(&ObjectMatrix);
|
|
// Read Texture Name.
|
|
mTextureName = StringTable->insert(stream->readSTString());
|
|
// Read Quad Size.
|
|
stream->read(&mQuadSize);
|
|
// Read Quad Rotate Speed.
|
|
stream->read(&mQuadRotateSpeed);
|
|
// Read Object Color
|
|
stream->read( &mObjColor );
|
|
|
|
|
|
// Set Transform.
|
|
setTransform(ObjectMatrix);
|
|
|
|
// Reset our previous texture handle.
|
|
mTextureHandle = NULL;
|
|
// Load the texture (if we've got one)
|
|
if (*mTextureName) mTextureHandle = TextureHandle(mTextureName, BitmapTexture, true);
|
|
|
|
// Calculate Quad Radius.
|
|
F32 QuadHalfSize = mQuadSize / 2.0f;
|
|
|
|
// Set bounding box.
|
|
//
|
|
// NOTE:- Set this box to completely encapsulate your object.
|
|
// You must reset the world box and set the render transform
|
|
// after changing this.
|
|
mObjBox.min.set( -QuadHalfSize, -0.5f, -QuadHalfSize );
|
|
mObjBox.max.set( QuadHalfSize, +0.5f, QuadHalfSize );
|
|
// Reset the World Box.
|
|
resetWorldBox();
|
|
// Set the Render Transform.
|
|
setRenderTransform(mObjToWorld);
|
|
}
|
|
}
|