BasicShape
by Daniel Buckmaster · 08/23/2007 (4:41 pm) · 11 comments
Download Code File
Abstract: (yeah, I can get fancy)
Very true, says I, but what if the fool wants his dragon to animate and play sounds? There are two ways to go when choosing an object to derive a class with a shape file from - TSStatic and ShapeBase. ShapeBase is derived from GameBase, which in turn comes from SceneObject. TSStatic is derived directly from SceneObject.
The key difference between these two classes is that ShapeBase is linked into the datablock system, by virtue of deriving from GameBase. That alon is a huge advantage over TSStatic. In addition, as I didn't know until Stephen Zepp pointed it out (see below), inheriting from GameBase allows objects to use process/interpolateTick and advanceTime. These methods basically allow the object to be dynamic, to be able to do useful things. They are required for things like animation, physics, indeed any system that requires objects to be constantly updated.
The downside of ShapeBase is, however, that it has a huge overhead. ShapeBase includes member fields and methods to do just about everything except clean the kitchen sink. If you're writing a custom class, chances are that only a few of these are relevant. Many of them are holdovers from Torque's beginnings, and either don't work or aren't useful except in Tribes.
So, what I determined to do was create a 'real' shapeBase class. This class inherits from GameBase, as the wise man says it should, and contains nothing except what is needed to display a simple shape and make it collideable.
I can't promise anything. I only purchased Torque a few months ago and I'm finding my feet with engine coding. But I figured this would be useful to have, even if it needs a little spit and polish. It's a start, and that's better than nothing, right?
Now, I think thanks are due to someone who really helped me with this code, if indirectly. So Thank You Melvyn May for writing the fxRenderObject tutorial. My code is based off yours, adding elements from TSStatic and ShapeBase. I think I even left some of your comments in there :).
To anyone just learning engine coding, I highly reccoment taking a look at game/fx/fxRenderObject.cc. In it is a wealth of knowledge about Torque's inner workings.
Non-abstract: (erm...)
Description: the two .cc and .h files included in the zip are the header and code files for a new class, BasicShape. I thought naming it BaseShape would be overkill... Also included are implementations of the Convex and GameBaseData classes for use with BasicShape. The .cs file gives an example of how to use the class.
Implementation: copy basicShape.cc and basicShape.h into your engine/game folder. Add them to the project and recompile. Copy basicShape.cs into your example/starter.fps/server/scripts folder. Open game.cs and find the following line:
Known issues: well, it's not perfect. BasicShapeData::preload can probably be stripped down even more, but I'm not too hot on Convexes and collisions, so I didn't know what to exclude. It's pretty much TSStatic::onAdd, though, so maybe not. As of now, shadows don't appear. This is probably my fault, but hey, it gives me (and you!) something to work on. I couldn't let you guys miss out on all the fun, now, could I?
Note: this class was meant to be a base for other objects. For example, the dragon KillerBunny mentions. I myself am going to be using it quite a lot in my game, and this will include adding animations, sounds and lights, and goodness knows what else. So look forward to 'how-to's showing how I did it. Assuming nobody else gets to it first, which actually isn't a bad alternative. All right, I'll stop rambling.
Note 2: I know how much screen shots add to a resource, but this time around it's really not necessary. I mean, there's not much to show. What? Oh right, yeah. I'll really stop this time.
Abstract: (yeah, I can get fancy)
Quote:// For truly it is written: "The wise man extends GameBase for his purposes,--gameBase.h
// while the fool has the ability to eject shell casings from the belly of his
// dragon." -- KillerBunny
Very true, says I, but what if the fool wants his dragon to animate and play sounds? There are two ways to go when choosing an object to derive a class with a shape file from - TSStatic and ShapeBase. ShapeBase is derived from GameBase, which in turn comes from SceneObject. TSStatic is derived directly from SceneObject.
The key difference between these two classes is that ShapeBase is linked into the datablock system, by virtue of deriving from GameBase. That alon is a huge advantage over TSStatic. In addition, as I didn't know until Stephen Zepp pointed it out (see below), inheriting from GameBase allows objects to use process/interpolateTick and advanceTime. These methods basically allow the object to be dynamic, to be able to do useful things. They are required for things like animation, physics, indeed any system that requires objects to be constantly updated.
The downside of ShapeBase is, however, that it has a huge overhead. ShapeBase includes member fields and methods to do just about everything except clean the kitchen sink. If you're writing a custom class, chances are that only a few of these are relevant. Many of them are holdovers from Torque's beginnings, and either don't work or aren't useful except in Tribes.
So, what I determined to do was create a 'real' shapeBase class. This class inherits from GameBase, as the wise man says it should, and contains nothing except what is needed to display a simple shape and make it collideable.
I can't promise anything. I only purchased Torque a few months ago and I'm finding my feet with engine coding. But I figured this would be useful to have, even if it needs a little spit and polish. It's a start, and that's better than nothing, right?
Now, I think thanks are due to someone who really helped me with this code, if indirectly. So Thank You Melvyn May for writing the fxRenderObject tutorial. My code is based off yours, adding elements from TSStatic and ShapeBase. I think I even left some of your comments in there :).
To anyone just learning engine coding, I highly reccoment taking a look at game/fx/fxRenderObject.cc. In it is a wealth of knowledge about Torque's inner workings.
Non-abstract: (erm...)
Description: the two .cc and .h files included in the zip are the header and code files for a new class, BasicShape. I thought naming it BaseShape would be overkill... Also included are implementations of the Convex and GameBaseData classes for use with BasicShape. The .cs file gives an example of how to use the class.
Implementation: copy basicShape.cc and basicShape.h into your engine/game folder. Add them to the project and recompile. Copy basicShape.cs into your example/starter.fps/server/scripts folder. Open game.cs and find the following line:
exec("./environment.cs");After this line, add the following:exec("./basicShape.cs");When you load a mission, you can now add a BasicShape to your mission from the World Editor Creator, in the folder Shapes/Misc. You'll note that you can't collide with the spawned object. This is because the Kork model has no collision mesh. If you want, change the shapeFile field in TestBasicShape to a shape of your own.Known issues: well, it's not perfect. BasicShapeData::preload can probably be stripped down even more, but I'm not too hot on Convexes and collisions, so I didn't know what to exclude. It's pretty much TSStatic::onAdd, though, so maybe not. As of now, shadows don't appear. This is probably my fault, but hey, it gives me (and you!) something to work on. I couldn't let you guys miss out on all the fun, now, could I?
Note: this class was meant to be a base for other objects. For example, the dragon KillerBunny mentions. I myself am going to be using it quite a lot in my game, and this will include adding animations, sounds and lights, and goodness knows what else. So look forward to 'how-to's showing how I did it. Assuming nobody else gets to it first, which actually isn't a bad alternative. All right, I'll stop rambling.
Note 2: I know how much screen shots add to a resource, but this time around it's really not necessary. I mean, there's not much to show. What? Oh right, yeah. I'll really stop this time.
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
Another similar type of resource I found useful is this one: Basic Scriptable Object.
08/06/2007 (9:24 am)
This is a great way to learn the inner workings of Torque. This can help in so many different ways down the road as you get deeper and deeper into your own game development.Another similar type of resource I found useful is this one: Basic Scriptable Object.
#3
I do have a suggestion or two, and a bug fix or two to help you clean it up some more.
This BasicShape isn't capable of moving without the aid of the editor. Which is probably the point anyway. If you need an object to move, inherit from it and implement the appropriate functionality.
1)
BasicShapeObjectMask is setup wrong. In fact, you don't even need it. First I'll explain why it's wrong, and then explain why you actually don't need it anyway.
Why its wrong:
This mask is used as a network mask. Network masks are part of a chain of enums with bit shift values. When inheriting you need to continue that chain otherwise you'll stomp the net masks the base classes have defined. Since you define BasicShapeObjectMask as 1<<0 you are actually stomping a net mask defined in one of the base classes. In fact what you should define BasicShapeObjectMask as is Parent::NextFreeMask. If you need more masks than you do Parent::NextFreeMask << 1 and << 2 and so on. Then when you are done you also define your own NextFreeMask that way any future classes which inherit from this one can continue the chain. Remember though, you can only define new netmasks as long as their is room in the 32 bits, that is you can have a max of 32 defined net masks from the child class down to the base class. That doesn't necessarily mean 32 "total" max though.
What do I mean by that? Well, suppose for a minute that we fix this BasicShape to not stomp the first net mask defined in one of the base classes. (The first net mask is defined in SceneObject btw). And then suppose we create three new classes. ComplexShape, PlayerShape, and ItemShape. ComplexShape would inherit from BasicShape. PlayerShape from ComplexShape and ItemShape from BasicShape. Now lets suppose I defined 5 additional netmasks in ComplexShape, 5 more in PlayerShape, and 5 more in ItemShape.
Here is how the net masks would break down:
BasicShape:
Whatever in SceneObject
Whatever in GameBase
+ BasicShapeObjectMask
ComplexShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
+ 5 more
PlayerShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
Whatever in ComplexShape
+ 5 more
ItemShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
+ 5 more
I dunno if that is clear. But basically, from the most derived class, down to the most basic, there can only be 32 different net masks defined. So if two branches occur from a net mask chain with, say, 13 net masks already defined. Branch A can define 19 addition, and branch B can define 19 additional.
Hope that makes sense.
Now here is why you don't need BasicShapeObjectMask.
In ::inspectPostApply you are called setMaskBits(inspectPostApply); Which is essentially saying, in the future, when the network processes this object, write information to the network pertinent to this mask. Now, if we look in ::packUpdate where you check for the BasicShapeObjectMask net mask we see that you write information out such as position, lighting values, etc. You do this because these are the kinds of values a user can change in the editor.. which is exactly why we told it to write this mask in ::inspectPostApply.
However, if we take a look at GameBase we can see that it also writes a net mask in ::inspectPostApply, called the ExtendedInfoMask. The ExtendedInfoMask is there to do exactly what you have BasicShapeObjectMask doing. So, we can remove BasicShapeObjectMask from your class which will free up 1 of the 32 net masks for that chain. Since net masks are in limited supply that change is important. Remember to remove your ::inspectPostApply as well, and change ::packUpdate to look for an ExtendedInfoMask instead of BasicShapeObjectMask.
2)
Now lets take a look at ::packUpdate for further optimization. That if(isServerObject()) block is wrong. You'll notice in the else block that it makes a remark to demo recording. Demo recording doesn't affect packUpdate and ::unpackUpdate in anyway so there is no reason to treat it differently. Furthermore, you'll never have a "client object" in packUpdate, so checking for it is pointless. That whole else block can be removed, and it is not necessary to check for a "server object".
08/06/2007 (10:49 am)
This is a good resource, great job.I do have a suggestion or two, and a bug fix or two to help you clean it up some more.
This BasicShape isn't capable of moving without the aid of the editor. Which is probably the point anyway. If you need an object to move, inherit from it and implement the appropriate functionality.
1)
BasicShapeObjectMask is setup wrong. In fact, you don't even need it. First I'll explain why it's wrong, and then explain why you actually don't need it anyway.
Why its wrong:
This mask is used as a network mask. Network masks are part of a chain of enums with bit shift values. When inheriting you need to continue that chain otherwise you'll stomp the net masks the base classes have defined. Since you define BasicShapeObjectMask as 1<<0 you are actually stomping a net mask defined in one of the base classes. In fact what you should define BasicShapeObjectMask as is Parent::NextFreeMask. If you need more masks than you do Parent::NextFreeMask << 1 and << 2 and so on. Then when you are done you also define your own NextFreeMask that way any future classes which inherit from this one can continue the chain. Remember though, you can only define new netmasks as long as their is room in the 32 bits, that is you can have a max of 32 defined net masks from the child class down to the base class. That doesn't necessarily mean 32 "total" max though.
What do I mean by that? Well, suppose for a minute that we fix this BasicShape to not stomp the first net mask defined in one of the base classes. (The first net mask is defined in SceneObject btw). And then suppose we create three new classes. ComplexShape, PlayerShape, and ItemShape. ComplexShape would inherit from BasicShape. PlayerShape from ComplexShape and ItemShape from BasicShape. Now lets suppose I defined 5 additional netmasks in ComplexShape, 5 more in PlayerShape, and 5 more in ItemShape.
Here is how the net masks would break down:
BasicShape:
Whatever in SceneObject
Whatever in GameBase
+ BasicShapeObjectMask
ComplexShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
+ 5 more
PlayerShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
Whatever in ComplexShape
+ 5 more
ItemShape:
Whatever in SceneObject
Whatever in GameBase
Whatever in BasicShape
+ 5 more
I dunno if that is clear. But basically, from the most derived class, down to the most basic, there can only be 32 different net masks defined. So if two branches occur from a net mask chain with, say, 13 net masks already defined. Branch A can define 19 addition, and branch B can define 19 additional.
Hope that makes sense.
Now here is why you don't need BasicShapeObjectMask.
In ::inspectPostApply you are called setMaskBits(inspectPostApply); Which is essentially saying, in the future, when the network processes this object, write information to the network pertinent to this mask. Now, if we look in ::packUpdate where you check for the BasicShapeObjectMask net mask we see that you write information out such as position, lighting values, etc. You do this because these are the kinds of values a user can change in the editor.. which is exactly why we told it to write this mask in ::inspectPostApply.
However, if we take a look at GameBase we can see that it also writes a net mask in ::inspectPostApply, called the ExtendedInfoMask. The ExtendedInfoMask is there to do exactly what you have BasicShapeObjectMask doing. So, we can remove BasicShapeObjectMask from your class which will free up 1 of the 32 net masks for that chain. Since net masks are in limited supply that change is important. Remember to remove your ::inspectPostApply as well, and change ::packUpdate to look for an ExtendedInfoMask instead of BasicShapeObjectMask.
2)
Now lets take a look at ::packUpdate for further optimization. That if(isServerObject()) block is wrong. You'll notice in the else block that it makes a remark to demo recording. Demo recording doesn't affect packUpdate and ::unpackUpdate in anyway so there is no reason to treat it differently. Furthermore, you'll never have a "client object" in packUpdate, so checking for it is pointless. That whole else block can be removed, and it is not necessary to check for a "server object".
#4
First, thanks to everyone for the extremely quick comments.
Stephen: that approach you mention in your last paragraph was what I had in mind for later tutorials. This object was sort of simple, and the main reason for using anything was 'that's how TSStatic does it'. I also added your information about processTick - that was something I hadn't actually thought about. I may have hyped it up a bit, but hey, it's in the abstract section!
Rubes: thanks for the link! I'll check that out - it should help seeing how objects and datablocks *should* interact :P.
EDIT: Oh, I see. It's not actual datablocks, just scripting functionality. Well, still usrful.
Robert: thank you so much for that explanation. The mask was done that way in fxRenderObject, so I left it that way, not understanding it at all. Both masks seem redundant, really - I suspect they were in there to illustrate how to define a mask, but if it's wrong anyway...
Anyway, I'll change the code. What you're suggesting is to remove the masks enum from BasicShape, remove the ::inspectPostApply function, and change the mask used in packUpdate, correct? Thank you again :) Resource now updated.
EDIT: Just thought of something cool. I reckon a noble goal for this class would be to eventually switch RigidShape to inheriting from it, instead of from ShapeBase, don't you? If RiidShape objects are going to be used for things like crates and boulders, there's really no sense to be carrying around the huge overhead from ShapeBase. Well meh, I guess that was the original point of this, but I never thought of it replacing ShapeBase anywhere.
Just a thought.
EDIT: In addition, it's a prime candidate for adding resources such as Davide Archetti's hitbox resource. That's just what I intend to do, actually.
08/06/2007 (12:46 pm)
There we go, I couldn't have done everything right :)First, thanks to everyone for the extremely quick comments.
Stephen: that approach you mention in your last paragraph was what I had in mind for later tutorials. This object was sort of simple, and the main reason for using anything was 'that's how TSStatic does it'. I also added your information about processTick - that was something I hadn't actually thought about. I may have hyped it up a bit, but hey, it's in the abstract section!
Rubes: thanks for the link! I'll check that out - it should help seeing how objects and datablocks *should* interact :P.
EDIT: Oh, I see. It's not actual datablocks, just scripting functionality. Well, still usrful.
Robert: thank you so much for that explanation. The mask was done that way in fxRenderObject, so I left it that way, not understanding it at all. Both masks seem redundant, really - I suspect they were in there to illustrate how to define a mask, but if it's wrong anyway...
Anyway, I'll change the code. What you're suggesting is to remove the masks enum from BasicShape, remove the ::inspectPostApply function, and change the mask used in packUpdate, correct? Thank you again :) Resource now updated.
EDIT: Just thought of something cool. I reckon a noble goal for this class would be to eventually switch RigidShape to inheriting from it, instead of from ShapeBase, don't you? If RiidShape objects are going to be used for things like crates and boulders, there's really no sense to be carrying around the huge overhead from ShapeBase. Well meh, I guess that was the original point of this, but I never thought of it replacing ShapeBase anywhere.
Just a thought.
EDIT: In addition, it's a prime candidate for adding resources such as Davide Archetti's hitbox resource. That's just what I intend to do, actually.
#5
08/07/2007 (4:35 am)
Okay, resource is updated. I added implementations of processTick, interpolateTick and advanceTime to basicShape. All they do is call the parent function, though.
#6
08/09/2007 (11:52 am)
Updated again, a minor change - basicShape.cc was including tsStatic.h...
#7
08/11/2007 (9:33 am)
Another small update. When I tried to inherit from BasicShape, I came up with some odd errors. Adding class definitions at the top of basicShape.h worked:Quote:class TSShape;ZIP updated.
class TSShapeInstance;
class Shadow;
#8
08/25/2007 (4:57 pm)
@Robert That info really needs to be in TDN somewhere. That's a very eloquent way of explaining chained netmasks to the newer generation Torquers....
#9
edit: fixed this :-) was looking at the wrong spot
04/28/2008 (6:45 am)
How do i add this object to the searchmasks for object selection?edit: fixed this :-) was looking at the wrong spot
#10
1. After adding the object TestbasicShape into the world with the world editor creator, the testshape doesn't appear in the scene list.
2. Saving. when trying to save the mission, the object isn't saved with it. So basically you need to re-add this object each tme you want to have it in your scene.
Does someone else experience this as well? And how can this be fixed?
regards
edit: never mind, i fixed it. It seems that commenting out // MissionCleanup.add(%shape); did the trick for me.
05/15/2008 (2:33 am)
I'm using tge 1.5.2 and I added your code. There is a small problem with it.1. After adding the object TestbasicShape into the world with the world editor creator, the testshape doesn't appear in the scene list.
2. Saving. when trying to save the mission, the object isn't saved with it. So basically you need to re-add this object each tme you want to have it in your scene.
Does someone else experience this as well? And how can this be fixed?
regards
edit: never mind, i fixed it. It seems that commenting out // MissionCleanup.add(%shape); did the trick for me.
#11
08/20/2008 (11:30 am)
In regards to saving and scene adding - these, I think, are editor script changes. I haven't delved very far into the script side of Torque, let alone the editor, so I can't offer much help there; sorry. 
Torque 3D Owner Stephen Zepp
Two more things to add to your Abstract (and possibly your code):
GameBase provides (in addition to datablocks), the ability for objects to process time events (::processTick(), interpolateTick(), and advanceTime() )--which is needed for things like animation, movement/physics, and other time related capabilities.
Finally, GameBase provides the ability for objects to become control objects -- in other words, objects that derive from GameBase allow the player to control the object with input events.
It would be great to get some more discussion on what specifically you did, and why--"I looked at the animation code from class XX and YY, but found that ZZ was the simplest form that still accomplished the ability to play an idle thread, and change back and forth between other animations", etc.