//
//  Screens.cpp
//  MetarealEngine
//
//  Created by David Van Brink on 12/24/14.
//  Copyright (c) 2014 David Van Brink. All rights reserved.
//

#include "Screens.h"
#include "ScreenG1.h"
#include "ScreenG2.h"
#include "ScreenG3.h"
#include "ScreenG4.h"
#include "MeGl.h"
#include <math.h>
#include <set>
#include <stdlib.h>
#include "Me.h"
#include "MeMathUtils.h"




// --------------------------------
// UTILS


float tau = 2.0 * 3.1415926535897932384623;

static void pushFloats(std::vector<float> &floats, MeVec3 &p)
{
    floats.push_back(p[0]);
    floats.push_back(p[1]);
    floats.push_back(p[2]);
}

static MeVec3 randomColor(MeVec3 color, float colorVariance)
{
    MeVec3 result = color + MeVec3(rr(-colorVariance, +colorVariance), rr(-colorVariance, +colorVariance), rr(-colorVariance, +colorVariance));
    return result;
}

MeGeometry *buildTube(int sides,MeVec3 p0, MeVec3 p1, std::vector<float> &positionRadiusList, MeVec3 color, float colorVariance)
{
    MeVec3 dir = (p1 - p0).normalize();
    // two perpendiculars, to let us trace rings nicely.
    MeVec3 per0 = dir.cross(MeVec3(dir[1], dir[2], dir[0])).normalize();
    MeVec3 per1 = per0.cross(dir).normalize();
    
    std::vector<MeVec3> lastRing;
    std::vector<MeVec3> thisRing;
    
    std::vector<MeVec3> thisRingColor;
    std::vector<MeVec3> lastRingColor;
    
    std::vector<float> posTriangles;
    std::vector<float> colTriangles;
    std::vector<float> normals;
    std::vector<float> colors;
    MeVec3 thisRingCenter;
    for(int i = 0; i < positionRadiusList.size(); i += 2)
    {
        lastRing = thisRing;
        float position = positionRadiusList[i];
        float radius = positionRadiusList[i + 1];
     
        thisRingCenter = p0 + dir * position;
        thisRing.clear();
        for(int j = 0; j <= sides; j++)
        {
            float theta = j * tau / sides;
            MeVec3 p = thisRingCenter + (per0 * sin(theta) + per1 * cos(theta)) * radius;
            thisRing.push_back(p);
        }
        
        lastRingColor = thisRingColor;
        thisRingColor.clear();
        for(int j = 0; j <= sides; j++)
            thisRingColor.push_back(randomColor(color, colorVariance));
        
        if(i == 0)
            continue; // need 2 rings of points for first ring of triangles.
        
        
        // add the triangles
        for(int j = 0; j < sides; j++)
        {
            MeVec3 lastRing0 = lastRing[j];
            MeVec3 thisRing0 = thisRing[j];
            MeVec3 thisRing1 = thisRing[j + 1];
            MeVec3 lastRing1 = lastRing[j + 1];

            pushFloats(posTriangles, lastRing0);
            pushFloats(posTriangles, thisRing0);
            pushFloats(posTriangles, thisRing1);
            pushFloats(posTriangles, lastRing0);
            pushFloats(posTriangles, thisRing1);
            pushFloats(posTriangles, lastRing1);
            
            // colors
            MeVec3 lastRingColor0 = lastRingColor[j];
            MeVec3 thisRingColor0 = thisRingColor[j];
            MeVec3 thisRingColor1 = thisRingColor[j + 1];
            MeVec3 lastRingColor1 = lastRingColor[j + 1];
            pushFloats(colors, lastRingColor0);
            pushFloats(colors, thisRingColor0);
            pushFloats(colors, thisRingColor1);
            pushFloats(colors, lastRingColor0);
            pushFloats(colors, thisRingColor1);
            pushFloats(colors, lastRingColor1);

            
            // the normals are smooth around the ring, and hard at each ring boundary.
            MeVec3 upVec0 = thisRing0 - lastRing0;
            MeVec3 upVec1 = thisRing1 - lastRing1;
            MeVec3 ringRadius0 = thisRing0 - thisRingCenter;
            MeVec3 ringRadius1 = thisRing1 - thisRingCenter;
            
            MeVec3 thisRingNormal0 = upVec0.cross(upVec0.cross(ringRadius0)).normalize();
            MeVec3 thisRingNormal1 = upVec1.cross(upVec1.cross(ringRadius1)).normalize();
            MeVec3 lastRingNormal0 = upVec0.cross(upVec0.cross(ringRadius0)).normalize();
            MeVec3 lastRingNormal1 = upVec1.cross(upVec1.cross(ringRadius1)).normalize();
            
            pushFloats(normals, lastRingNormal0);
            pushFloats(normals, thisRingNormal0);
            pushFloats(normals, thisRingNormal1);
            
            pushFloats(normals, lastRingNormal0);
            pushFloats(normals, thisRingNormal1);
            pushFloats(normals, lastRingNormal1);
        }
    }
    
    MeGeometry *g = new MeGeometry();
    g->setTriangleCount((int)posTriangles.size() / 9);
    g->addAttribute("pos", MAT_FLOAT, 3, (void *)(posTriangles.data()));
    g->addAttribute("nor", MAT_FLOAT, 3, (void *)(normals.data()));
    g->addAttribute("col", MAT_FLOAT, 3, (void *)(colors.data()));
    
    //g->printVertexes();
    return g;
}

//static MeVec3 vec3FromFloatPtr(float **ww)
//{
//    float *w = *ww;
//    float f0 = *w++;
//    float f1 = *w++;
//    float f2 = *w++;
//    *ww = w;
//    return MeVec3(f0, f1, f2);
//}
//static void createNormals(MeGeometry *g)
//{
//    std::vector<float> nor;
//    float *pos = (float *)g->getAttributeData("pos");
//    int tc = g->getTriangleCount();
//    for(int i = 0; i < tc; i++)
//    {
//        MeVec3 p0 = vec3FromFloatPtr(&pos);
//        MeVec3 p1 = vec3FromFloatPtr(&pos);
//        MeVec3 p2 = vec3FromFloatPtr(&pos);
//        
//        MeVec3 n = (p1 - p0).cross(p2 - p0).normalize();
//        pushFloats(nor, n);
//        pushFloats(nor, n);
//        pushFloats(nor, n);
//    }
//    g->addAttribute("nor", MAT_FLOAT, 3, nor.data());
//}
//
//static void createColors(MeGeometry *g, MeVec3 co, float variation)
//{
//    std::vector<float> col;
//    int tc = g->getTriangleCount();
//    for(int i = 0; i < tc * 3; i++)
//    {
//        col.push_back(co[0] + rr(-variation,+variation));
//        col.push_back(co[1] + rr(-variation,+variation));
//        col.push_back(co[2] + rr(-variation,+variation));
//    }
//    g->addAttribute("col", MAT_FLOAT, 3, col.data());
//}

/// Add a Part which draws this texture flat.
/// TODO this is a good shared utility, quit recopying it.
MeGaud *buildTextureDraw(MeITexture *texture, EMeBlendMode blendMode, float left, float bottom, float right, float top, const char *fsh = "texture2d.fsh", MeIMaterial **materialOut = NULL)
{
    MeGeometry *g = makeRectXyGeometry(left, bottom, right, top);
    
    MeIMaterial *m = loadMaterialGl("texture2d.vsh", fsh);
    
    m->setBlendMode(blendMode);
    m->setUniformSampler("texture1", texture);
    
    
    MePart *p = new MePart(m, g);
    MeGaud *gaud = new MeGaud();
    gaud->addPart(p);
    
    if(materialOut)
        *materialOut = m;
    return gaud;
}


// build a gaud and its material. the material uses a uniform named "texture1" for its source sampler.
void buildCopyingGaud(MeIMaterial **materialOut, MeGaud **gaudOut, const char *fsh = "texture2d.fsh")
{
    MeGeometry *g = makeRectXyGeometry(-1.0, -1.0, 1.0, 1.0);
    
    MeIMaterial *m = loadMaterialGl("texture2d.vsh", fsh);
    m->setBlendMode(MBM_REPLACE_2D);
    
    MePart *p = new MePart(m, g);
    MeGaud *gaud = new MeGaud();
    gaud->addPart(p);
    
    *materialOut = m;
    *gaudOut = gaud;
}

/// Simplistic copying. Consider if we need a copy-with-depth, hi-fi copy.
void xaddCopyingStep(MeRenderWorld *rw, std::string sourceFrameBuffer, std::string destFrameBuffer, const char *fsh, bool doDepth)
{
    MeIMaterial *material;
    MeGaud *gaud;
    buildCopyingGaud(&material, &gaud, fsh);
    rw->addGaud(gaud);
    if(doDepth)
        rw->addStepSetTextureDepth(sourceFrameBuffer, material, "texture1");
    else
        rw->addStepSetTextureColor(sourceFrameBuffer, material, "texture1");
    rw->addStepDraw(material, destFrameBuffer);
}

class TerminalHam : public MeHam
{
    MeIMaterial *consoleMaterial;
    MeITexture *termTexture;
    int width = 80;
    int height = 24;
    int cursorX = 0;
    int cursorY = 0;
    bool updated = false;
public:
    TerminalHam(MeRenderWorld *renderWorld, int widthN, int heightN)
    {
        this->width = widthN;
        this->height = heightN;
        this->consoleMaterial = loadMaterialGl("texture2D.vsh", "textConsole.fsh");
        
        MeGeometry *rect = makeRectXyGeometry();
        MePart *p = new MePart(this->consoleMaterial, rect);
        MeGaud *g = new MeGaud();
        g->addPart(p);
        
        
        char term[this->width * this->height];
        for(int i = 0; i < this->width * this->height; i++)
            term[i] = 0;
        this->termTexture = this->consoleMaterial->newTexture(MTK_R8, this->width, this->height, term);
        termTexture->setLinearInterpolation(false);
        this->consoleMaterial->setUniformSampler("termTexture", termTexture);
        this->consoleMaterial->setUniformVec2("termSize", this->width, this->height);
        
        MeITexture *fontTexture = this->consoleMaterial->newTexture("font1.png");
        this->consoleMaterial->setUniformSampler("fontTexture", fontTexture);
        
        this->consoleMaterial->setUniformSampler("texture1", fontTexture);
        renderWorld->addGaud(g);
        renderWorld->addStepDraw(this->consoleMaterial, "");
    }
    
    void updateFrame()
    {
        if(this->updated)
        {
            this->termTexture->update();
            this->updated = false;
        }
    }
    
    void setChar(int x, int y, int ch)
    {
        x = pinRangeI(x, 0, this->width);
        y = pinRangeI(y, 0, this->height);
        
        char *term = (char *)this->termTexture->getData();
        term[y * this->width + x] = ch;
        this->updated = true;
    }
    
    void eraseCursor()
    {
        this->setChar(this->cursorX, this->cursorY, 0);
    }
    
    void drawCursor()
    {
        this->setChar(this->cursorX, this->cursorY, '_');
    }
    
    void setCursorPosition(int x, int y)
    {
        this->eraseCursor();
        this->cursorX = x;
        this->cursorY = y;
        this->drawCursor();
    }
    
    void clear()
    {
        char *term = (char *)this->termTexture->getData();
        for(int i = 0; i < this->width * this->height; i++)
            term[i] = 0;
        this->setCursorPosition(0,0);
        this->updateFrame();
    }
    
    void scroll()
    {
        char *term = (char *)this->termTexture->getData();
        
        memcpy(term, term + width, this->width * (this->height - 1));
        for(int i = 0; i < this->width; i++)
            term[this->width * (this->height - 1) + i] = 0;
        this->updated = true;
    }
    
    void cr()
    {
        if(this->cursorY < this->height - 1)
        {
            this->setCursorPosition(0, this->cursorY + 1);
        }
        else
        {
            this->eraseCursor();
            this->scroll();
            this->cursorX = 0;
            this->cursorY = this->height - 1;
            this->drawCursor();
        }
    }
    
    void ch(char c)
    {
        if(c == '\n' || c == '\r')
        {
            this->cr();
            return;
        }
        if(c == 8)
        {
            if(this->cursorX > 0)
                this->setCursorPosition(this->cursorX - 1, this->cursorY);
            return;
        }
        
        this->setChar(this->cursorX, this->cursorY, c);
        this->cursorX++;
        if(this->cursorX >= this->width)
        {
            this->cursorX = 0;
            this->cursorY++;
            if(this->cursorY >= this->height - 1)
            {
                this->cursorY--;
                this->scroll();
            }
        }
        this->drawCursor();
    }
    
    void printf(const char *fmt, ...)
    {
        va_list v;
        va_start(v,fmt);
        char b[2222];
        vsprintf(b, fmt, v);
        int len = (int)strlen(b);
        for(int i = 0; i < len; i++)
            this->ch(b[i]);
    }
    
    void setFront(bool front)
    {
        this->setZ(front ? -0.999999 : +0.999999);
    }

    void setZ(float z)
    {
        this->consoleMaterial->setUniformFloat("z", z);
    }
};




enum
{
    KEY_rightArrow = 0x4000004f,
    KEY_leftArrow = 0x40000050,
    KEY_downArrow = 0x40000051,
    KEY_upArrow = 0x40000052,
};


Screen::Screen()
{
    this->handyMaterial = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
    this->buffer0 = this->handyMaterial->newFrameBuffer(buffer0Dim, buffer0Dim);
}

void Screen::begin()
{
    
}

void Screen::setSize(int width, int height)
{
    if(this->renderWorld)
    {
        this->renderWorld->resize(width, height);
        
        float yRatio = (float)height / (float)width;
        this->renderWorld->setUniformFloat("yRatio", yRatio);
    }

}

void Screen::keyDown(int k)
{
    switch(k)
    {
        case 'i': this->cameraMoving.v[2] = -v; break;
        case 'k': this->cameraMoving.v[2] = +v; break;
        case 'm': this->cameraMoving.v[1] = -v; break;
        case 'u': this->cameraMoving.v[1] = +v; break;
        case 'j': this->cameraMoving.v[0] = -v; break;
        case 'l': this->cameraMoving.v[0] = +v; break;
            
        case 'e': this->cameraTurning.v[0] = +t; break;
        case 'd': this->cameraTurning.v[0] = -t; break;
        case 's': this->cameraTurning.v[1] = +t; break;
        case 'f': this->cameraTurning.v[1] = -t; break;
        case 'w': this->cameraTurning.v[2] = +t; break;
        case 'r': this->cameraTurning.v[2] = -t; break;

        case KEY_upArrow: this->eyePosChange = -0.005; break;
        case KEY_downArrow: this->eyePosChange = +0.005; break;
            
        case '\\':
        {
            this->draw(this->buffer0);
            MeTextureGl *t = (MeTextureGl *)this->buffer0->getDepthTexture();
            int n = t->getGlName();
            
            float pixels[buffer0Dim * buffer0Dim * 4];
            glBindTexture(GL_TEXTURE_2D, n);
            glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, pixels);
            SHOW_GL_ERROR;
            
            int bucketCount = 10;
            int countTooLow = 0;
            int countTooHigh = 0;
            int pixelCounts[bucketCount];
            for(int i = 0; i < bucketCount; i++)
                pixelCounts[i] = 0;
            
            for(int i = 0; i < buffer0Dim * buffer0Dim; i++)
            {
                float pixel = pixels[i];
                int bucket = floor(pixel * bucketCount);
                if(bucket < 0)
                    countTooLow++;
                else if(bucket < bucketCount)
                    pixelCounts[bucket] ++;
                else
                    countTooHigh++;
            }
            
            printf("too low: %d, too high: %d\n", countTooLow, countTooHigh);
            for(int i = 0; i < bucketCount; i++)
            {
                printf("bucket %d: %d\n", i, pixelCounts[i]);
            }
            break;
        }
            
        case 'z':
        {
            this->pipelineEnabled ^= true;
            meLogInfo("pipeline: %s\n", this->pipelineEnabled ? "yes" : "no");
            break;
        }
            
        case 'x':
        {
            meLogInfo("frame draw time: %gms", this->renderWorld->getRenderReport()->drawTime * 1000.0);
            break;
        }

        default:
            this->keyTook = false;
            
    }
}

void Screen::partClicked(int partId)
{
    meLogInfo("part clicked: %d", partId);
}

void Screen::keyUp(int k)
{
    switch(k)
    {
        case 'i': this->cameraMoving.v[2] = 0; break;
        case 'k': this->cameraMoving.v[2] = 0; break;
        case 'm': this->cameraMoving.v[1] = 0; break;
        case 'u': this->cameraMoving.v[1] = 0; break;
        case 'j': this->cameraMoving.v[0] = 0; break;
        case 'l': this->cameraMoving.v[0] = 0; break;
            
        case 'e': this->cameraTurning.v[0] = 0; break;
        case 'd': this->cameraTurning.v[0] = 0; break;
        case 's': this->cameraTurning.v[1] = 0; break;
        case 'f': this->cameraTurning.v[1] = 0; break;
        case 'w': this->cameraTurning.v[2] = 0; break;
        case 'r': this->cameraTurning.v[2] = 0; break;

        case KEY_upArrow: this->eyePosChange = 0; break;
        case KEY_downArrow: this->eyePosChange = 0; break;
    }
}


void Screen::tick()
{
    this->ticks++;
    this->time += 1.0 / 60.0;
    if(this->renderWorld)
        this->renderWorld->setUniformFloat("time", this->time);
    
    for(int i = 0; i < 3; i++)
    {
        float a = cameraMoving.v[i];
        float b = cameraMovingP.v[i];
        if(fabs(a) > fabs(b))
        {
            b = migrate(b, a, vD);
        }
        else
        {
            b = a;
        }
        cameraMovingP.v[i] = b;
        
        
        a = cameraTurning.v[i];
        b = cameraTurningP.v[i];
        if(fabs(a) > fabs(b))
        {
            b = migrate(b, a, tD);
        }
        else
        {
            b = a;
        }
        cameraTurningP.v[i] = b;
    }
    cameraMatrix.lTranslate(this->cameraMovingP[0], this->cameraMovingP[1], this->cameraMovingP[2]);
    cameraMatrix.lRotateX(this->cameraTurningP[0]);
    cameraMatrix.lRotateY(this->cameraTurningP[1]);
    cameraMatrix.lRotateZ(this->cameraTurningP[2]);
    
    this->eyePos += this->eyePosChange;
    if(this->eyePos < 0.2)
        this->eyePos = 0.2;
    
    float yRatio = 1.0;
    this->renderWorld->setUniformFloat("yRatio", yRatio);
    this->renderWorld->setUniformFloat("eyePos", this->eyePos);
    MeMatrix4 camM = this->cameraMatrix;
    rigidInvert(&camM);
    this->renderWorld->setUniformMatrix4("cameraMatrix", camM);
    
}

void Screen::draw(MeIFrameBuffer *destination, int renderSequence)
{
    if(renderSequence < 0)
        renderSequence = this->renderSequence;
    this->renderWorld->setActiveSequence(renderSequence);
    this->renderWorld->clearRenderReport();
    this->renderWorld->draw(destination);
}

void Screen::end()
{
    
}


// ---------------------------------
// SOME HAM TYPES

class PathHam : public MeHam
{
public:
    PathHam(MeGaud *g, const std::vector<MeVec4> &path)
    {
        this->yRotRate = rr(-.05,+0.05);
        this->g = g;
        this->path = path;
        this->pathPos = 0.0;
        
        std::vector<std::string> names {"xform1", "xform2", "xform3", "xform4"};
        std::vector<int> indexes = this->g->getPartAttributeIndexes(names);
        this->ixXform1 = indexes[0];
        this->ixXform2 = indexes[1];
        this->ixXform3 = indexes[2];
        this->ixXform4 = indexes[3];
    }
    
    void updateFrame()
    {
        int pathIx0 = floor(this->pathPos);
        int pathIx1 = (pathIx0 + 1) % this->path.size();
        
        MeVec4 posFrom = this->path[pathIx0];
        MeVec4 posTo= this->path[pathIx1];
        
        float portion = this->pathPos - pathIx0;
        float bump = 1.0 / posTo[3] / 60.0;
        
        MeVec4 newPos;
        portion += bump;
        if(portion >= 1)
        {
            // we have arrived at the end point! pin there, and bump everyone.
            portion = 0;
            this->pathPos = pathIx1;
            newPos = posTo;
        }
        else
        {
            this->pathPos = pathIx0 + portion;
            newPos.v[0] = mapRange(portion, 0, 1, posFrom[0], posTo[0]);
            newPos.v[1] = mapRange(portion, 0, 1, posFrom[1], posTo[1]);
            newPos.v[2] = mapRange(portion, 0, 1, posFrom[2], posTo[2]);
        }
        
        MeMatrix4 m;
        m.wTranslate(newPos.v[0], newPos.v[1], newPos.v[2]);
        m.lRotateY(this->yRot);
        this->yRot += this->yRotRate;
        
        MeVec4 row1 = MeVec4(&m.m[0]);
        MeVec4 row2 = MeVec4(&m.m[4]);
        MeVec4 row3 = MeVec4(&m.m[8]);
        MeVec4 row4 = MeVec4(&m.m[12]);
        
        g->changesHold();
        g->setPartAttribute(this->ixXform1,row1);
        g->setPartAttribute(this->ixXform2,row2);
        g->setPartAttribute(this->ixXform3,row3);
        g->setPartAttribute(this->ixXform4,row4);
        g->changesGo();
    }
    
private:
    MeGaud *g;
    std::vector<MeVec4> path;
    float pathPos;
    float yRot = 0;
    float yRotRate = 0;
    
    int ixXform1;
    int ixXform2;
    int ixXform3;
    int ixXform4;
};

static MeGeometry *makeCubeGeometry(float r, int color = -1)
{
    MeGeometry *g = makeBoxGeometry(-r, -r, -r, r, r, r, color);
    //g->printVertexes();
    return g;
}

static void flipGeometry(MeGeometry *g)
{
    // reverse order of every triangle...
    for(auto attributeName : g->getAttributeNames())
    {
        EMeAttributeType type = g->getAttributeType(attributeName);
        int n = g->getAttributeN(attributeName);
        int elementSize = g->sizeFor(type);
        
        int triangleCount = g->getTriangleCount();
        uint8_t *data = (uint8_t *)g->getAttributeData(attributeName);
        int triangleBytes = 3 * n * elementSize;
        int vertexBytes = n * elementSize;
        for(int t = 0; t < triangleCount; t++)
        {
            uint8_t temp[512];
            uint8_t *v0 = data + triangleBytes * t;
            uint8_t *v2 = v0 + 2 * vertexBytes;
            memcpy(temp, v0, vertexBytes);
            memcpy(v0, v2, vertexBytes);
            memcpy(v2, temp, vertexBytes);
        }
        
    }
}

static void moveGeometry(MeGeometry *g, MeVec3 translation)
{
    int triangleCount = g->getTriangleCount();
    float *data = (float *)g->getAttributeData("pos");
    if(data)
    {
        for(int i = 0; i < triangleCount * 3 * 3; i += 3)
        {
            data[i] += translation[0];
            data[i+1] += translation[1];
            data[i+2] += translation[2];
        }
    }
}


static MeGeometry *makeCubeFrameGeometry(float cubeR, float rodR, int color = -1)
{
    MeGeometry *result = NULL;
    for(int xi = -1; xi <= +1; xi += 2)
        for(int yi = -1; yi <= +1; yi += 2)
            for(int zi = -1; zi <= +1; zi += 2)
            {
                float x = xi * cubeR;
                float y = yi * cubeR;
                float z = zi * cubeR;
                
                MeGeometry *gX = makeBoxGeometry(-cubeR, y - rodR, z - rodR, +cubeR, y + rodR, z + rodR, color);
                MeGeometry *gY = makeBoxGeometry(x - rodR, -cubeR, z - rodR, x + rodR, +cubeR, z + rodR, color);
                MeGeometry *gZ = makeBoxGeometry(x - rodR, y - rodR, -cubeR, x + rodR, y + rodR, +cubeR, color);
                
                if(result == NULL)
                    result = gX;
                else
                    result->append(gX);
                
                result->append(gY);
                result->append(gZ);
            }
    return result;
}


// ----------------------------------
// SIMPLE Screen1Still and SCREEN2




void addPathers(MeWorld *w, MeRenderWorld *rw, MeGeometry *g, MeIMaterial *material, MeGeometry *g2, MeIMaterial *material2, int count, float span = 10, float speed = 1.0)
{
    float r = span;
    for(int i = 0; i < count; i++)
    {
        int steps = ir(10,36) * r / 3;
        steps = 33;
        std::vector<MeVec4> path;
        for(int j = 0; j < steps; j++)
        {
            MeVec4 step(rr(-r,r), rr(-r,r), rr(-r,r), speed * rr(0.333, 1.1));
            path.push_back(step);
        }
        MePart *p = new MePart(material, g);
        MeGaud *ga = new MeGaud();
        ga->addPart(p);

        if(g2 && material2)
        {
            MePart *p2 = new MePart(material2, g2);
            ga->addPart(p2);
        }

        rw->addGaud(ga);

        MeHam *ham = new PathHam(ga, path);
        w->addHam(ham);
        
    }
}

#include <unistd.h>

void addPathers(MeWorld *w, MeRenderWorld *rw, MeGeometry *g, MeIMaterial *material, int count, float span = 10, float speed = 1.0)
{
    addPathers(w, rw, g, material, NULL, NULL, count, span, speed);
}



MeITexture *loadTexture(MeIMaterial *m, const char *imageFile)
{
    char x[2222];
    getcwd(x,2222);
    meLogInfo("cwd = %s", x);
    if( access( imageFile, F_OK ) == -1 )
    {
        meLogError("no such file %s", imageFile);
        return NULL;
    }
    
    MeITexture *texture = m->newTexture(imageFile);
    return texture;
}

void Screen::makeSkybox()
{
    this->materialSky = loadMaterialGl("skybox1.vsh", "skybox1.fsh");
    MeGeometry *ge = makeRectXyGeometry();
    MePart *pa = new MePart(this->materialSky, ge);
    MeGaud *ga = new MeGaud();
    ga->addPart(pa);
    this->renderWorld->addGaud(ga);
    
    MeITexture *nasaSkymap = loadTexture(this->materialSky, "nasaSkymap.png");
    if(nasaSkymap)
        this->materialSky->setUniformSampler("texture1", nasaSkymap);
    this->materialSky->setUniformInt("doTexture", 1);
}

class Screen1Still : public Screen
{
public:
    std::string getName() override { return "Screen1Still"; }
    
    MeITexture *t1, *t2;
    MeIMaterial *m;

    Screen1Still(const char *imageFile) : Screen()
    {
        this->renderWorld = new MeRenderWorld();

        MeIMaterial *materialTemp = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
        MeITexture *texture = loadTexture(materialTemp, imageFile);
        
        MeIMaterial *materialTexture;
        MeGaud *textureGaud = buildTextureDraw(texture, MBM_ALPHA_2D, -1, -1, 1, 1, "texture2d.fsh", &materialTexture);
        this->renderWorld->addGaud(textureGaud);
        
        this->renderWorld->addStepClear("");
        this->renderWorld->addStepDraw(materialTexture, "");
        
        // experiment with uniform performance killing
        MeITexture *texture2 = loadTexture(materialTemp, imageFile);
        
        t1 = texture;
        t2 = texture2;
        m = materialTexture;

    }
    
    void draw(MeIFrameBuffer *destination, int renderSequence)
    {
        this->m->setUniformSampler("texture1", t1);
        Screen::draw(destination, renderSequence);
        this->m->setUniformSampler("texture1", t2);
        Screen::draw(destination, renderSequence);
    }
    
    void tick()
    {
    }
};

class Screen2Pathers : public Screen
{
    std::string getName() override { return "Screen2Pathers"; }
    MeWorld *world;
    MeIMaterial *material;
    int bgColor;
    MeGaud *bigCubeGaud;
    MeMatrix4 bigCubeMatrix;
public:
    Screen2Pathers(int bgColor, float r, int count) : Screen()
    {
        this->bgColor = bgColor;
        this->world = new MeWorld();
        this->renderWorld = new MeRenderWorld();
        this->material = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
        this->material->setTwoSided(true);
        
        MeGeometry *b = makeCubeGeometry(r);
        addPathers(this->world, this->renderWorld, b, this->material, count);
        
        MeGeometry *bigCube = makeCubeGeometry(20);
        flipGeometry(bigCube);
        MePart *bigCubePart = new MePart(this->material, bigCube);
        this->bigCubeGaud = new MeGaud();
        this->bigCubeGaud->addPart(bigCubePart);
        this->renderWorld->addGaud(this->bigCubeGaud);

        this->renderWorld->addStepClear("", 0xff008000);
        this->renderWorld->addStepDraw(this->material, "");
        

        this->renderWorld->addStepClear("d", 0xff800000);
        this->renderWorld->addStepDraw(this->material, "d");

        this->renderWorld->addStepClear("", 0xff00ff00);
        addCopyingStep(this->renderWorld, "d", "");
    }

    void keyDown(int k)
    {
        this->Screen::keyDown(k);
    }
    
    
    void keyUp(int k)
    {
        this->Screen::keyUp(k);
    }

    void tick()
    {
        this->bigCubeMatrix.lRotateX(0.0004);
        this->bigCubeMatrix.lRotateY(0.000191);
        setGaudMatrix(this->bigCubeGaud, this->bigCubeMatrix);
        float yRatio = 1;
        this->renderWorld->setUniformFloat("yRatio", yRatio);
        this->renderWorld->setUniformFloat("eyePos", this->eyePos);
     
        this->Screen::tick();
        this->world->updateFrame();

        MeMatrix4 camM = this->cameraMatrix;
        rigidInvert(&camM);
        this->renderWorld->setUniformMatrix4("cameraMatrix", camM);
    }
};





class Screen3Feedback : public Screen
{
    std::string getName() override { return "Screen3Feedback"; }
    MeWorld *world;
    MeIMaterial *material;
    MeIMaterial *materialWindow;
    int bgColor;
    
    MeGaud *g;
    
    MeITexture *windowTexture; // texture to map into the Window Material.
    
public:
    Screen3Feedback(int bgColor) : Screen()
    {
        this->g = new MeGaud();
        this->bgColor = bgColor;
        this->world = new MeWorld();
        this->renderWorld = new MeRenderWorld();
        this->material = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
        this->materialWindow = loadMaterialGl("window.vsh", "window.fsh");
        this->materialWindow->setTwoSided(true);
        
        {
            float pos[] = {
//                -10,-10,0, 0,-10,-10, 0,10,-10,
//                -10,-10,0, 0,10,-10, -10,10,0
                -10,-10,0, 10,-10,0, 10,10,0,
                -10,-10,0, 10,10,0, -10,10,0
            };

            float uv[] = {
                0,0, 1,0, 1,1,
                0,0, 1,1, 0,1,
            };

            float col[] = {
                1,1,1, 1,0,0, 1,1,0,
                1,1,1, .8,.8,.8, .5,.5,1
                
            };
            MeGeometry *qg = new MeGeometry();
            qg->setTriangleCount(2);
            qg->addAttribute("pos", MAT_FLOAT, 3, pos);
            
            qg->addAttribute("col", MAT_FLOAT, 3, col);
            qg->addAttribute("nor", MAT_FLOAT, 3, 0.0);
            qg->addAttribute("uv", MAT_FLOAT, 2, uv);
            
//            qg = makeCubeGeometry(5);

            MePart *p = new MePart(this->materialWindow, qg);
            MeGaud *g = new MeGaud();
            g->addPart(p);
            this->renderWorld->addGaud(g);
            
            MeMatrix4 one;
            //setGaudMatrix(g, one);
            
            this->cameraMatrix.lTranslate(0, 0, 25);

        }
        
        MeGeometry *cubesGe = NULL;
        {
            float r = .3;
            float co = 6.0;
            int perSide = 4;
            float st = (co * 2.0) / (perSide - 1.0);
            float coR = co + st * 0.1;
            for(float x = -co; x <= coR; x += st)
                for(float y = -co; y <= coR; y += st)
                    for(float z = -co; z <= coR; z += st)
                    {
                        int re = mapRange(x, -co, co, 0, 255);
                        int gr = mapRange(y, -co, co, 0, 255);
                        int bl = mapRange(z, -co, co, 0, 255);
                        int color = (bl<<16) | (gr<<8) | (re<<0);
                        MeGeometry *aCube = makeBoxGeometry(x - r, y - r, z - r, x + r, y + r, z + r, color);
                        if(cubesGe)
                            cubesGe->append(aCube);
                        else
                            cubesGe = aCube;
                    }
        }
        MePart *pa = new MePart(this->material, cubesGe);
        
        this->g->addPart(pa);
        this->renderWorld->addGaud(this->g);

        MeMatrix4 one;
        setGaudMatrix(this->g, one);
        
        MeGeometry *b = makeCubeGeometry(1.1);
        addPathers(this->world, this->renderWorld, b, this->material, 2);
        
        this->cameraMatrix.lTranslate(0, 0, 12);

        MeRenderWorld *rw = this->renderWorld;

        // pfb0 will use pfb1 for the window
        // then copy pfb0 to pfb1
        // then copy pfb0 to output
        
        rw->addStepSetTextureColor("pfb1", this->materialWindow, "texture1");
        rw->addStepClear("pfb0", 0xff000060);
        rw->addStepDraw(this->materialWindow, "pfb0");
        rw->addStepDraw(this->material, "pfb0");
        

        rw->addStepClear("",0xff300000);
        
        addCopyingStep(this->renderWorld, "pfb0", "");
        addCopyingStep(this->renderWorld, "pfb0", "pfb1"); // TODO: same->several could reuse gaud & material
    }
    
    void setWindowTexture(MeITexture *texture)
    {
        this->windowTexture = texture;
        this->materialWindow->setUniformSampler("texture1", texture);
    }
    
    
    
    void tick()
    {
        this->world->updateFrame();

        this->Screen::tick();
        MeMatrix4 camM = this->cameraMatrix;
        rigidInvert(&camM);
        this->renderWorld->setUniformMatrix4("cameraMatrix", camM);
        
        this->renderWorld->setUniformFloat("eyePos", this->eyePos);
        this->renderWorld->setUniformFloat("yRatio", 1.0);


    }
};




class Screen4Translucent : public Screen
{
    std::string getName() override { return "Screen4Translucent"; }
    MeWorld *world;
    MeIMaterial *material;
    MeIMaterial *materialTransparent;
    int bgColor;
    bool crunching = false;
    float crunch = 0.0;
    bool stats = false;

public:
    
    Screen4Translucent(int bgColor) : Screen()
    {
        this->bgColor = bgColor;
        this->world = new MeWorld();
        this->renderWorld = new MeRenderWorld();
        
        this->makeSkybox();

        this->material = loadMaterialGl("vert1_v4.vsh", "frag1_v4.fsh");
        this->materialTransparent = loadMaterialGl("vert1_v4.vsh", "transparent.fsh");
        this->materialTransparent->setTwoSided(false);
        this->cameraMatrix.lTranslate(0, 0, 50);

        MeGeometry *cubesGe = NULL;
        {
            float r = .5;
            float co = 10.0;
            int perSide = 7;
            float st = (co * 2.0) / (perSide - 1.0);
            float coR = co + st * 0.1;
            for(float x = -co; x <= coR; x += st)
                for(float y = -co; y <= coR; y += st)
                    for(float z = -co; z <= coR; z += st)
                    {
                        int re = mapRange(x, -co, co, 0, 255);
                        int gr = mapRange(y, -co, co, 0, 255);
                        int bl = mapRange(z, -co, co, 0, 255);
                        int color = (bl<<16) | (gr<<8) | (re<<0);
                        MeGeometry *aCube = makeBoxGeometry(x - r, y - r, z - r, x + r, y + r, z + r, color);
                        if(cubesGe)
                            cubesGe->append(aCube);
                        else
                            cubesGe = aCube;
                    }
        }
        MePart *pa = new MePart(this->material, cubesGe);
        
        MeGaud *g = new MeGaud();
        g->addPart(pa);
        this->renderWorld->addGaud(g);
        
        MeMatrix4 one;
        setGaudMatrix(g, one);
        
        float cubeR = 5.7;
        MeGeometry *b = makeCubeGeometry(cubeR, 0x0000FF);
        MeGeometry *bF = makeCubeFrameGeometry(cubeR, 0.4);

        MeGeometry *b2 = makeCubeGeometry(1.87);

        addPathers(this->world, this->renderWorld, b, this->materialTransparent, bF, this->material, 2);
        addPathers(this->world, this->renderWorld, b2, this->material, 4);

        float sr = 8.0;
        MeGeometry *b3 = makeStellatedOctohedronGeometry(-sr, -sr, -sr, +sr, +sr, +sr, 0.8);
        addPathers(this->world, this->renderWorld, b3, this->materialTransparent, 1, 45, 370.05);

        this->cameraMatrix.lTranslate(0, 0, 22);
        
        MeRenderWorld *rw = this->renderWorld;
        rw->addStepClear("b0", this->bgColor);
        rw->addStepDraw(this->materialSky, "b0");
        rw->addStepDraw(this->material, "b0");
        addCopyingStep(rw, "b0", "b1");
        
        rw->addStepSetTextureColor("b1", this->materialTransparent, "texture1");
        rw->addStepDraw(this->materialTransparent, "b0");
        
        addCopyingStep(rw, "b0", "");

        // enable for an interesting effect where only the transparent objects
        // provide visibility to what is behind...
#if 0
        rw->addStepClear("");
        rw->addStepDraw(this->materialTransparent,"");
#endif
        this->th = new TerminalHam(this->renderWorld,96,48);
        this->world->addHam(this->th);
        this->th->setZ(0.99999);
    }
    
    void tick()
    {
        this->world->updateFrame();

        if(this->crunching)
            this->crunch += 1.0 / 60.0;
        if(this->renderWorld)
            this->renderWorld->setUniformFloat("crunch", this->crunch);

        this->Screen::tick();
        MeMatrix4 camM = this->cameraMatrix;
        rigidInvert(&camM);
        this->renderWorld->setUniformMatrix4("cameraMatrix", camM);
        
        this->renderWorld->setUniformFloat("eyePos", this->eyePos);
        this->renderWorld->setUniformFloat("yRatio", 1.0);
        
    }
    
    void draw(MeIFrameBuffer *destination, int renderSequence)
    {
        Screen::draw(destination, renderSequence);
        MeRenderReport *rr = this->renderWorld->getRenderReport();

        if(this->stats)
            this->th->printf("[%7d] %d triangles, %d draws, %d pipes\n", this->ticks, rr->triangleCount, rr->materialDrawCount, rr->texturePipeCount);
        
    }
    
    void keyDown(int k)
    {
        switch(k)
        {
            case ' ':
                this->crunching = !this->crunching;
                break;
            case 9:
                this->stats = !this->stats;
                this->th->clear();
            default:
                Screen::keyDown(k);
                break;
        }
    }
};




class CubeFrameThing : public MeThing
{
    MeIMaterial *material = 0;
    MeGaud *gaud = 0;
    MeMatrix4 ma;
    
    MeVec3 pos;
    float rpm;
    int maIx1,maIx2,maIx3,maIx4;
    
    float orbit = rr(0.0008,0.007);
    
    int turningCountdown;
    MeMatrix4 turningMatrix;
    
    
public:
    METHING_NEWINSTANCE(CubeFrameThing)
    void configureKind(MeThingKind *kind)
    {
//        kind->setCategory("animatedGeo");
        kind->addParameterVec3("startPos");
    }
    
    void setMaterial(MeIMaterial *material)
    {
        this->material = material;
    }
    
    MeGaud *getGaud(MeRenderWorld *rw)
    {
        if(!this->gaud)
        {
            float cr = 4.0;
            MeGeometry *g = makeCubeFrameGeometry(cr, .4);
            MePart *p = new MePart(this->material,g);
            this->gaud = new MeGaud();
            this->gaud->addPart(p);
            
            this->ma.lTranslate(this->getValueVec3("startPos"));
            
            rw->addGaud(this->gaud);
            setGaudMatrix(this->gaud, this->ma);

            std::vector<int> xformIndexes =
            this->gaud->getPartAttributeIndexes(std::vector<std::string>{"xform1","xform2","xform3","xform4"});
            this->maIx1 = xformIndexes[0];
            this->maIx2 = xformIndexes[1];
            this->maIx3 = xformIndexes[2];
            this->maIx4 = xformIndexes[3];
        }
        return this->gaud;
    }
    
    void tick()
    {
        
        if(this->turningCountdown)
        {
            this->ma = this->ma * this->turningMatrix;
            this->turningCountdown--;
        }
        else
        {
            if(rr(0,15) < 1)
            {
                float qu = rr(0,1) < 0.5 ? MePi/2 : -MePi/2;
                int steps = 20;
                qu /= steps;
                MeMatrix4 turn;
                switch(ir(0,3))
                {
                    case 0:
                        turn.lRotateX(qu);
                        break;
                    case 1:
                        turn.lRotateY(qu);
                        break;
                    case 2:
                        turn.lRotateZ(qu);
                        break;
                }
                this->turningCountdown = steps;
                this->turningMatrix = turn;
            }
        }
        
        this->gaud->changesHold();
        this->gaud->setPartAttribute(this->maIx1, this->ma.column0());
        this->gaud->setPartAttribute(this->maIx2, this->ma.column1());
        this->gaud->setPartAttribute(this->maIx3, this->ma.column2());
        this->gaud->setPartAttribute(this->maIx4, this->ma.column3());
        this->gaud->changesGo();
    }
};


class Screen5Term : public Screen, public MeILogListener
{
    MeIMaterial *material;
    bool spewing = false;
    bool controlling = true;
    bool front = false;
    
    MeWorld *world;
    
    std::vector<CubeFrameThing *> cubeFrames;
    
    MeThingManager tm;
    
public:
    std::string getName() override { return "Screen5Term"; }
    Screen5Term() : Screen()
    {
        tm.addKind(new CubeFrameThing());

        //        meLogAddListener(this);
        this->renderWorld = new MeRenderWorld();
//        this->renderWorld->addGaud(g);
        
        this->world = new MeWorld();
        this->material = loadMaterialGl("vert1_v4.vsh","frag1_v4.fsh");
        
        bool oneBigGeometry = false;
        
        {
            MeGeometry *gN = NULL;
            int kk = 7;
            float cr = 4.0;
            for(int x = -kk; x <= kk; x++)
                for(int y = -kk; y <= kk; y++)
                    for(int z = -kk; z <= kk; z++)
                    {
                        MeGeometry *g = makeCubeFrameGeometry(cr, .4);
                        MeVec3 position = MeVec3(x,y,z) * cr * 2.5;
                        if(oneBigGeometry)
                        {
                            moveGeometry(g, position);
                            if(gN)
                                gN->append(g);
                            else
                                gN = g;
                        }
                        else
                        {
                            CubeFrameThing *cfg = (CubeFrameThing *)this->tm.newInstance("CubeFrameThing");
                            this->cubeFrames.push_back(cfg);
                            cfg->setMaterial(this->material);
                            cfg->setValue("startPos", position);
                            delete g;
                            
                            cfg->getGaud(this->renderWorld);
                        }
                    }
            if(oneBigGeometry)
            {
                MePart *p = new MePart(this->material,gN);
                MeGaud *ga = new MeGaud();
                ga->addPart(p);
                setGaudMatrix(ga);
                this->renderWorld->addGaud(ga);
            }
        }
        
        {
            std::vector<float> prList{0,1, 1,1, 1,2,  10,2, 12,5, 15,4, 20,7, 22,6, 30,10, 25,8, 40,1};
            MeGeometry *tubeG = buildTube(48, MeVec3(-20,0,0), MeVec3(-30,10,10), prList, MeVec3(.4,0,0), 0.03);
            MePart *tubeP = new MePart(this->material, tubeG);
            MeGaud *tubeGa = new MeGaud();
            tubeGa->addPart(tubeP);
            this->renderWorld->addGaud(tubeGa);
            setGaudMatrix(tubeGa);
        }
        
        this->material->setTwoSided(true);

        this->renderWorld->addStepClear("", 0x003020);
        this->renderWorld->addStepDraw(this->material, "");
        
        this->th = new TerminalHam(this->renderWorld,96,48);
        this->world->addHam(this->th);
    }
    
    void logMessage(int level, const std::string &message)
    {
        if(this->th == NULL)
            return;
        const char *c = message.c_str();
        for(int ix = 0; ix < strlen(c); ix++)
            this->th->ch(c[ix]);
        this->th->cr();
    }
    
    void keyDown(int k)
    {
        switch(k)
        {
            case '\\':
                spewing = !spewing;
                break;
            case 9:
                this->front = !this->front;
                this->th->setFront(this->front);
                break;
            case 1073742049:
                this->controlling = !this->controlling;
                break;
            case 1073742048:
                this->th->clear();
                break;
            default:
                if(!controlling && !spewing)
                    this->th->ch(k);
                else
                    this->Screen::keyDown(k);
                break;
        }
    }
    
    void keyUp(int k)
    {
        switch(k)
        {
//            case 1073742049:
//                this->controlling = false;
//                break;
            default:
                this->Screen::keyUp(k);
                break;
        }
    }
    
    void tick()
    {
        for(auto cfg : this->cubeFrames)
            cfg->tick();
        this->world->updateFrame();
        this->Screen::tick();
        
        if(spewing)
        {
            int lineW = ir(10, 80);
            for(int i = 0; i < lineW; i++)
                this->th->ch(ir(32,127));
            this->th->cr();
        }
    }
    
    void draw(MeIFrameBuffer *destination, int renderSequence)
    {
        Screen::draw(destination, renderSequence);
        MeRenderReport *rr = this->renderWorld->getRenderReport();
        this->th->printf("%d triangles, %d draws, %d pipes\n", rr->triangleCount, rr->materialDrawCount, rr->texturePipeCount);
    }
};





//-------------------------------
// SCREENS

Screens::Screens(int width, int height, const char *baseDir)
{
    meGlSetBaseDir(baseDir);
    
    
    // junk -- any material will do. :-/
    MeIMaterial *m = new MeMaterialGl("", "");
    this->windowFrameBuffer = m->newFrameBuffer(width, height, true);
    this->fadeInFrameBuffer = m->newFrameBuffer(width, height);
    
    MeGaud *gaud = buildTextureDraw(this->fadeInFrameBuffer->getColorTexture(), MBM_ALPHA_2D, -1,-1,1,1, "fader.fsh", &this->faderMaterial);
    this->faderWorld = new MeRenderWorld();
    this->faderWorld->addGaud(gaud);
    
//#define justG4 1

#if !justG4
    ScreenG2 *sg2 = new ScreenG2();
    ScreenG3 *sg3 = new ScreenG3();

    Screen3Feedback *s3 = new Screen3Feedback(0x808088);
    ScreenG1 *sg1 = new ScreenG1();
    Screen4Translucent *s4 = new Screen4Translucent(0xffffff);
    Screen5Term *s5 = new Screen5Term();
#endif
    ScreenG4 *sg4 = new ScreenG4();

#if !justG4
    this->screens.push_back(sg2); // zero key

    this->screens.push_back(s4);
    this->screens.push_back(s3);
    this->screens.push_back(new Screen1Still("testImage1.png"));
    this->screens.push_back(new Screen1Still("sphereCap.jpg"));
    this->screens.push_back(new Screen2Pathers(0x9000ff, 0.4, 16000));
    this->screens.push_back(new Screen2Pathers(0xFF3010, 2.0, 5));
    this->screens.push_back(new Screen2Pathers(0x1390e0, 1.0, 12));
    this->screens.push_back(s5);
    this->screens.push_back(sg1);

    this->screens.push_back(sg3);
#endif
    this->screens.push_back(sg4);
    
    this->currentScreen = 0;
    this->previousScreen = 0;
    this->framesSinceChange = 0;
    
//    meLogQuitIfErrors();
}


void Screens::begin()
{
    
}
void Screens::setSize(int width, int height)
{
    this->windowFrameBuffer->resize(width, height);
    this->fadeInFrameBuffer->resize(width, height);
    if(this->captureBuffer)
        this->captureBuffer->resize(width, height);
    for(auto screen : this->screens)
    {
        screen->setSize(width, height);
    }
}

void Screens::captureFrame()
{
    MeObjectBase::doCounting = true;
    // screen capture!
    MeObjectSnapshot snapshot;
    if(this->m == NULL)
    {
        this->m = new MeMaterialGl("","");
        this->captureBuffer = m->newFrameBuffer(1024, 1024);
    }
    this->draw(captureBuffer);
    MeITexture *pixels = captureBuffer->getColorTexture();
    std::string s;
    s = ssprintf("screenCapTick%06d.jpg", this->tickCount);
    bool did = pixels->writeFile(s);
    meLogInfo("wrote %s result %d", s.c_str(), did);
    printf("captureframe %d\n",this->tickCount);
    snapshot.printDelta();
}

void Screens::keyDown(int k, int mod)
{
    Screen *currentScreenP = this->screens[this->currentScreen];
    currentScreenP->keyTook = true;
    currentScreenP->keyDown(k);
    if(currentScreenP->keyTook)
        return;

    int newScreen;
    k = (mod << 8) + k;
    switch(k)
    {
        case '0' + 0x0100:
            newScreen = 10;
            goto doNewScreen;

        case '9' + 0x0100:
            newScreen = 11;
            goto doNewScreen;

        default:
            if(k >= '0' && k <= '9')
            {
                newScreen = k - '0';
            doNewScreen:
                if(newScreen < this->screens.size() && newScreen != currentScreen)
                {
                    this->previousScreen = this->currentScreen;
                    this->currentScreen = newScreen;
                    this->framesSinceChange = 0;
                }
            }
            break;
    }
}

void Screens::keyUp(int k)
{
    Screen *currentScreen = this->screens[this->currentScreen];
    currentScreen->keyUp(k);
}

void Screens::mouseDown(int x, int y)
{

    // screen capture!
    MeObjectSnapshot snapshot;
    
    if(this->m == NULL)
    {
        this->m = new MeMaterialGl("","");
        this->captureBuffer = m->newFrameBuffer(1024, 1024);
    }
    this->draw(captureBuffer, 1);
    MeITexture *pixels = captureBuffer->getColorTexture();
    uint32_t *data = (uint32_t *)pixels->getData(MTK_RGBA8);
    y = 1023 - y;
    uint32_t pixel = data[pixels->getWidth() * y + x];
    meLogInfo("pixel at (%d,%d) is 0x%08x", x, y, pixel);
    MeObjectBase::freeAt((void **)(&data));
    
    int partId = pixel & 0x00ffffff;
    partId -= 0x00400000; // the BLUE component is biased up for visibility. remove that.
    Screen *currentScreen = this->screens[this->currentScreen];
    currentScreen->partClicked(partId);
}

void Screens::mouseUp(int x, int y)
{
    
}


void Screens::tick()
{
    std::set<Screen *> tickableScreens;
//    tickableScreens.insert(this->screens[4]);
    
    this->tickCount++;
    Screen *currentScreen = this->screens[this->currentScreen];
    Screen *previousScreen = this->screens[this->previousScreen];

    tickableScreens.insert(currentScreen);
    tickableScreens.insert(previousScreen);

    for(auto screen : tickableScreens)
        screen->tick();

//    this->captureFrame();
}

void Screens::draw(MeIFrameBuffer *destinationBuffer, int renderSequence)
{
    if(destinationBuffer == 0)
        destinationBuffer = this->windowFrameBuffer;
    destinationBuffer->clearFrame();
    Screen *currentScreen = this->screens[this->currentScreen];
    if(this->currentScreen == this->previousScreen)
    {
        currentScreen->draw(destinationBuffer, renderSequence);
    }
    else
    {
        // we are in process of fading. So:

        // draw the old one to the window
        Screen *previousScreen = this->screens[this->previousScreen];
        previousScreen->draw(destinationBuffer);

        // draw the new onto the fader frame buffer
        currentScreen->draw(this->fadeInFrameBuffer);

        // composite the new onto the old in some fraction.
        float framesToTransition = 10;
        float opacity = this->framesSinceChange / framesToTransition;
        this->faderMaterial->setUniformFloat("opacity", opacity);
        this->faderWorld->draw(destinationBuffer);
        
        this->framesSinceChange++;
        if(this->framesSinceChange >= framesToTransition)
        {
            // all done. no more fade.
            this->previousScreen = this->currentScreen;
            this->framesSinceChange = 0;
        }
    }

    // always leave frame buffer bound to 0. SDL doesnt reset it before page-flipping & stuff!
    // TODO this will be part of the job of MeWorld, when it does everything
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Screens::end()
{
    
}
