IteratedFractal         Using ManualObjects as Point Sprites to create a Pickover attractor

Iterated Fractal

Here's a sample of the Pickover function, I saw used on GameDev.net a few times. It can be used to produce some spectacular images. I had a need for this in a project, so I thought I'd post the code here for other people to pick apart. It has some handy code for point sprites, updating manual buffers and RenderTargets.

How to use this

You can now play around with it to your heart's content! The idea is that you can adjust the A,B,C,D values (dA, dB, dC, dD in the code) from [-3,3] to change the Pickover attractor function. You can do this with numbers 1-4 on your Number Pad and switching from adding to subtracting values by hitting the Plus and Minus signs on the num-pad, respectively. If it's going too slow, you can use a combination of Ctrl and Alt on your keyboard with the number(s) you have selected to change the speed.

The particle simulation should change over time instead of plowing all the changes in every frame. In this case I also have it set to render to multiple RenderTargets to let you render over time as well.

I'll cover this code a bit more in depth if people ask for the "how" and "why" of most if it. The main reason I'm using ManualObjects instead of BillboardSets is that BillboardSets are very heavy in comparison.

Good luck!

The Code

This is from the Smoke demo using the ExampleFramework, but I cut it down to one file for easy copy/paste use:

Your .cpp file

#include "ExampleApplication.h"
#include <Ogre.h>

using namespace std;

Real dA,dB,dC,dD;
ManualObject** pObjects;
SceneNode** pNodes;
int iManualObjects;
int iParticles;
int iPartPerSet;
Vector3 vScale;

int iControlPerKey;
int iNodes;
Vector3 vPos;

RenderTarget* pTarget;
RenderTarget** pRenderTargets;

SceneNode* pPlaneNode;

inline const Vector3 iterate(const Vector3& vPos){
    return Vector3(
        Math::Sin(dA * vPos.y) - vPos.z * Math::Cos(dB * vPos.x),
        vPos.z * Math::Sin(dC * vPos.x) - Math::Cos(dD * vPos.y),
        Math::Sin(vPos.x)
        );

};

inline void updateManualObject(ManualObject* pObj){
    pObj->beginUpdate(0);
    for (int i=0;i<iPartPerSet;i++)
    {
        pObj->position(vPos * vScale);
        vPos = iterate(vPos);
    }
    pObj->end();
}

// Event handler to add ability to alter curvature
class ParticleFrameListener : public ExampleFrameListener
{
protected:

public:
    ParticleFrameListener(RenderWindow* win, Camera* cam)
        : ExampleFrameListener(win, cam)
    {
    }

    bool frameStarted(const FrameEvent& evt)
    {

        if(ExampleFrameListener::frameStarted(evt) == false)
            return false;

        if(iNodes > 0)
        {
            for (int i=0;i<iNodes;i++)
            {
                if (mKeyboard->isKeyDown((OIS::KeyCode)((int)OIS::KC_1 + i)) && mTimeUntilNextToggle<=0)
                {
                    pNodes[i]->flipVisibility();
                    mTimeUntilNextToggle = 0.5;
                }
            }
        }
        bool bShift = false;
        bool bControl = false;
        static bool bUp = true;

        if (mKeyboard->isKeyDown(OIS::KC_ADD))
        {
            bUp = true;
        }

        if (mKeyboard->isKeyDown(OIS::KC_SUBTRACT))
        {
            bUp = false;
        }

        if (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT))
        {
            bShift = true;
        }

        if (mKeyboard->isKeyDown(OIS::KC_LCONTROL) || mKeyboard->isKeyDown(OIS::KC_RCONTROL))
        {
            bControl = true;
        }

        static bool bUpdate = false;

        static const Real dTiny = 0.0001;
        static const Real dSmall = 0.001;
        static const Real dMed = 0.01;
        static const Real dLarge = 0.1;

        static Real rVal = 0.0;
        // A
        if (mKeyboard->isKeyDown(OIS::KC_NUMPAD1))
        {
            rVal = 0.0;
            if (bControl && bShift)
            {
                rVal = dLarge;
            }else if (bControl && !bShift)
            {
                rVal = dMed;
            }else if (bShift && !bControl)
            {
                rVal = dSmall;
            }else{
                rVal = dTiny;
            }

            if(bUp == false)
                rVal = -rVal;

            dA += rVal;
            dA = max(min(dA,3.0),-3.0);
            bUpdate = true;
        }

        // B
        if (mKeyboard->isKeyDown(OIS::KC_NUMPAD2))
        {
            rVal = 0.0;
            if (bControl && bShift)
            {
                rVal = dLarge;
            }else if (bControl && !bShift)
            {
                rVal = dMed;
            }else if (bShift && !bControl)
            {
                rVal = dSmall;
            }else{
                rVal = dTiny;
            }

            if(bUp == false)
                rVal = -rVal;

            dB += rVal;
            dB = max(min(dB,3.0),-3.0);
            bUpdate = true;
        }

        // C
        if (mKeyboard->isKeyDown(OIS::KC_NUMPAD3))
        {
            rVal = 0.0;
            if (bControl && bShift)
            {
                rVal = dLarge;
            }else if (bControl && !bShift)
            {
                rVal = dMed;
            }else if (bShift && !bControl)
            {
                rVal = dSmall;
            }else{
                rVal = dTiny;
            }

            if(bUp == false)
                rVal = -rVal;

            dC += rVal;
            dC = max(min(dC,3.0),-3.0);
            bUpdate = true;
        }

        // D
        if (mKeyboard->isKeyDown(OIS::KC_NUMPAD4))
        {
            rVal = 0.0;
            if (bControl && bShift)
            {
                rVal = dLarge;
            }else if (bControl && !bShift)
            {
                rVal = dMed;
            }else if (bShift && !bControl)
            {
                rVal = dSmall;
            }else{
                rVal = dTiny;
            }

            if(bUp == false)
                rVal = -rVal;

            dD += rVal;
            dD = max(min(dD,3.0),-3.0);
            bUpdate = true;
        }

        static int iObj = 0;

        if (bUpdate)
        {
            if (iObj == 0)
                vPos = iterate(Vector3::ZERO);

            updateManualObject(pObjects[iObj++]);

            if (iObj == iManualObjects)
            {
                iObj = 0;
                bUpdate = false;
            }
        }

        static int iRenderTarget = 0;

        if(iNodes > 0){
            pPlaneNode->setVisible(false);
            pNodes[iRenderTarget]->setVisible(true);
            pRenderTargets[iRenderTarget]->update();
            pNodes[iRenderTarget++]->setVisible(false);
            pPlaneNode->setVisible(true);
            if(iRenderTarget == iNodes)
                iRenderTarget = 0;
        }

        std::stringstream buf;
        buf << "A: " << dA << " B: " << dB << " C: " << dC << " D: " << dD;
        mDebugText = buf.str();

        return true;

    }
};

class ParticleApplication : public ExampleApplication
{
public:
    ParticleApplication() {
        iManualObjects = 0;
        pObjects = 0;
        pNodes = 0;
        iNodes = 0;
        pRenderTargets = 0;
    }
    ~ParticleApplication(){
        ManualObject* pObj = 0;
        SceneNode* pSN = 0;
        for (int i=0;i<iNodes;i++)
        {
            pSN = pNodes[i];
            pSN->detachAllObjects();
            mSceneMgr->destroySceneNode(pSN->getName());
        }
        for (int i=0;i<iManualObjects;i++)
        {
            pObj = pObjects[i];
            mSceneMgr->destroyManualObject(pObj);
        }
        if(pObjects)
            delete[] pObjects;

        if(pNodes)
            delete[] pNodes;

        if(pRenderTargets)
            delete[] pRenderTargets;
    }

protected:

    // Just override the mandatory create scene method
    void createScene(void)
    {
        // Set ambient light
        mSceneMgr->setAmbientLight(ColourValue(1.0, 1.0, 1.0));

        mCamera->setPosition(30,15,-65);
        mCamera->setOrientation(Quaternion(0.100439, -0.0187613, 0.977853, 0.182656));

        mCamera->setFarClipDistance(5000);
        mCamera->setNearClipDistance(0.001);

        dA = -0.9629629;
        dB = 2.791139;
        dC = 1.85185185;
        dD = 1.5;

        //dA = 0.296296296296296;
        //dB = 2.48148148148148;
        //dC = 1.18518518518519;
        //dD = 1.2962962962963;

        //dA = -1.22222222222222;
        //dB = 1.2037037037037;
        //dC = 1.55555555555556;
        //dD = 1.55555555555556;

        //dA = -2.81481481481482;
        //dB = 0.462962962962963;
        //dC = 1.11111111111111;
        //dD = 1.11111111111111;

        vScale = Vector3::UNIT_SCALE * 50;

        iParticles = 1000000;
        iManualObjects = 40;

        iControlPerKey = 20;

        vPos = iterate(Vector3::ZERO);

        iPartPerSet = iParticles/iManualObjects;
        iNodes = iManualObjects/iControlPerKey;

        pObjects = new ManualObject*[iManualObjects];
        pNodes = new SceneNode*[iNodes];
        for (int i=0;i<iManualObjects;i++)
        {
            pObjects[i] = mSceneMgr->createManualObject("MyManual" + StringConverter::toString(i));
            pObjects[i]->setDynamic(true);
            pObjects[i]->begin("PointTest",RenderOperation::OT_POINT_LIST);
            for (int j=0;j<iPartPerSet;j++)
            {

                pObjects[i]->position(vPos * vScale);
                vPos = iterate(vPos);
            }
            pObjects[i]->end();
        }
        int iSet = 0;
        for (int i=0;i<iNodes;i++)
        {
            pNodes[i] = mSceneMgr->getRootSceneNode()->createChildSceneNode();
            for (int j=0;j<iControlPerKey;j++)
            {
                if(iSet < iManualObjects)
                    pNodes[i]->attachObject(pObjects[iSet++]);
            }
            pNodes[i]->setVisible(false);
        }

        MaterialPtr pMat = MaterialManager::getSingleton().getByName("BlendAll");

        pRenderTargets = new RenderTarget*[iNodes];

        for (int i=0;i<iNodes;i++)
        {
            TexturePtr pTex = TextureManager::getSingleton().createManual( "RttTex" + StringConverter::toString(i), 
                ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 
                mCamera->getViewport()->getActualWidth(), mCamera->getViewport()->getActualHeight(), 0, PF_R8G8B8, TU_RENDERTARGET );

            pRenderTargets[i] = pTex->getBuffer()->getRenderTarget();
            pRenderTargets[i]->setAutoUpdated(false);
            Viewport* v = pRenderTargets[i]->addViewport(mCamera);
            v->setClearEveryFrame(true);
            v->setBackgroundColour(ColourValue::Black);
            v->setOverlaysEnabled(false);
            pMat->getTechnique(0)->getPass(0)->createTextureUnitState(pTex->getName());

        }

        Plane plane;
        plane.normal = Vector3::NEGATIVE_UNIT_Z;

        MeshPtr pMesh = MeshManager::getSingleton().createPlane("MyPlane",ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,plane,
            100,100,1,1,true,1,1,1,Vector3::UNIT_Y);

        Entity* pEnt = mSceneMgr->createEntity("MyEnt","MyPlane");
        pPlaneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
        pPlaneNode->attachObject(pEnt);

        pEnt->setMaterialName("BlendAll");

        pPlaneNode->setVisible(false);
    }

    // Create new frame listener
    void createFrameListener(void)
    {
        mFrameListener= new ParticleFrameListener(mWindow, mCamera);
        mRoot->addFrameListener(mFrameListener);
    }
};

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    // Create application object
    ParticleApplication app;

    try {
        app.go();
    } catch( Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        std::cerr << "An exception has occured: " << e.getFullDescription();
#endif
    }

    return 0;
}

#ifdef __cplusplus
}
#endif

Point.material

material PointTest
{
    technique
    {
        pass
        {
            cull_hardware none
            cull_software none
            point_sprites on
            //point_size_attenuation on linear
            //point_size_min 0.5
            //point_size_max 1.25
            point_size 1
            //alpha_rejection greater_equal 128
            scene_blend add
            ambient 0.2 0.2 0.0 1.0
            //diffuse 1.0 0.2 0.0 1.0

            texture_unit
            {
                texture GLX_icon.png
                colour_op add
            }
        }
    }
}

BlendAll.material

fragment_program BlendAllPS hlsl
{
    source BlendAllPS.source
    target ps_2_0
    entry_point main_ps
}

vertex_program BlendAllVS hlsl
{
    source BlendAllVS.source
    target vs_2_0
    entry_point main_vs
}

material BlendAll
{
    technique
    {
        pass
        {
            cull_hardware none
            cull_software none
            vertex_program_ref BlendAllVS
            {
            }

            fragment_program_ref BlendAllPS
            {
            }
        }
    }
}

BlendAllVS.source

struct VS_OUTPUT
{
   float4 pos       : POSITION0;
   float2 texCoord  : TEXCOORD0;
};

VS_OUTPUT main_vs( float4 inPos: POSITION )
{
   VS_OUTPUT o = (VS_OUTPUT) 0;

   inPos.xy = sign( inPos.xy);
   o.pos = float4( inPos.xy, 0.0f, 1.0f);

   // get into range [0,1]
   o.texCoord = (float2(o.pos.x, -o.pos.y) + 1.0f)/2.0f;
   return o;
}

BlendAllPS.source

sampler2D tex0;
sampler2D tex1;

float4 main_ps(float2 texCoord : TEXCOORD0) : COLOR0 {
    float4 oColor = float4(0,0,0,0);
    oColor += tex2D(tex0 , texCoord);
    oColor += tex2D(tex1 , texCoord);

    return oColor;
};