void GameResizeOrMove( NimblePixMap& map ) { WindowWidth = map.width(); WindowHeight = map.height(); // Set PanelWidth so that remaining width is mulitple of 4 PanelWidth = PANEL_MIN_WIDTH+WindowWidth%4; CreateNewArea(); }
void ColorMatrix::buildFrom( const NimblePixMap& map ) { Assert(!myColors); int w = myWidth = map.width(); int h = myHeight = map.height(); myColors = new NimbleColor[myWidth*myHeight]; for( int i=0; i<h; ++i ) for( int j=0; j<w; ++j ) myColors[i*w+j] = map.colorAt(j,i); }
bool DrawCircle(const NimblePixMap& map, NimblePixel color, float x0, float y0, float r, bool dashed) { float w = map.width()-1; float h = map.height()-1; float ymin = std::numeric_limits<float>::infinity(); float ymax = -ymin; ClipQuadrant(ymin, ymax, x0, y0, r, w, h); ClipQuadrant(ymin, ymax, w-x0, y0, r, w, h ); ClipQuadrant(ymin, ymax, x0, h-y0, r, w, h ); ClipQuadrant(ymin, ymax, w-x0, h-y0, r, w, h ); if( ymin>ymax ) return false; float y = ymin; float x = std::sqrt(r*r - ymin*ymin); float d = 2*(x*x + y*y - r*r) + (2*y+1) + (1-2*x); while(y <= ymax) { // FIXME - use lookup table indexed by quotients instead of trig if( !dashed || int(std::atan2(y,x)/(3.1415926535/4)*8+1)&2 ) { // FIXME - really cannot use octant symmetry since sub-pixel accuracy is desired DrawPixel(map, color, x + x0, y + y0); // Octant 1 DrawPixel(map, color, y + x0, x + y0); // Octant 2 DrawPixel(map, color, -x + x0, y + y0); // Octant 4 DrawPixel(map, color, -y + x0, x + y0); // Octant 3 DrawPixel(map, color, -x + x0, -y + y0); // Octant 5 DrawPixel(map, color, -y + x0, -x + y0); // Octant 6 DrawPixel(map, color, x + x0, -y + y0); // Octant 7 DrawPixel(map, color, y + x0, -x + y0); // Octant 8 } ++y; if( d<=0 ) { d += 4*y + 2; } else { --x; d += 4*(y-x) + 2; } } return true; }
static void DrawPixel( const NimblePixMap& map, NimblePixel color, float xf, float yf ) { if( 0<=xf && xf<map.width() && 0<=yf && yf<map.height() ) { *(NimblePixel*)map.at(int(xf),int(yf)) = color; } }
//! Draw the potential field on the given map void DrawPotentialFieldBilinear(const NimblePixMap& map) { using namespace Universe; int w = map.width(); int h = map.height(); size_t n = NParticle; float cutoffRadius = 8*ViewScale*PATCH_SIZE; // FIXME - deal with pixels near boundary that do not lie in a complete patch. for(int i0=0; i0<h; i0+=PATCH_SIZE) { int iSize = std::min(PATCH_SIZE,h-i0); for(int j0=0; j0<w; j0+=PATCH_SIZE) { float a00 = 0, a01 = 0, a10 = 0, a11 = 0; float y0 = ViewOffsetY + ViewScale*i0; float x0 = ViewOffsetX + ViewScale*j0; float y1 = ViewOffsetY + ViewScale*(i0 + PATCH_SIZE); float x1 = ViewOffsetX + ViewScale*(j0 + PATCH_SIZE); float xm = 0.5f*(x0+x1); float ym = 0.5f*(y0+y1); size_t nearN = 0; for(size_t k=0; k<n; ++k) { float sx = Sx[k]; float sy = Sy[k]; if( std::sqrt(Dist2(sx,sy,xm,ym)) <= cutoffRadius ) { // Use particle exactly NearX[nearN] = sx; NearY[nearN] = sy; NearCharge[nearN] = Charge[k]; ++nearN; } else { // Use bilinear interpolation a00 += PotentialAt(k, x0, y0); a01 += PotentialAt(k, x0, y1); a10 += PotentialAt(k, x1, y0); a11 += PotentialAt(k, x1, y1); } } float x[PATCH_SIZE], p[PATCH_SIZE]; for(int j=0; j<PATCH_SIZE; ++j) { x[j] = ViewOffsetX + ViewScale*(j0+j); } // Work one row at a time to optimize cache usage // j-loops really only have to do jSize iterations, but do PATCH_SIZE anyway on theory // that it avoids remainder loops. int jSize = std::min(PATCH_SIZE, w-j0); for(int i=0; i<iSize; ++i) { // Set potential accumulator to bilinear interpolation values float fy = i*(1.0f/PATCH_SIZE); float b0 = a00*(1-fy) + a01*fy; float b1 = ((a10*(1-fy) + a11*fy) - b0)*(1.0f/PATCH_SIZE); for(int j=0; j<PATCH_SIZE; ++j) { float fx = j*(1.0f/PATCH_SIZE); p[j] = b0 + b1*j; } // Do loop over particles as middle loop to hide latency of summation. float y = ViewOffsetY + ViewScale*(i0+i); for(size_t k=0; k<nearN; ++k) { float dy = NearY[k]-y; float q = NearCharge[k]; // Inner loop. for(int j=0; j<PATCH_SIZE; ++j) { float dx = NearX[k]-x[j]; float r = std::sqrt(dx*dx+dy*dy); p[j] += q/r; } } DrawPotentialRow((NimblePixel*)map.at(j0, i0+i), p, jSize); } } } }
void GameUpdateDraw( NimblePixMap& map, NimbleRequest request ) { #if WRITING_DOCUMENTATION new(&TheMap) NimblePixMap( map ); #endif /* WRITING_DOCUMENTATION */ WavefieldRect = NimbleRect(PanelWidth,map.height()/2,map.width(),map.height()); NimblePixMap subsurface(map,WavefieldRect); NimblePixMap seismogramClip( map,NimbleRect(PanelWidth,0,map.width(),map.height()/2) ); if( request & NimbleUpdate ) { ShowGeology.update(); ShowSeismic.update(); } const NimbleRequest pausedRequest = IsPaused ? request-NimbleUpdate : request; SeismogramUpdateDraw( seismogramClip, pausedRequest&NimbleUpdate, TheColorFunc, IsAutoGainOn ); WavefieldFunctor wf(subsurface, pausedRequest); SeismogramFunctor sf( seismogramClip, pausedRequest&NimbleDraw ); ReservoirFunctor rf( pausedRequest ); #if PARALLEL tbb::parallel_invoke( wf, sf, rf ); #else wf(); sf(); rf(); #endif if( pausedRequest & NimbleUpdate ) { DrillBit.update(); UpdateDuckAndRig(); } if( request & NimbleDraw ) { ClickableSetEnd = ClickableSet; const int wavefieldWidth = map.width()-PanelWidth; Assert(wavefieldWidth%4==0); const int wavefieldHeight = map.height()/2; ReservoirDrawHoles( subsurface ); if( DrillY>0 ) DrillBit.drawOn( subsurface, RigX-DrillBit.width()/2, DrillY-5 ); if( ShowReservoir ) ReservoirDraw( subsurface ); NimblePixMap spriteClip( map, NimbleRect( PanelWidth, 0, map.width(), map.height() ));\ NimblePixel greenPixel = map.pixel( NimbleColor( 0, NimbleColor::FULL, 0 )); NimblePixel redPixel = map.pixel( NimbleColor( NimbleColor::FULL, 0, 0 )); const int lineHeight = map.height()/2-1; if( CultureBeginX<CultureEndX ) { spriteClip.draw( NimbleRect( 0, lineHeight, CultureBeginX, lineHeight+1 ), greenPixel ); spriteClip.draw( NimbleRect( CultureBeginX, lineHeight-1, CultureEndX, lineHeight+2 ), redPixel ); spriteClip.draw( NimbleRect( CultureEndX, lineHeight, spriteClip.width(), lineHeight+1 ), greenPixel ); Culture.drawOn( spriteClip, CultureBeginX+(CultureEndX-CultureBeginX)/2-Culture.width()/2, map.height()/2-Culture.height() ); } else { spriteClip.draw( NimbleRect( 0, lineHeight, spriteClip.width(), lineHeight+1 ), greenPixel ); } OilRig->drawOn( spriteClip, RigX-OilRig->width()/2, OilRigVerticalOffset ); if( DuckGoingLeft ) DuckLeft.drawOn( spriteClip, DuckX-50, map.height()/2-DuckLeft.height()+12 ); else DuckRight.drawOn( spriteClip, DuckX-(DuckRight.width()-50), map.height()/2-DuckLeft.height()+12 ); NimblePixMap panelClip( map, NimbleRect( 0, 0, PanelWidth, map.height() )); PanelBackground.drawOn( panelClip ); int cashMeterY = map.height()- 50 -CashMeter.height(); int levelMeterY = cashMeterY - 10 - LevelMeter.height(); // The Min is there so that if there is room, the green line artistically lines up // with the top of the fluid meters. int fluidMeterY = Min(levelMeterY - 10 - WaterMeter.height(), map.height()/2 ); if( ScoreState.isDisplayingScore() ) { LevelMeter.drawOn( map, PanelWidth/2-LevelMeter.width()/2, levelMeterY ); CashMeter.drawOn( map, PanelWidth/2-CashMeter.width()/2, cashMeterY ); } int meterMarginX = (PanelWidth-WaterMeter.width()-OilMeter.width()-GasMeter.width())/4; WaterMeter.drawOn( map, meterMarginX, fluidMeterY ); OilMeter.drawOn( map, PanelWidth/2-OilMeter.width()/2, fluidMeterY ); GasMeter.drawOn( map, PanelWidth-meterMarginX-GasMeter.width(), fluidMeterY ); if( VisibleDialog ) { if( VisibleDialog==&TheAboutTheAuthorDialog || VisibleDialog==&TheLevelContinueDialog ) // Center it on screen VisibleDialog->drawOn( map, map.width()/2-VisibleDialog->width()/2, map.height()/2-VisibleDialog->height()/2 ); else if( VisibleDialog==&TheKeyboardHelpDialog ) // Put in upper right corner VisibleDialog->drawOn( map, map.width()*.95f-VisibleDialog->width(), map.height()*.05f ); else // Put it in upper left portion of seismogram VisibleDialog->drawOn( map, PanelWidth+24, 24 ); } int tabTop1 = 50; int tabTop2 = tabTop1+2*TheFont.height(); int tabLeft1 = PanelWidth/8; int tabLeft2 = PanelWidth*5/8; int airGunTop = tabTop2+2*TheFont.height();; AirgunMeter.drawOn( map, PanelWidth/2-AirgunMeter.width()/2, airGunTop ); if( ShowFrameRate ) { FrameRateMeter.setValue( EstimateFrameRate() ); FrameRateMeter.drawOn( map, PanelWidth/2-FrameRateMeter.width()/2, fluidMeterY-FrameRateMeter.height()-15 ); } const int tabSpacing = PanelWidth/4; DrawClickable( TheFileMenu, map, tabLeft1, tabTop1 ); DrawClickable( TheHelpMenu, map, tabLeft2, tabTop1 ); // The "isTabbed" tests below do some ad-hoc occlusion testing. if( TheFileMenu.isTabbed() ) DrawClickable( TheModelMenu, map, tabLeft1, tabTop2 ); if( TheFileMenu.isTabbed() && TheHelpMenu.isTabbed() ) DrawClickable( TheViewMenu, map, tabLeft2, tabTop2 ); } ScoreState.update(); }