STL algorithms
UPDATE@2010-Feb-23 22:11: Thanks to Arseny Kapoulkine (his blog is : http://zeuxcg.blogspot.com/ ) I discovered that there was a bug in the code I showed. I have fixed this bug
I'm not a huge fan of the STL library (you know [cci_cpp]std::vector[/cci_cpp]and it's relatives) as I don't like the syntax and the naming of some functions and yes, I'm well aware those are mood points but then again I'm always looking for perfection. But there is one part of the STL library I really love which is STL algorithms.
For example, let's say we have the following code
#include <vector> /* ... * Somewhere in the code * ... */ std::vector<int> numbers; numbers.push_back(1); numbers.push_back(2); numbers.push_back(3);
And we want to remove the number "2" from the vector, sadly [cci_cpp]std::vector[/cci_cpp] is lacking a remove function. It does have an erase function but that would require an iterator. We could switch to [cci_cpp]std::list[/cci_cpp] who does have a remove function. Without STL algorithms we would write something like this:
/* Written for readability
* And it only removes the first one it finds.
*/
template <typename T>
void RemoveFromVector(const T val, std::vector<T> vec)
{
for(std::vector<T>::iterator begin = vec.begin(); begin != vec.end(); ++begin)
{
if(*begin == val)
{
vec.erase(begin);
return;
}
}
}
/* ... And here we call it */
RemoveFromVector<int>(2, numbers);
The only thing I can say about it is that it looks ugly. Now let's use stl algorithms and see how that looks like.
/** * Previously the code was: * std::remove(numbers.begin(), numbers.end(), 2); * But that only removes the number, but keeps the same size. std::remove instead * returns an iterator which holds the end. (numbers.size() would give the same * result before and after the above code. * Thanks to Arseny Kapoulkine this has been corrected and the good version is * now here available. */ numbers.erase( std::find(numbers.begin(), numbers.end(), 2) );
One line of code and that's all. Well, it's still one line of code, but two rather simple functions. The [cci_cpp]erase[/cci_cpp] function takes an iterator as input (which [cci_cpp]std::find[/cci_cpp] returns for you). However keep in mind that if [cci_cpp]std::find[/cci_cpp] can't find a result (for example "2" was already missing, your application will crash.One thing I always hate was when I stored pointers in an object that needed destruction
#include <vector>
#define SAFE_DELETE(p) if(p) delete(p); (p)=0;
class StorageObject1;
class StorageObject2;
class StorageContainer
{
std::vector<StorageObject1*> m_Objects1;
std::vector<StorageObject2*> m_Objects2;
public:
~StorageContainer()
{
for(size_t i = 0; i < m_Objects1.size(); ++i)
{
SAFE_DELETE(m_Objects1[i]);
}
for(size_t i = 0; i < m_Objects2.size(); ++i)
{
SAFE_DELETE(m_Objects2[i]);
}
}
};
The above example is rather tame but if you have a lot of different objects it quickly becomes ugly to look at. However with a bit of ingenuity you can become a lazier programmer. Here is the same example but then easier to read
#include <algorithm>
#include <vector>
#define SAFE_DELETE(p) if(p) delete(p); (p)=0;
template <typename T> void SAFE_DELETE_FUNC(T* object) { SAFE_DELETE(object); }
class StorageObject1;
class StorageObject2;
class StorageContainer
{
std::vector<StorageObject1*> m_Objects1;
std::vector<StorageObject2*> m_Objects2;
public:
~StorageContainer()
{
std::for_each(m_Objects1.begin(), m_Objects1.end(), SAFE_DELETE_FUNC<StorageObject1>);
std::for_each(m_Objects2.begin(), m_Objects2.end(), SAFE_DELETE_FUNC<StorageObject2>);
}
}
Of course you can make the function even a bit smarter so that you don't have to write the start and end iterators, but I prefer it like this. Here you can find the other versions of STL algorithm: http://www.cplusplus.com/reference/algorithm/
Undesired template specification
Updated 10 January 2010: The download was not working, this is now fixed
Yesterday I decided to improve my math library and to make it more flexible. One of the things that I wanted to do was convert it to a template, so that I could easily make vectors of different types. However when it was finished and I started testing it I came across a lot of issues which my old math library didn't have. The reason that my old library didn't have them was because all of the structures used floats and when used with any other variable type would be automatically be casted, promoted or demoted to match the type float. However templates are more strict on that area since templates need to specialize. I know this is confusing so let me explain by example.
I will show three examples of the above problem and I will explain how to solve any of them. I hope that throughout the progress you will also understand why the problems happen.
But first let me define the class that I use. This piece of code won't change until I cover the solution of the third problem.
The Vector2 class:
[cc_cpp]
template
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigrnent operator
tVector2
};
// Multiplication operator
template
[/cc_cpp]
FIRST PROBLEM: ONLY A SINGLE TEMPLATE ARGUMENT
The first problem shows itself rather fast.
[cc_cpp]
int testMain1() // Think of this as a normal main
{
tVector2
Vec2 = Vec2 * 2.0f; // Does compile
Vec2 = Vec2 * 2.0; // Does not compile
return 0;
}
[/cc_cpp]
Visual Studio comes with two errors:
[ccWN_text]error C2782: 'tVector2
error C2676: binary '*' : 'tVector2
If we think about the reason for this problem is rather easy. If we would think as the compiler we would have the following two forms of multiplication:
[cc_cpp]
tVector2
tVector2
[/cc_cpp]
The first multiplication in the example does compile as it makes use of the first form, the second multiplication in the example however raises two errors. As you can see neither form can be applied on our multiplication, which has the form [cci_cpp]tVector2
The second error comes forth because the compiler still tries to solve. However it cannot convert [cci_cpp]tVector2
[cc_cpp]
void TestFunction(float v);
void TestFunction(double v);
int main()
{
TestFunction(2.0f); // Use first form
TestFunction(2.0 ); // Use second form
return 0;
}
[/cc_cpp]
FIRST SOLUTION: USE MULTIPLE TEMPLATE ARGUMENTS
The title of the first solution might feel contradicting as [cci_cpp]tVector2
[cc_cpp]
template
tVector2
return tVector2
}[/cc_cpp]
The biggest change is that because of using two template arguments, which are used for the first and second argument, is that compiler no longer have to match the types. So the compiler can generate the following:
[cc_cpp]
tVector2
tVector2
tVector2
[/cc_cpp]
And the everything works. You can even remove the old multiplication method if you want.
SECOND PROBLEM: MULTIPLYING DIFFERENT TYPES
This problem is exactly the same as the first one, but I cover it none the less as it took me a few seconds more to realize this than I wanted.
[cc_cpp]
int testMain2() // Think of it as a seperate program
{
// Second problem: Multiplying different types
tVector2
tVector2
Vec2d = Vec2d * Vec2; // tVector2
return 0;
}[/cc_cpp]
When we compile this visual studio comes two times with two different errors (total is four):
[ccWN_text]error C2784: 'tVector2
error C2677: binary '*' : no global operator found which takes type 'tVector2
The errors all take place in the solution of the previous problem as it tries to solve that function in to the following form:
[cc_cpp]tVector2
return tVector2
}[/cc_cpp]
I have added on purpose also the body of the form because that is what you should concentrate on. As you can see it tries to multiply [cci_cpp]v.X[/cci_cpp] with [cci_cpp]r[/cci_cpp]. While [cci_cpp]v.X[/cci_cpp]is a float, which is correct. [cci_cpp]r[/cci_cpp] is of the type [cci_cpp]tVector2
SECOND SOLUTION: USE MULTIPLE TEMPLATE ARGUMENTS WITH TYPES
Similar to the first problem we add the following function:
[cc_cpp]template
tVector2
return tVector2
}[/cc_cpp]
There is however one down side to this solution and that is that type returned is the same as the first template argument, even if the second template argument would be bigger. For example the first template argument is a float and the second is of the type double. In that case we would lose precision.
THIRD PROBLEM: CONVERTING FROM ONE TYPE TO ANOTHER TYPE
The test case is similar to that of the second problem, but with a little change.
[cc_cpp]int testMain3() // Think of it as a seperate program
{
// Third problem: Storing different types
tVector2
tVector2
Vec2d = Vec2 * Vec2d; // tVector2
return 0;
}[/cc_cpp]
As you can see the result of the multiplication is now [cci_cpp]tVector2
However unlike the solutions of the first two problems, this operator needs to be a member function. So we should store a tVector of a unknown type in a tVector of a specialized type (which is actually also unknown, but the compiler specializes this so it is known to him, but not to us).
THIRD SOLUTION: TEMPLATE FUNCTION INSIDE A TEMPLATE CLASS
To be honest, I didn't think this was at all possible but since I had solved the previous two problems I was intended to find a solution for this one as well. In retrospect the solution was just as simple as the previous one, but then again everything seems easy in retrospect.
The new class looks as followed:
[cc_cpp]template
{
public:
T X, Y;
tVector2(T x, T y) : X(x), Y(y) {}
// Assigment operator (didn't solve the third problem)
tVector2
// Assigment operator which allows other types (solved third problem)
template
X = v.X, Y=v.Y; return *this; }
};[/cc_cpp]
As you can see a new function has been added, which is a template function. Quite a simple and elegant solution once you accept the fact that you are allowed to create template functions inside a class as well.
FINAL WORDS
Although the title of the article is "Undesired template specification" it was only how I felt when I encountered all these problems. Afterwards I think it was not undesired at all, because this is how the language should work. If it wouldn't work like this we would barely have control what some of the functions would and then it would really become undesired.
I hope that the all of the above was easy enough to follow (assuming you are familiar with templates) and helped you understand in what went wrong and how to solve it.
As usual I have added the full source code at the bottom of the article. All the functions are in it, however in a production environment I would recommend you remove the old versions of some of the functions since the new functions also cover the functionality of the old ones.
Source code: templatetest_0.zip (2 kb)
The new and improved acyclic visitor with observer pattern (C++)
I have improved my “Acyclic Visitor with Observer” pattern (here is a link to the old one). Now you can create classes that are derived from two Acyclic visitors, something that was not possible with the previous version. On top of that I have made it less complex to use. On top of that they no longer have generic names (like “INotification”). The coolest thing is the registering. Previously you had to create the objects in the user code and also specify the class that should receive it. Now, it's simply done when you define the class.
Anyway just compare the code:
The old code
class HelloWorldReceiver : public IReceiver
{
public:
HelloWorldReceiver()
{
m_Translators.push_back( CTranslator<HelloWorldReceiver, WorldNote>() );
m_Translators.push_back( CTranslator<HelloWorldReceiver, HelloNote>() );
}
void Receive(WorldNote* aWorld) { }
void Receive(HelloNote* aHello) { }
};
The new code
class HelloWorldVisitor : public tAcyclicVisitor<HelloWorldVisitor>
public:
HelloWorldVisitor() {
RegisterAVObject<Hello>();
RegisterAVObject<World>();
}
void Receive(Hello* obj) { }
void Receive(World* obj) { }
};
And here is all the code you need to start using it, it's licensed as I promised in the previous post, but as you can see you all you need to do is keep the license in the code (all 21 lines).
// The MIT License
//
// Copyright (c) 2009 Wouter Lindenhof (http://limegarden.net)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef __ACYCLIC_VISITOR_H__
#define __ACYCLIC_VISITOR_H__
#include <list>
/**
* @brief if set to 1 it will allows the user to combine derived acyclic
* visitors, but this is likely to have a cost because of virtual inheritance.
*/
#define AV_ALLOW_VIRTUAL_INHERITANCE 1
/**
* @brief this is the base object
*/
class AVBaseObject
{
public:
virtual ~AVBaseObject() {}
};
class AcyclicVisitor;
class IAVTranslator
{
public:
virtual const bool Accept(AVBaseObject* object, AcyclicVisitor* visitor) const = 0;
};
class AcyclicVisitor
{
typedef std::list<IAVTranslator*> TransList;
protected:
TransList m_AV_Translators;
public:
virtual ~AcyclicVisitor() {
for( TransList::iterator it = m_AV_Translators.begin(); it != m_AV_Translators.end(); ++it )
delete (*it);
m_AV_Translators.clear();
}
void Visit(AVBaseObject* baseObject)
{
for( TransList::iterator it = m_AV_Translators.begin(); it != m_AV_Translators.end(); ++it )
(*it)->Accept(baseObject, this);
}
};
template <typename V, typename O>
class tAVTranslator : public IAVTranslator
{
public:
const bool Accept(AVBaseObject* object, AcyclicVisitor* visitor) const
{
V* original_visitor = dynamic_cast<V*>(visitor);
O* original_object = dynamic_cast<O*>(object);
if(original_object == 0 || original_visitor == 0) return false;
original_visitor->Receive(original_object);
return true;
}
};
template <typename V>
#if AV_ALLOW_VIRTUAL_INHERITANCE == 1
class tAcyclicVisitor : public virtual AcyclicVisitor
#else
class tAcyclicVisitor : public AcyclicVisitor
#endif
{
protected:
template <typename O> void RegisterAVObject()
{
m_AV_Translators.push_back(new tAVTranslator<V, O>());
}
};
#endif
I have also uploaded the entire example that I have used while testing so that you can some more interesting stuff.
Rewriting examples: Direct3D tutorial 1 with D3DVERTEXELEMENT9
A while ago a friend asked me how to do something with Direct3D (better known as DirectX) about streams. I really enjoyed it since it was sometime I have worked with my favorite 3D API and so I explained the usage of VertexDeceleration by rewriting the very first tutorial of Direct3D of which you can see the result below
#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
#include <d3dx9.h>
#pragma comment(lib, "d3dx9.lib")
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("DirectXStreamExample");
HWND hwnd;
MSG msg;
WNDCLASSEX wndclassex = {0};
wndclassex.cbSize = sizeof(WNDCLASSEX);
wndclassex.style = CS_HREDRAW | CS_VREDRAW;
wndclassex.lpfnWndProc = WndProc;
wndclassex.cbClsExtra = 0;
wndclassex.cbWndExtra = 0;
wndclassex.hInstance = hInstance;
wndclassex.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndclassex.hCursor = LoadCursor (NULL, IDC_ARROW);
wndclassex.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
wndclassex.lpszMenuName = NULL;
wndclassex.lpszClassName = szAppName;
wndclassex.hIconSm = wndclassex.hIcon;
if (!RegisterClassEx (&wndclassex))
{
MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW,
szAppName,
TEXT ("DirectXStreamExample"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
640,
480,
NULL,
NULL,
hInstance,
NULL);
ShowWindow (hwnd, iCmdShow);
UpdateWindow (hwnd);
// Now setup DirectX
IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS presentParameters;
memset(&presentParameters, 0, sizeof(D3DPRESENT_PARAMETERS));
presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentParameters.BackBufferFormat = D3DFMT_UNKNOWN;
presentParameters.Windowed = true;
IDirect3DDevice9* device = 0;
d3d9->CreateDevice(0, D3DDEVTYPE_HAL, hwnd, D3DCREATE_MIXED_VERTEXPROCESSING, &presentParameters, &device);
// Create our custom vertex format
struct POSCOLORVERTEX
{
float X, Y, Z, W;
DWORD color;
};
POSCOLORVERTEX vertices[] = {
{150.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255,0,0)},
{250.0f, 250.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0,255,0)},
{ 50.0f, 250.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0,0,255)},
};
// Create a vertex decleration
// Think of a vertex decleration as an object that describes a single custom
// vertex.
// typedef struct _D3DVERTEXELEMENT9
// {
// WORD Stream; // Stream index
// WORD Offset; // Offset in the stream in bytes
// BYTE Type; // Data type
// BYTE Method; // Processing method
// BYTE Usage; // Semantics
// BYTE UsageIndex; // Semantic index
// } D3DVERTEXELEMENT9, *LPD3DVERTEXELEMENT9;
// Example 1: Tutorial 1 of the DirectX SDK
// using weighted position (aka screen coordinates) and colors but without
// using the Fixed Pipeline
D3DVERTEXELEMENT9 PosColorVertexElements[] = {
{0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0},
{0, 16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
D3DDECL_END()
};
#if 0
// Example 2: If you want to add UV it would become something like this:
D3DVERTEXELEMENT9 PosColorUVVertexElements[] = {
{0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0},
{0, 16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
{0, 20, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};
// Example 3: And if you want UVs first
D3DVERTEXELEMENT9 PosColorNormalVertexElements[] = {
{0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
{0, 8, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0},
{0, 8+16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
D3DDECL_END()
};
// Example 4: And if you want to send two UV's
D3DVERTEXELEMENT9 PosColorNormalVertexElements[] = {
{0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0},
{0, 16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
{0, 20, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // Maps to TEXCOORD0 in shader
{1, 28, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // Maps to TEXCOORD1 in shader
D3DDECL_END()
};
#endif
IDirect3DVertexDeclaration9* vertexDecleration = 0;
device->CreateVertexDeclaration(PosColorVertexElements, &vertexDecleration);
device->SetVertexDeclaration(vertexDecleration);
// Create vertex buffer
IDirect3DVertexBuffer9* vertexBuffer;
device->CreateVertexBuffer(
3*sizeof(POSCOLORVERTEX),
0,
0, // DON'T pass the FVF code if you are using vertex decleration
D3DPOOL_DEFAULT,
&vertexBuffer,
0);
// Fill the vertex buffer
void* pVertices = 0;
vertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0);
memcpy(pVertices, vertices, sizeof(vertices));
vertexBuffer->Unlock();
bool keepRunning = true;
while(keepRunning)
{
// Render
device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
device->BeginScene();
// Rendering of scene objects happens here
// 1. Set the vertex decleration
// 2. Set the stream source
device->SetVertexDeclaration(vertexDecleration);
device->SetStreamSource(0, vertexBuffer, 0, sizeof(POSCOLORVERTEX));
device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// End the scene
device->EndScene();
device->Present(0,0,0,0);
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT) keepRunning = false;
else
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
}
vertexBuffer->Release();
vertexDecleration->Release();
device->Release();
d3d9->Release();
return msg.wParam;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage (0);
return (0);
}break;
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
Donuts! (Procedural Torus in C++)

Recently I got a mail through the contact form of my website about texture mapping on torus (a donut) who said that [cci_cpp]D3DXCreateTorus[/cci_cpp] didn't not provide texture coordinates. The reason why DirectX and OpenGL (and GLUT) don't provide texture coordinates with these function is best seen when you generate a cube. A cube has 8 vertices, however when you add texture coordinates it will increase because otherwise they will share the same texture coordinate. With normals it is no problem (in fact you would prefer that) but if you have a dice (from 1 to 6) than each vertex would use a single texture coordinate for three sides. Just draw an unfolded dice on paper where each edge is connected, but does not cross another edge. It is simply not possible. These complexities are the reason why those procedural mesh functions are so simple. Adding these functionalities would increase complexity of the function and requires you to explain it. On top of that the developers have no idea how you are going to use that function. Maybe you don't want to use the legal texture range [0...1] but [0...3].
Anyway I have decided to take it on and here is the result. You can download the source code here: TorusDX.zip.I have not commented it, but it is pretty straight forward. The texture that I used is from Dilbert.
Random distribution
One of the special effects I recently worked one was cellular noise (original made by Steve Worley) and the book that I read used Poisson distribution. The Poisson distribution is a table which holds random values that guarantees certain conditions (like an average mean). However I’m not going to talk about Poisson distribution, it just remembered me of some old error I made and how I finally solved it. Besides there are better sources than me who can explain the math behind it.
But first a talk of a prehistoric me talking about some game that I worked on.
One of the games that I worked on as a student was a shooter like Tyrian but then in 3D. You would race around in hovering car through a fixed path in a 3D city and you would shoot down enemy cars. Since the path was a circle, you would go around and around until you were death, you could jump from one car to another and that was pretty cool to see (and made the game quite easy if you knew the timing). However some cars would be rare to encounter because they had better armor or better weapons and we needed fact in the spawning code. I made up some kind of obscure code for it that based on random number and a “common” value would spawn one of the cars. Before I started on it I thought it wouldn’t be too hard. But this is one of the times I was actually wrong. It worked to some degree but I noticed that some cars were rarely spanned, even though they had a high “common” value. The problem was obvious in the code I had written but at that time I couldn’t think of an algorithm that would solve this issue.
Ok, enough history, let’s go back to the future.
By now I have worked on a lot more of code and when I read how Poisson distribution is used, which is properly how more distribution work, I noticed it was similar to what I once invented.
So to illustrate the problem I will try and remember and describe what I used for that game.
We could use [cci_cpp]rand() % MAX_CAR_ID[/cci_cpp] to generate a lot of random numbers so that we have a list of cars that we use for the game. Using [cci_cpp]std::vector
A better method is using two lists. The first list contains all the values we want to have like for every Saab there are five Fords, then we would add the ID of a Saab once and then we add the ID of a Ford five times. Problem with this list is that the numbers are ordered or are in a way ordered. We could still use [cci_cpp]CarSpawnList[rand()%ListSize][/cci_cpp] but if we have a bad seed we could still have three Saabs before we encounter a ford.
So let’s introduce the second list. The second list will start empty and we move every item in the initial list to the second least. So we remove it in the first list and add it to the second list. The item chosen in the first list is random and because it is removed we will make certain that the Saabs and Fords balance each other. In code it would look something like:
[cc_cpp]
#define CAR_SAAB_ID 1
#define CAR_SAAB_MAX 1
#define CAR_FORD_ID 2
#define CAR_FORD_MAX 5
#define CAR_MAX_ORDER 1
void FullListRandom()
{
std::vector
std::vector
for(int i = 0; i < CAR_SAAB_MAX * CAR_MAX_ORDER; i++)
lInitialList.push_back(CAR_SAAB_ID);
for(int i = 0; i < CAR_FORD_MAX * CAR_MAX_ORDER; i++)
lInitialList.push_back(CAR_FORD_ID);
// now create the final list
while(lInitialList.size())
{
int CurId = rand() % lInitialList.size();
lFinalList.push_back( lInitialList[CurId] );
lInitialList.erase( lInitialList.begin() + CurId );
}
};
[/cc_cpp]
Of course you have to put [cci_cpp]lFinalList[/cci_cpp] somewhere more global, I just wrote this code for this post, but if you increase [cci_cpp]CAR_MAX_ORDER[/cci_cpp] then you can prevent a repeating pattern. If you would think of [cci_cpp]lFinalList[/cci_cpp] as circular list (meaning, once you reach the end you start over) this will provide a quite good and consistent distrubution.
By now the above method is common knowledge for me and it is rather simply once you know how to do it.
References
Shimmer shaders, practical jokes and farts…
"What? Farts in the title? I bet this must be interesting!"
And yes, it is interesting!
During one of the SFX lectures at school we were discussing shimmer shaders. Shimmering is an effect when something is extremely hot and you can see the air around it vibrate. In real life we have this with highway or deserts. The air above the ground is then so hot that it will hold a different density which causes the light to refract. In deserts you can get mirage where you think you see water in the desert, but in reality you are looking at the sky. This is because of light refraction.
In games we often use this for our lava monsters or on the lave itself or anything else that represents extreme heat. However as we were discussing this in the class I suddenly got this crazy idea of using to simulate a fart. Of course only suggesting this idea in class was good for a few laughs, but since I liked the idea, I actually made it and I'm ready to show it in class. Yes, I love practical jokes (the kind that don't piss anyone off).
The example is quite simple. Use WASD to move around and when you hold the left mouse button down you can use the mouse to rotate. Further you might notice that the girl is also shimmering. This is because she is hot as well.
PS: The fart sound is not mine
Download Fart.rar
Requirements: You need to have the latest DirectX 9.0 installed (June 2008) release
If you are missing something like [cci_text]"D3DX9_39.dll"[/cci_text] than you need to run the DirectX webupdate.
Using DXUT
DXUT is the DirectX Utility Toolkiit and until recently whenever you called it's name I would immediately note down your name on my list of people to ignore. And in case your wondering, yes, I always lose the list.
Anyhow, as a programmer I always want to reinvent the wheel, not because I think I know better or can do a better job, but more because I want to get familiar with. By know I have rewritten a lot of my own engines because there was always something missing here or there.
Is DXUT better than my own engines? Maybe, but DXUT is not an engine, it's a template, you could even combine DXUT with few of my engines. The only thing it does is creating structure in your application (by events like OnDeviceCreate, OnDeviceDestroy, OnDeviceReset, etc) and further it creates the window for you.
You also got access to a lot of other features, but they are optional. The only thing I dislike is the fact it includes DX10 specific elements in it, which you don't always want. I believe that a earlier version would allow you to use a define to disable it and else you could add it back yourself.
