Saturday, November 27, 2010

Tile-Based Mesh Generator

I put together a quick prototype for a tile-based mesh generator for Battle Balloons today. It is getting increasingly cumbersome to create levels by manually typing out YAML properties for each level, so I am now focusing on speeding up the content-creation process.

This came about from my latest programming hiccup, which was related to depth buffering. I originally thought I could simply stack my tile meshes (rectangular boxes) together as-is, but when I tried it on a scrolling level, I was seeing seams flickering in and out, along the tile boundaries. It was hard to get a screenshot, but here is one I caught while inspecting the normal render buffer (or render target for D3D folks):


The teal-coloured line on the left is what I was crying about! Teal represents normals pointing left; I store normals in RGB, mapping the range [-1,1] to the range [0,1], use a right-handed coordinate system and camera facing down negative Z. So it seems like the left face of those particular tile meshes were bleeding through. Note that I had back-face culling turned off at the time. After some searching, reading & head-scratching, I figured out the cause...

I naively assumed the front (camera-facing) faces would cover up the left/right faces behind. But this would only be correct most of the time. When depth sampling (which presumably happens at the centre of each pixel) hit the exact edge of a left face, that face would receive a depth value equal to what the front-face would later get - and so begins the gruesome z-fight between left-face and front-face over that one pixel. :)

To confirm this theory, I put in some quick hacks in my vertex shader to snap all vertex positions (after transformation to clip space) to the closest pixel corner. For a grid of axis-aligned meshes like my scene, this ensured no pixels were ever centred over the edge of any left faces. This did fix the seams, but is not a good, general solution (only works for axis-aligned boxes).

It makes complete sense now... but not immediately obvious to me in the beginning!

Learning my lesson, I now weld tile meshes together - any overlapping/shared faces are hidden. My tile meshes group their triangles into sections (one section for each face) and I can selectively turn off sections for a particular mesh instance. For now, the tile generator just handles all that cumbersome work of turning off the appropriate face if there is a neighbouring tile. But I plan on retro-fitting more content-creation goodness later.

Here's a video of the latest build with some scrolling action (and no seams!). The generator is being used for the floating islands:

Wednesday, November 17, 2010

Serialisable Enums in C++ Using Templates and the Preprocessor

I have been too busy at work lately to spend any significant time on Battle Balloons. On the upside, it has been very interesting late-night work delving deep into console graphics (both Microsoft Xbox 360 and Sony PlayStation 3)!

Anyway, here's a little bit of unrelated C++ hackery I use in Battle Ballons code. It allows you to declare an enum once (even within a class) and have it automatically convertible to/from strings - all without any auto-generation scripts. I use it to allow my config files (specified in YAML) to reference C++ enums defined in code. Super-useful since Battle Balloons is heavily data-driven!

Common implementations out there seem to use code-generators or multiple #includes of the same file. This is similar to the latter, except I pass in the list of enums twice (automated for you through a small macro).

The template class is not entirely necessary - you could easily embed the same code into the macro. But the big advantage the template has here is that you can actually step through the code in a debugger (at least in Visual Studio you can)!

More details in the code snippets below. It is rough and references project-specific code not shown, but the general concept is there and should be possible to reproduce.

First, a quick example on how it is used:
ENUM_CLASS(TestEnum, TE_A, TE_B = 2, TE_C, TE_D = 6);

void testEnum
{
    ASSERT(TE_A == 0);
    ASSERT(TE_B == 2);
    ASSERT(TE_C == 3);
    ASSERT(TE_D == 6);
    ASSERT(MAX_TestEnum == 7);

    TestEnum te;
    ASSERT(te == TestEnum::max());
    ASSERT(te == MAX_TestEnum);

    te.setFromString("TE_A");
    ASSERT(te == TE_A);

    te.setFromString("TE_B");
    ASSERT(te == TE_B);

    ASSERT(TestEnum::getEnum("TE_C") == TE_C);
    ASSERT(TestEnum::getString(TE_D) == "TE_D");
}

Header file (.h):
#define ENUM_CLASS(Name, ...)   enum E##Name { __VA_ARGS__, MAX_##Name };                           \
                                struct Get##Name##EnumString                                        \
                                {                                                                   \
                                    static const Char *get()                                        \
                                    {                                                               \
                                        return #__VA_ARGS__ ", MAX_" #Name;                         \
                                    }                                                               \
                                };                                                                  \
                                typedef EnumT Name

void tokeniseEnumString(String *enumArray, Size enumArrayCapacity, const String &enumString);

template <class Enum, const Size MAX_ENUM, class GetEnumString>
class EnumT : public EnumBase
{
public:
    static const String &getString(Size i)
    {
        ASSERT(i <= MAX_ENUM);
        return enumNames()[i];
    }

    static Enum getEnum(const String &s)
    {
        for (Size i = 0; i < MAX_ENUM; ++i)
        {
            if (enumNames()[i] == s)
                return static_cast(i);
        }
        return static_cast<Enum>(MAX_ENUM);
    }

    static Size max() { return MAX_ENUM; }

    EnumT() : m_value(static_cast<Enum>(MAX_ENUM)) {}
    EnumT(Size value) : m_value(static_cast<Enum>(value)) {}
    EnumT(Enum value) : m_value(value) {}

    EnumT &operator=(Enum e) { m_value = e; return *this; }
    operator Enum() const { return m_value; } 

    virtual bool setFromString(const String &s) { m_value = getEnum(s); return m_value != MAX_ENUM; }
    const Char *str() const { return *getString(m_value); }

private:
    static String *enumNames()
    {
        static bool s_init = false;
        static String s_enumNames[MAX_ENUM + 1];
        if (!s_init)
        {
            s_init = true;
            tokeniseEnumString(s_enumNames, MAX_ENUM + 1, GetEnumString::get());
        }
        return s_enumNames;
    }

    Enum m_value;
};

Implementation file (.cpp), which is is just the tokenising function that splits the enum declaration string (passed by the ENUM_CLASS macro function) into its individual names (and also respecting explicit number values, if given). This is only called once (on first use) and the result is cached off for all future calls:
void tokeniseEnumString(String *enumArray, Size enumArrayCapacity, const String &enumString)
{
    Size enumArraySize = 0;

    Size cIndex = 0;
    while(cIndex < enumString.len())
    {
        String name;
        bool hasExplicitValue = false;

        for (; cIndex < enumString.len(); ++cIndex)
        {
            const Char nameChar = enumString[cIndex];

            if (nameChar == ',')
            {
                ++cIndex;
                break;
            }

            if (nameChar == '=')
            {
                hasExplicitValue = true;
                ++cIndex;
                break;
            }

            if (std::isspace(nameChar))
                continue;

            name += nameChar;
        }

        if (hasExplicitValue)
        {
            String numberStr;

            for (; cIndex < enumString.len(); ++cIndex)
            {
                const Char numberChar = enumString[cIndex];

                if (std::isspace(numberChar))
                    continue;

                if (numberChar == ',')
                {
                    ++cIndex;
                    break;
                }

                ASSERT(std::isdigit(numberChar));
                numberStr += numberChar;
            }

            Int intValue;
            ASSERT(numberStr.toInt(intValue));
            ASSERT(intValue >= 0);

            const Size value = static_cast(intValue);
            ASSERT(value >= enumArraySize);
            while (enumArraySize < value)
                ++enumArraySize;
        }

        enumArray[enumArraySize] = name;
        ++enumArraySize;
    }

    ASSERT(enumArraySize == enumArrayCapacity);
}