delta3d

Creating an Actor Tutorial, Part 2 - Create Your Actor

Note: This tutorial is dated and may not compile, link, or run exactly as written!

This tutorial will demonstrate how to make a new actor, import it into the Editor, and then see it in a dtCore::Scene. The actor that we will be creating will apply a sine wave to its vertices to simulate cloth physics, like a flag waving in the breeze. The actor will also expose appropriate properties related to the flags physics that can then be modified in the Editor. There are a couple of steps that need to be completed in order to accomplish this. First, you have to code the actor that will be placed in the scene. Secondly, you have to code the actor proxy that will represent the actor in the Editor. Lastly, you have to write a library to which the actor shall be exported.

Before any of this takes place, we need to set up a project. Look at delta3d/RTIPlugins/CMakeLists.txt in the release or on sourceforge for an example cmake project for building a library.

These are the Delta3D and Open Scene Graph libraries you will need to link to in order to successfully build this project. you will need to do these things included in your cmake.

 


FIND_PACKAGE(Delta3D COMPONENTS dtUtil dtCore dtGame dtABC REQUIRED) 
   INCLUDE_DIRECTORIES(
      ${DTCORE_INCLUDE_DIRECTORIES}
      ${DTUTIL_INCLUDE_DIRECTORIES}
      ${DTGAME_INCLUDE_DIRECTORIES}
      ${DTABC_INCLUDE_DIRECTORIES}
   )

SET(LIB_NAME Tesselator)

SET(HEADER_PATH ${CMAKE_SOURCE_DIR}/include/${LIB_NAME})
SET(SOURCE_PATH ${CMAKE_SOURCE_DIR}/source/${LIB_NAME})

file(GLOB LIB_SOURCES
   "${SOURCE_PATH}/*.cpp"
   )
file(GLOB LIB_HEADERS
   "${HEADER_PATH}/*.h"
   )

ADD_LIBRARY(${LIB_NAME} SHARED
    ${LIB_PUBLIC_HEADERS}
    ${LIB_SOURCES}
)
TARGET_LINK_LIBRARIES(${LIB_NAME}
    ${DTUTIL_LIBRARIES}
    ${DTCORE_LIBRARIES}
    ${DTABC_LIBRARIES}
    ${DTGAME_LIBRARIES}
)


SET_TARGET_PROPERTIES(${LIB_NAME}
           PROPERTIES DEFINE_SYMBOL DT_PLUGIN)

Once the project has been set up correctly, it’s time to start coding our actual actor. Please note that this actor uses complex drawing behavior that requires an understanding of some basics of how Open Scene Graph works. In particular, its use of callback functions and scene graph traversal. The majority of the code present to actually tessellate (waving motion) the actor can be found in the Open Scene Graph example “osgprerender” which is distributed freely with the Open Scene Graph source.

Let’s start by creating two files for the class TessellationActor. In the header file, we need to declare a callback class for Open Scene Graph to use for updating the tessellation plane. Place the following code in TessellationActor.h:


#include <dtCore/transformable.h>
#include <osg/geode>
#include <osg/Vec3>

class GeometryCallback : public osg::Drawable::UpdateCallback, 
   public osg::Drawable::AttributeFunctor
{
public:
   GeometryCallback(const osg::Vec3& o, const osg::Vec3& x, 
      const osg::Vec3& y, const osg::Vec3& z, double period, double xphase, 
      double amplitude): _firstCall(true),_startTime(0.0),_time(0.0),_period(period),
      _xphase(xphase), _amplitude(amplitude),_origin(o),_xAxis(x),_yAxis(y), _zAxis(z) 
   {}
   virtual void update(osg::NodeVisitor* nv,osg::Drawable* drawable)
   {
      const osg::FrameStamp* fs = nv->getFrameStamp();
      double referenceTime = fs->getReferenceTime();
      if (_firstCall)
      {
         _firstCall = false;
         _startTime = referenceTime;
      }

      _time = referenceTime-_startTime;

      drawable->accept(*this);
      drawable->dirtyBound();
   }

   virtual void apply(osg::Drawable::AttributeType type,unsigned int count,osg::Vec3* begin) 
   {
      if (type == osg::Drawable::VERTICES)
      {
         const float TwoPI=2.0f*osg::PI;
         const float phase = -_time/_period;
         
         osg::Vec3* end = begin+count;
         for (osg::Vec3* itr=begin;itr<end;++itr)
         {
            osg::Vec3 dv(*itr-_origin);
            osg::Vec3 local(dv*_xAxis,dv*_yAxis,dv*_zAxis);

            local.z() = local.x()*_amplitude*
               sinf(TwoPI*(phase+local.x()*_xphase)); 

            (*itr) = _origin + 
               _xAxis*local.x()+
               _yAxis*local.y()+
               _zAxis*local.z();
         }
      }
   }

   bool    _firstCall;
   double  _startTime, _time, _period, _xphase;
   float   _amplitude;

   osg::Vec3 _origin, _xAxis, _yAxis, _zAxis;
};
 

This code defines the call back class that the Open Scene Graph will use to tessellate the actor. Now, we need to define our tessellation actor class. Add the following code to your TessellationActor.h file:


class TessellationDrawable : public dtCore::Transformable
{
  public:
    TessellationDrawable();
    virtual ~TessellationDrawable();

    void GeneratePlane();
    
    // Accessors
    inline float GetPeriod()            { return period;        }
    inline float GetAmplitude()         { return amplitude;     }
    inline float GetPhase()             { return phase;         }
    inline int   GetNumberOfSteps()     { return numberOfSteps; }
    inline int   GetWidth()             { return width;         }
    inline int   GetHeight()            { return height;        }
    inline std::string GetTextureFile() { return textureFile;   }
    // Mutators
    inline void SetPeriod(float newPeriod)     { period    = newPeriod;  GeneratePlane(); }
    inline void SetAmplitude(float newAmp) { amplitude = newAmp; GeneratePlane(); }
    inline void SetPhase(float newPhase)       { phase     = newPhase;   GeneratePlane(); }
    inline void SetWidth(int newWidth)         { width     = newWidth;  GeneratePlane(); }
    inline void SetHeight(int newHeight)       { height    = newHeight;  GeneratePlane(); }
    inline void SetNumberOfSteps(int numSteps) { numberOfSteps = numSteps; 	GeneratePlane();}
    inline void SetTextureFile(const std::string &fileName) { textureFile = fileName; GeneratePlane(); }

  private:
    osg::Geode *geode;
    std::string    textureFile;
    int width, height, numberOfSteps;
    float period, amplitude, phase;
};

Now that we have our TessellationDrawable defined, you may notice some member variables that would make good properties. The texture file, for example, is a resource property. The width, height, number of steps in between each plane segment, period, amplitude, and phase are all properties of this tessellating plane that can be edited in the Level Editor. Now, let’s head over to the TessellationActor.cpp file and define our class functions. The function GeneratePlane() is responsible for generating a new tessellation plane whenever a property is edited. Place the following code in your TessellationActor.cpp file:


#include "TessellationActor.h"

#include <osg/Texture2D>
#include <osg/Vec3>
#include <osg/Vec2>
#include <osg/Image>
#include <osg/Geometry>
#include <osg/StateSet>
#include <osgDB/ReadFile>
#include <osg/MatrixTransform>

TessellationDrawable::TessellationDrawable() :
geode(NULL),width(200), height(100), 
numberOfSteps(20), period(1.0), amplitude(0.2)
{
    phase = 1.0 / width;
    GeneratePlane();
}

TessellationDrawable::~TessellationDrawable()
{
}

void TessellationDrawable::GeneratePlane()
{
    // Since the actor will only ever have one plane, remove the old one
    // if it contains it
    if(geode && geode->getDrawable(0))
        geode->removeDrawables(0, 1);

    // Initialize the data
    osg::Geometry  *geometry = new osg::Geometry;
    osg::Vec3Array *verts    = new osg::Vec3Array;
    geode = new osg::Geode;
    
    osg::Vec3 origin(0.0f,0.0f,0.0f);
    osg::Vec3 xAxis(1.0f,0.0f,0.0f);
    osg::Vec3 yAxis(0.0f,0.0f,1.0f);
    osg::Vec3 zAxis(0.0f,-1.0f,0.0f);

    osg::Vec3 bottom = origin;
    osg::Vec3 top    = origin; 
    top.z() += height;
    osg::Vec3 dv = xAxis*(width/((float)(numberOfSteps-1)));

    osg::Vec2Array* texcoords = new osg::Vec2Array;
    osg::Vec2 bottom_texcoord(0.0f,0.0f);
    osg::Vec2 top_texcoord(0.0f,1.0f);
    osg::Vec2 dv_texcoord(1.0f/(float)(numberOfSteps-1),0.0f);

    // Set up the vertices and texture coordinates
    for(int i = 0; i < numberOfSteps; i++)
    {
        verts->push_back(top);
        verts->push_back(bottom);
        top += dv;
        bottom += dv;

        texcoords->push_back(top_texcoord);
        texcoords->push_back(bottom_texcoord);
        top_texcoord += dv_texcoord;
        bottom_texcoord += dv_texcoord;
    }

    // Apply the texture
    osg::Image *texture = osgDB::readImageFile(textureFile);
    osg::Texture2D *temp = new osg::Texture2D(texture);
    temp->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
    temp->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
    osg::StateSet *ss = new osg::StateSet;
    ss->setTextureAttributeAndModes(0, temp, osg::StateAttribute::ON);  
    temp->setUnRefImageDataAfterApply(true);

    // Set up the geometry
    geode->setStateSet(ss);
    geometry->setVertexArray(verts);
    geometry->setTexCoordArray(0, texcoords);
    geode->addDrawable(geometry);
    geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP, 0, verts->size()));
    geometry->setUpdateCallback(new                        
    GeometryCallback(origin,xAxis,yAxis,zAxis,period,phase,amplitude));
    geometry->setSupportsDisplayList(false);
    
    // Add to the transformable
    GetMatrixNode()->addChild(geode);
}

Now we have our constructor defined with reasonable default values, and our GeneratePlane function is defined to create our plane, and is called from each of our mutator functions so our plane will be regenerated with its new values correctly.

Now that we have the TessellationDrawable class set up, it’s time to build its actor class. The main function to know in any actor proxy class is the BuildPropertyMap function. This function is responsible for adding properties associated with an actor. Another function to note is the CreateDrawable function, which actually instantiates the actor that the proxy is abstracting. Add this additional class to TesselationActor.h You can separate the drawable to a separate file you want.



#include "dtCore/transformableactorproxy.h"
#include "TessellationActor.h"

class TessellationActor : public dtCore::TransformableActorProxy
{
public:
   
   void CreateDrawable() { 
      
      SetDrawable(*new TessellationDrawable);
   }
   TessellationActor(void);
   virtual ~TessellationActor(void);

   void BuildPropertyMap();

   void SetTextureFile(const std::string &fileName)
   {
      TessellationDrawable* drawable = NULL;
      GetDrawable(drawable);
      if(drawable == NULL)
      {
         printf("Actor initialized incorrectly\n");
         return;
      }
      drawable->SetTextureFile(fileName);
   }
};
#endif

Notice that the TessellationActor class inherits from dtCore::TransformableActorProxy. This class will later be renamed. We also have a SetTextureFile function, which is responsible for keeping track of the resource texture file that is applied to the actor. Now, let’s head over to TessellationActor.cpp file and define our functions. Add the following code in TessellationActor.cpp:


#include "TessellationActorProxy.h"
#include "dtDAL/enginepropertytypes.h"
#include "dtDAL/datatype.h"

TessellationActor::TessellationActor()
{
}

TessellationActor::~TessellationActor()
{
}

void TessellationActor::BuildPropertyMap()
{
   // The property group
   const dtUtil::RefString GM_COMP_GROUP("PolyGrid");

   // Make sure to build the base class properties
   TransformableActorProxy::BuildPropertyMap();

   // Make sure our actor is valid
   TessellationDrawable* drawable = NULL
   GetDrawable(drawable);
   if(drawable == NULL)
   {
      printf("Actor was initialized incorrectly\n");
      return;
   }
   
   typedef dtCore::PropertyRegHelper<TesselationActor&, TesselationDrawable> RegHelperType;
   RegHelperType propReg(*this, drawable, GM_COMP_GROUP);

   DT_REGISTER_PROPERTY(Width,
            "Tesselation Plane width",
            RegHelperType, propReg);
   DT_REGISTER_PROPERTY(Height,
            "Tesselation Plane Height",
            RegHelperType, propReg);
   DT_REGISTER_PROPERTY(NumberOfSteps,
            "Tesselation Plane steps between each segment",
            RegHelperType, propReg);

   DT_REGISTER_PROPERTY(Period,
            "Tesselation Plane sine wave period",
            RegHelperType, propReg);
   DT_REGISTER_PROPERTY(Amplitude,
            "Tesselation Plane sine wave amplitude",
            RegHelperType, propReg);
   DT_REGISTER_PROPERTY(Phase,
            "Tesselation Plane sine wave starting phase",
            RegHelperType, propReg);
   DT_REGISTER_RESOURCE_PROPERTY(dtDAL::DataType::TEXTURE,
            Texture,
            "Tesselation Plane sine wave starting phase",
            RegHelperType, propReg);

   // Property for the texture file of the plane
   // Note that resource properties are a little different that other properties. The editor 
   // DAL needs to store a reference to the actual proxy due to its internals, so all we do 
   // is add a function onto the proxy which simply calls the actual actor function. 

   AddProperty(new  dtDAL::ResourceActorProperty(*this,dtDAL::DataType::TEXTURE,"Texture",
      "Texture", dtDAL::MakeFunctor(*this, &TessellationActorProxy::SetTextureFile), groupName));
}

The BuildPropertyMap function is responsible for adding all of properties for the tessellation actor. Notice that we added a property to represent each of the pieces of data that the tessellation actor contains. Now we are ensured that our proxy will abstract our tessellation actor properly and that these properties will be available for alteration in the editor. Please note that when adjusting the period, amplitude, or phase that no visible changes will take place in the editor, as these are runtime properties. Now that we have our actor, and actor proxy set up, it’s time to write a library so we can import our new actor into the editor.

Please see the 3rd tutorial, which is located right here .

Trackback

Trackback URL for this entry: http://delta3dengine.org/trackback.php?id=20050720135053455

Here's what others have to say about 'Creating an Actor Tutorial, Part 2 - Create Your Actor':

ืขื‘ื•ื“ื” ื‘ืื™ืจื•ืคื” ืžื•ืฆืจื™ ื™ื ื”ืžืœื— from ืขื‘ื•ื“ื” ื‘ืื™ืจื•ืคื” ืžื•ืฆืจื™ ื™ื ื”ืžืœื—
... ืขื‘ื•ื“ื” ื‘ืงื ื“ื” - ืขื‘ื•ื“ื” ื‘ืืจื”"ื‘ ื”ื™ื ื”ื–ื“ืžื ื•ืช ืœื”ื›ื™ืจ ืืช ืื—ืช ื”ืžื“ื™ื ื•ืช ืฉื”ื›ื™ ืžืขื•ื“ื“ืช ื”ื’ื™ืจื” ื‘ื’ืœืœ ืขื’ืœื•ืช ื™ื ื”ืžืœื— ื‘ืžื™ื™ืŸ. ืืคื™ืœื• ื—ื˜ื™ืคื™ื ืœืืจืฅ  ื™ื›ื•ืœื™ื ืœื”ื•ื•ื... [read more]
Tracked on Monday, July 06 2015 @ 11:48 PM UTC

About delta3d

delta3d is a game and simulation engine appropriate for a wide variety of simulation and entertainment applications. delta3d uses best-of-breed open source technologies to create a fully integrated game engine and with content creation tools.MORE

Twitter

User Functions





Don't have an account yet? Sign up as a New User!

Lost your password?