Class to draw a 3d path

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
cheshirekow
Posts: 105
Joined: Mon Jul 27, 2009 4:06 pm
Location: Cambridge, MA

Class to draw a 3d path

Post by cheshirekow »

This code came from the discussion here: http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=34545 which also refers to the discussion here: http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=34439

This is a derived SceneNode class which allows you to quickly create a scene node for a static 3D path by generating a collection of vertices and edges that sample the path's defining function. Probably that's not a good explanation, so this should help:

ImageImage

Note that these are not wire-frames of predefined models, they are generated programmatically from a coded function.

UPDATE: Added code to generate bounding box in constructor, Removed unnecessary grab() in constructor (thanks vitek)

The code for the class is here:

CPathNode.h

Code: Select all

/**
 * 	\file		CPathNode.h
 *  \date:      Aug 4, 2009
 *  \brief:
 *
 *  detail:
 */

#ifndef CPATHNODE_H_
#define CPATHNODE_H_

#include <irrlicht.h>
#include "CIndexedPrimitiveNode.h"
#include "IVectorFunction.h"


namespace irr
{

namespace scene
{


/**
 *  A PathNode is a scene node which contains a visible path, which is a thin
 *  line represented in the visual system as line segments connecting vertices.
 *  The path is defined by a three dimensional function of a single parameter
 *  (often time).
 */
class CPathNode : public CIndexedPrimitiveNode
{
    private:
        SMeshBuffer     *m_pMeshBuf;

    public:

        /**
         *  \brief  generates a collection of vertices from the supplied
         *          function over the supplied range and with the supplied
         *          resolution
         *
         *  \param  parent      the parent node to this node in the scene graph
         *  \param  mgr         the scene manager managing this node
         *  \param  id          the id of this node (may be -1)
         *  \param  first       the start value of the scalar input parameter
         *  \param  last        the end value of the scalar input parameter
         *  \param  nSegments   the number of line segments to generate
         *  \return
         */
        CPathNode( ISceneNode* parent, ISceneManager* mgr,
                    s32 id, core::IVectorFunction *function,
                    f32 first, f32 last, u32 nSegments );


        /**
         *  \brief  destroys the generated collection of vertices
         */
        virtual ~CPathNode();



};

}

}

#endif /* CPATHNODE_H_ */


CPathNode.cpp

Code: Select all

/**
 * 	\file		CPathNode.cpp
 *  \author:    Joshua J Bialkowski (jbialk@mit.edu)
 *  \date:      Aug 4, 2009
 *  \brief:
 *
 *  detail:
 */

#include <iostream>
#include "CPathNode.h"

using namespace irr;
using namespace scene;
using namespace core;


/**
 *
 *  \todo:  update bounding box prior to rendering, either by overloading the
 *          render() function for CPathNode, or by updating it in the
 *          constructor
 */
CPathNode::CPathNode( ISceneNode* parent, ISceneManager* mgr,
                    s32 id, IVectorFunction *function,
                    f32 first, f32 last, u32 nSegments ) :
    CIndexedPrimitiveNode( parent, mgr, id, 0, nSegments, EPT_LINE_STRIP),
    m_pMeshBuf(new SMeshBuffer())
{
    u32         nIndices = nSegments+1;
    vector3df   min = function->f(first);
    vector3df   max = function->f(first);

    SMeshBuffer& meshBuf = *m_pMeshBuf;

    meshBuf.Vertices.set_used(nIndices);
    meshBuf.Indices.set_used(nIndices);

    // map the indices to their index
    for (u32 i = 0; i < nIndices; ++i)
        meshBuf.Indices[i] = i;

    // define the step size
    f32 step    = (last-first)/nSegments;

    // define the vertex positions
    for (u32 i = 0; i < nIndices; ++i)
    {
        vector3df   point = function->f(first + i*step);
        meshBuf.Vertices[i].Pos = point;

        if( point.X < min.X )
            min.X = point.X;
        if( point.Y < min.Y )
            min.Y = point.Y;
        if( point.Z < min.Z )
            min.Z = point.Z;

        if( point.X > max.X )
            max.X = point.X;
        if( point.Y > max.Y )
            max.Y = point.Y;
        if( point.Z > max.Z )
            max.Z = point.Z;
    }

    aabbox3df bounds( min, max );
    meshBuf.setBoundingBox( bounds );

    setNewMesh(m_pMeshBuf, nSegments);
}




CPathNode::~CPathNode()
{
    m_pMeshBuf->drop();
}


This class derives from Pellaeon's CIndexedPrimitiveNode which is recreated here with some documentation and some minor changes

CIndexedPrimitiveNode.h

Code: Select all

/**
 * 	\file		CIndexedPrimitiveNode.h
 *  \date:      Aug 4, 2009
 *  \brief:
 *
 *  detail:
 */

#pragma once

#include <ISceneNode.h>
#include "CMeshBuffer.h"

namespace irr
{
namespace scene
{


/**
 *  A scene node which is composed of indexed primitives for drawing a particle
 *  cloud or line drawing
 */
class CIndexedPrimitiveNode : public ISceneNode
{
    public:
        /**
         *  \brief  Creates the scene node from a pre-existing collection of
         *          vertices as an object of the IMeshBuffer interface
         *
         *  \param  parent          the parent scene node
         *  \param  mgr             the scene manager for this node
         *  \param  id              the nodes id
         *  \param  pMesh           pointer to the collection of vertices to
         *                          use
         *  \param  primitiveCount  the number of primitives that are defined by
         *                          the vertices in the MeshBuffer
         */
          CIndexedPrimitiveNode(ISceneNode*         parent,
                                ISceneManager*      mgr,
                                s32                 id,
                                IMeshBuffer*        pMesh,
                                u32                 primitiveCount,
                                E_PRIMITIVE_TYPE    primitiveType = EPT_LINES);


        /**
         *  \brief  drops grabbed references
         */
        ~CIndexedPrimitiveNode();


        /**
         *  \brief  redefine the underlying mesh buffer that this node contains
         *  \param  pMesh           pointer to an object implementing the
         *                          IMeshBuffer interface
         *  \param  primitiveCount  the number of primitives stored in the mesh
         *                          buffer
         */
        void setNewMesh(IMeshBuffer* pMesh, u32 primitiveCount)
        {
            if(m_pMesh)
                m_pMesh->drop();

            m_pMesh = pMesh;
            m_pMesh->grab();
            m_primitiveCount = primitiveCount;
        }


        /**
         *  \brief  redefine the primitive type that the underlying mesh buffer
         *          represents
         *  \param  type    member of the E_PRIMITIVE_TYPE union specifiying the
         *                  primative type of the underlying mesh buffer
         */
        void setPrimitiveType(E_PRIMITIVE_TYPE type) { m_primitiveType = type; }


        /**
         *  \brief  ensures that this node is registered with the renderer
         *  \see    irr::scene::ISceneNode::OnRegisterSceneNode()
         */
        virtual void OnRegisterSceneNode();


        /**
         *  \brief  renders this node
         *  \see    irr::scene::ISceneNode::render()
         */
        virtual void render();


        /**
         *  \brief  returns the bounding box that defines a region that is
         *          guarenteed to contain all of the geometry of this node
         *  \return the bounding box
         *  \see    irr::scene::ISceneNode::getBoundingBox()
         */
        virtual const core::aabbox3d<f32>& getBoundingBox() const;


        /**
         *  \brief  returns the number of materials used by this node, which is
         *          incidentally only one
         *  \return 1, there is only one material
         *  \see    irr::scene::ISceneNode::getMatrialCount()
         */
        virtual u32 getMaterialCount() const;


        /**
         *  \brief  return a reference to the [i]th material of this node
         *  \param  i   the index of the material to return
         *  \return a reference to the [i]th material
         */
        virtual video::SMaterial& getMaterial(u32 i);


    private:
        IMeshBuffer*      m_pMesh;              ///< pointer to the underlying
                                                ///  mesh buffer
        E_PRIMITIVE_TYPE  m_primitiveType;      ///< defines how to interpret
                                                ///  the underlying mesh buffer
        u32               m_primitiveCount;     ///< number of total things to
                                                ///  draw
};

}

}

CIndexedPrimitivedNode.cpp

Code: Select all

/**
 * 	\file		CIndexedPrimitiveNode.cpp
 *  \author:    Joshua J Bialkowski (jbialk@mit.edu)
 *  \date:      Aug 4, 2009
 *  \brief:
 *
 *  detail:
 */

#include "CIndexedPrimitiveNode.h"
#include "ISceneManager.h"
#include "IVideoDriver.h"

using namespace irr;
using namespace scene;
using namespace core;



CIndexedPrimitiveNode::CIndexedPrimitiveNode(
    ISceneNode*         parent,
    ISceneManager*      mgr,
    s32                 id,
    IMeshBuffer*        pMesh,
    u32                 primitiveCount,
    E_PRIMITIVE_TYPE    primitiveType):
        ISceneNode(parent, mgr, id),
        m_pMesh(pMesh),
        m_primitiveType(primitiveType),
        m_primitiveCount(primitiveCount)
{
    if(pMesh)
        pMesh->grab();
}




CIndexedPrimitiveNode::~CIndexedPrimitiveNode()
{
    if(m_pMesh)
        m_pMesh->drop();
}




void CIndexedPrimitiveNode::OnRegisterSceneNode()
{
   if (IsVisible) SceneManager->registerNodeForRendering(this);

   ISceneNode::OnRegisterSceneNode();
}



void CIndexedPrimitiveNode::render()
{
   if(!m_pMesh)
       return;

   if (!m_pMesh->getVertexCount() || !m_pMesh->getIndexCount()) return;


   video::IVideoDriver* driver = SceneManager->getVideoDriver();
   driver->setMaterial(m_pMesh->getMaterial());
   driver->setTransform(video::ETS_WORLD,AbsoluteTransformation);
   driver->drawVertexPrimitiveList(m_pMesh->getVertices(),
                           m_pMesh->getVertexCount(),
                           m_pMesh->getIndices(),
                           m_primitiveCount,
                           m_pMesh->getVertexType(),
                           m_primitiveType,
                           m_pMesh->getIndexType());

}



/**
 *  \todo:  what happens if m_pMesh is not yet defined?
 */
const aabbox3d<f32>& CIndexedPrimitiveNode::getBoundingBox() const
{

    return m_pMesh->getBoundingBox();
}



u32 CIndexedPrimitiveNode::getMaterialCount() const
{
   return 1;
}



/**
 *  \todo:  what happens if m_pMesh is not yet defined?
 */
video::SMaterial& CIndexedPrimitiveNode::getMaterial(u32 i)
{
   return m_pMesh->getMaterial();
}


The function that is used to initialize the node must be an object that extends this simple interface

IVectorFunction.h

Code: Select all

/**
 * 	\file		IVectorFunction.h
 *  \date:      Aug 4, 2009
 *  \brief:
 *
 *  detail:
 */

#ifndef IVECTORFUNCTION_H_
#define IVECTORFUNCTION_H_

#include <irrlicht.h>


namespace irr
{

namespace core
{


/**
 *  Interface for any object that maps a scalar input to a three dimensional
 *  vector output. The reason this is an interface, rather than just using
 *  a function pointer in it's place, is that the function may be parameterized
 *  by certain other static values.
 */
class IVectorFunction
{
    public:
        /**
         *  \brief  function that takes a scalar input and returns a 3d vector
         *          value
         *  \param  t   scalar input parameter
         *  \return the value of the function evaluated at [t]
         */
        virtual vector3df   f( f32 t ) = 0;
};

}

}

#endif /* IVECTORFUNCTION_H_ */

[/list]
Last edited by cheshirekow on Wed Aug 05, 2009 4:40 pm, edited 2 times in total.
cheshirekow
Posts: 105
Joined: Mon Jul 27, 2009 4:06 pm
Location: Cambridge, MA

Post by cheshirekow »

Oh, and here is the code for the helix function and for the sphere-ish function that are shown in the screenshots:

Helix:

Code: Select all

class HelixFunction : public IVectorFunction
{
    private:
        f32     m_radius;   ///< radius of a single loop
        f32     m_pitch;    ///< vertical distance between successive loops

    public:
        /**
         *  \brief  constructs a new helix function
         *  \param  radius  the radius of each loop
         *  \param  pitch   the distance between successive loops
         */
        HelixFunction( f32 radius, f32 pitch ) :
            m_radius(radius), m_pitch(pitch){}

        virtual ~HelixFunction(){}

        virtual vector3df f( f32 t )
        {
            vector3df   returnValue;

            returnValue.X = m_radius * cos( 2*PI* t / m_pitch );
            returnValue.Y = m_radius * sin( 2*PI* t / m_pitch );
            returnValue.Z = t;

            return returnValue;
        }
};
Yarn Ball:

Code: Select all

class YarnBallFunction : public IVectorFunction
{
    private:
        f32     m_radius;   ///< radius of a single loop
        f32     m_pitch;    ///< number of loops before covering the sphere

    public:
        /**
         *  \brief  constructs a new yarnball function
         *  \param  radius  the radius of each loop
         *  \param  pitch   number of loops before covering the sphere
         */
        YarnBallFunction( f32 radius, f32 pitch ) :
            m_radius(radius), m_pitch(pitch){}

        virtual ~YarnBallFunction(){}

        virtual vector3df f( f32 t )
        {
            vector3df   returnValue;

            returnValue.X = m_radius * cos( 2*PI* t )*cos( 2*PI*t/m_pitch);
            returnValue.Y = m_radius * sin( 2*PI* t );
            returnValue.Z = m_radius * cos( 2*PI* t )*sin( 2*PI*t/m_pitch);

            return returnValue;
        }
};

And, just for good measure, a simple test function

Code: Select all


int main(int argc, char** argv)
{
    IrrlichtDevice *device = createDevice(
                                  EDT_OPENGL,
                                  dimension2d<s32>(800,600),
                                  32,
                                  false,
                                  false,
                                  false,
                                  0);

    if (device == 0)
        return 1;


    // grab handles to the driver and the scene manager, this should be familiar
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();


    // create a standard First-Person-Shooter kind of key map
    SKeyMap keyMap[8];
    keyMap[0].Action = EKA_MOVE_FORWARD;
    keyMap[0].KeyCode = KEY_UP;
    keyMap[1].Action = EKA_MOVE_FORWARD;
    keyMap[1].KeyCode = KEY_KEY_W;

    keyMap[2].Action = EKA_MOVE_BACKWARD;
    keyMap[2].KeyCode = KEY_DOWN;
    keyMap[3].Action = EKA_MOVE_BACKWARD;
    keyMap[3].KeyCode = KEY_KEY_S;

    keyMap[4].Action = EKA_STRAFE_LEFT;
    keyMap[4].KeyCode = KEY_LEFT;
    keyMap[5].Action = EKA_STRAFE_LEFT;
    keyMap[5].KeyCode = KEY_KEY_A;

    keyMap[6].Action = EKA_STRAFE_RIGHT;
    keyMap[6].KeyCode = KEY_RIGHT;
    keyMap[7].Action = EKA_STRAFE_RIGHT;
    keyMap[7].KeyCode = KEY_KEY_D;

    // then create the camera
    ICameraSceneNode*   camera =
            smgr->addCameraSceneNodeFPS(0, 100, .01, -1, keyMap, 8);

    // and put it somewhere that we can see the object from the beginning
    camera->setPosition(vector3df(0,1,0));

    // make the cursor invisible
    device->getCursorControl()->setVisible(false);

    // Create a new function object, I'll define this to be a helix with radius
    // of 0.5 units and a pitch of 3 units between spirals
    IVectorFunction*    helixFunction = new HelixFunction( 0.5, 3 );
    IVectorFunction*    yarnballFunction = new YarnBallFunction( 1, 10 );

    // then create the scene node that will contain our 3d-path, and specify the
    // scene's root node as it's parent
    CPathNode* helixNode =
            new CPathNode( smgr->getRootSceneNode(), smgr, -1,
                               helixFunction, 0, 100, 10000 );


    // now do the regular render loop
    while(device->run())
    {
        if (device->isWindowActive())
        {
            driver->beginScene(true, true, video::SColor(255,200,200,200));
            smgr->drawAll();
            driver->endScene();
        }
        else
            device->yield();
    }

    device->drop();
    return 0;
}

vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Nice. A couple of comments...

Code: Select all

CPathNode::CPathNode( ISceneNode* parent, ISceneManager* mgr, 
                    s32 id, IVectorFunction *function, 
                    f32 first, f32 last, u32 nSegments ) : 
    CIndexedPrimitiveNode( parent, mgr, id, 0, nSegments, EPT_LINE_STRIP), 
    m_pMeshBuf(new SMeshBuffer()) 
{ 
    m_pMeshBuf->grab(); 
You shouldn't need to grab a IReferenceCounted object that you just allocated with a call to new. It gets an implicit reference count of 1 on construction [see line 47]. Calling grab() here should result in a memory leak of the allocated mesh buffer. You could either not grab it, or you could drop it after the call to setNewMesh(). If you were picky, you don't even really need the m_pMeshBuf member at all. You can allocate the mesh buffer in the constructor, and pass it on to the base class for lifetime management.

Another issue is that the CIndexedPrimitiveNode seems to expect that the bounding box of the mesh buffer is already up to date. I think that you should probably update the bounding box of that mesh buffer before rendering.

It seems that you aren't consistently checking the mesh buffer pointers for validity. You do it in the constructor before calling grab(), but you don't do it in the destructor or in other places where it would seem to be equally important.

Code: Select all

IVectorFunction*    helixFunction = new HelixFunction( 0.5, 3 ); 
IVectorFunction*    yarnballFunction = new YarnBallFunction( 1, 10 ); 
Finally, the above two function objects are allocated on the heap, but are never deallocated. You could add code to delete them, or you could create the objects on the stack and pass their addresses to the scene node constructors.

Travis
cheshirekow
Posts: 105
Joined: Mon Jul 27, 2009 4:06 pm
Location: Cambridge, MA

Post by cheshirekow »

Thanks for the comments!
You shouldn't need to grab a IReferenceCounted object that you just allocated with a call to new.
Oh good. I didn't read the documentation well enough I guess.

P.S. I allocate it on the heap because I didn't want it to be possible to destroy the PathNode object and cause that to destroy the underlying mesh buffer, since another object might have a reference to it.
I think that you should probably update the bounding box of that mesh buffer before rendering.
Ok, thanks for the tip. I'll look into that more.
It seems that you aren't consistently checking the mesh buffer pointers for validity
oops, you're right. There was no check in the original class, it's something I added to allow the PathNode class to initialize it with a null object... To be fixed!
the above two function objects are allocated on the heap, but are never deallocated
that was due to laziness and trying to slap together the demo as quick as possible. You're right though, they should be deleted.
Dorth
Posts: 931
Joined: Sat May 26, 2007 11:03 pm

Post by Dorth »

Awesome, VERY useful code... I needed something like that right about now, so this couldn't have been posted at a better time :)
zillion42
Posts: 324
Joined: Wed Aug 29, 2007 12:32 am
Location: Hamburg, Germany

Post by zillion42 »

nice...
Arcoroc
Posts: 51
Joined: Wed Oct 01, 2008 11:40 pm
Location: Canada

Post by Arcoroc »

Gave it a try - works great. Thanks!
L/
Post Reply