3D C/C++ tutorials - Ray tracing - CPU ray tracer 02 - Uniform grid
3D C/C++ tutorials -> Ray tracing -> CPU ray tracer 02 - Uniform grid
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
cpu_ray_tracer.h
#include <windows.h>

#include "string.h"
#include "glmath.h"

// ----------------------------------------------------------------------------------------------------------------------------

class CCamera
{
public:
    vec3 X, Y, Z, Reference, Position;
    mat4x4 Vin, Pin, VPin, RayMatrix;

public:
    CCamera();
    ~CCamera();

    void CalculateRayMatrix();
    void Look(const vec3 &Position, const vec3 &Reference, bool RotateAroundReference = false);
    bool OnKeyDown(UINT nChar);
    void OnMouseMove(int dx, int dy);
    void OnMouseWheel(short zDelta);
};

// ----------------------------------------------------------------------------------------------------------------------------

class CTriangle
{
public:
    float D, D1, D2, D3, lab, lbc, lca;
    vec3 a, b, c, ab, bc, ca, Color, N, N1, N2, N3;

public:
    CTriangle();
    CTriangle(const vec3 &a, const vec3 &b, const vec3 &c, const vec3 &Color);

    bool Inside(float x, float y, float z);
    bool Inside(const vec3 &Point);
    bool Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance, float &Distance, vec3 &Point);
    bool Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance, float &Distance);
    bool Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance);
};

// ----------------------------------------------------------------------------------------------------------------------------

class RTData
{
public:
    float Distance, TestDistance;
    vec3 Color, Point, TestPoint;
    CTriangle *Triangle;

public:
    RTData();
};

// ----------------------------------------------------------------------------------------------------------------------------

class CVoxel
{
public:
    CTriangle **Triangles;
    int TrianglesCount;

protected:
    int MaxTrianglesCount;
    float Size;
    vec3 Min, Max, MinE, MaxE;

public:
    CVoxel();
    ~CVoxel();

    void Add(CTriangle *Triangle);
    void Delete();
    bool Inside(const vec3 &Point);
    bool Intersect(CTriangle *Triangle);
    bool IntersectEdgesX(CTriangle *Triangle, float x, float y1, float y2, float z1, float z2);
    bool IntersectEdgesY(CTriangle *Triangle, float y, float x1, float x2, float z1, float z2);
    bool IntersectEdgesZ(CTriangle *Triangle, float z, float x1, float x2, float y1, float y2);
    bool IntersectFacesX(CTriangle *Triangle, float D1, float D2);
    bool IntersectFacesY(CTriangle *Triangle, float D1, float D2);
    bool IntersectFacesZ(CTriangle *Triangle, float D1, float D2);
    void Set(const vec3 &Min, float Size);
};

// ----------------------------------------------------------------------------------------------------------------------------

class CUniformGrid
{
protected:
    vec3 Min, Max;

protected:
    int X, Y, Z, Xm1, Ym1, Zm1, XY, XYZ;
    float VoxelSize;
    CVoxel *Voxels;

public:
    CUniformGrid();
    ~CUniformGrid();

public:
    void Delete();
    void Generate(CTriangle *Triangles, int TrianglesCount, float VoxelSize = 1.0f);
    vec3 Traverse(const vec3 &Voxel, const vec3 &Origin, const vec3 &Ray);

    float VoxelToWorldX(float x);
    float VoxelToWorldY(float y);
    float VoxelToWorldZ(float z);
    vec3 VoxelToWorld(const vec3 &Voxel);
    int WorldToVoxelX(float x);
    int WorldToVoxelY(float y);
    int WorldToVoxelZ(float z);
    vec3 WorldToVoxel(const vec3 &World);
};

// ----------------------------------------------------------------------------------------------------------------------------

class CRayTracer
{
private:
    BYTE *ColorBuffer;
    BITMAPINFO ColorBufferInfo;
    int Width, LineWidth, Height;

protected:
    CTriangle *Triangles;
    int TrianglesCount;

private:
    CUniformGrid UniformGrid;

public:
    bool SuperSampling;

public:
    CRayTracer();
    ~CRayTracer();

    bool Init();
    void RayTrace(int x, int y);
    void Resize(int Width, int Height);
    void Destroy();

    void ClearColorBuffer();
    void SwapBuffers(HDC hDC);

protected:
    virtual bool InitScene() = 0;
};

// ----------------------------------------------------------------------------------------------------------------------------

class CMyRayTracer : public CRayTracer
{
protected:
    bool InitScene();
};

// ----------------------------------------------------------------------------------------------------------------------------

class CWnd
{
protected:
    char *WindowName;
    HWND hWnd;
    int Width, Height, x, y, LastX, LastY;

public:
    CWnd();
    ~CWnd();

    bool Create(HINSTANCE hInstance, char *WindowName, int Width, int Height);
    void RePaint();
    void Show(bool Maximized = false);
    void MsgLoop();
    void Destroy();

    void OnKeyDown(UINT Key);
    void OnMouseMove(int X, int Y);
    void OnMouseWheel(short zDelta);
    void OnPaint();
    void OnRButtonDown(int X, int Y);
    void OnSize(int Width, int Height);
};

// ----------------------------------------------------------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow);
cpu_ray_tracer.cpp
#include "cpu_ray_tracer.h"

// ----------------------------------------------------------------------------------------------------------------------------

CString ErrorLog;

// ----------------------------------------------------------------------------------------------------------------------------

CCamera::CCamera()
{
    X = vec3(1.0, 0.0, 0.0);
    Y = vec3(0.0, 1.0, 0.0);
    Z = vec3(0.0, 0.0, 1.0);

    Reference = vec3(0.0, 0.0, 0.0);
    Position = vec3(0.0, 0.0, 5.0);
}

CCamera::~CCamera()
{
}

void CCamera::CalculateRayMatrix()
{
    Vin[0] = X.x; Vin[4] = Y.x; Vin[8] = Z.x;
    Vin[1] = X.y; Vin[5] = Y.y; Vin[9] = Z.y;
    Vin[2] = X.z; Vin[6] = Y.z; Vin[10] = Z.z;

    RayMatrix = Vin * Pin * BiasMatrixInverse * VPin;
}

void CCamera::Look(const vec3 &Position, const vec3 &Reference, bool RotateAroundReference)
{
    this->Reference = Reference;
    this->Position = Position;

    Z = normalize(Position - Reference);
    X = normalize(cross(vec3(0.0f, 1.0f, 0.0f), Z));
    Y = cross(Z, X);

    if(!RotateAroundReference)
    {
        this->Reference = this->Position;
        this->Position += Z * 0.05f;
    }

    CalculateRayMatrix();
}

bool CCamera::OnKeyDown(UINT nChar)
{
    float Distance = 0.125f;

    if(GetKeyState(VK_CONTROL) & 0x80) Distance *= 0.5f;
    if(GetKeyState(VK_SHIFT) & 0x80) Distance *= 2.0f;

    vec3 Up(0.0f, 1.0f, 0.0f);
    vec3 Right = X;
    vec3 Forward = cross(Up, Right);

    Up *= Distance;
    Right *= Distance;
    Forward *= Distance;

    vec3 Movement;

    if(nChar == 'W') Movement += Forward;
    if(nChar == 'S') Movement -= Forward;
    if(nChar == 'A') Movement -= Right;
    if(nChar == 'D') Movement += Right;
    if(nChar == 'R') Movement += Up;
    if(nChar == 'F') Movement -= Up;

    Reference += Movement;
    Position += Movement;

    return Movement.x != 0.0f || Movement.y != 0.0f || Movement.z != 0.0f;
}

void CCamera::OnMouseMove(int dx, int dy)
{
    float sensitivity = 0.25f;

    float hangle = (float)dx * sensitivity;
    float vangle = (float)dy * sensitivity;

    Position -= Reference;

    Y = rotate(Y, vangle, X);
    Z = rotate(Z, vangle, X);

    if(Y.y < 0.0f)
    {
        Z = vec3(0.0f, Z.y > 0.0f ? 1.0f : -1.0f, 0.0f);
        Y = cross(Z, X);
    }

    X = rotate(X, hangle, vec3(0.0f, 1.0f, 0.0f));
    Y = rotate(Y, hangle, vec3(0.0f, 1.0f, 0.0f));
    Z = rotate(Z, hangle, vec3(0.0f, 1.0f, 0.0f));

    Position = Reference + Z * length(Position);

    CalculateRayMatrix();
}

void CCamera::OnMouseWheel(short zDelta)
{
    Position -= Reference;

    if(zDelta < 0 && length(Position) < 500.0f)
    {
        Position += Position * 0.1f;
    }

    if(zDelta > 0 && length(Position) > 0.05f)
    {
        Position -= Position * 0.1f;
    }

    Position += Reference;
}

// ----------------------------------------------------------------------------------------------------------------------------

CCamera Camera;

// ----------------------------------------------------------------------------------------------------------------------------

CTriangle::CTriangle()
{
}

CTriangle::CTriangle(const vec3 &a, const vec3 &b, const vec3 &c, const vec3 &Color) : a(a), b(b), c(c), Color(Color)
{
    ab = b - a; bc = c - b; ca = a - c;

    N = normalize(cross(ab, -ca));
    D = -dot(N, a);

    N1 = normalize(cross(N, ab));
    D1 = -dot(N1, a);

    N2 = normalize(cross(N, bc));
    D2 = -dot(N2, b);

    N3 = normalize(cross(N, ca));
    D3 = -dot(N3, c);

    lab = length(ab); ab /= lab;
    lbc = length(bc); bc /= lbc;
    lca = length(ca); ca /= lca;
}

bool CTriangle::Inside(float x, float y, float z)
{
    if(N1.x * x + N1.y * y + N1.z * z + D1 < 0.0f) return false;
    if(N2.x * x + N2.y * y + N2.z * z + D2 < 0.0f) return false;
    if(N3.x * x + N3.y * y + N3.z * z + D3 < 0.0f) return false;

    return true;
}

bool CTriangle::Inside(const vec3 &Point)
{
    if(dot(N1, Point) + D1 < 0.0f) return false;
    if(dot(N2, Point) + D2 < 0.0f) return false;
    if(dot(N3, Point) + D3 < 0.0f) return false;

    return true;
}

bool CTriangle::Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance, float &Distance, vec3 &Point)
{
    float NdotR = -dot(N, Ray);

    if(NdotR > 0.0f)
    {
        Distance = (dot(N, Origin) + D) / NdotR;

        if(Distance >= 0.0f && Distance < MaxDistance)
        {
            Point = Ray * Distance + Origin;

            return Inside(Point);
        }
    }

    return false;
}

bool CTriangle::Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance, float &Distance)
{
    float NdotR = -dot(N, Ray);

    if(NdotR > 0.0f)
    {
        Distance = (dot(N, Origin) + D) / NdotR;

        if(Distance >= 0.0f && Distance < MaxDistance)
        {
            return Inside(Ray * Distance + Origin);
        }
    }

    return false;
}

bool CTriangle::Intersect(const vec3 &Origin, const vec3 &Ray, float MaxDistance)
{
    float NdotR = -dot(N, Ray);

    if(NdotR > 0.0f)
    {
        float Distance = (dot(N, Origin) + D) / NdotR;

        if(Distance >= 0.0f && Distance < MaxDistance)
        {
            return Inside(Ray * Distance + Origin);
        }
    }

    return false;
}

// ----------------------------------------------------------------------------------------------------------------------------

RTData::RTData()
{
    Distance = 1048576.0f;
    Triangle = NULL;
}

// ----------------------------------------------------------------------------------------------------------------------------

CVoxel::CVoxel()
{
    Triangles = NULL;
    TrianglesCount = 0;
    MaxTrianglesCount = 1;
    Size = 0.0f;
}

CVoxel::~CVoxel()
{
}

void CVoxel::Add(CTriangle *Triangle)
{
    if(TrianglesCount % MaxTrianglesCount == 0)
    {
        CTriangle **OldTriangles = Triangles;

        MaxTrianglesCount *= 2;

        Triangles = new CTriangle*[MaxTrianglesCount];

        for(int i = 0; i < TrianglesCount; i++)
        {
            Triangles[i] = OldTriangles[i];
        }

        if(OldTriangles != NULL)
        {
            delete [] OldTriangles;
        }
    }

    Triangles[TrianglesCount] = Triangle;

    TrianglesCount++;
}

void CVoxel::Delete()
{
    if(Triangles != NULL)
    {
        delete [] Triangles;
        Triangles = NULL;
        TrianglesCount = 0;
        MaxTrianglesCount = 1;
        Size = 0.0f;
        Min = Max = MinE = MaxE = vec3(0.0f);
    }
}

bool CVoxel::Inside(const vec3 &Point)
{
    if(MinE.x < Point.x && Point.x < MaxE.x)
    {
        if(MinE.y < Point.y && Point.y < MaxE.y)
        {
            if(MinE.z < Point.z && Point.z < MaxE.z)
            {
                return true;
            }
        }
    }

    return false;
}

bool CVoxel::IntersectEdgesX(CTriangle *Triangle, float x, float y1, float y2, float z1, float z2)
{
    float NdotR = -Triangle->N.x;

    if(NdotR != 0.0f)
    {
        vec3 Origin = vec3(x, y1, z1);

        float Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x + Distance, Origin.y, Origin.z);
        }

        Origin.z = z2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x + Distance, Origin.y, Origin.z);
        }

        Origin.y = y2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x + Distance, Origin.y, Origin.z);
        }

        Origin.z = z1;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x + Distance, Origin.y, Origin.z);
        }
    }

    return false;
}

bool CVoxel::IntersectEdgesY(CTriangle *Triangle, float y, float x1, float x2, float z1, float z2)
{
    float NdotR = -Triangle->N.y;

    if(NdotR != 0.0f)
    {
        vec3 Origin = vec3(x1, y, z1);

        float Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y + Distance, Origin.z);
        }

        Origin.x = x2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y + Distance, Origin.z);
        }

        Origin.z = z2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y + Distance, Origin.z);
        }

        Origin.x = x1;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y + Distance, Origin.z);
        }
    }

    return false;
}

bool CVoxel::IntersectEdgesZ(CTriangle *Triangle, float z, float x1, float x2, float y1, float y2)
{
    float NdotR = -Triangle->N.z;

    if(NdotR != 0.0f)
    {
        vec3 Origin = vec3(x1, y1, z);

        float Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y, Origin.z + Distance);
        }

        Origin.x = x2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y, Origin.z + Distance);
        }

        Origin.y = y2;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y, Origin.z + Distance);
        }

        Origin.x = x1;

        Distance = (dot(Triangle->N, Origin) + Triangle->D) / NdotR;

        if(Distance >= 0.0f && Distance <= Size)
        {
            return Triangle->Inside(Origin.x, Origin.y, Origin.z + Distance);
        }
    }

    return false;
}

bool CVoxel::IntersectFacesX(CTriangle *Triangle, float D1, float D2)
{
    vec3 *Origin = (vec3*)&Triangle->a;
    vec3 *Ray = (vec3*)&Triangle->ab;
    float *Length = &Triangle->lab;

    float NdotR, d, y, z;

    for(int i = 0; i < 3; i++)
    {
        NdotR = -Ray->x;

        if(NdotR != 0.0f)
        {
            d = (Origin->x - D1) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                y = Ray->y * d + Origin->y;

                if(Min.y <= y && y <= Max.y)
                {
                    z = Ray->z * d + Origin->z;

                    if(Min.z <= z && z <= Max.z)
                    {
                        return true;
                    }
                }
            }

            d = (Origin->x - D2) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                y = Ray->y * d + Origin->y;

                if(Min.y <= y && y <= Max.y)
                {
                    z = Ray->z * d + Origin->z;

                    if(Min.z <= z && z <= Max.z)
                    {
                        return true;
                    }
                }
            }
        }

        Ray++;
        Origin++;
        Length++;
    }

    return false;
}

bool CVoxel::IntersectFacesY(CTriangle *Triangle, float D1, float D2)
{
    vec3 *Origin = (vec3*)&Triangle->a;
    vec3 *Ray = (vec3*)&Triangle->ab;
    float *Length = &Triangle->lab;

    float NdotR, d, x, z;

    for(int i = 0; i < 3; i++)
    {
        NdotR = -Ray->y;

        if(NdotR != 0.0f)
        {
            d = (Origin->y - D1) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                x = Ray->x * d + Origin->x;

                if(Min.x <= x && x <= Max.x)
                {
                    z = Ray->z * d + Origin->z;

                    if(Min.z <= z && z <= Max.z)
                    {
                        return true;
                    }
                }
            }

            d = (Origin->y - D2) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                x = Ray->x * d + Origin->x;

                if(Min.x <= x && x <= Max.x)
                {
                    z = Ray->z * d + Origin->z;

                    if(Min.z <= z && z <= Max.z)
                    {
                        return true;
                    }
                }
            }
        }

        Ray++;
        Origin++;
        Length++;
    }

    return false;
}

bool CVoxel::IntersectFacesZ(CTriangle *Triangle, float D1, float D2)
{
    vec3 *Origin = (vec3*)&Triangle->a;
    vec3 *Ray = (vec3*)&Triangle->ab;
    float *Length = &Triangle->lab;

    float NdotR, d, x, y;

    for(int i = 0; i < 3; i++)
    {
        NdotR = -Ray->z;

        if(NdotR != 0.0f)
        {
            d = (Origin->z - D1) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                x = Ray->x * d + Origin->x;

                if(Min.x <= x && x <= Max.x)
                {
                    y = Ray->y * d + Origin->y;

                    if(Min.y <= y && y <= Max.y)
                    {
                        return true;
                    }
                }
            }

            d = (Origin->z - D2) / NdotR;

            if(0.0f <= d && d <= *Length)
            {
                x = Ray->x * d + Origin->x;

                if(Min.x <= x && x <= Max.x)
                {
                    y = Ray->y * d + Origin->y;

                    if(Min.y <= y && y <= Max.y)
                    {
                        return true;
                    }
                }
            }
        }

        Ray++;
        Origin++;
        Length++;
    }

    return false;
}

bool CVoxel::Intersect(CTriangle *Triangle)
{
    if(Inside(Triangle->a)) return true;
    if(Inside(Triangle->b)) return true;
    if(Inside(Triangle->c)) return true;

    if(IntersectFacesX(Triangle, Min.x, Max.x)) return true;
    if(IntersectFacesY(Triangle, Min.y, Max.y)) return true;
    if(IntersectFacesZ(Triangle, Min.z, Max.z)) return true;

    if(IntersectEdgesX(Triangle, Min.x, Min.y, Max.y, Min.z, Max.z)) return true;
    if(IntersectEdgesY(Triangle, Min.y, Min.x, Max.x, Min.z, Max.z)) return true;
    if(IntersectEdgesZ(Triangle, Min.z, Min.x, Max.x, Min.y, Max.y)) return true;

    return false;
}

void CVoxel::Set(const vec3 &Min, float Size)
{
    this->Size = Size;
    this->Min = Min;
    this->Max = this->Min + Size;
    this->MinE = this->Min - 0.001f;
    this->MaxE = this->Max + 0.001f;
}

// ----------------------------------------------------------------------------------------------------------------------------

CUniformGrid::CUniformGrid()
{
    X = Y = Z = Xm1 = Ym1 = Zm1 = XY = XYZ = 0;
    VoxelSize = 0.0f;
    Voxels = NULL;
}

CUniformGrid::~CUniformGrid()
{
}

void CUniformGrid::Delete()
{
    if(Voxels != NULL)
    {
        for(int i = 0; i < XYZ; i++)
        {
            Voxels[i].Delete();
        }

        Min = Max = vec3(0.0f);
        X = Y = Z = Xm1 = Ym1 = Zm1 = XY = XYZ = 0;
        VoxelSize = 0.0f;
        delete [] Voxels;
        Voxels = NULL;
    }
}

void CUniformGrid::Generate(CTriangle *Triangles, int TrianglesCount, float VoxelSize)
{
    Delete();

    if(Triangles != NULL && TrianglesCount > 0)
    {
        this->VoxelSize = VoxelSize;

        CTriangle *LastTriangle = Triangles + TrianglesCount;

        Min = Max = Triangles->a;

        for(CTriangle *Triangle = Triangles; Triangle < LastTriangle; Triangle++)
        {
            if(Triangle->a.x < Min.x) Min.x = Triangle->a.x;
            if(Triangle->a.y < Min.y) Min.y = Triangle->a.y;
            if(Triangle->a.z < Min.z) Min.z = Triangle->a.z;

            if(Triangle->b.x < Min.x) Min.x = Triangle->b.x;
            if(Triangle->b.y < Min.y) Min.y = Triangle->b.y;
            if(Triangle->b.z < Min.z) Min.z = Triangle->b.z;

            if(Triangle->c.x < Min.x) Min.x = Triangle->c.x;
            if(Triangle->c.y < Min.y) Min.y = Triangle->c.y;
            if(Triangle->c.z < Min.z) Min.z = Triangle->c.z;

            if(Triangle->a.x > Max.x) Max.x = Triangle->a.x;
            if(Triangle->a.y > Max.y) Max.y = Triangle->a.y;
            if(Triangle->a.z > Max.z) Max.z = Triangle->a.z;

            if(Triangle->b.x > Max.x) Max.x = Triangle->b.x;
            if(Triangle->b.y > Max.y) Max.y = Triangle->b.y;
            if(Triangle->b.z > Max.z) Max.z = Triangle->b.z;

            if(Triangle->c.x > Max.x) Max.x = Triangle->c.x;
            if(Triangle->c.y > Max.y) Max.y = Triangle->c.y;
            if(Triangle->c.z > Max.z) Max.z = Triangle->c.z;
        }

        Min /= VoxelSize; Max /= VoxelSize;

        Min.x = floor(Min.x); Max.x = ceil(Max.x);
        Min.y = floor(Min.y); Max.y = ceil(Max.y);
        Min.z = floor(Min.z); Max.z = ceil(Max.z);

        if(Min.x == Max.x) Max.x += 1.0f;
        if(Min.y == Max.y) Max.y += 1.0f;
        if(Min.z == Max.z) Max.z += 1.0f;

        X = (int)(Max.x - Min.x); Xm1 = X - 1;
        Y = (int)(Max.y - Min.y); Ym1 = Y - 1;
        Z = (int)(Max.z - Min.z); Zm1 = Z - 1;

        XY = X * Y;
        XYZ = XY * Z;

        Min *= VoxelSize; Max *= VoxelSize;

        Voxels = new CVoxel[XYZ];

        for(int z = 0; z < Z; z++)
        {
            for(int y = 0; y < Y; y++)
            {
                for(int x = 0; x < X; x++)
                {
                    Voxels[XY * z + X * y + x].Set(VoxelToWorld(vec3((float)x, (float)y, (float)z)), VoxelSize);
                }
            }
        }

        for(CTriangle *Triangle = Triangles; Triangle < LastTriangle; Triangle++)
        {
            vec3 tmin = Triangle->a, tmax = tmin;

            if(Triangle->b.x < tmin.x) tmin.x = Triangle->b.x;
            if(Triangle->b.y < tmin.y) tmin.y = Triangle->b.y;
            if(Triangle->b.z < tmin.z) tmin.z = Triangle->b.z;

            if(Triangle->b.x > tmax.x) tmax.x = Triangle->b.x;
            if(Triangle->b.y > tmax.y) tmax.y = Triangle->b.y;
            if(Triangle->b.z > tmax.z) tmax.z = Triangle->b.z;

            if(Triangle->c.x < tmin.x) tmin.x = Triangle->c.x;
            if(Triangle->c.y < tmin.y) tmin.y = Triangle->c.y;
            if(Triangle->c.z < tmin.z) tmin.z = Triangle->c.z;

            if(Triangle->c.x > tmax.x) tmax.x = Triangle->c.x;
            if(Triangle->c.y > tmax.y) tmax.y = Triangle->c.y;
            if(Triangle->c.z > tmax.z) tmax.z = Triangle->c.z;

            int vminx = WorldToVoxelX(tmin.x), vmaxx = WorldToVoxelX(tmax.x);
            int vminy = WorldToVoxelY(tmin.y), vmaxy = WorldToVoxelY(tmax.y);
            int vminz = WorldToVoxelZ(tmin.z), vmaxz = WorldToVoxelZ(tmax.z);

            if(vminx >= X) vminx = Xm1; if(vmaxx >= X) vmaxx = Xm1;
            if(vminy >= Y) vminy = Ym1; if(vmaxy >= Y) vmaxy = Ym1;
            if(vminz >= Z) vminz = Zm1; if(vmaxz >= Z) vmaxz = Zm1;

            for(int z = vminz; z <= vmaxz; z++)
            {
                for(int y = vminy; y <= vmaxy; y++)
                {
                    for(int x = vminx; x <= vmaxx; x++)
                    {
                        CVoxel *Voxel = Voxels + (XY * z + X * y + x);

                        if(Voxel->Intersect(Triangle))
                        {
                            Voxel->Add(Triangle);
                        }
                    }
                }
            }
        }
    }
}

vec3 CUniformGrid::Traverse(const vec3 &Voxel, const vec3 &Origin, const vec3 &Ray)
{
    vec3 voxel = Voxel, step, out, t, delta = VoxelSize / Ray;

    if(Ray.x < 0.0f)
    {
        step.x = -1.0f;
        out.x = voxel.x <= 0.0f ? voxel.x - 1.0f : -1.0f;
        t.x = (VoxelToWorldX(voxel.x) - Origin.x) / Ray.x;
    }
    else
    {
        step.x = 1.0f;
        out.x = voxel.x >= X ? voxel.x + 1 : X;
        t.x = (VoxelToWorldX(voxel.x + 1.0f) - Origin.x) / Ray.x;
    }

    if(Ray.y < 0.0f)
    {
        step.y = -1.0f;
        out.y = voxel.y <= 0.0f ? voxel.y - 1.0f : -1.0f;
        t.y = (VoxelToWorldY(voxel.y) - Origin.y) / Ray.y;
    }
    else
    {
        step.y = 1.0f;
        out.y = voxel.y >= Y ? voxel.y + 1 : Y;
        t.y = (VoxelToWorldY(voxel.y + 1.0f) - Origin.y) / Ray.y;
    }

    if(Ray.z < 0.0f)
    {
        step.z = -1.0f;
        out.z = voxel.z <= 0.0f ? voxel.z - 1.0f : -1.0f;
        t.z = (VoxelToWorldZ(voxel.z) - Origin.z) / Ray.z;
    }
    else
    {
        step.z = 1.0f;
        out.z = voxel.z >= Z ? voxel.z + 1 : Z;
        t.z = (VoxelToWorldZ(voxel.z + 1.0f) - Origin.z) / Ray.z;
    }

    delta *= step;

    while(1)
    {
        int x = (int)voxel.x, y = (int)voxel.y, z = (int)voxel.z;

        if(x >= 0 && x < X && y >= 0 && y < Y && z >= 0 && z < Z)
        {
            CVoxel *Voxel = Voxels + (XY * z + X * y + x);
            CTriangle **Triangles = Voxel->Triangles, *Triangle;
            int TrianglesCount = Voxel->TrianglesCount;

            RTData rtdata;

            for(int i = 0; i < TrianglesCount; i++)
            {
                Triangle = Triangles[i];

                if(Triangle->Intersect(Origin, Ray, rtdata.Distance, rtdata.TestDistance, rtdata.TestPoint))
                {
                    if(Voxel->Inside(rtdata.TestPoint))
                    {
                        rtdata.Point = rtdata.TestPoint;
                        rtdata.Distance = rtdata.TestDistance;
                        rtdata.Triangle = Triangle;
                    }
                }
            }

            if(rtdata.Triangle)
            {
                rtdata.Color = rtdata.Triangle->Color;

                float NdotL = -dot(rtdata.Triangle->N, Ray);

                if(NdotL < 0.0f)
                {
                    NdotL = 0.0f;
                }

                rtdata.Color *= 0.75f * NdotL + 0.25f;

                return rtdata.Color;
            }
        }

        float min = t.x;

        if(t.y < min) min = t.y;
        if(t.z < min) min = t.z;

        if(t.x == min)
        {
           voxel.x += step.x;
           if(voxel.x == out.x) break;
           t.x += delta.x;
        }

        if(t.y == min)
        {
           voxel.y += step.y;
           if(voxel.y == out.y) break;
           t.y += delta.y;
        }

        if(t.z == min)
        {
           voxel.z += step.z;
           if(voxel.z == out.z) break;
           t.z += delta.z;
        }
    }

    return vec3(0.0f);
}

float CUniformGrid::VoxelToWorldX(float x)
{
    return x * VoxelSize + Min.x;
}

float CUniformGrid::VoxelToWorldY(float y)
{
    return y * VoxelSize + Min.y;
}

float CUniformGrid::VoxelToWorldZ(float z)
{
    return z * VoxelSize + Min.z;
}

vec3 CUniformGrid::VoxelToWorld(const vec3 &Voxel)
{
    return Voxel * VoxelSize + Min;
}

int CUniformGrid::WorldToVoxelX(float x)
{
    return (int)floor((x - Min.x) / VoxelSize);
}

int CUniformGrid::WorldToVoxelY(float y)
{
    return (int)floor((y - Min.y) / VoxelSize);
}

int CUniformGrid::WorldToVoxelZ(float z)
{
    return (int)floor((z - Min.z) / VoxelSize);
}

vec3 CUniformGrid::WorldToVoxel(const vec3 &World)
{
    vec3 voxel = (World - Min) / VoxelSize;

    voxel.x = floor(voxel.x);
    voxel.y = floor(voxel.y);
    voxel.z = floor(voxel.z);

    return voxel;
}

// ----------------------------------------------------------------------------------------------------------------------------

CRayTracer::CRayTracer()
{
    ColorBuffer = NULL;

    Triangles = NULL;
    TrianglesCount = 0;

    SuperSampling = false;
}

CRayTracer::~CRayTracer()
{
}

bool CRayTracer::Init()
{
    if(InitScene() == false)
    {
        return false;
    }

    UniformGrid.Generate(Triangles, TrianglesCount);

    return true;
}

void CRayTracer::RayTrace(int x, int y)
{
    if(ColorBuffer != NULL && Triangles != NULL && TrianglesCount > 0)
    {
        vec3 Color, Voxel = UniformGrid.WorldToVoxel(Camera.Position);

        if(!SuperSampling)
        {
            Color = UniformGrid.Traverse(Voxel, Camera.Position, normalize(*(vec3*)&(Camera.RayMatrix * vec4((float)x + 0.5f, (float)y + 0.5f, 0.0f, 1.0f))));
        }
        else
        {
            for(float yy = 0.125f; yy < 1.0f; yy += 0.25f)
            {
                for(float xx = 0.125f; xx < 1.0f; xx += 0.25f)
                {
                    Color += UniformGrid.Traverse(Voxel, Camera.Position, normalize(*(vec3*)&(Camera.RayMatrix * vec4((float)x + xx, (float)y + yy, 0.5f, 1.0f))));
                }
            }

            Color /= 16.0f;
        }

        BYTE *colorbuffer = (LineWidth * y + x) * 3 + ColorBuffer;

        colorbuffer[2] = Color.r <= 0.0f ? 0 : Color.r >= 1.0 ? 255 : (BYTE)(Color.r * 255);
        colorbuffer[1] = Color.g <= 0.0f ? 0 : Color.g >= 1.0 ? 255 : (BYTE)(Color.g * 255);
        colorbuffer[0] = Color.b <= 0.0f ? 0 : Color.b >= 1.0 ? 255 : (BYTE)(Color.b * 255);
    }
}

void CRayTracer::Resize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    if(ColorBuffer != NULL)
    {
        delete [] ColorBuffer;
        ColorBuffer = NULL;
    }

    if(Width > 0 && Height > 0)
    {
        LineWidth = Width;

        int WidthMod4 = Width % 4;

        if(WidthMod4 > 0)
        {
            LineWidth += 4 - WidthMod4;
        }

        ColorBuffer = new BYTE[LineWidth * Height * 3];

        memset(&ColorBufferInfo, 0, sizeof(BITMAPINFOHEADER));
        ColorBufferInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        ColorBufferInfo.bmiHeader.biPlanes = 1;
        ColorBufferInfo.bmiHeader.biBitCount = 24;
        ColorBufferInfo.bmiHeader.biCompression = BI_RGB;
        ColorBufferInfo.bmiHeader.biWidth = LineWidth;
        ColorBufferInfo.bmiHeader.biHeight = Height;

        Camera.VPin[0] = 1.0f / (float)Width;
        Camera.VPin[5] = 1.0f / (float)Height;

        float tany = tan(45.0f / 360.0f * (float)M_PI), aspect = (float)Width / (float)Height;

        Camera.Pin[0] = tany * aspect;
        Camera.Pin[5] = tany;
        Camera.Pin[10] = 0.0f;
        Camera.Pin[14] = -1.0f;

        Camera.CalculateRayMatrix();
    }
}

void CRayTracer::Destroy()
{
    UniformGrid.Delete();

    if(Triangles != NULL)
    {
        delete [] Triangles;
        Triangles = NULL;
        TrianglesCount = 0;
    }

    if(ColorBuffer != NULL)
    {
        delete [] ColorBuffer;
        ColorBuffer = NULL;
    }
}

void CRayTracer::ClearColorBuffer()
{
    if(ColorBuffer != NULL)
    {
        memset(ColorBuffer, 0, LineWidth * Height * 3);
    }
}

void CRayTracer::SwapBuffers(HDC hDC)
{
    if(ColorBuffer != NULL)
    {
        StretchDIBits(hDC, 0, 0, Width, Height, 0, 0, Width, Height, ColorBuffer, &ColorBufferInfo, DIB_RGB_COLORS, SRCCOPY);
    }
}

// ----------------------------------------------------------------------------------------------------------------------------

bool CMyRayTracer::InitScene()
{
    bool Error = false;

    if(Error)
    {
        return false;
    }

    int BoxesCount = 256;

    TrianglesCount = 12 * BoxesCount;

    Triangles = new CTriangle[TrianglesCount];

    int t = 0;

    srand(GetTickCount());

    for(int i = 0; i < BoxesCount; i++)
    {
        vec3 m = -8.0f + 16.0f * vec3((float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX);
        vec3 s = 0.25f + 1.5f * vec3((float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX);
        vec3 color = vec3((float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX, (float)rand() / (float)RAND_MAX);

        mat3x3 RS;
        
        RS = RS * mat3x3(rotate(360.0f * (float)rand() / (float)RAND_MAX, vec3(1.0f, 0.0f, 0.0f)));
        RS = RS * mat3x3(rotate(360.0f * (float)rand() / (float)RAND_MAX, vec3(0.0f, 1.0f, 0.0f)));
        RS = RS * mat3x3(rotate(360.0f * (float)rand() / (float)RAND_MAX, vec3(0.0f, 0.0f, 1.0f)));
        
        RS = RS * mat3x3(scale(s.x, s.y, s.z));

        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f, -0.5f,  0.5f), m + RS * vec3( 0.5f, -0.5f, -0.5f), m + RS * vec3( 0.5f,  0.5f, -0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f,  0.5f, -0.5f), m + RS * vec3( 0.5f,  0.5f,  0.5f), m + RS * vec3( 0.5f, -0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f, -0.5f, -0.5f), m + RS * vec3(-0.5f, -0.5f,  0.5f), m + RS * vec3(-0.5f,  0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f,  0.5f,  0.5f), m + RS * vec3(-0.5f,  0.5f, -0.5f), m + RS * vec3(-0.5f, -0.5f, -0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f,  0.5f,  0.5f), m + RS * vec3( 0.5f,  0.5f,  0.5f), m + RS * vec3( 0.5f,  0.5f, -0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f,  0.5f, -0.5f), m + RS * vec3(-0.5f,  0.5f, -0.5f), m + RS * vec3(-0.5f,  0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f, -0.5f, -0.5f), m + RS * vec3( 0.5f, -0.5f, -0.5f), m + RS * vec3( 0.5f, -0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f, -0.5f,  0.5f), m + RS * vec3(-0.5f, -0.5f,  0.5f), m + RS * vec3(-0.5f, -0.5f, -0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f, -0.5f,  0.5f), m + RS * vec3( 0.5f, -0.5f,  0.5f), m + RS * vec3( 0.5f,  0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f,  0.5f,  0.5f), m + RS * vec3(-0.5f,  0.5f,  0.5f), m + RS * vec3(-0.5f, -0.5f,  0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3( 0.5f, -0.5f, -0.5f), m + RS * vec3(-0.5f, -0.5f, -0.5f), m + RS * vec3(-0.5f,  0.5f, -0.5f), color);
        Triangles[t++] = CTriangle(m + RS * vec3(-0.5f,  0.5f, -0.5f), m + RS * vec3( 0.5f,  0.5f, -0.5f), m + RS * vec3( 0.5f, -0.5f, -0.5f), color);
    }

    Camera.Look(vec3(0.0f, 0.0f, 10.0f), vec3(0.0f, 0.0f, 0.0f), true);

    return true;
}

// ----------------------------------------------------------------------------------------------------------------------------

CMyRayTracer RayTracer;

// ----------------------------------------------------------------------------------------------------------------------------

CWnd::CWnd()
{
}

CWnd::~CWnd()
{
}

bool CWnd::Create(HINSTANCE hInstance, char *WindowName, int Width, int Height)
{
    WNDCLASSEX WndClassEx;

    memset(&WndClassEx, 0, sizeof(WNDCLASSEX));

    WndClassEx.cbSize = sizeof(WNDCLASSEX);
    WndClassEx.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    WndClassEx.lpfnWndProc = WndProc;
    WndClassEx.hInstance = hInstance;
    WndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClassEx.lpszClassName = "Win32CPURayTracerWindow";

    if(RegisterClassEx(&WndClassEx) == 0)
    {
        ErrorLog.Set("RegisterClassEx failed!");
        return false;
    }

    this->WindowName = WindowName;

    this->Width = Width;
    this->Height = Height;

    DWORD Style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

    if((hWnd = CreateWindowEx(WS_EX_APPWINDOW, WndClassEx.lpszClassName, WindowName, Style, 0, 0, Width, Height, NULL, NULL, hInstance, NULL)) == NULL)
    {
        ErrorLog.Set("CreateWindowEx failed!");
        return false;
    }

    return RayTracer.Init();
}

void CWnd::RePaint()
{
    x = y = 0;
    InvalidateRect(hWnd, NULL, FALSE);
}

void CWnd::Show(bool Maximized)
{
    RECT dRect, wRect, cRect;

    GetWindowRect(GetDesktopWindow(), &dRect);
    GetWindowRect(hWnd, &wRect);
    GetClientRect(hWnd, &cRect);

    wRect.right += Width - cRect.right;
    wRect.bottom += Height - cRect.bottom;

    wRect.right -= wRect.left;
    wRect.bottom -= wRect.top;

    wRect.left = dRect.right / 2 - wRect.right / 2;
    wRect.top = dRect.bottom / 2 - wRect.bottom / 2;

    MoveWindow(hWnd, wRect.left, wRect.top, wRect.right, wRect.bottom, FALSE);

    ShowWindow(hWnd, Maximized ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL);
}

void CWnd::MsgLoop()
{
    MSG Msg;

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}

void CWnd::Destroy()
{
    RayTracer.Destroy();

    DestroyWindow(hWnd);
}

void CWnd::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case VK_F1:
            RayTracer.SuperSampling = !RayTracer.SuperSampling;
            RePaint();
            break;
    }

    if(Camera.OnKeyDown(Key))
    {
        RePaint();
    }
}

void CWnd::OnMouseMove(int X, int Y)
{
    if(GetKeyState(VK_RBUTTON) & 0x80)
    {
        Camera.OnMouseMove(LastX - X, LastY - Y);

        LastX = X;
        LastY = Y;

        RePaint();
    }
}

void CWnd::OnMouseWheel(short zDelta)
{
    Camera.OnMouseWheel(zDelta);

    RePaint();
}

void CWnd::OnPaint()
{
    PAINTSTRUCT ps;

    HDC hDC = BeginPaint(hWnd, &ps);

    static DWORD Start;
    static bool RayTracing;

    if(x == 0 && y == 0)
    {
        RayTracer.ClearColorBuffer();

        Start = GetTickCount();

        RayTracing = true;
    }

    DWORD start = GetTickCount();

    while(GetTickCount() - start < 125 && y < Height)
    {
        int x16 = x + 16, y16 = y + 16;

        for(int yy = y; yy < y16; yy++)
        {
            if(yy < Height)
            {
                for(int xx = x; xx < x16; xx++)
                {
                    if(xx < Width)
                    {
                        RayTracer.RayTrace(xx, yy);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            else
            {
                break;
            }
        }

        x = x16;

        if(x >= Width)
        {
            x = 0;
            y = y16;
        }
    }

    RayTracer.SwapBuffers(hDC);

    if(RayTracing)
    {
        if(y >= Height)
        {
            RayTracing = false;
        }

        DWORD End = GetTickCount();

        CString text = WindowName;

        text.Append(" - %dx%d", Width, Height);
        text.Append(", Time: %.03f s", (float)(End - Start) * 0.001f);

        SetWindowText(hWnd, text);

        InvalidateRect(hWnd, NULL, FALSE);
    }

    EndPaint(hWnd, &ps);
}

void CWnd::OnRButtonDown(int X, int Y)
{
    LastX = X;
    LastY = Y;
}

void CWnd::OnSize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    RayTracer.Resize(Width, Height);

    RePaint();
}

// ----------------------------------------------------------------------------------------------------------------------------

CWnd Wnd;

// ----------------------------------------------------------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uiMsg)
    {
        case WM_CLOSE:
            PostQuitMessage(0);
            break;

        case WM_MOUSEMOVE:
            Wnd.OnMouseMove(LOWORD(lParam), HIWORD(lParam));
            break;

        case 0x020A: // WM_MOUSWHEEL
            Wnd.OnMouseWheel(HIWORD(wParam));
            break;

        case WM_KEYDOWN:
            Wnd.OnKeyDown((UINT)wParam);
            break;

        case WM_PAINT:
            Wnd.OnPaint();
            break;

        case WM_RBUTTONDOWN:
            Wnd.OnRButtonDown(LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_SIZE:
            Wnd.OnSize(LOWORD(lParam), HIWORD(lParam));
            break;

        default:
            return DefWindowProc(hWnd, uiMsg, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow)
{
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    if(Wnd.Create(hInstance, "CPU ray tracer 02 - Uniform grid", 800, 600))
    {
        Wnd.Show();
        Wnd.MsgLoop();
    }
    else
    {
        MessageBox(NULL, ErrorLog, "Error", MB_OK | MB_ICONERROR);
    }

    Wnd.Destroy();

    return 0;
}
Download
cpu_ray_tracer_02_uniform_grid.zip (Visual Studio 2005 Professional)
traversing_uniform_grid_visualization.zip (Visual Studio 2005 Professional)
© 2010 - 2016 Bc. Michal Belanec, michalbelanec@centrum.sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.