root/src/apps/glteapot/ObjectView.cpp
/*
 * Copyright 2008 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Alexandre Deckner
 *
 */

/*
 * Original Be Sample source modified to use a quaternion for the object's orientation
 */

/*
        Copyright 1999, Be Incorporated.   All Rights Reserved.
        This file may be used under the terms of the Be Sample Code License.
*/

#include "ObjectView.h"

#include <Application.h>
#include <Catalog.h>
#include <Cursor.h>
#include <InterfaceKit.h>
#include <FindDirectory.h>

#include "FPS.h"
#include "GLObject.h"
#include "ResScroll.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ObjectView"

float displayScale = 1.0;
float depthOfView = 30.0;
float zRatio = 10.0;

float white[3] = {1.0, 1.0, 1.0};
float dimWhite[3] = {0.25, 0.25, 0.25};
float black[3] = {0.0, 0.0, 0.0};
float foggy[3] = {0.4, 0.4, 0.4};
float blue[3] = {0.0, 0.0, 1.0};
float dimBlue[3] = {0.0, 0.0, 0.5};
float yellow[3] = {1.0, 1.0, 0.0};
float dimYellow[3] = {0.5, 0.5, 0.0};
float green[3] = {0.0, 1.0, 0.0};
float dimGreen[3] = {0.0, 0.5, 0.0};
float red[3] = {1.0, 0.0, 0.0};

float* bgColor = black;

const char *kNoResourceError = B_TRANSLATE("The Teapot 3D model was "
                                                                        "not found in application resources. "
                                                                        "Please repair the program installation.");

struct light {
        float *ambient;
        float *diffuse;
        float *specular;
};


light lights[] = {
        {NULL, NULL, NULL},
        {dimWhite, white, white},
        {dimWhite, yellow, yellow},
        {dimWhite, red, red},
        {dimWhite, blue, blue},
        {dimWhite, green, green}
};



long
signalEvent(sem_id event)
{
        int32 c;
        get_sem_count(event,&c);
        if (c < 0)
                release_sem_etc(event,-c,0);

        return 0;
}


long
setEvent(sem_id event)
{
        int32 c;
        get_sem_count(event,&c);
        if (c < 0)
          release_sem_etc(event,-c,0);

        return 0;
}


long
waitEvent(sem_id event)
{
        acquire_sem(event);

        int32 c;
        get_sem_count(event,&c);
        if (c > 0)
                acquire_sem_etc(event,c,0,0);

        return 0;
}


static int32
simonThread(void* cookie)
{
        ObjectView* objectView = reinterpret_cast<ObjectView*>(cookie);
        BScreen screen(objectView->Window());

        int noPause = 0;
        while (acquire_sem_etc(objectView->quittingSem, 1, B_TIMEOUT, 0) == B_NO_ERROR) {
                if (objectView->SpinIt()) {
                        objectView->DrawFrame(noPause);
                        release_sem(objectView->quittingSem);
                        noPause = 1;
                } else {
                        release_sem(objectView->quittingSem);
                        noPause = 0;
                        waitEvent(objectView->drawEvent);
                }
                if (objectView->LimitFps())
                        screen.WaitForRetrace();
        }
        return 0;
}


ObjectView::ObjectView(BRect rect, const char *name, ulong resizingMode,
        ulong options)
        : BGLView(rect, name, resizingMode, 0, options),
        fHistEntries(0),
        fOldestEntry(0),
        fFps(true),
        fLimitFps(true),
        fLastGouraud(true),
        fGouraud(true),
        fLastZbuf(true),
        fZbuf(true),
        fLastCulling(true),
        fCulling(true),
        fLastLighting(true),
        fLighting(true),
        fLastFilled(true),
        fFilled(true),
        fLastPersp(false),
        fPersp(false),
        fLastTextured(false),
        fTextured(false),
        fLastFog(false),
        fFog(false),
        fForceRedraw(false),
        fLastYXRatio(1),
        fYxRatio(1)
{
        fTrackingInfo.isTracking = false;
        fTrackingInfo.pickedObject = NULL;
        fTrackingInfo.buttons = 0;
        fTrackingInfo.lastX = 0.0f;
        fTrackingInfo.lastY = 0.0f;
        fTrackingInfo.lastDx = 0.0f;
        fTrackingInfo.lastDy = 0.0f;

        fLastObjectDistance = fObjectDistance = depthOfView / 8;
        quittingSem = create_sem(1, "quitting sem");
        drawEvent = create_sem(0, "draw event");

        TriangleObject *Tri = new TriangleObject(this);
        if (Tri->InitCheck() == B_OK) {
                fObjListLock.Lock();
                fObjects.AddItem(Tri);
                fObjListLock.Unlock();
        } else {
                BAlert *NoResourceAlert = new BAlert(B_TRANSLATE("Error"),
                                                kNoResourceError, B_TRANSLATE("OK"), NULL, NULL,
                                                B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
                NoResourceAlert->SetFlags(NoResourceAlert->Flags() | B_CLOSE_ON_ESCAPE);
                NoResourceAlert->Go();
                delete Tri;
        }
}


ObjectView::~ObjectView()
{
        delete_sem(quittingSem);
        delete_sem(drawEvent);
}


void
ObjectView::AttachedToWindow()
{
        float position[] = {0.0, 3.0, 3.0, 0.0};
        float position1[] = {-3.0, -3.0, 3.0, 0.0};
        float position2[] = {3.0, 0.0, 0.0, 0.0};
        float local_view[] = {0.0, 0.0};
//      float ambient[] = {0.1745, 0.03175, 0.03175};
//      float diffuse[] = {0.61424, 0.10136, 0.10136};
//      float specular[] = {0.727811, 0.626959, 0.626959};
//      rgb_color black = {0, 0, 0, 255};
        BRect bounds = Bounds();

        BGLView::AttachedToWindow();
        Window()->SetPulseRate(100000);

        LockGL();

        glEnable(GL_DITHER);
        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);
        glDepthFunc(GL_LESS);

        glShadeModel(GL_SMOOTH);

        glLightfv(GL_LIGHT0, GL_POSITION, position);
        glLightfv(GL_LIGHT0 + 1, GL_POSITION, position1);
        glLightfv(GL_LIGHT0 + 2, GL_POSITION, position2);
        glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

        glEnable(GL_LIGHT0);
        glLightfv(GL_LIGHT0, GL_SPECULAR, lights[lightWhite].specular);
        glLightfv(GL_LIGHT0, GL_DIFFUSE,lights[lightWhite].diffuse);
        glLightfv(GL_LIGHT0, GL_AMBIENT,lights[lightWhite].ambient);
        glEnable(GL_LIGHT1);
        glLightfv(GL_LIGHT1, GL_SPECULAR, lights[lightBlue].specular);
        glLightfv(GL_LIGHT1, GL_DIFFUSE,lights[lightBlue].diffuse);
        glLightfv(GL_LIGHT1, GL_AMBIENT,lights[lightBlue].ambient);

        glFrontFace(GL_CW);
        glEnable(GL_LIGHTING);
        glEnable(GL_AUTO_NORMAL);
        glEnable(GL_NORMALIZE);

        glMaterialf(GL_FRONT, GL_SHININESS, 0.6 * 128.0);

        glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
        glColor3f(1.0, 1.0, 1.0);

        glViewport(0, 0, (GLint)bounds.IntegerWidth() + 1,
                                (GLint)bounds.IntegerHeight() + 1);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();

        float scale = displayScale;
        glOrtho(-scale, scale, -scale, scale, -scale * depthOfView,
                        scale * depthOfView);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        UnlockGL();

        fDrawThread = spawn_thread(simonThread, "Simon", B_NORMAL_PRIORITY, this);
        resume_thread(fDrawThread);
        fForceRedraw = true;
        setEvent(drawEvent);
}


void
ObjectView::DetachedFromWindow()
{
        BGLView::DetachedFromWindow();

        status_t dummy;
        long locks = 0;

        while (Window()->IsLocked()) {
                locks++;
                Window()->Unlock();
        }

        acquire_sem(quittingSem);
        release_sem(drawEvent);
        wait_for_thread(fDrawThread, &dummy);
        release_sem(quittingSem);

        while (locks--)
                Window()->Lock();
}


void
ObjectView::Pulse()
{
        Window()->Lock();
        BRect parentBounds = Parent()->Bounds();
        BRect bounds = Bounds();
        parentBounds.OffsetTo(0, 0);
        bounds.OffsetTo(0, 0);
        if (bounds != parentBounds) {
                ResizeTo(parentBounds.right - parentBounds.left,
                                 parentBounds.bottom - parentBounds.top);
        }
        Window()->Unlock();
}


void
ObjectView::MessageReceived(BMessage* msg)
{
        BMenuItem* item = NULL;
        bool toggleItem = false;

        switch (msg->what) {
                case kMsgFPS:
                        fFps = (fFps) ? false : true;
                        msg->FindPointer("source", reinterpret_cast<void**>(&item));
                        item->SetMarked(fFps);
                        fForceRedraw = true;
                        setEvent(drawEvent);
                        break;
                case kMsgAddModel: 
                {
                        TriangleObject *Tri = new TriangleObject(this);
                        if (Tri->InitCheck() == B_OK) {
                                fObjListLock.Lock();
                                fObjects.AddItem(Tri);
                                fObjListLock.Unlock();
                        } else {
                                BAlert *NoResourceAlert = new BAlert(B_TRANSLATE("Error"),
                                                kNoResourceError, B_TRANSLATE("OK"), NULL, NULL,
                                                B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
                                NoResourceAlert->SetFlags(NoResourceAlert->Flags() | B_CLOSE_ON_ESCAPE);
                                NoResourceAlert->Go();
                                delete Tri;
                        }
                        setEvent(drawEvent);
                        break;
                }
                case kMsgLights:
                {
                        msg->FindPointer("source", reinterpret_cast<void**>(&item));
                        long lightNum = msg->FindInt32("num");
                        long color = msg->FindInt32("color");
                        BMenu *menu = item->Menu();
                        long index = menu->IndexOf(item);
                        menu->ItemAt(index)->SetMarked(true);
                        for (int i = 0; i < menu->CountItems(); i++) {
                                if (i != index)
                                        menu->ItemAt(i)->SetMarked(false);
                        }

                        LockGL();
                        if (color != lightNone) {
                                glEnable(GL_LIGHT0 + lightNum - 1);
                                glLightfv(GL_LIGHT0 + lightNum - 1, GL_SPECULAR,
                                        lights[color].specular);
                                glLightfv(GL_LIGHT0 + lightNum - 1, GL_DIFFUSE,
                                        lights[color].diffuse);
                                glLightfv(GL_LIGHT0 + lightNum - 1, GL_AMBIENT,
                                        lights[color].ambient);
                        } else {
                                glDisable(GL_LIGHT0 + lightNum - 1);
                        }
                        UnlockGL();
                        fForceRedraw = true;
                        setEvent(drawEvent);
                        break;
                }
                case kMsgGouraud:
                        fGouraud = !fGouraud;
                        toggleItem = true;
                        break;
                case kMsgZBuffer:
                        fZbuf = !fZbuf;
                        toggleItem = true;
                        break;
                case kMsgCulling:
                        fCulling = !fCulling;
                        toggleItem = true;
                        break;
                case kMsgLighting:
                        fLighting = !fLighting;
                        toggleItem = true;
                        break;
                case kMsgFilled:
                        fFilled = !fFilled;
                        toggleItem = true;
                        break;
                case kMsgPerspective:
                        fPersp = !fPersp;
                        toggleItem = true;
                        break;
                case kMsgFog:
                        fFog = !fFog;
                        toggleItem = true;
                        break;
                case kMsgLimitFps:
                        fLimitFps = !fLimitFps;
                        toggleItem = true;
                        break;
        }

        if (toggleItem && msg->FindPointer("source", reinterpret_cast<void**>(&item)) == B_OK){
                item->SetMarked(!item->IsMarked());
                setEvent(drawEvent);
        }

        BGLView::MessageReceived(msg);
}


int
ObjectView::ObjectAtPoint(const BPoint &point)
{
        LockGL();
        glShadeModel(GL_FLAT);
        glDisable(GL_LIGHTING);
        glDisable(GL_FOG);
        glClearColor(black[0], black[1], black[2], 1.0);
        glClear(GL_COLOR_BUFFER_BIT | (fZbuf ? GL_DEPTH_BUFFER_BIT : 0));

        float idColor[3];
        idColor[1] = idColor[2] = 0;
        for (int i = 0; i < fObjects.CountItems(); i++) {
                // to take into account 16 bits colorspaces,
                // only use the 5 highest bits of the red channel
                idColor[0] = (255 - (i << 3)) / 255.0;
                reinterpret_cast<GLObject*>(fObjects.ItemAt(i))->Draw(true, idColor);
        }
        glReadBuffer(GL_BACK);
        uchar pixel[256];
        glReadPixels((GLint)point.x, (GLint)(Bounds().bottom - point.y), 1, 1,
                GL_RGB, GL_UNSIGNED_BYTE, pixel);
        int objNum = pixel[0];
        objNum = (255 - objNum) >> 3;

        EnforceState();
        UnlockGL();

        return objNum;
}


void
ObjectView::MouseDown(BPoint point)
{
        GLObject* object = NULL;

        BMessage *msg = Window()->CurrentMessage();
        uint32 buttons = msg->FindInt32("buttons");
        object = reinterpret_cast<GLObject*>(fObjects.ItemAt(ObjectAtPoint(point)));

        if (object != NULL){
                if (buttons == B_PRIMARY_MOUSE_BUTTON || buttons == B_SECONDARY_MOUSE_BUTTON) {
                        fTrackingInfo.pickedObject = object;
                        fTrackingInfo.buttons = buttons;
                        fTrackingInfo.isTracking = true;
                        fTrackingInfo.lastX = point.x;
                        fTrackingInfo.lastY = point.y;
                        fTrackingInfo.lastDx = 0.0f;
                        fTrackingInfo.lastDy = 0.0f;
                        fTrackingInfo.pickedObject->Spin(0.0f, 0.0f);


                        SetMouseEventMask(B_POINTER_EVENTS,
                                                B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);

                        BCursor grabbingCursor(B_CURSOR_ID_GRABBING);
                        SetViewCursor(&grabbingCursor);
                } else {
                        ConvertToScreen(&point);
                        object->MenuInvoked(point);
                }
        }
}


void
ObjectView::MouseUp(BPoint point)
{
        if (fTrackingInfo.isTracking) {

                //spin the teapot on release, TODO: use a marching sum and divide by time
                if (fTrackingInfo.buttons == B_PRIMARY_MOUSE_BUTTON
                        && fTrackingInfo.pickedObject != NULL
                        && (fabs(fTrackingInfo.lastDx) > 1.0f
                                || fabs(fTrackingInfo.lastDy) > 1.0f) ) {

                        fTrackingInfo.pickedObject->Spin(0.5f * fTrackingInfo.lastDy, 0.5f * fTrackingInfo.lastDx);

                        setEvent(drawEvent);
                }

                //stop tracking
                fTrackingInfo.isTracking = false;
                fTrackingInfo.buttons = 0;
                fTrackingInfo.pickedObject = NULL;
                fTrackingInfo.lastX = 0.0f;
                fTrackingInfo.lastY = 0.0f;
                fTrackingInfo.lastDx = 0.0f;
                fTrackingInfo.lastDy = 0.0f;

                BCursor grabCursor(B_CURSOR_ID_GRAB);
                SetViewCursor(&grabCursor);
        }
}


void
ObjectView::MouseMoved(BPoint point, uint32 transit, const BMessage *msg)
{
        if (fTrackingInfo.isTracking && fTrackingInfo.pickedObject != NULL) {

                float dx = point.x - fTrackingInfo.lastX;
                float dy = point.y - fTrackingInfo.lastY;
                fTrackingInfo.lastX = point.x;
                fTrackingInfo.lastY = point.y;

                if (fTrackingInfo.buttons == B_PRIMARY_MOUSE_BUTTON) {

                        fTrackingInfo.pickedObject->Spin(0.0f, 0.0f);
                        fTrackingInfo.pickedObject->RotateWorldSpace(dx,dy);
                        fTrackingInfo.lastDx = dx;
                        fTrackingInfo.lastDy = dy;

                        setEvent(drawEvent);

                } else if (fTrackingInfo.buttons == B_SECONDARY_MOUSE_BUTTON) {

                        float xinc = (dx * 2 * displayScale / Bounds().Width());
                        float yinc = (-dy * 2 * displayScale / Bounds().Height());
                        float zinc = 0;

                        if (fPersp) {
                                zinc = yinc * (fTrackingInfo.pickedObject->z / displayScale);
                                xinc *= -(fTrackingInfo.pickedObject->z * 4 / zRatio);
                                yinc *= -(fTrackingInfo.pickedObject->z * 4 / zRatio);
                        }

                        fTrackingInfo.pickedObject->x += xinc;
                        if (modifiers() & B_SHIFT_KEY)
                                fTrackingInfo.pickedObject->z += zinc;
                        else
                                fTrackingInfo.pickedObject->y += yinc;

                        fForceRedraw = true;
                        setEvent(drawEvent);
                }
        } else {
                void* object = fObjects.ItemAt(ObjectAtPoint(point));
                BCursor cursor(object != NULL
                        ? B_CURSOR_ID_GRAB : B_CURSOR_ID_SYSTEM_DEFAULT);
                SetViewCursor(&cursor);
        }
}


void
ObjectView::FrameResized(float width, float height)
{
        BGLView::FrameResized(width, height);

        LockGL();

        width = Bounds().Width();
        height = Bounds().Height();
        fYxRatio = height / width;
        glViewport(0, 0, (GLint)width + 1, (GLint)height + 1);

        // To prevent weird buffer contents
        glClear(GL_COLOR_BUFFER_BIT);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        float scale = displayScale;

        if (fPersp) {
                gluPerspective(60, 1.0 / fYxRatio, 0.15, 120);
        } else {
                if (fYxRatio < 1) {
                        glOrtho(-scale / fYxRatio, scale / fYxRatio, -scale, scale, -1.0,
                                depthOfView * 4);
                } else {
                        glOrtho(-scale, scale, -scale * fYxRatio, scale * fYxRatio, -1.0,
                                depthOfView * 4);
                }
        }

        fLastYXRatio = fYxRatio;

        glMatrixMode(GL_MODELVIEW);

        UnlockGL();

        fForceRedraw = true;
        setEvent(drawEvent);
}


bool
ObjectView::RepositionView()
{
        if (!(fPersp != fLastPersp) &&
                !(fLastObjectDistance != fObjectDistance) &&
                !(fLastYXRatio != fYxRatio)) {
                return false;
        }

        LockGL();

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        float scale = displayScale;

        if (fPersp) {
                gluPerspective(60, 1.0 / fYxRatio, 0.15, 120);
        } else {
                if (fYxRatio < 1) {
                        glOrtho(-scale / fYxRatio, scale / fYxRatio, -scale, scale, -1.0,
                                depthOfView * 4);
                } else {
                        glOrtho(-scale, scale, -scale * fYxRatio, scale * fYxRatio, -1.0,
                                depthOfView * 4);
                }
        }

        glMatrixMode(GL_MODELVIEW);

        UnlockGL();

        fLastObjectDistance = fObjectDistance;
        fLastPersp = fPersp;
        fLastYXRatio = fYxRatio;
        return true;
}


void
ObjectView::EnforceState()
{
        glShadeModel(fGouraud ? GL_SMOOTH : GL_FLAT);

        if (fZbuf)
                glEnable(GL_DEPTH_TEST);
        else
                glDisable(GL_DEPTH_TEST);

        if (fCulling)
                glEnable(GL_CULL_FACE);
        else
                glDisable(GL_CULL_FACE);

        if (fLighting)
                glEnable(GL_LIGHTING);
        else
                glDisable(GL_LIGHTING);

        if (fFilled)
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        else
                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        if (fFog) {
                glFogf(GL_FOG_START, 10.0);
                glFogf(GL_FOG_DENSITY, 0.2);
                glFogf(GL_FOG_END, depthOfView);
                glFogfv(GL_FOG_COLOR, foggy);
                glEnable(GL_FOG);
                bgColor = foggy;
                glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
        } else {
                glDisable(GL_FOG);
                bgColor = black;
                glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
        }
}


bool
ObjectView::SpinIt()
{
        bool changed = false;

        if (fGouraud != fLastGouraud) {
                LockGL();
                glShadeModel(fGouraud ? GL_SMOOTH : GL_FLAT);
                UnlockGL();
                fLastGouraud = fGouraud;
                changed = true;
        }

        if (fZbuf != fLastZbuf) {
                LockGL();
                if (fZbuf)
                        glEnable(GL_DEPTH_TEST);
                else
                        glDisable(GL_DEPTH_TEST);
                UnlockGL();
                fLastZbuf = fZbuf;
                changed = true;
        }

        if (fCulling != fLastCulling) {
                LockGL();
                if (fCulling)
                        glEnable(GL_CULL_FACE);
                else
                        glDisable(GL_CULL_FACE);
                UnlockGL();
                fLastCulling = fCulling;
                changed = true;
        }

        if (fLighting != fLastLighting) {
                LockGL();
                if (fLighting)
                        glEnable(GL_LIGHTING);
                else
                        glDisable(GL_LIGHTING);
                UnlockGL();
                fLastLighting = fLighting;
                changed = true;
        }

        if (fFilled != fLastFilled) {
                LockGL();
                if (fFilled) {
                        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                } else {
                        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                }
                UnlockGL();
                fLastFilled = fFilled;
                changed = true;
        }

        if (fFog != fLastFog) {
                if (fFog) {
                        glFogf(GL_FOG_START, 1.0);
                        glFogf(GL_FOG_DENSITY, 0.2);
                        glFogf(GL_FOG_END, depthOfView);
                        glFogfv(GL_FOG_COLOR, foggy);
                        glEnable(GL_FOG);
                        bgColor = foggy;
                        glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
                } else {
                        glDisable(GL_FOG);
                        bgColor = black;
                        glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
                }
                fLastFog = fFog;
                changed = true;
        }

        changed = changed || RepositionView();
        changed = changed || fForceRedraw;
        fForceRedraw = false;

        for (int i = 0; i < fObjects.CountItems(); i++) {
                bool hack = reinterpret_cast<GLObject*>(fObjects.ItemAt(i))->SpinIt();
                changed = changed || hack;
        }

        return changed;
}


void
ObjectView::DrawFrame(bool noPause)
{
        LockGL();
        glClear(GL_COLOR_BUFFER_BIT | (fZbuf ? GL_DEPTH_BUFFER_BIT : 0));

        fObjListLock.Lock();
        for (int i = 0; i < fObjects.CountItems(); i++) {
          GLObject *object = reinterpret_cast<GLObject*>(fObjects.ItemAt(i));
                if (object->Solidity() == 0)
                        object->Draw(false, NULL);
        }
        EnforceState();
        for (int i = 0; i < fObjects.CountItems(); i++) {
                GLObject *object = reinterpret_cast<GLObject*>(fObjects.ItemAt(i));
                if (object->Solidity() != 0)
                        object->Draw(false, NULL);
        }
        fObjListLock.Unlock();

        glDisable(GL_BLEND);
        glDepthMask(GL_TRUE);

        if (noPause) {
                uint64 now = system_time();
                float fps = 1.0 / ((now - fLastFrame) / 1000000.0);
                fLastFrame = now;
                int entry;
                if (fHistEntries < HISTSIZE) {
                        entry = (fOldestEntry + fHistEntries) % HISTSIZE;
                        fHistEntries++;
                } else {
                        entry = fOldestEntry;
                        fOldestEntry = (fOldestEntry + 1) % HISTSIZE;
                }

                fFpsHistory[entry] = fps;

                if (fHistEntries > 5) {
                        fps = 0;
                        for (int i = 0; i < fHistEntries; i++)
                                fps += fFpsHistory[(fOldestEntry + i) % HISTSIZE];

                        fps /= fHistEntries;

                        if (fFps) {
                                glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING_BIT);
                                glPushMatrix();
                                glLoadIdentity();
                                glTranslatef(-0.9, -0.9, 0);
                                glScalef(0.10, 0.10, 0.10);
                                glDisable(GL_LIGHTING);
                                glDisable(GL_DEPTH_TEST);
                                glDisable(GL_BLEND);
                                glColor3f(1.0, 1.0, 0);
                                glMatrixMode(GL_PROJECTION);
                                glPushMatrix();
                                glLoadIdentity();
                                glMatrixMode(GL_MODELVIEW);

                                FPS::drawCounter(fps);

                                glMatrixMode(GL_PROJECTION);
                                glPopMatrix();
                                glMatrixMode(GL_MODELVIEW);
                                glPopMatrix();
                                glPopAttrib();
                        }
                }
        } else {
                fHistEntries = 0;
                fOldestEntry = 0;
        }
        SwapBuffers();
        UnlockGL();
}