view grid.mm @ 37:8ed7522deaaa

Interpolation.
author Robert Tubb <rt300@eecs.qmul.ac.uk>
date Tue, 09 Apr 2013 17:14:31 +0100
parents 790939017078
children 0dfe9e0c01aa
line wrap: on
line source
//
//  grid.cpp
//  oscSenderExample
//
//  Created by Robert Tubb on 03/10/2012.
//
// This is the grid view, i.e. the viewable representation of hilbert surface

#include "grid.h"


#include <sstream>

extern PresetManager presetManager;
extern EventLogger eventLogger;
extern Hilbert hilbert;
//--------------------------------------------------------------
Grid::Grid(): maxValue(pow(32.0,7.0)-1), minValue(30),     paramsPerDim(5), paramBitDepth(7){

    interpLevel = 4;
}
//--------------------------------------------------------------
//--------------------------------------------------------------
Grid::~Grid(){

}
void Grid::init(){

    interpolateMode = INTERPOLATE_GRID;
    
    maxZoom = false;
    minZoom = false;

    pixSize.setCoord(ofGetWidth(), ofGetHeight());
 
    //set scale and position to random
    scale = 15500.0;
    
    smallestGridSpacing = 3; //pixels
    
    snapDist = TwoVector(9,9);
    snapped = false;
    
    size.setCoord(pixSize.x*scale, pixSize.y*scale);
    centre.setCoord(maxValue/2 , maxValue/2);
    topLeft.setCoord(centre.x - size.x/2, centre.y - size.y/2);

    hilbert.init(paramBitDepth, paramsPerDim);

    // set a starting param value, find coord that does it.
    vector<int> params(paramsPerDim*2);
    for(int i = 0;i<2*paramsPerDim;i++){
        midiCC[i] = 12;
        params[i] = 12;
    }
    
    
    TwoVector coord = calculateCoordFromParams(params);
    setCoord(coord);
    
    viewWasChanged();
    calculateInterpolateLevel();
}

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}
//--------------------------------------------------------------
int Grid::calculateInterpolateLevel(){
    if(interpolateMode == INTERPOLATE_GRID){
        // calculate according to smallest gridlines
        // exactly same algorithm as for drawing lines
        // i.e. kinda duplicated code.
        float markpow = pow(paramsPerDim,2.0);
        for(int p = 0; p < paramBitDepth; p++){
            
            double gridSize = pow(markpow,p);
            double divsr = size.x / gridSize;
            
            // return the spacing 
            if( ofGetWidth()/divsr >= smallestGridSpacing){
                interpLevel = p;
                return p;
            }
        }
        interpLevel = 0;
        return 0;
    }else{
        interpLevel = 0;
        return 0; // ???
    }
}
//--------------------------------------------------------------

void Grid::draw(){
    if(interpolateMode == NO_INTERPOLATION){
        drawGridLines();
        drawPresets();
    }else if(interpolateMode == INTERPOLATE_GRID){
        // calculate according to smallest gridlines
        drawGridLines();
        
    }else if(interpolateMode == INTERPOLATE_PRESET){
        drawPresets();
        return; // ???
    }
    
    
    
    // draw centre cross hairs
    drawCrossHairs();
    
    
    // draw the undo history trail, given viewing area
    eventLogger.drawTrail(topLeft, topLeft + size);
}
//--------------------------------------------------------------
void Grid::drawGridLines(){  // draw lines
    // TODO too friggin long
    
    pixSize.setCoord(ofGetWidth(), ofGetHeight()); // incase of rotation?

    int power = 0;
   
    // these get big!
    double  divsr;
    double yl,xl;
    double gridSize = 0;

    double firstLineXPos, lastLineXPos,firstLineYPos, lastLineYPos, remleft, xoffset, yoffset = 0.0;
    double xstart, xfinish, ystart,yfinish;
    // these don't
    int xpos, ypos = 0;
    
    float alpha;
    bool done = false;
    float markpow = 1 << paramsPerDim;  // power that denotes next grid markings e.g. 32
    int lineWidth = 0;
    int colCycle = 0;

 
    // loop thru powers of (base?) smallest to biggest
    // to determine which should be shown as grids
    while (!done){
        gridSize = pow(markpow,power);
 
        colCycle = power % paramBitDepth;

        divsr = size.x / gridSize;

        // if (divisor i.e. number of lines is less than 1000
        if( ofGetWidth()/divsr >= smallestGridSpacing){

            // calculate transparency
            float visCont = log10(divsr*2); // 0 if only 1 line, 3 if 1000 lines
            alpha = 90*(3 - visCont);
            ofSetLineWidth(lineWidth);
            
            // cycle colors for different scales
            if(colCycle == 0){
                ofSetColor(255,255,255,alpha);
            }else if(colCycle == 1){
                ofSetColor(255,0,0,alpha);
            }else if(colCycle == 2){
                ofSetColor(0,0,255,alpha);
            }else if(colCycle == 3){
                ofSetColor(0,255,0,alpha);
            }else if(colCycle == 4){
                ofSetColor(255,0,255,alpha);
            }else if(colCycle == 5){
                ofSetColor(0,255,255,alpha);
            }else if(colCycle == 6){
                ofSetColor(255,255,0,alpha);
            }else if(colCycle == 7){
                ofSetColor(255,255,255,alpha);
            }else{
                cout << "err colour messed up\n";
            }
            
            
            // draw level numbers associated with each color. This would not be true if markpow wasnt 2^d
            ////////-------/////////
            /*
            temp << "THIS LEVEL = " << (int)pow(2.0,power) << "   ";
            string s = temp.str();
            ofDrawBitmapString( s, 10, line );
            line+=30;
            temp.str("");
            *////////-------/////////
            
            // draw presets at this level
            // fill in smallest squares if they contain a preset
            // how to do this efficiently?
            
            
            if(topLeft.x < 0.0){
                firstLineXPos = 0.0;
                xoffset = firstLineXPos - topLeft.x;
                xstart = xoffset/scale;
            }else{
                firstLineXPos = topLeft.x;
                // kinda float version of % operator
                remleft = ceil(firstLineXPos/gridSize);
                xoffset = (remleft * gridSize) - topLeft.x;
                xstart = 0;
            }
            if(topLeft.x + size.x > maxValue){
                lastLineXPos = maxValue+1 - topLeft.x;
                
                
            }else{
                lastLineXPos = size.x;
                
            }
            xfinish = lastLineXPos/scale;
            
            //////////------------------------
            // same for y
            
            if(topLeft.y < 0.0){
                firstLineYPos = 0.0;
                yoffset = firstLineYPos - topLeft.y;
                ystart = yoffset/scale;
            }else{
                firstLineYPos = topLeft.y;
                // kinda float version of % operator
                remleft = ceil(firstLineYPos/gridSize);
                yoffset = (remleft * gridSize) - topLeft.y;
                ystart = 0;
            }
            if(topLeft.y + size.y > maxValue){
                lastLineYPos = maxValue+1 - topLeft.y;
               
            }else{
                lastLineYPos = size.y;
                
            }
            yfinish = lastLineYPos/scale;
            // -------------------------------------------
                // now draw
            for(xl = xoffset; xl <= (lastLineXPos); xl+= gridSize){

                xpos = xl/scale;
                ofLine(xpos, ystart, xpos, yfinish);
            
            }

            for(yl = yoffset; yl <= (lastLineYPos); yl+= gridSize){
                
                ypos = yl/scale;
                ofLine(xstart, ypos, xfinish, ypos);
            }
            
            
        }
        /*else if (divsr < 1){
            // maximum only one line. worth checking so that bigger lines still show up?
            done = true;
        }
*/
        power++;
        if(power > paramBitDepth)
            done = true;

    }
    //cout << "draw done" << "\n";
    //displayInfo();


    
    ////////-------////////
    /*
    ostringstream temp;
    temp << "Centre x = " << centre.x << "\n      y = " << centre.y << "   ";
    string s = temp.str();
    ofDrawBitmapString( s, pixSize.x/2+10, pixSize.y/2+10 );
    */
}
//-----------------------------------------------------------------------
void Grid::drawCrossHairs(){
    // snapping
    TwoVector pos;
    if(snapped){
        pos = coordToPixel(snapCentre);
    }else{
        pos = coordToPixel(centre);
        
    }
    ofSetColor(255, 0, 0);
    ofNoFill();
    ofLine(pos.x-20, pos.y, pos.x+20, pos.y);
    ofLine(pos.x, pos.y-20, pos.x, pos.y+20);
    ofEllipse(pos.x, pos.y, 20, 20);
    ofFill();
}
//-----------------------------------------------------------------------
TwoVector Grid::coordToPixel(TwoVector coord){
    TwoVector pix;
    pix.x = (coord.x - topLeft.x)/scale;
    pix.y = (coord.y - topLeft.y)/scale;
    return pix;
    
}

//-----------------------------------------------------------------------
void Grid::drawPresets(){
    presetManager.drawPresetsInRange(topLeft, topLeft + size);
    // draw snapped preset info
    if(snapped && closestPreset != NULL){
        ofDrawBitmapString( closestPreset->displayTextDescription(), pixSize.x/2+10, pixSize.y/2+10 );
    }
    
}

//-----------------------------------------------------------------------
void Grid::displayInfo(){
    
    // display some "useful information"
    
    ofSetColor(255,255,255,255);
    
    ostringstream temp;
    int line = 10; // text output pos
    
    
    ////////-------/////////
    temp << "scale = " << scale << "   ";
    string s = temp.str();
	ofDrawBitmapString( s, 10, line );
    line+=30;
    temp.str("");
    ////////-------/////////
    temp << "Top Left = " << topLeft.x << "," << topLeft.y << "   ";
    s = temp.str();
    ofDrawBitmapString( s, 10, line );
    line+=60;
    temp.str("");
    
    ////////-------/////////
    temp << "View Size = " << size.x << "," << size.y << "   ";
    s = temp.str();
    ofDrawBitmapString( s, 10, line );
    line+=60;
    temp.str("");
    
    ////////-------/////////
    
    for(int i=0;i<10;i++){ // TODO 5bit specific
        temp << midiCC[i] << " ";
        s = temp.str();
        ofDrawBitmapString( s, 10, line );
        line+=20;
        temp.str("");
    }
}
 //--------------------------------------------------------------
void Grid::update(){  // ?


}

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

void Grid::move(TwoVector moveP){
    // numspacing, pixelspacing stay the same
    
    // convert pixels to surf units
    TwoVector moveS;
    moveS = moveP * scale;
    
    topLeft = topLeft - moveS;  // - because moving to the right means taking away from offset
    centre = centre - moveS;
    
    viewWasChanged();
    eventLogger.logEvent(SCROLL, centre, scale);
    
}
//--------------------------------------------------------------
void Grid::zoom(float factor){
    if(snapped)centre = (centre + snapCentre)*0.5; // clunky
    
    if(maxZoom && factor > 1.0){
        return;
        
    }
    if(factor < 1.0){
        maxZoom = false;
    }
    if(minZoom && factor < 1.0){
        return;
        
    }
    if(factor > 1.0){
        minZoom = false;
    }
    scale = scale*factor; // simple eh?

    // update view size using centre
    // and scale...
    size.x = size.x*factor; // zooming in, size gets SMALLER (view less)
    size.y = size.y*factor;

    
    
    viewWasChanged();
    calculateInterpolateLevel();
    eventLogger.logEvent(ZOOM, centre, scale);

}
//--------------------------------------------------------------
void Grid::shiftCentreToSnapped(){
    // TODO just in case we're freezing something
    // snapping actually change centre
    centre = snapCentre;
    
}
//--------------------------------------------------------------
void Grid::snapCheck(){
    // check environs for presets.
   
    vector<Preset *> closePresets = presetManager.getPresetsInRange(centre - snapDist*scale, centre + snapDist*scale);
    if(closePresets.size() > 0){
        snapped = true;
        // find closest
        double dist, mindist = maxValue;
        closestPreset = closePresets[0];
        
        for(vector<Preset *>::iterator piter = closePresets.begin(); piter < closePresets.end(); piter++){
            dist = (*piter)->coordinates.distanceTo(centre);
            
            if(dist < mindist){
                mindist = dist;
                closestPreset = *piter;
            }
        }
        snapCentre = closestPreset->coordinates;
        eventLogger.logEvent(SNAPPED_TO_PRESET, getCoord(),closestPreset->creationTime );
        //cout << "SNAPPED CHECK\n";
    }else{
        snapped = false;
        closestPreset = NULL;
        snapCentre = centre;
    }

    
}
//--------------------------------------------------------------
void Grid::setMaxZoom(){ // got to smallest (white)
    if(snapped)centre = snapCentre;
    size.x = maxValue*2.0;
    scale = size.x/pixSize.x;
    size.y = scale * pixSize.y;
    maxZoom = true;
    minZoom = false;
    viewWasChanged();
}
//--------------------------------------------------------------
void Grid::setMinZoom(){ // go to entire space (orange)
    if(snapped)centre = snapCentre;
    size.x = minValue*2.0;
    scale = size.x/pixSize.x;
    size.y = scale * pixSize.y;
    minZoom = true;
    maxZoom = false;
    viewWasChanged();
}
//--------------------------------------------------------------
void Grid::viewWasChanged(){
    snapCheck();
    checkLimits();
    // calculate new params?
    vector<int> params;
    if(snapped){
        params = calculateParamsFromCoord(snapCentre);
    }else{
        if(interpolateMode == NO_INTERPOLATION){
            params = calculateParamsFromCoord(centre);
        }else if(interpolateMode == INTERPOLATE_GRID){
            params = calculateInterpolatedParamsFromCoord(centre);
        }
    }
    for(int i = 0;i<2*paramsPerDim;i++){
        midiCC[i] = params[i];
    }

}

//--------------------------------------------------------------
void Grid::checkLimits(){
    // check for maximum zoom level
    

    if(size.x > maxValue*2.0){
        cout << "maxZoom\n";
        maxZoom = true;
        size.x = maxValue*2.0;
        // need to also set y size back to
    }
    if(size.y > maxValue*2.0){
        cout << "maxZoom\n";
        maxZoom = true;
        size.y = maxValue*2.0;
    }
    
    if(size.x < minValue){
        cout << "min Zoom\n";
        minZoom = true;
        size.x = minValue;
        // need to also set y size back to
    }
    if(size.y < minValue){
        minZoom = true;
        cout << "min Zoom\n";
        size.y = minValue;
    }
    
    scale = size.x/pixSize.x;
    size.y = scale * pixSize.y;
    
    topLeft.x = centre.x - size.x/2;
    topLeft.y = centre.y - size.y/2;
    // check for negatives
    
    // allow centre to be at limits
    if((topLeft.x + size.x*0.5) < 0.0){
        cout << "left Wall\n";
        topLeft.x = 0.0 - size.x*0.5;
        centre.x = 0.0;
    }

    if(topLeft.y + size.y*0.5 < 0.0) {
        cout << "top Wall\n";
        topLeft.y = 0.0 - size.y*0.5;
        centre.y = 0.0;
    }
    
   // does topleft refer to lines or view?
    
        // check max 
    if(topLeft.x + size.x/2 > maxValue){
        cout << "right Wall\n";
        topLeft.x = maxValue - size.x/2;
        centre.x = maxValue;
    }
    if(topLeft.y + size.y/2 > maxValue) {
        cout << "bottom Wall\n";
        topLeft.y = maxValue - size.y/2;
        centre.y = maxValue;
    }

}
//--------------------------------------------------------------
void Grid::checkConsistencies(){
    // debug function to check all the parameters are consistent maybe

}
//--------------------------------------------------------------
void Grid::setCoord(TwoVector coord){
    
    centre = coord;
    
    viewWasChanged();
}
//--------------------------------------------------------------
TwoVector Grid::getCoord(){
    // return read point crosshairs
    if(snapped){
        return snapCentre;
    }
    return centre;
}
//--------------------------------------------------------------
vector<int> Grid::getParams(){
    // return a vector with midiCCs in
    // should we store params somewhere and use this as a low computation get ?
    vector<int> params(2*paramsPerDim,0);
    //
    for(int i = 0;i<2*paramsPerDim;i++){
        params[i] = midiCC[i];
    }
    return params;
}
//--------------------------------------------------------------
void Grid::setParams(vector<int> params){
    // input midiCCs, and go to the appropriate coordinate

    for(int i = 0;i<2*paramsPerDim;i++){
        midiCC[i] = 64;
    }
    TwoVector coord = calculateCoordFromParams(params);
    setCoord(coord);
    
    // snapping?
    viewWasChanged();

}
//--------------------------------------------------------------
//--------------------------------------------------------------
//--------------------------------------------------------------
//--------------------------------------------------------------
//--------------------------------------------------------------
#pragma mark const utils

TwoVector Grid::calculateCoordFromParams(vector<int> params){

    vector<int> ccValueX(paramsPerDim);
    vector<int> ccValueY(paramsPerDim);
    for(int i=0;i<paramsPerDim;i++){
        ccValueX[i] = params[i];
        ccValueY[i] = params[i+paramsPerDim];
    }
    
    TwoVector result;
    result.x = hilbert.calculateIndexFromParams(ccValueX);
    result.y = hilbert.calculateIndexFromParams(ccValueY);
    return result;
}

//--------------------------------------------------------------
vector<int> Grid::calculateParamsFromCoord(TwoVector coord){

    if (coord.x > maxValue || coord.y > maxValue){
        cout << "calculateParams Error: centre double value is too large" << "\n";
        vector<int> empty;
        return empty;
    }
    if (coord.x < 0 || coord.y < 0){
        cout << "calculateParams Error: centre double value is negative" << "\n";
        vector<int> empty;
        return empty;
    }

    // X
    vector<int> resultX;
    resultX = hilbert.calculateParamsFromIndex(coord.x);
    // Y
    vector<int> resultY;
    resultY = hilbert.calculateParamsFromIndex(coord.y);
    
    // concatenate
    vector<int> result = resultX;
    
    result.insert( result.end(), resultY.begin(), resultY.end() );
    return result;
}
//--------------------------------------------------------------
vector<int> Grid::calculateInterpolatedParamsFromCoord(TwoVector coord){

    vector<int> result;
    // round by masking according to interpLevel
    
    long long x = coord.x;
    long long y = coord.y;
    cout << interpLevel << "\n";
    int roundDigits = (paramsPerDim*interpLevel);
    // knock off last digits
    x = x >> roundDigits;
    x = x << roundDigits;
    float frac = (coord.x - x)/(1 << roundDigits);
    long long xlower = x;
    long long xupper = x + (1 << roundDigits);

    vector<int> pupper = hilbert.calculateParamsFromIndex(xupper);
    vector<int> plower = hilbert.calculateParamsFromIndex(xlower);
    
    result = interpVector(pupper,plower,frac);
// now Y
    y = y >> roundDigits;
    y = y << roundDigits;
    frac = (coord.y - y)/(1 << roundDigits);
    long long ylower = y;
    long long yupper = y + (1 << roundDigits);
    
    pupper = hilbert.calculateParamsFromIndex(yupper);
    plower = hilbert.calculateParamsFromIndex(ylower);
    
    vector<int> resultY = interpVector(pupper,plower,frac);
// stickem together
    result.insert( result.end(), resultY.begin(), resultY.end() );
    return result;
}
//--------------------------------------------------------------
vector<int> Grid::interpVector(vector<int> upper, vector<int> lower, float frac){
    vector<int> result;
    if(upper.size() != lower.size()){
        cout << "Error: interpVector takes vectors of same length" << "\n";
        return result;
    }
    if(frac > 1){
        cout << "Error: bad fraction for vector interpolation" << "\n";
        return result;
    }
    int N = upper.size();
    for(int i=0; i<N; i++){
        result.push_back(frac*upper[i] + (1-frac)*lower[i]);
    }
    return result;
}
//--------------------------------------------------------------
vector<int> Grid::walkDiff(vector<bool> left, vector<bool> right){
    // horrible
    vector<int> result(2,0);

    int size = left.size();
    for(int i = 0; i < size; i++){
        if(left[i] && !right[i]){  // 1 - 0
            // dim
            result[0] = i;
            result[1] = 1;
        }else if(!left[i] && right[i]){ // 0 - 1
            result[0] = i;
            result[1] = -1;
        }else{ // equal do nothing
            
            
        }
    }
    
    return result;
}
//--------------------------------------------------------------
//--------------------------------------------------------------
//--------------------------------------------------------------