Laser Sensing Display for UI interfaces in the real world

Dependencies:   mbed

Fork of skinGames_forktest by Alvaro Cassinelli

WrapperFunctions.cpp

Committer:
mbedalvaro
Date:
2014-04-17
Revision:
47:199042980678
Parent:
44:2432c218f191

File content as of revision 47:199042980678:

#include "WrapperFunctions.h"
#include "textData.h" // this just contains a special switch/case function to load an array of data, so as to store data in FLASH.

using namespace std;

extern LaserRenderer lsr;
extern Scene scene;
extern laserSensingDisplay lsd;

bool previousLsdState; //local scope - only used in this file.

// =============== SOME IMPORTANT LOW LEVEL FUNCTIONS CONTROLLING THE DISPLAYING ENGINE =========================================

// This is a special function that needs to be called at least once to set the displaying engine ON, thus setting the current state of the engine:
void startDisplay() {
 previousLsdState=lsd.isRunning();
 IO.setLaserLockinPower(1);
 lsd.run(); // note: current state of the Lsd renderer is lsd.isRunning()
}

void stopDisplay() // save previous displaying status, stops the displaying engine, and switch off lasers
{
    previousLsdState=lsd.isRunning();
    lsd.stop();
    // also, switch off lasers:
    IO.setLaserLockinPower(0);
    IO.switchOffDisplayLasers();
}

void toggleDisplay() {
    if (lsd.isRunning()) stopDisplay(); else startDisplay();
}

void resumeDisplay() // resume display engine if it was displaying, otherwise does not do anything
// and set the sensing laser ON (no need to explicitly switch the DISPLAYING lasers ON - this is done in the displaying engine ISR). 
{
    if (previousLsdState) {
    //switch on sensing lasers:
    IO.setLaserLockinPower(1);
    lsd.run();
    // rem: no need to set the displaying lasers: this is done per-object basis
    }
}

// =================================   BASIC "OPENGL-like" object/scene builders and query functions ("sense") ==========================================

void clearScene()   // THIS SHOULD BE CALLED WHENEVER we want to create a new scene from scratch.
{
    // This is delicate: first, we have to STOP the displaying engine, then we clear the scene and we are ready to add objects (begin/end, or primitive builders)
    // BUT WE NEED TO LET THE DISPLAYING ENGINE FINISH THE WORK IT'S DOING on the current buffer. TWO WAYS: DOUBLE BUFFERING, or wait:
    // Check in which point of the drawing we are:
    if (lsd.isRunning()) {
        lsd.startDisplayCheck();
        while(!lsd.isDisplayingOver());
        stopDisplay();
    } else { //do nothing, the display engine is not working (we don't need to wait for the display to end!)
    }
    scene.clear();
}

void updateScene()   // THIS SHOULD BE CALLED WHENEVER we ended CREATING or MODIFYING a scene
{
    //We need to stop the display, and then indicate to the lsd that the scene has changed. NOTE: we don't resume displaying until we haven't drawn the scene again. 
    // Of course, it could 
     if (lsd.isRunning()) {
        lsd.startDisplayCheck();
        while(!lsd.isDisplayingOver());
        stopDisplay();
    }
    lsd.setSceneToDisplay(&scene); // this will compute the number of objects, points, etc, to prepare the displaying engine
    resumeDisplay();// this will resume display IF it was displaying before. 
}


void deleteObject(int _id) {
    if (lsd.isRunning()) {
        lsd.startDisplayCheck();
        while(!lsd.isDisplayingOver());
        stopDisplay();
    } else { //do nothing, the display engine is not working (we don't need to wait for the display to end!)
    }
    // DELETE THE OBJECT and update the scene:
    scene.deleteObject(_id);
    updateScene();
}

// Object creation ("openGL-style").
// NOTE: returning the pointer will make it possible to create COLLECTIONS of objects (by creating a vector<BaseObject*> firstCollection, which is a SUBSET of the
// scene collection. In the future, I will make a "collection" class with specific sensing methods, but for the time being this can be done by hand.
BaseObject* begin(unsigned char _id=0)
{
    // This will add a new object. In the future, we can have modes - lines, bezier, and points - as well as specification of the type of object (enum)
    // TO DO interesting feature: MERGING OF OBJECTS if they have the same ID? Note that if we DON'T specify an ID, the index will grow automatically starting from 0.
    // for (int i=0; i<scene.totalObjects(); i++) if ((scene.objectArray[i]->ID())==_id) ptr_object=scene.objectArray[i];

    BaseObject* ptr_newObject=new BaseObject(_id); // note: I cannot simply do: BaseObject newObject(_id) because this would be a LOCAL object, deallocated at the exit of this function.
    // NOTE: by allocating the memory for the object pointed by ptr_newObject using new BaseObject, we lost the possibility of intantiating CHILD objects.
    // One possibility is to have a parameter in the begin() method to indicate the type of object (for instance: GENERIC_OBJECT, LETTER, etc...). This way,
    // we can properly instantiate the object. Another possiblity is to

    if (ptr_newObject) { // check to see if we could allocate memory
        scene.ptr_currentObject=ptr_newObject; //  type casting "slices" the child object (but in principle, "scene" WON'T use derived methods)
        ptr_newObject->setColor(lsr.color); // use current color in state machine
        //pc.printf("Could allocate object: %d", _id);
    } else {
        //pc.printf("Could not allocate object: %d", _id);
    }
    return(ptr_newObject);
}

void vertex(V3& _v3)
{
    // Add a 3d point to the "current" object (ie, the latest added object in principle!), but do not RENDER it yet (no final projection computed).
    // NOTE: the point WILL however be added by transforming it with the current MODELVIEW matrix. This way, we can create a whole 3d "scene" before rendering,
    // that can be rotated again without the need to rebuilt it.

    scene.ptr_currentObject->addVertex(_v3, lsr.RT);
    // Or, to avoid using ptr_currentObject: scene.objectArray.back()->addVertex(_v3, lsr.RT);

    // Another way: ptr_currentObject->vertexArray.push_back(lsr.RT*_v3);
    // yet another way: use a method of lsr that "transform" a 3d point. Too heavy...
}

void vertex(float x, float y, float z)
{
    V3 v(x, y, z);
    vertex(v); // why I cannot do: vertex(V3(x, y, z)) ? because vertex(V3& _v3) needs a REFERENCE to an object that has larger scope that this method itself!
}
  
void vertexArray(vector<V3>& _vertexArray) {
    for (int i=0; i<_vertexArray.size(); i++) {
        vertex(_vertexArray[i]);
    }
}

void end()   // add the object to the current scene (we could add to a specific scene in the future...)
{
// note: the current object pointed by ptr_newObject can be BaseObject or any child class. This is not a problem as long as the methods applied to the
// scene vector array don't use child methods (then we can do a dynamic cast before including in the array: pb = dynamic_cast<CBase*>(&d); ). IF we want
// the scene class to be able to use child methods, then we need to make BaseObject polymorphic, by declaring all usable methods VIRTUAL.
    scene.addCurrentObject(); // add the currently tracked object to the vector array
    //scene.addObject(scene.ptr_newObject); // use Scene method to add object to the scene (global).
    // NOTE: scene structure changed, BUT we don't need to inform the displaying engine if we don't want (the new object won't be displayed though)
}

// Sensing methods (query and process sensed data for all objects):
// Objects: we can query the object by their ID or by just giving the object pointer. Now, since we are using begin/end to create objects (at least for now), it will
// be better to query by ID (and assume also that their IDs are different). Now, note that matching the ID may be costly (well, I assume they are not more than ten or so objects).
bool senseObject(int _id)
{
    BaseObject* ptr_object=NULL;
    for (int i=0; i<scene.totalObjects(); i++) if ((scene.objectArray[i]->ID())==_id) ptr_object=scene.objectArray[i];
    if (ptr_object!=NULL) return(ptr_object->sense());
    else return(false); // if there is no object with this ID, return false (convention)
}

bool senseScene()
{
    return(scene.sense());
}

// =================================   "GLOBAL" TRANSFORMATIONS (i.e. "viewing transforms") ON OBJECTS and SCENE without rebuilding them ============================================================
// NOTE: There are two ways of doing this: (1) one is to apply the required transformation on the object/scene 3d points themselves; (2) the other is to mantain the 3d points values, but apply
// the transformation FOR rendering only. The advantage of this last technique is that there will not be any drifting of the values because
// of approximations after a while; the disadvantage is that we cannot easily track an "incremental" interactive transformation (say, scrolling a sphere). But this can be solved by using
// an "accumulator" tranformation matrix (or a quaternion). Also, the second methodology closely matches the OpenGL philosophy: defining the "modelview transformation" and the "viewing transformation",
// even though these transformations apply to the same modelview matrix.
// I will implement both, because even the first option can be useful (to create more complex scenes by rotating already created objects for instance - but it will be trickier to understand though)

// (1) applying a transformation on the 3d points (NOTE: we could also apply transformations on the projected points! this is however a work for the "viewport").
// NOTE: I may later create specific transform methods (rotation and translations), but in general we can use the lsr stack. So, I assume the user has set RT to what she wants. Then, we will
// just apply RT to the object or whole scene. This method can be used to RESIZE the object (in particular when it is centered).
//(a) Objects:
void transformObject(int _id)   // use maps in the future!!!
{
    BaseObject* ptr_object=NULL;
    for (int i=0; i<scene.totalObjects(); i++) if ((scene.objectArray[i]->ID())==_id) ptr_object=scene.objectArray[i];
    if (ptr_object!=NULL) {
        // Apply the current RT transformation:
        ptr_object->transform(lsr.RT);
        // Them RENDER the object? no, this will be done only when drawing: the reason is that ON TOP of this transform, we may have the GLOBAL MODELVIEW matrix to apply
        //lsr.renderObject(ptr_object);
    }
}
// If we know the object already (this is the output of "begin" method for instance), we can apply the transformation without having to match the ID:
void transformObject(BaseObject* ptr_object)
{
    ptr_object->transform(lsr.RT);
    // lsr.renderObject(ptr_object);
}
// (b) Whole scene:
void transformScene()
{
    scene.transform(lsr.RT); // or equialently: for (int i=0; i<scene.totalObjects(); i++) scene.objectArray[i]->transform(lsr.RT);
    // lsr.renderScene(&scene);
}

// Some elementary transformations (again, note that this if applied too much will make the object eventually deform - it would be much better to do the 
// rotation/translation actions as part of a program IN the mbed, instead of sending commands:
void translateObject(int _id, int xx, int yy, int zz) {
    lsr.pushPoseMatrix();
    lsr.setIdentityPose(); // we could use a wrapper, but I won't for the time being.
    lsr.flipY(); // there is a pb here...
    //lsr.flipX();
    lsr.translate(xx,yy,zz);
    transformObject(_id);
    lsr.popPoseMatrix();
}

void rotateObjectX(int _id, float alpha) {
    lsr.pushPoseMatrix();
    lsr.setIdentityPose(); // we could use a wrapper, but I won't for the time being.
    lsr.flipY();
    //lsr.flipX();
    lsr.rotateX(alpha);
    transformObject(_id);
    lsr.popPoseMatrix();
    }
    
void rotateObjectY(int _id, float alpha){
     lsr.pushPoseMatrix();
    lsr.setIdentityPose(); // we could use a wrapper, but I won't for the time being.
    lsr.flipY();
    //lsr.flipX();
    lsr.rotateY(alpha);
    transformObject(_id);
    lsr.popPoseMatrix();
    }
    
void rotateObjectZ(int _id, float alpha) { lsr.pushPoseMatrix();
    lsr.setIdentityPose(); // we could use a wrapper, but I won't for the time being.
    lsr.flipY();
    //lsr.flipX();
    lsr.rotateZ(alpha);
    transformObject(_id);
    lsr.popPoseMatrix();
    }
 
// =======================================================================================================

//(2) Apply current RT transformation and render but KEEPING the original values of the 3d points:
//(a) Objects:
void drawObject(int _id)   // use maps in the future!!!
{
    BaseObject* ptr_object=NULL;
    for (int i=0; i<scene.totalObjects(); i++) if ((scene.objectArray[i]->ID())==_id) ptr_object=scene.objectArray[i];
    if (ptr_object!=NULL) {
        // We don't apply the current RT transformation (ptr_object->transform(lsr.RT)), but instead we directly RENDER it with a pre-transformation:
        lsr.renderObject(ptr_object, lsr.RT);
    }
}
// If we know the object already (this is the output of "begin" method for instance), we can apply the transformation without having to match the ID:
void drawObject(BaseObject* ptr_object)
{
    //ptr_object->transform(lsr.RT);
    lsr.renderObject(ptr_object, lsr.RT);
}
// (b) Whole scene:
// ATTENTION: we may need START the display engine the FIRST time or nothing will be seeing (this is done by calling startDisplay()).
void drawScene()
{
   //NOTE: there is no need, AND IT IS better NOT to stop/resume the display engine (detaching and reattaching the ISR). 
   // There is no need in principle, because we only call drawScene AFTER the scene to draw has been passed to the laserSensingDisplay object. This means
   // that the number of objects and points per object did NOT change. 
    //scene.transform(lsr.RT); // or equialently: for (int i=0; i<scene.totalObjects(); i++) scene.objectArray[i]->transform(lsr.RT);
    lsr.renderScene(&scene, lsr.RT);
}

// Color attribute transformation (for the time being, only to the whole scene):
void changeColorScene(unsigned char _color) {
    for (int i=0; i<scene.totalObjects(); i++) scene.objectArray[i]->setColor(_color);
}

// =================================   OBJECT PRIMITIVES (this could be in a different file) ============================================================
// NOTE: these methods will not create new objects, unless we explicitly say so (by preceding them with "begin" and ending by "end". 
// Instead, they will add the vertices to the "current object" being created.

// A line:
void line(V3& v0, V3& v1, int npoints)
{
    V3 stepVector=(v1-v0)/(npoints-1);
    for (int i=0; i<npoints; i++) {
        V3 v(v0+stepVector*i);    // vertex added to the current object
        vertex(v);
    }
}
void line(float x0, float y0, float z0, float x1, float y1, float z1, int npoints)
{
    // NOTE: we don't clear the current modelview, but use it's current value.
    V3 A(x0, y0, z0), B(x1, y1, z1);
    line(A, B, npoints);
}
// A line in the z=0 plane:
void line(float x0, float y0, float x1, float y1, int npoints)
{
    // NOTE: we don't clear the current modelview, but use it's current value.
    V3 A(x0, y0, 0), B(x1, y1, 0);
    line(A, B, npoints);
}

// A square in the z=0 plane
void square(float sideSize, int npointsSide)
{
    V3 A(0,0, 0), B(sideSize, 0, 0);
    line(A, B, npointsSide);
    A.x = sideSize;
    A.y = sideSize;
    line(B, A, npointsSide);
    B.x = 0;
    B.y = sideSize;
    line(A, B, npointsSide);
    A.x = 0;
    A.y = 0;
    line(B, A, npointsSide);
}

// A rectangle in the z=0 plane
void rectangle(float sideSizeX, float sideSizeY, int interPointDistance) 
// note: interPointDistance (degrees per point) would be the equivalent of "pixels" separation for the laser projector
{
    V3 A(0,0, 0), B(sideSizeX, 0, 0);
    line(A, B, sideSizeX/interPointDistance);
    A.x = sideSizeX;
    A.y = sideSizeY;
    line(B, A, sideSizeY/interPointDistance);
    B.x = 0;
    B.y = sideSizeY;
    line(A, B, sideSizeX/interPointDistance);
    A.x = 0;
    A.y = 0;
    line(B, A, sideSizeY/interPointDistance);
}



void circle(float radius, int numpoints)
{
    float angleStep=360.0/numpoints;
    lsr.pushPoseMatrix();
    for (int i=0; i<numpoints; i++) {
        vertex(radius, 0, 0);
        lsr.rotateZ(angleStep);
    }
    // Redo the first point to close the cirlce:
    vertex(radius, 0, 0);
    lsr.popPoseMatrix();
}

// A "cube" (with one corner at the origin):
void cube(float sideSize, int nbpointsSide)
{
    lsr.pushPoseMatrix();
    square(sideSize, nbpointsSide);
    line(0,0,0,0,0,sideSize, nbpointsSide);
    lsr.translate(0,0,sideSize);
    square(sideSize, nbpointsSide);
    lsr.popPoseMatrix();
}

// LETTERS and STRINGS:
// NOTE: for the time being, these letters are not special object. I will just add the vertex of the letter to a normal BaseObject.
void letter3d(char _letter, float width, float height)
{
    unsigned char numpoints=fillAuxBuffer(_letter);
    lsr.pushPoseMatrix();
    lsr.resize(0.1*width, 1.0/15.0*height, 1); // note: letter size as defined in textData.h is: width = 10, height = 15
    for (int i=0; i<numpoints; i++ ) vertex(auxbuffer[2*i], auxbuffer[2*i+1], 0);
    lsr.popPoseMatrix();
}

// To create a string: either as a single object, or separate objects. In the later case, we should NOT call to "begin/end", because this will be done
// in THIS wrapping function... how to indicate this? and which index? So, for the time being, I will consider the FIRST case only. If we want to have
// separate objects, we will need to do this explicitly, basically copying this code and interspeeding it with "begin/end" and numbering the objects (cool
// for detecting individual touched letters).
void string3d(string _text, float totalWidth, float height)
{
    float stepX=1.0*totalWidth/_text.length(); // this is just fontWidth, or some percentage of it
    lsr.pushPoseMatrix();
    for (unsigned short i=0; i<_text.length(); i++) {
        char ch=_text.at(i);
        lsr.translate(1.3*stepX,0,0); // slightly larger than the fontWidth...
        if (ch!=' ') letter3d(ch, stepX, height);
    }
    lsr.popPoseMatrix();
}



// ========= MULTI OBJECTS ==================================================================================

// A simple "normalized" grid on the z=0 plane (with points, repeated to get them more "clear").
// Also, it may be interesting to make each group of points a separate object (for switching off laser, but also querying touch)
// Corner of the grid at the origin.
// NOTE: this is an example of a "multi-object" that does NOT need "begin/end" in the main
void grid(int nx, int ny, int repeatpoint)
{
    float px=1.0/(nx-1), py=1.0/(ny-1);
    //lsr.pushPoseMatrix();
    for (int i=0; i<ny; i++) {
      //  pc.printf("\n");
        if (i%2==0) { // to do zig-zag...
            for (int j=0; j<nx; j++) {
                begin(ny*i+j);
                for (int k=0; k<repeatpoint; k++)
                    //vertex(1.0*j*px,1.0*i*py,0); // faster than using translations...
                    vertex(1.0*i*py,1.0*j*px,0);
                end();
                //pc.printf("%4.2f ", 1.0*j*px);
            }
        }  else { // odd line:
            for (int j=nx-1; j>=0; j--) {
                begin(ny*i+j);
                for (int k=0; k<repeatpoint; k++)
                   // vertex(1.0*j*px,1.0*i*py,0); // faster than using translations...
                   vertex(1.0*i*py,1.0*j*px,0); 
                end();
                //pc.printf("%4.2f ", 1.0*j*px);
            }
        }
    }
    //lsr.popPoseMatrix();
}

// A simple "muti-object" grid, not normalized (this is just for convenience):
void grid(float sizeX, float sizeY, int nx, int ny, int repeatpoint)
{
    lsr.pushPoseMatrix();
    lsr.resize(sizeX, sizeY, 1);
    grid(nx, ny, repeatpoint);
    lsr.popPoseMatrix();
}

//Normalized grid of circles:
void gridCircles(int nx, int ny, float radius, int nbpointsCircle)
{
    float px=1.0/(nx-1), py=1.0/(ny-1);
    lsr.pushPoseMatrix();
    for (int i=0; i<ny; i++) {
        if (i%2==0) { // to do zig-zag...
            for (int j=0; j<nx; j++) {
                begin(ny*i+j);
                circle(radius, nbpointsCircle);
                end();
                lsr.translate(px, 0, 0);
            }
        }  else { // odd line:
            for (int j=nx-1; j>=0; j--) {
                begin(ny*i+j);
                circle(radius, nbpointsCircle);
                end();
                lsr.translate(-px, 0, 0);
            }
        }
        lsr.translate(0, py, 0);
    }
    lsr.popPoseMatrix();
}

// Not normalized grid of circles:
void gridCircles(float sizeX, float sizeY, int nx, int ny, float radius, int nbpointsCircle)
{
    float px=sizeX/(nx-1), py=sizeY/(ny-1);
    lsr.pushPoseMatrix();
    for (int i=0; i<ny; i++) {
        if (i%2==0) { // to do zig-zag...
            for (int j=0; j<nx; j++) {
                begin(ny*i+j);
                circle(radius, nbpointsCircle);
                end();
                lsr.translate(px, 0, 0);
            }
        }  else { // odd line:
            for (int j=nx-1; j>=0; j--) {
                lsr.translate(-px, 0, 0);
                begin(ny*i+j);
                circle(radius, nbpointsCircle);
                end();
            }
        }
        lsr.translate(0, py, 0);
    }
    lsr.popPoseMatrix();
}

// WRAPPERS TO CREATE ARBITRARY OBJECTS FROM SENT DATA POINTS (V3 array) =============================================
void createShapeObject(int idobject, vector<V3> &arrayVertices) {
   begin(idobject);
   vertexArray(arrayVertices);
   end();
}

// WRAPPERS TO LOAD OBJECTS FROM SYSTEM FILE =========================================================================
// ... to do

// WRAPPERS TO LOAD MATRICES FROM SYSTEM FILE ==========================================================================
// ...to do

// ================================= WRAPPERS FOR MORE BASIC IO FUNCTIONS   =================================
void showLimitsMirrors(unsigned short pointsPerLine, unsigned short durationSecs)
{
    // Stop the display engine and lasers:
    stopDisplay();
    // but we need to ensure that the DISPLAYING lasers are ON:
     IO.setRGBPower(0x07); 
     IO.showLimitsMirrors(pointsPerLine, durationSecs);
     resumeDisplay();
}

void scanSerial(unsigned short pointsPerLine)
{
    // Stop the display engine and lasers:
    stopDisplay();
    // ...but we need to ensure that the sensing laser is ON:
    IO.setLaserLockinPower(1); 
    IO.scan_serial(pointsPerLine);
    resumeDisplay();
}

void recomputeLookUpTable()
{
    // Stop the display engine and lasers:
    stopDisplay();
    // but we need to ensure that the sensing laser is ON:
    IO.setLaserLockinPower(1); 
    IO.scanLUT(); // this recreates and SAVES the LUT table
    resumeDisplay();
}



// =============================== HARDWARE KNOBS: switches, potentiometers... =================================================
void hardwareKnobs()
{
    // Potentiometer, switches, etc:
    //(1) Check for change of threshold mode button (switch one):
    //!!! ATTENTION: this does not work very well to say the truth: bouncing+adc settings problems...
    bool stateswitch;
    if (IO.switchOneCheck(stateswitch)) {
        if (stateswitch) pc.printf("Set: AUTO threshold mode\n");
        else pc.printf("Set: FIXED threshold mode\n");
        for (int i=0; i< scene.totalObjects(); i++) scene.objectArray[i]->displaySensingBuffer.setThresholdMode((stateswitch? 1 : 0));
    }
    //(2) Check the current pot value and update the "fixed threshold" when pressing second switch (I don't care if the threshold mode
    // is fixed or not - this will set the VALUE of the fixed threshold)
    // NOTE: as  thresholding mode CAN be object dependent though, but this is a simple hardware command that set the fixed threshold for all the objects.
    if (IO.switchTwoCheck(stateswitch)) {
        IO.updatePotValue();
        for (int i=0; i< scene.totalObjects(); i++) scene.objectArray[i]->displaySensingBuffer.setFixedThreshold(IO.potValue);
        pc.printf("Fixed Threshold :%d\n", IO.potValue);
    }
}