Skip to main content
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

Copy to clipboard
#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

Copy to clipboard
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

Copy to clipboard
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

Copy to clipboard
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

Copy to clipboard
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; };