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);
 | |
| 	}
 | |
| }
 | 
