Game Development Community

dev|Pro Game Development Curriculum

Attachable SceneObjects in Hierarchical Coordinate Spaces

by Anthony Lovell · 11/15/2005 (2:49 pm) · 74 comments

Download Code File

There is a README.txt file in the resource (which only supplies SceneObject.cc and SceneObject.h -- which are altered versions of what is presently on the 1.4rc2 HEAD as of Oct 16 2005).

What follows is from the README.txt file enclosed in the zip file.

WHAT IT DOES

Allows dynamic "physical attachment" of SceneObjects to other SceneObjects.
Whenever a SceneObject is rotated or translated in any manner, any other
SceneObjects previously attached as children move and rotate as though physically
bolted into place. Thus, it works much like the limited "ShapeBase mounting"
facilities elsewhere in TGE.

For intsance, one can have an fxLight mounted on the end of a magic wand -- no
special code is required. Want to have knobs, sliders, and doors that can move
about, but you don't want to bother artists to get the animation done right? It
should now be easy to do this in script.

Just about every existing TorqueScript function pertaining to (world) transform
data now has a "Local" version pertaining to the relationship between an object
and the parent to which it is attached. I left the old functions unchanged for
backward compatibility, but seeing that the local versions know how to behave when
there is no parent object defined, the old functions should be renamed to explicitly
indicate that they operate in the "root coordinate system" (I try not to say "World",
as I feel that, too, is a misnomer as a later resource may make clear).

REPLACES: SceneObject.h, SceneObject.cc (both in the engine\sim folder)

INSTALLATION

For details, check the Resource page for this, but if your TGE source code is
approx v1.4, you will be off to a good start if you unzip this file into the
TGE root folder (that is, the one that has "engine", "example" and "vc7" as
subdirectories), preserving and applying the path information in the zip.

Alternatively, you can hand-place the files into your project tree according
to the instructions in the resource page.

NEW SCENEOBJECT C++ FUNCTIONS

/// also useful for setting NULL parent (making SceneObject a root object)
   virtual bool attachToParent(SceneObject *parent, MatrixF *atThisOffset = NULL);

   /// returns NULL if this is a root SceneObject
   SceneObject *getParent();
   
   /// attach a subobject, but do not alter the subObject's present absolute position or orientation
   /// returns FALSE if the operation would cause a cycle in the scene graph
   bool attachChild(SceneObject* subObject);   

   /// attach a subobject, at the specified offset expressed in our local coordinate space
   /// returns FALSE if the operation would cause a cycle in the scene graph
   bool attachChildAt(SceneObject* subObject, MatrixF atThisTransform);   

   /// attach a subobject, at the specified position expressed in our local coordinate space
   /// returns FALSE if the operation would cause a cycle in the scene graph
   bool attachChildAt(SceneObject* subObject, Point3F atThisPosition);   
   
   /// how many child SceneObjects are (directly) attached to this one?
   U32 getNumChildren() const;

   /// how many child objects does this SceneObject have when we count them recursively?
   U32 getNumProgeny() const;

   /// returns the (direct) child SceneObject at the given index (0 <= index <= getNumChildren() - 1)
   SceneObject *getChild(U32 index) const; 
   
   /// is this SceneObject a child (directly or indirectly) of the given object?
   /// this is used internally to disallow creation of cycles in the scene graph
   bool isChildOf(SceneObject *);

   /// set position in parent SceneObject's coordinate space (or in world space if no parent)
   void setLocalPosition(const Point3F &pos);

   /// move the object in parent SceneObject's coordinate space (or in world space if no parent)
   void localMove(const Point3F ?);
   /// as localMove(const Point3F ?), with different signature
   void localMove(F32 x, F32 y, F32 z);

   /// move the object in world space, without altering place in scene hierarchy
   void move(const Point3F ?);
   /// as move(const Point3F ?), with different signature   
   void move(F32 x, F32 y, F32 z);

   /// returns the transform relative to parent SceneObject transform (or world transform if no parent)
   const MatrixF& getLocalTransform() const;
   /// returns the position within parent SceneObject space (or world space if no parent)
   Point3F getLocalPosition() const;
   
   
   virtual void onParentScaleChanged();   
   virtual void onParentTransformChanged();
      
   /// Sets the Object -> Parent transform.  If no parent SceneObject, this is equivalent to 
   ///  setTransform()
   virtual void setLocalTransform(const MatrixF & mat);


   /// Called to let instance specific code happen 
   virtual void onLostParent(SceneObject *oldParent);   

   /// Called to let instance specific code happen 
   virtual void onNewParent(SceneObject *newParent);   

   /// notification that a direct child object has been attached
   virtual void onNewChild(SceneObject *subObject);   

   /// notification that a direct child object has been detached
   virtual void onLostChild(SceneObject *subObject);


NEW SCENEOBJECT SCRIPT FIELDS

In a new section called "SceneGraph"
parent -- a reference to a parent SimObject (better be NULL or a SceneObject!)
pos -- a position relative to the parent (same as "position" -- which is to say root position -- when there is no parent)
rot -- a rotation relative to the parent (same as "rotation" -- which is to say root rotation -- when there is no parent)



NEW SCENEOBJECT SCRIPT FUNCTIONS

ConsoleMethod( SceneObject, getLocalTransform, const char*, 2, 2, "Get transform of object relative to its parent.")
ConsoleMethod( SceneObject, getLocalPosition, const char*, 2, 2, "Get position of object relative to its parent.")
ConsoleMethod( SceneObject, setLocalTransform, void, 3, 3, "(Transform T)")
ConsoleMethod( SceneObject, getLocalForward, const char*, 2, 2, "Returns a vector indicating the direction this object is facing within parent's space.")
ConsoleMethod( SceneObject, setLocalPosition, void, 3, 3, "(Point3F p)")

ConsoleMethod( SceneObject, move, void, 3, 3, "(Point3F deltaInWorldSpace)") // how was THIS not already there?
ConsoleMethod( SceneObject, localMove, void, 3, 3, "(Point3F deltaInParentsSpace)")
ConsoleMethod( SceneObject, setPosition, void, 3, 3, "(Point3F worldPos)")  // how was THIS not already there?

ConsoleMethod(SceneObject, getNumChildren, S32, 2, 2, "returns number of direct child objects")
ConsoleMethod(SceneObject, getNumProgeny, S32, 2, 2, "returns number of recursively-nested child objects")
ConsoleMethod(SceneObject, getChild, S32, 3, 3, "getChild(int index) -- returns child SceneObject at given index")
ConsoleMethod(SceneObject, attachChildAt, bool, 4, 4, "(SceneObject subObject, MatrixF offset)"
              "Mount object to this one with the specified offset expressed in our coordinate space.")
ConsoleMethod(SceneObject, attachToParent, bool, 3, 3, "attachToParent(SceneObject)"
              "specify a null or non-null parent")
ConsoleMethod(SceneObject, getParent, S32, 2, 2, "returns ID of parent SceneObject")
ConsoleMethod(SceneObject, attachChild, bool, 3, 3, "(SceneObject subObject)"
              "attach an object to this one, preserving its present transform.")
ConsoleMethod(SceneObject, detachChild, bool, 3, 3, "SceneObject subObject")

CAVEATS

I do not understand fully the network implications of this change. I attempted to
mimic things done in support of the existing ShapeBase::mounting logic in TGE, but
it is likely that people will have corrections to offer from this side of things.

Similarly, I was unsure just how to handle onDelete() notifications.

Lastly, it is possible that my efforts to have children objects follow their parent object's
changes in transform might be keying off the wrong transform. Should it perhaps be the renderTransform? My kingdom for some compact comments right in the code!

TODOS

1. base existing ShapeBase mounting function upon this platform
2. rename the existing unqualified functions (e.g.: setPosition(), setTransform())
to names which properly and fully convey their actual effect (e.g.: setRootPosition(),
setRootTransform()).
3. remove cruft in TGE such as special code to mount a camera on something
4. GameBase::getProcessAfter() may be nearly or completely unnecessary after #1?
Page «Previous 1 2 3 4 Last »
#1
11/15/2005 (2:56 pm)
I'll be the first to thank you. You saved me allot of work.
#2
11/15/2005 (3:04 pm)
Can you re-upload the file, it is damaged.
#3
11/15/2005 (8:13 pm)
yeah, links not working.
@Robert, I have the files if you want them.
#4
11/16/2005 (3:31 am)
can you email them to me to? This is a really great resource.
#5
11/16/2005 (5:52 am)
Well done Anthony!
#6
11/16/2005 (12:12 pm)
Anthony for president!

And once this is debugged+confirmed working in a networked environment - add to head ;-)
#7
11/16/2005 (12:59 pm)
Ramen, I would love it if you would email me the files.
#8
11/16/2005 (2:35 pm)
Works fine with TSE, great job!
#9
11/16/2005 (3:10 pm)
How is everyone downloading this? Someone please send it to me.
#10
11/16/2005 (5:28 pm)
Erm... clicking on the zip keeps resolving back to this page ... not the zip file. Can you update this please?
#11
11/17/2005 (6:43 am)
The page, as composed, seems bad with its link.

Try this copy on my server until such time as this page is fixed:

http://dreadnoughtproject.org/HierarchicalCoordinateSpaces_20051101a.zip

tone
#12
11/17/2005 (6:48 am)
Works great! Thanks Anthony.
#13
11/17/2005 (8:35 am)
@Anthony if you edited or fixed any thing after the inital posting of the resource you must re-upload the zip file for the d/l link work, a little bug in the system =)
#14
11/17/2005 (8:50 am)
@Anthony -- I will try that, but the issue was that the previous link was fundamentally incorrect. I have not changed anything and don't think a further upload will remedy the situation. I have emailed Ben with a pointer to my original copy if indeed they fumbled it, but I think hand-editing the PHP is probably necessary to fix whatever went wrong.

I wonder if my use of mixed case hampered issues.

tone
#15
11/17/2005 (8:53 am)
Ahh -- it did work (or Ben already fixed it).
All should be well now.

tone
#16
11/18/2005 (10:20 am)
When I attach a building to a player, all works great except.. when I rotate my player, the building rotates around my player, instead of on its center. Anyone know what I should do to fix this?

NM, I just noticed that some default TGE interiors do this.
#17
11/18/2005 (10:49 am)
I may not understand what you are saying, but the behavior you describe is the behavior my design SEEKS. The hierarchy of objects has the root objects being unattached and moving about within the global coordinate space, and their child objects move about with their parents like possums riding on their mom's back.

You should attach your player to your BUILDING, not the other way around. Then, the player should move independently in parent-space (localMode() or setLocalTransform()) or in world space (e.g.: move() or setTransform()).

Note that most existing code (script and C++) in TGE has calls to change the global transform of an object -- operations that explcitly alter absolute position and orientation. This is not desireable -- the more common case is that you want to altert your transform relative to the thing you are ON, and in my code these have separate names suich as setLocalTransform(). IDEALLY, I feel that when my code is working fully as intended that we should rename all existing methods to explicit names that indicate they operate in absolute (or "root" or "scene" or "stage" or whatever word seems nice) space and this will clarify what is going on. Since my system equates local transform to world transform when an object has no parent, its use should be nearly universal in any application. Explicit resort to world operations should be rare indeed when one considers things to be "on" an object.

as to your building, however ---
If your building is an InteriorInstance, I do not know if this will work, simply because I have little idea how InteriorInstances work. If it does not work, it should be made to work, as InteriorInstances should not be any different than any other SceneObject in terms of transform -- but that may only apply in an ideal code world.

tone
#18
11/18/2005 (3:47 pm)
So what would be the easiest way to make the players local transform update, from what I can see it does not do this right?
#19
11/18/2005 (6:42 pm)
If my code is truly ready, add the player to the parent object he should walk upon/within and then adjust the player's localTransform. If you are working in script, the first thing you should do is eye ANY get/setPosition and get/setTransforms with suspicion because 90+% of them should be the "local" variant, e.g.: setLocalPosition()

For instance, if I recall that +Z == forward

thingPlayerShouldBeWalkingOn->attachChild(player);

// move player forward on the thing he is walking on 1 meter
player->localMove(0, 0, 1);

Keep in mind that I have done very little testing of this. I frankly have little idea how TYGE works, because I've only been at it... what... 2 years. :(

I focused only on calls which allow you to structure geometry in the world that was previously a flat collection of objects. I don't know to what extent it works with the "move" systems and networking, but if you find that I a missing things you need, let me know and I'll figure out the right way to add them.

tone
#20
11/18/2005 (7:04 pm)
I'm not seeing where I would update the player's localtransform in the engine. Do you know what piece of code gets called when the player move's forward? That should be the location to update the localtransform, I cant find that though.

I'm sure somewhere in MoveManager there is an appropriate place to do player.localmove(0,0,1);
Page «Previous 1 2 3 4 Last »