A Series Of Tubes         Creates a set of tubes along a path

A Series of Tubes

A member of the forums (StillmatikX) needed some tubes for his application, and I might need something like it in the future, so I whipped this up:
Tubes3.jpg

It may not look amazing, but it does the job. Adjusting the material and some initial settings will let you control size, shape and any material attributes. I haven't tested it thoroughly, but you can update your list of points that the tubes follow if you need to add/remove points on the tube.

The Code

This is the header for the SeriesOfTubes class.

Tubes.h

#ifndef _TUBES_H_
#define _TUBES_H_

#include <OgrePrerequisites.h>
#include <OgreMaterial.h>
#include <OgreMesh.h>

namespace Ogre
{
    class SeriesOfTubes
    {
    public:
        SeriesOfTubes(
            Ogre::SceneManager* sceneMgr, 
            const Ogre::uint numberOfSides = 0, 
            const Ogre::Real radius = 0.0,
            const Ogre::uint sphereRings = 0,
            const Ogre::uint sphereSegments = 0,
            const Ogre::Real sphereRadius = 0.0,
            const Ogre::Real sphereMaxVisibilityDistance = 0.0);
        ~SeriesOfTubes();

        void addPoint(const Ogre::Vector3& pos);
        void removePoint(const Ogre::uint pointNumber);

        void setRadius(const Ogre::Real radius){mRadius = radius;}
        void setSides(const Ogre::uint numberOfSides){mSideCount = numberOfSides;}

        const Ogre::Real getRadius(){return mRadius;}
        const Ogre::uint getSides(){return mSideCount;}

        void setSceneNode(Ogre::SceneNode* sceneNode){mSceneNode = sceneNode;}
        Ogre::SceneNode* getSceneNode(){return mSceneNode;}

        Ogre::MaterialPtr getMaterial(){return mMaterial;}

        Ogre::ManualObject* createTubes(
            const Ogre::String& name, 
            const Ogre::String& materialName, 
            bool uniqueMaterial = false, 
            bool isDynamic = false, 
            bool disableUVs = false, 
            bool disableNormals = false);

        void _update(bool disableUVs = false, bool disableNormals = false);
        void _destroy();

        void _createSphere(const Ogre::String& strName);

        Ogre::ManualObject* createDebug(const Ogre::String& name);
        

    private:
        Ogre::SceneManager* mSceneMgr;

        typedef std::vector<Ogre::Vector3> VVec;
        VVec mLineVertices;

        Ogre::uint mSideCount;
        Ogre::Real mRadius;
        bool mUniqueMaterial;

        Ogre::uint mSphereRings;
        Ogre::uint mSphereSegments;
        Ogre::Real mSphereRadius;
        Ogre::Real mSphereMaxVisDistance;

        Ogre::MaterialPtr mMaterial;
        Ogre::ManualObject* mTubeObject;

        typedef std::vector<Ogre::Entity*> SphereStorage;
        SphereStorage mSpheresJoints;
        Ogre::MeshPtr mSphereMesh;

        Ogre::SceneNode* mSceneNode;
        

    };
}

#endif



Here's the matching source file for Tubes.h

Tubes.cpp


#include "Tubes.h"

#include <OgreManualObject.h>
#include <OgreMaterialManager.h>
#include <OgreSceneManager.h>
#include <OgreStringConverter.h>
#include <OgreEntity.h>
#include <OgreMeshManager.h>
#include <OgreHardwareVertexBuffer.h>
#include <OgreHardwareIndexBuffer.h>
#include <OgreSubMesh.h>

using namespace Ogre;

namespace Ogre
{

    SeriesOfTubes::SeriesOfTubes( 
        Ogre::SceneManager* sceneMgr, 
        const Ogre::uint numberOfSides /*= 0*/, 
        const Ogre::Real radius /*= 0.0*/,
        const Ogre::uint sphereRings /*= 0*/,
        const Ogre::uint sphereSegments /*= 0*/,
        const Ogre::Real sphereRadius /*= 0.0*/,
        const Ogre::Real sphereMaxVisibilityDistance /*= 0.0*/ )
        : mSceneMgr(sceneMgr),
        mSideCount(numberOfSides),
        mRadius(radius),
        mTubeObject(0),
        mUniqueMaterial(false),
        mSphereRings(sphereRings),
        mSphereSegments(sphereSegments),
        mSphereRadius(sphereRadius),
        mSphereMaxVisDistance(sphereMaxVisibilityDistance),
        mSceneNode(0)
    {

    }

    SeriesOfTubes::~SeriesOfTubes()
    {
        _destroy();
    }

    void SeriesOfTubes::addPoint( const Ogre::Vector3& pos )
    {
        mLineVertices.push_back(pos);
    }

    void SeriesOfTubes::removePoint( const Ogre::uint pointNumber )
    {
        if (pointNumber < mLineVertices.size())
        {
            VVec::iterator it = mLineVertices.begin() + pointNumber;
            mLineVertices.erase(it);
        }
    }

    Ogre::ManualObject* SeriesOfTubes::createTubes( 
        const Ogre::String& name, 
        const Ogre::String& materialName, 
        bool uniqueMaterial /* = false*/,
        bool isDynamic /*= false*/,
        bool disableUVs /*= false*/, 
        bool disableNormals /*= false*/)
    {
        if (mTubeObject)
            return mTubeObject;

        mMaterial = MaterialManager::getSingleton().getByName(materialName);

        mUniqueMaterial = uniqueMaterial;

        if (mUniqueMaterial)
            mMaterial = mMaterial->clone(materialName + "_" + name);
                

        mTubeObject = mSceneMgr->createManualObject(name);
        mTubeObject->setDynamic(isDynamic);

        _update(disableUVs,disableNormals);

        if (mSceneNode)
            mSceneNode->attachObject(mTubeObject);

        return mTubeObject;

        
    }

    void SeriesOfTubes::_update(bool disableUVs /*= false*/, bool disableNormals /*= false*/)
    {
        if (mTubeObject == 0 || mLineVertices.size() < 2)
            return;

        if (mTubeObject->getDynamic() == true && mTubeObject->getNumSections() > 0)
            mTubeObject->beginUpdate(0);
        else
            mTubeObject->begin(mMaterial->getName());

        Quaternion qRotation(Degree(360.0/(Real)mSideCount),Vector3::UNIT_Z);

        const uint iVertCount = mSideCount + 1;

        Vector3* vCoreVerts = new Vector3[iVertCount];
        Vector3 vPos = Vector3::UNIT_Y * mRadius;
        

        for (int i=0;i<iVertCount;i++)
        {
            vCoreVerts[i] = vPos;
            vPos = qRotation * vPos;
        }

        Vector3 vLineVertA, vLineVertB;
        Vector3 vLine;
        Real dDistance;
        int A,B,C,D;
        int iOffset;

        Vector3* vCylinderVerts = new Vector3[iVertCount * 2];
        
        for (int i=1;i<mLineVertices.size();i++)
        {
            vLineVertA = mLineVertices[i-1];
            vLineVertB = mLineVertices[i];

            vLine = vLineVertB - vLineVertA;
            dDistance = vLine.normalise();

            qRotation = Vector3::UNIT_Z.getRotationTo(vLine);

            for (int j=0;j<iVertCount;j++)
            {
                vCylinderVerts[j] = (qRotation * vCoreVerts[j]);
                vCylinderVerts[j + iVertCount] = (qRotation * (vCoreVerts[j] + (Vector3::UNIT_Z * dDistance)));
            }

            Real u,v,delta;
            delta = 1.0 / (Real)(iVertCount-1);
            u = 0.0;
            v = 1.0;
            
            for (int j=0;j<(iVertCount*2);j++)
            {
                mTubeObject->position(vCylinderVerts[j] + vLineVertA);
                if (disableNormals == false)
                {
                    mTubeObject->normal(vCylinderVerts[j].normalisedCopy());
                }
                if (disableUVs == false)
                {
                    if (j == iVertCount){
                        u = 0.0;
                        v = 0.0;
                    }
                    mTubeObject->textureCoord(u,v);
                    u += delta;
                }
            }

            iOffset = (i-1) * (iVertCount*2);
            for (int j=0;j<iVertCount;j++)
            {
                // End A: 0-(Sides-1)
                // End B: Sides-(Sides*2-1)

                // Verts:
                /*

                A = (j+1)%Sides        C = A + Sides
                B = j                D = B + Sides

                */

                

                A = ((j+1) % iVertCount);
                B = j;
                C = A + iVertCount;
                D = B + iVertCount;

                A += iOffset;
                B += iOffset;
                C += iOffset;
                D += iOffset;

                // Tri #1
                // C,B,A

                mTubeObject->triangle(C,B,A);

                // Tri #2
                // C,D,B

                mTubeObject->triangle(C,D,B);

            }

        }

        delete[] vCoreVerts;
        delete[] vCylinderVerts;
        vCoreVerts = 0;
        vCylinderVerts = 0;

        if (mSphereMesh.isNull() == true)
            _createSphere(mTubeObject->getName() + "_SphereMesh");

        if (mSceneNode)
            mSceneNode->removeAndDestroyAllChildren();
        

        Entity* pEnt = 0;
        SceneNode* pChildNode = 0;
        VVec::iterator it = mLineVertices.begin()+1;
        for (int i=1; it != (mLineVertices.end()-1);++it,i++)
        {
            if (mSpheresJoints.size() < i)
            {
                pEnt = mSceneMgr->createEntity(mTubeObject->getName() + "_SphereEnt" + StringConverter::toString(i),mSphereMesh->getName());
                pEnt->setMaterialName(mMaterial->getName());
                mSpheresJoints.push_back(pEnt);
            }
            else
            {
                pEnt = mSpheresJoints[i-1];
            }
            pEnt->setRenderingDistance(mSphereMaxVisDistance);
            
            if (mSceneNode)
            {
                pChildNode = mSceneNode->createChildSceneNode();
                pChildNode->setPosition((*it));                
                pChildNode->attachObject(pEnt);
            }
        }

        mTubeObject->end();

    }

    void SeriesOfTubes::_destroy()
    {
        if (mTubeObject)
        {
            if (mTubeObject->getParentSceneNode())
                mTubeObject->getParentSceneNode()->detachObject(mTubeObject);

            mSceneMgr->destroyManualObject(mTubeObject);
        }

        

        if (mUniqueMaterial)
        {
            MaterialManager::getSingleton().remove(mMaterial->getName());
        }
        mMaterial.setNull();

        if (mSpheresJoints.size() > 0)
        {
            Entity* pEnt = 0;
            SphereStorage::iterator it = mSpheresJoints.begin();
            for (; it != mSpheresJoints.end(); ++it)
            {
                pEnt = (*it);
                pEnt->getParentSceneNode()->detachObject(pEnt);
                mSceneMgr->destroyEntity(pEnt);
            }
        }

        if (mSphereMesh.isNull() == false)
        {
            MeshManager::getSingleton().remove(mSphereMesh->getName());
            mSphereMesh.setNull();
        }

        if (mSceneNode)
        {
            mSceneNode->removeAndDestroyAllChildren();
            mSceneNode->getParentSceneNode()->removeAndDestroyChild(mSceneNode->getName());
            mSceneNode = 0;
        }

    }

    Ogre::ManualObject* SeriesOfTubes::createDebug( const Ogre::String& name )
    {
        ManualObject* pObj = mSceneMgr->createManualObject(name);
        pObj->begin("BaseWhiteNoLighting",RenderOperation::OT_LINE_STRIP);

        VVec::iterator it = mLineVertices.begin();
        for (; it != mLineVertices.end();++it)
        {
            pObj->position((*it));
            pObj->colour(Math::UnitRandom(),Math::UnitRandom(),Math::UnitRandom());
        }
        pObj->end();

        return pObj;

    }

    ///////////////////////////////////////////////////////////////////////////////////
    // Courtesy of the Wiki: http://www.ogre3d.org/wiki/index.php/ManualSphereMeshes //
    ///////////////////////////////////////////////////////////////////////////////////

    void SeriesOfTubes::_createSphere( const Ogre::String& strName)
    {
        mSphereMesh = MeshManager::getSingleton().createManual(strName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
        SubMesh *pSphereVertex = mSphereMesh->createSubMesh();

        mSphereMesh->sharedVertexData = new VertexData();
        VertexData* vertexData = mSphereMesh->sharedVertexData;

        // define the vertex format
        VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
        size_t currOffset = 0;
        // positions
        vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
        currOffset += VertexElement::getTypeSize(VET_FLOAT3);
        // normals
        vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
        currOffset += VertexElement::getTypeSize(VET_FLOAT3);
        // two dimensional texture coordinates
        vertexDecl->addElement(0, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
        currOffset += VertexElement::getTypeSize(VET_FLOAT2);

        // allocate the vertex buffer
        vertexData->vertexCount = (mSphereRings + 1) * (mSphereSegments+1);
        HardwareVertexBufferSharedPtr vBuf = HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
        VertexBufferBinding* binding = vertexData->vertexBufferBinding;
        binding->setBinding(0, vBuf);
        float* pVertex = static_cast<float*>(vBuf->lock(HardwareBuffer::HBL_DISCARD));

        // allocate index buffer
        pSphereVertex->indexData->indexCount = 6 * mSphereRings * (mSphereSegments + 1);
        pSphereVertex->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
        HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
        unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(HardwareBuffer::HBL_DISCARD));

        float fDeltaRingAngle = (Math::PI / mSphereRings);
        float fDeltaSegAngle = (2 * Math::PI / mSphereSegments);
        unsigned short wVerticeIndex = 0 ;

        // Generate the group of rings for the sphere
        for( int ring = 0; ring <= mSphereRings; ring++ ) {
            float r0 = mSphereRadius * sinf (ring * fDeltaRingAngle);
            float y0 = mSphereRadius * cosf (ring * fDeltaRingAngle);

            // Generate the group of segments for the current ring
            for(int seg = 0; seg <= mSphereSegments; seg++) {
                float x0 = r0 * sinf(seg * fDeltaSegAngle);
                float z0 = r0 * cosf(seg * fDeltaSegAngle);

                // Add one vertex to the strip which makes up the sphere
                *pVertex++ = x0;
                *pVertex++ = y0;
                *pVertex++ = z0;

                Vector3 vNormal = Vector3(x0, y0, z0).normalisedCopy();
                *pVertex++ = vNormal.x;
                *pVertex++ = vNormal.y;
                *pVertex++ = vNormal.z;

                *pVertex++ = (float) seg / (float) mSphereSegments;
                *pVertex++ = (float) ring / (float) mSphereRings;

                if (ring != mSphereRings) {
                    // each vertex (except the last) has six indices pointing to it
                    *pIndices++ = wVerticeIndex + mSphereSegments + 1;
                    *pIndices++ = wVerticeIndex;               
                    *pIndices++ = wVerticeIndex + mSphereSegments;
                    *pIndices++ = wVerticeIndex + mSphereSegments + 1;
                    *pIndices++ = wVerticeIndex + 1;
                    *pIndices++ = wVerticeIndex;
                    wVerticeIndex ++;
                }
            }; // end for seg
        } // end for ring

        // Unlock
        vBuf->unlock();
        iBuf->unlock();
        // Generate face list
        pSphereVertex->useSharedVertices = true;

        // the original code was missing this line:
        mSphereMesh->_setBounds( 
            AxisAlignedBox( 
                Vector3(-mSphereRadius, -mSphereRadius, -mSphereRadius), 
                Vector3(mSphereRadius, mSphereRadius, mSphereRadius) 
            ), false );
        mSphereMesh->_setBoundingSphereRadius(mSphereRadius);
        // this line makes clear the mesh is loaded (avoids memory leaks)
        mSphereMesh->load();

    }
}

Usage

It's pretty simple to use, but has a few options to make it suit your application:

Example

SceneNode* pNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();

        SeriesOfTubes* mTubes = new SeriesOfTubes(mSceneMgr,16,10.0,12,12,12.0);

        mTubes->addPoint(Vector3(0,0,0));
        mTubes->addPoint(Vector3(100,0,200));
        mTubes->addPoint(Vector3(0,200,400));
        mTubes->addPoint(Vector3(50,340,300));
        mTubes->addPoint(Vector3(500,340,200));
        mTubes->addPoint(Vector3(400,100,100));
        mTubes->addPoint(Vector3(50,-20,10));
        mTubes->addPoint(Vector3(0,-100,-300));

        mTubes->setSceneNode(pNode);
        mTubes->createTubes("MyTubes","Examples/GrassFloor");



That created a simple, bendy tube with spheres at the joints (the joints I originally had were far uglier - feel free to improve on that!). It's got the "Example/GrassFloor" material, 16 sided cylinders for the tubes at 10.0 units radius and spheres with 12 rings, 12 segments and radius of 12.0 units. It also has vertex normals and UV coordinates.

Some customization info

Here is the function for the tube creation

Ogre::ManualObject* createTubes(
            const Ogre::String& name, 
            const Ogre::String& materialName, 
            bool uniqueMaterial = false, 
            bool isDynamic = false, 
            bool disableUVs = false, 
            bool disableNormals = false);


As you can see, you're able to name it and give it a material. The optional functions are as follows:

'''uniqueMaterial''' - If you plan on manipulating materials separately for each SeriesOfTubes, set this to true.
 '''isDynamic''' - This is if you plan on adding and/or removing points from the line; this might cause some weird issues as I haven't tested this much.
 '''disableUVs''' - If you're not going to have a texture in your material, you can set this to false to save some vertex data space.
 '''disableNormals''' - If you're not using lighting information at all, set this to false and save more vertex data space.

Conclusion



Well that's pretty much all there is to it. It might be a bit quirky if you have it dynamic, but I don't have much time to test it out now. It should be fairly straight forward to deal with otherwise, following the Example code. Just create a new SeriesOfTubes for each separate set of tubes you want to make - just don't forget to destroy them when you're done (it handles all the cleanup!)


Alias: A_Series_Of_Tubes