Example #1
0
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);
            }
        }
    }
}