Beispiel #1
0
void TerrainNode::update(ptr<SceneNode> owner)
{
    deformedCameraPos = owner->getLocalToCamera().inverse() * vec3d::ZERO;
    SceneManager::getFrustumPlanes(owner->getLocalToScreen(), deformedFrustumPlanes);
    localCameraPos = deform->deformedToLocal(deformedCameraPos);

    mat4d m = deform->localToDeformedDifferential(localCameraPos, true);
    distFactor = max(vec3d(m[0][0], m[1][0], m[2][0]).length(), vec3d(m[0][1], m[1][1], m[2][1]).length());

    ptr<FrameBuffer> fb = SceneManager::getCurrentFrameBuffer();
    vec3d left = deformedFrustumPlanes[0].xyz().normalize();
    vec3d right = deformedFrustumPlanes[1].xyz().normalize();
    float fov = (float) safe_acos(-left.dotproduct(right));
    splitDist = splitFactor * fb->getViewport().z / 1024.0f * tan(40.0f / 180.0f * M_PI) / tan(fov / 2.0f);
    if (splitDist < 1.1f || !(isFinite(splitDist))) {
        splitDist = 1.1f;
    }

    // initializes data structures for horizon occlusion culling
    if (horizonCulling && localCameraPos.z <= root->zmax) {
        vec3d deformedDir = owner->getLocalToCamera().inverse() * vec3d::UNIT_Z;
        vec2d localDir = (deform->deformedToLocal(deformedDir) - localCameraPos).xy().normalize();
        localCameraDir = mat2f(localDir.y, -localDir.x, -localDir.x, -localDir.y);
        for (int i = 0; i < HORIZON_SIZE; ++i) {
            horizon[i] = -INFINITY;
        }
    }

    root->update();
}
Beispiel #2
0
static MT_Vector3 MatrixToAxisAngle(const MT_Matrix3x3& R)
{
	MT_Vector3 delta = MT_Vector3(R[2][1] - R[1][2],
	                              R[0][2] - R[2][0],
	                              R[1][0] - R[0][1]);

	MT_Scalar c = safe_acos((R[0][0] + R[1][1] + R[2][2] - 1)/2);
	MT_Scalar l = delta.length();
	
	if (!MT_fuzzyZero(l))
		delta *= c/l;
	
	return delta;
}
double PlanetViewController::interpolate(double sx0, double sy0, double stheta, double sphi, double sd,
            double dx0, double dy0, double dtheta, double dphi, double dd, double t)
{
    vec3d s = vec3d(cos(sx0) * cos(sy0), sin(sx0) * cos(sy0), sin(sy0));
    vec3d e = vec3d(cos(dx0) * cos(dy0), sin(dx0) * cos(dy0), sin(dy0));
    double dist = max(safe_acos(s.dotproduct(e)) * R, 1e-3);

    t = min(t + min(0.1, 5000.0 / dist), 1.0);
    double T = 0.5 * atan(4.0 * (t - 0.5)) / atan(4.0 * 0.5) + 0.5;

    interpolateDirection(sx0, sy0, dx0, dy0, T, x0, y0);
    interpolateDirection(sphi, stheta, dphi, dtheta, T, phi, theta);

    const double W = 10.0;
    d = sd * (1.0 - t) + dd * t + dist * (exp(-W * (t - 0.5) * (t - 0.5)) - exp(-W * 0.25));

    return t;
}
Beispiel #4
0
void EditorHandler::redisplay(double t, double dt)
{
    if (editors.empty()) {
        return;
    }

    if (update) {
        for (unsigned int i = 0; i < editors.size(); ++i) {
            if (editors[i]->isActive()) {
                editors[i]->update();
            }
        }
        update = false;
    }
    // mouse in world space
    vec3d p = getPosition(lastPos.x, lastPos.y);

    // camera in local space, altitude in local space
    float altitude = -1.0f;
    for (unsigned int i = 0; i < editors.size(); ++i) {
        if (editors[i]->isActive()) {
            mat4d screenToLocal = editors[i]->getTerrain()->getLocalToScreen().inverse();
            vec4d c = screenToLocal * vec4d(0.0, 0.0, 1.0, 0.0);
            double cx = c.x / c.w;
            double cy = c.y / c.w;
            double cz = c.z / c.w;
            if (isFinite(cx) && isFinite(cy)) {
                vec3d dv = editors[i]->getTerrainNode()->deform->deformedToLocal(vec3d(cx, cy, cz));
                if (isFinite(dv.z)) {
                    altitude = dv.z;
                    break;
                }
            }
        }
    }

    // field of view angle
    vec4d frustum[6];
    ptr<SceneManager> manager = editors[0]->getTerrain()->getOwner();
    SceneManager::getFrustumPlanes(manager->getCameraToScreen(), frustum);
    vec3d left = frustum[0].xyz().normalize();
    vec3d right = frustum[1].xyz().normalize();
    float fov = (float) safe_acos(-left.dotproduct(right));

    // pencil radius
    radius = altitude * tan(fov / 2.0) * relativeRadius;

    for (unsigned int i = 0; i < editors.size(); ++i) {
        if (editors[i]->isActive()) {
            editors[i]->setPencil(vec4f(p.x, p.y, p.z, radius), vec4f(brushColor[0], brushColor[1], brushColor[2], brushColor[3]), paint);
        }
    }
    if (newStrokes > 0) {
        for (unsigned int i = 0; i < editors.size(); ++i) {
            if (editors[i]->isActive()) {
                editors[i]->edit(strokes);
            }
        }
    }
    newStrokes = 0;
}
static float angle(const MT_Vector3& v1, const MT_Vector3& v2)
{
	return safe_acos(v1.dot(v2));
}
Beispiel #6
0
bool
TextureSystemImpl::environment(TextureHandle* texture_handle_,
                               Perthread* thread_info_, TextureOpt& options,
                               const Imath::V3f& _R, const Imath::V3f& _dRdx,
                               const Imath::V3f& _dRdy, int nchannels,
                               float* result, float* dresultds,
                               float* dresultdt)
{
    // Handle >4 channel lookups by recursion.
    if (nchannels > 4) {
        int save_firstchannel = options.firstchannel;
        while (nchannels) {
            int n   = std::min(nchannels, 4);
            bool ok = environment(texture_handle_, thread_info_, options, _R,
                                  _dRdx, _dRdy, n, result, dresultds,
                                  dresultdt);
            if (!ok)
                return false;
            result += n;
            if (dresultds)
                dresultds += n;
            if (dresultdt)
                dresultdt += n;
            options.firstchannel += n;
            nchannels -= n;
        }
        options.firstchannel = save_firstchannel;  // restore what we changed
        return true;
    }

    PerThreadInfo* thread_info = m_imagecache->get_perthread_info(
        (PerThreadInfo*)thread_info_);
    TextureFile* texturefile = verify_texturefile((TextureFile*)texture_handle_,
                                                  thread_info);
    ImageCacheStatistics& stats(thread_info->m_stats);
    ++stats.environment_batches;
    ++stats.environment_queries;

    if (!texturefile || texturefile->broken())
        return missing_texture(options, nchannels, result, dresultds,
                               dresultdt);

    const ImageSpec& spec(texturefile->spec(options.subimage, 0));

    // Environment maps dictate particular wrap modes
    options.swrap = texturefile->m_sample_border
                        ? TextureOpt::WrapPeriodicSharedBorder
                        : TextureOpt::WrapPeriodic;
    options.twrap = TextureOpt::WrapClamp;

    options.envlayout  = LayoutLatLong;
    int actualchannels = Imath::clamp(spec.nchannels - options.firstchannel, 0,
                                      nchannels);

    // Initialize results to 0.  We'll add from here on as we sample.
    for (int c = 0; c < nchannels; ++c)
        result[c] = 0;
    if (dresultds) {
        for (int c = 0; c < nchannels; ++c)
            dresultds[c] = 0;
        for (int c = 0; c < nchannels; ++c)
            dresultdt[c] = 0;
    }
    // If the user only provided us with one pointer, clear both to simplify
    // the rest of the code, but only after we zero out the data for them so
    // they know something went wrong.
    if (!(dresultds && dresultdt))
        dresultds = dresultdt = NULL;

    // Calculate unit-length vectors in the direction of R, R+dRdx, R+dRdy.
    // These define the ellipse we're filtering over.
    Imath::V3f R = _R;
    R.normalize();  // center
    Imath::V3f Rx = _R + _dRdx;
    Rx.normalize();  // x axis of the ellipse
    Imath::V3f Ry = _R + _dRdy;
    Ry.normalize();  // y axis of the ellipse
    // angles formed by the ellipse axes.
    float xfilt_noblur = std::max(safe_acos(R.dot(Rx)), 1e-8f);
    float yfilt_noblur = std::max(safe_acos(R.dot(Ry)), 1e-8f);
    int naturalres = int((float)M_PI / std::min(xfilt_noblur, yfilt_noblur));
    // FIXME -- figure naturalres sepearately for s and t
    // FIXME -- ick, why is it x and y at all, shouldn't it be s and t?
    // N.B. naturalres formulated for latlong

    // Account for width and blur
    float xfilt = xfilt_noblur * options.swidth + options.sblur;
    float yfilt = yfilt_noblur * options.twidth + options.tblur;

    // Figure out major versus minor, and aspect ratio
    Imath::V3f Rmajor;  // major axis
    float majorlength, minorlength;
    bool x_is_majoraxis = (xfilt >= yfilt);
    if (x_is_majoraxis) {
        Rmajor      = Rx;
        majorlength = xfilt;
        minorlength = yfilt;
    } else {
        Rmajor      = Ry;
        majorlength = yfilt;
        minorlength = xfilt;
    }

    sampler_prototype sampler;
    long long* probecount;
    switch (options.interpmode) {
    case TextureOpt::InterpClosest:
        sampler    = &TextureSystemImpl::sample_closest;
        probecount = &stats.closest_interps;
        break;
    case TextureOpt::InterpBilinear:
        sampler    = &TextureSystemImpl::sample_bilinear;
        probecount = &stats.bilinear_interps;
        break;
    case TextureOpt::InterpBicubic:
        sampler    = &TextureSystemImpl::sample_bicubic;
        probecount = &stats.cubic_interps;
        break;
    default:
        sampler    = &TextureSystemImpl::sample_bilinear;
        probecount = &stats.bilinear_interps;
        break;
    }

    TextureOpt::MipMode mipmode = options.mipmode;
    bool aniso                  = (mipmode == TextureOpt::MipModeDefault
                  || mipmode == TextureOpt::MipModeAniso);

    float aspect, trueaspect, filtwidth;
    int nsamples;
    float invsamples;
    if (aniso) {
        aspect    = anisotropic_aspect(majorlength, minorlength, options,
                                    trueaspect);
        filtwidth = minorlength;
        if (trueaspect > stats.max_aniso)
            stats.max_aniso = trueaspect;
        nsamples   = std::max(1, (int)ceilf(aspect - 0.25f));
        invsamples = 1.0f / nsamples;
    } else {
        filtwidth  = options.conservative_filter ? majorlength : minorlength;
        nsamples   = 1;
        invsamples = 1.0f;
    }

    ImageCacheFile::SubimageInfo& subinfo(
        texturefile->subimageinfo(options.subimage));
    int min_mip_level = subinfo.min_mip_level;

    // FIXME -- assuming latlong
    bool ok   = true;
    float pos = -0.5f + 0.5f * invsamples;
    for (int sample = 0; sample < nsamples; ++sample, pos += invsamples) {
        Imath::V3f Rsamp = R + pos * Rmajor;
        float s, t;
        vector_to_latlong(Rsamp, texturefile->m_y_up, s, t);

        // Determine the MIP-map level(s) we need: we will blend
        //  data(miplevel[0]) * (1-levelblend) + data(miplevel[1]) * levelblend
        int miplevel[2]  = { -1, -1 };
        float levelblend = 0;

        int nmiplevels = (int)subinfo.levels.size();
        for (int m = min_mip_level; m < nmiplevels; ++m) {
            // Compute the filter size in raster space at this MIP level.
            // Filters are in radians, and the vertical resolution of a
            // latlong map is PI radians.  So to compute the raster size of
            // our filter width...
            float filtwidth_ras = subinfo.spec(m).full_height * filtwidth
                                  * M_1_PI;
            // Once the filter width is smaller than one texel at this level,
            // we've gone too far, so we know that we want to interpolate the
            // previous level and the current level.  Note that filtwidth_ras
            // is expected to be >= 0.5, or would have stopped one level ago.
            if (filtwidth_ras <= 1) {
                miplevel[0] = m - 1;
                miplevel[1] = m;
                levelblend  = Imath::clamp(2.0f * filtwidth_ras - 1.0f, 0.0f,
                                          1.0f);
                break;
            }
        }
        if (miplevel[1] < 0) {
            // We'd like to blur even more, but make due with the coarsest
            // MIP level.
            miplevel[0] = nmiplevels - 1;
            miplevel[1] = miplevel[0];
            levelblend  = 0;
        } else if (miplevel[0] < min_mip_level) {
            // We wish we had even more resolution than the finest MIP level,
            // but tough for us.
            miplevel[0] = min_mip_level;
            miplevel[1] = min_mip_level;
            levelblend  = 0;
        }
        if (options.mipmode == TextureOpt::MipModeOneLevel) {
            // Force use of just one mipmap level
            miplevel[1] = miplevel[0];
            levelblend  = 0;
        } else if (mipmode == TextureOpt::MipModeNoMIP) {
            // Just sample from lowest level
            miplevel[0] = min_mip_level;
            miplevel[1] = min_mip_level;
            levelblend  = 0;
        }

        float levelweight[2] = { 1.0f - levelblend, levelblend };

        int npointson = 0;
        for (int level = 0; level < 2; ++level) {
            if (!levelweight[level])
                continue;
            ++npointson;
            int lev = miplevel[level];
            if (options.interpmode == TextureOpt::InterpSmartBicubic) {
                if (lev == 0
                    || (texturefile->spec(options.subimage, lev).full_height
                        < naturalres / 2)) {
                    sampler = &TextureSystemImpl::sample_bicubic;
                    ++stats.cubic_interps;
                } else {
                    sampler = &TextureSystemImpl::sample_bilinear;
                    ++stats.bilinear_interps;
                }
            } else {
                *probecount += 1;
            }

            OIIO_SIMD4_ALIGN float sval[4] = { s, 0.0f, 0.0f, 0.0f };
            OIIO_SIMD4_ALIGN float tval[4] = { t, 0.0f, 0.0f, 0.0f };
            OIIO_SIMD4_ALIGN float weight[4]
                = { levelweight[level] * invsamples, 0.0f, 0.0f, 0.0f };
            vfloat4 r, drds, drdt;
            ok &= (this->*sampler)(1, sval, tval, miplevel[level], *texturefile,
                                   thread_info, options, nchannels,
                                   actualchannels, weight, &r,
                                   dresultds ? &drds : NULL,
                                   dresultds ? &drdt : NULL);
            for (int c = 0; c < nchannels; ++c)
                result[c] += r[c];
            if (dresultds) {
                for (int c = 0; c < nchannels; ++c) {
                    dresultds[c] += drds[c];
                    dresultdt[c] += drdt[c];
                }
            }
        }
    }
    stats.aniso_probes += nsamples;
    ++stats.aniso_queries;

    if (actualchannels < nchannels && options.firstchannel == 0
        && m_gray_to_rgb)
        fill_gray_channels(spec, nchannels, result, dresultds, dresultdt);

    return ok;
}
Beispiel #7
0
bool DrawOceanTask::Impl::run()
{
    if (Logger::DEBUG_LOGGER != NULL) {
        Logger::DEBUG_LOGGER->log("OCEAN", "DrawOcean");
    }
    ptr<FrameBuffer> fb = SceneManager::getCurrentFrameBuffer();
    ptr<Program> prog = SceneManager::getCurrentProgram();

    if (o->nbWavesU == NULL) {
        o->nbWavesU = prog->getUniform1f("nbWaves");
        o->wavesU = prog->getUniformSampler("wavesSampler");
        o->cameraToOceanU = prog->getUniformMatrix4f("cameraToOcean");
        o->screenToCameraU = prog->getUniformMatrix4f("screenToCamera");
        o->cameraToScreenU = prog->getUniformMatrix4f("cameraToScreen");
        o->oceanToCameraU = prog->getUniformMatrix3f("oceanToCamera");
        o->oceanToWorldU = prog->getUniformMatrix4f("oceanToWorld");
        o->oceanCameraPosU = prog->getUniform3f("oceanCameraPos");
        o->oceanSunDirU = prog->getUniform3f("oceanSunDir");
        o->horizon1U = prog->getUniform3f("horizon1");
        o->horizon2U = prog->getUniform3f("horizon2");
        o->timeU = prog->getUniform1f("time");
        o->radiusU = prog->getUniform1f("radius");
        o->heightOffsetU = prog->getUniform1f("heightOffset");
        o->lodsU = prog->getUniform4f("lods");

        assert(o->nbWavesU != NULL);
        o->generateWaves();
    }

    vector< ptr<TileSampler> > uniforms;
    SceneNode::FieldIterator ui = n->getFields();
    while (ui.hasNext()) {
        ptr<TileSampler> u = ui.next().cast<TileSampler>();
        if (u != NULL && u->getTerrain(0) != NULL) {
            u->setTileMap();
        }
    }

    // compute ltoo = localToOcean transform, where ocean frame = tangent space at
    // camera projection on sphere o->radius in local space
    mat4d ctol = n->getLocalToCamera().inverse();
    vec3d cl = ctol * vec3d::ZERO; // camera in local space

    if ((o->radius == 0.0 && cl.z > o->zmin) ||
        (o->radius > 0.0 && cl.length() > o->radius + o->zmin) ||
        (o->radius < 0.0 && vec2d(cl.y, cl.z).length() < -o->radius - o->zmin))
    {
        o->oldLtoo = mat4d::IDENTITY;
        o->offset = vec3d::ZERO;
        return true;
    }

    vec3d ux, uy, uz, oo;

    if (o->radius == 0.0) {
        // flat ocean
        ux = vec3d::UNIT_X;
        uy = vec3d::UNIT_Y;
        uz = vec3d::UNIT_Z;
        oo = vec3d(cl.x, cl.y, 0.0);
    } else if (o->radius > 0.0) {
        // spherical ocean
        uz = cl.normalize(); // unit z vector of ocean frame, in local space
        if (o->oldLtoo != mat4d::IDENTITY) {
            ux = vec3d(o->oldLtoo[1][0], o->oldLtoo[1][1], o->oldLtoo[1][2]).crossProduct(uz).normalize();
        } else {
            ux = vec3d::UNIT_Z.crossProduct(uz).normalize();
        }
        uy = uz.crossProduct(ux); // unit y vector
        oo = uz * o->radius; // origin of ocean frame, in local space
    } else {
        // cylindrical ocean
        uz = vec3d(0.0, -cl.y, -cl.z).normalize();
        ux = vec3d::UNIT_X;
        uy = uz.crossProduct(ux);
        oo = vec3d(cl.x, 0.0, 0.0) + uz * o->radius;
    }

    mat4d ltoo = mat4d(
        ux.x, ux.y, ux.z, -ux.dotproduct(oo),
        uy.x, uy.y, uy.z, -uy.dotproduct(oo),
        uz.x, uz.y, uz.z, -uz.dotproduct(oo),
        0.0,  0.0,  0.0,  1.0);
    // compute ctoo = cameraToOcean transform
    mat4d ctoo = ltoo * ctol;

    if (o->oldLtoo != mat4d::IDENTITY) {
        vec3d delta = ltoo * (o->oldLtoo.inverse() * vec3d::ZERO);
        o->offset += delta;
    }
    o->oldLtoo = ltoo;

    mat4d ctos = n->getOwner()->getCameraToScreen();
    mat4d stoc = ctos.inverse();
    vec3d oc = ctoo * vec3d::ZERO;

    if (o->oceanSunDirU != NULL) {
        // TODO how to get sun dir in a better way?
        SceneManager::NodeIterator i = n->getOwner()->getNodes("light");
        if (i.hasNext()) {
            ptr<SceneNode> l = i.next();
            vec3d worldSunDir = l->getLocalToParent() * vec3d::ZERO;
            vec3d oceanSunDir = ltoo.mat3x3() * (n->getWorldToLocal().mat3x3() * worldSunDir);
            o->oceanSunDirU->set(oceanSunDir.cast<float>());
        }
    }

    vec4<GLint> screen = fb->getViewport();

    vec4d frustum[6];
    SceneManager::getFrustumPlanes(ctos, frustum);
    vec3d left = frustum[0].xyz().normalize();
    vec3d right = frustum[1].xyz().normalize();
    float fov = (float) safe_acos(-left.dotproduct(right));
    float pixelSize = atan(tan(fov / 2.0f) / (screen.w / 2.0f)); // angle under which a screen pixel is viewed from the camera

    o->cameraToOceanU->setMatrix(ctoo.cast<float>());
    o->screenToCameraU->setMatrix(stoc.cast<float>());
    o->cameraToScreenU->setMatrix(ctos.cast<float>());
    o->oceanToCameraU->setMatrix(ctoo.inverse().mat3x3().cast<float>());
    o->oceanCameraPosU->set(vec3f(float(-o->offset.x), float(-o->offset.y), float(oc.z)));
    if (o->oceanToWorldU != NULL) {
        o->oceanToWorldU->setMatrix((n->getLocalToWorld() * ltoo.inverse()).cast<float>());
    }

    if (o->horizon1U != NULL) {
        float h = oc.z;
        vec3d A0 = (ctoo * vec4d((stoc * vec4d(0.0, 0.0, 0.0, 1.0)).xyz(), 0.0)).xyz();
        vec3d dA = (ctoo * vec4d((stoc * vec4d(1.0, 0.0, 0.0, 0.0)).xyz(), 0.0)).xyz();
        vec3d B = (ctoo * vec4d((stoc * vec4d(0.0, 1.0, 0.0, 0.0)).xyz(), 0.0)).xyz();
        if (o->radius == 0.0) {
            o->horizon1U->set(vec3f(-(h * 1e-6 + A0.z) / B.z, -dA.z / B.z, 0.0));
            o->horizon2U->set(vec3f::ZERO);
        } else {
            double h1 = h * (h + 2.0 * o->radius);
            double h2 = (h + o->radius) * (h + o->radius);
            double alpha = B.dotproduct(B) * h1 - B.z * B.z * h2;
            double beta0 = (A0.dotproduct(B) * h1 - B.z * A0.z * h2) / alpha;
            double beta1 = (dA.dotproduct(B) * h1 - B.z * dA.z * h2) / alpha;
            double gamma0 = (A0.dotproduct(A0) * h1 - A0.z * A0.z * h2) / alpha;
            double gamma1 = (A0.dotproduct(dA) * h1 - A0.z * dA.z * h2) / alpha;
            double gamma2 = (dA.dotproduct(dA) * h1 - dA.z * dA.z * h2) / alpha;
            o->horizon1U->set(vec3f(-beta0, -beta1, 0.0));
            o->horizon2U->set(vec3f(beta0 * beta0 - gamma0, 2.0 * (beta0 * beta1 - gamma1), beta1 * beta1 - gamma2));
        }
    }

    o->timeU->set(n->getOwner()->getTime() * 1e-6);
    if (o->radiusU != NULL) {
        o->radiusU->set(o->radius < 0.0 ? -o->radius : o->radius);
    }
    o->heightOffsetU->set(-o->meanHeight);
    o->lodsU->set(vec4f(o->resolution,
            pixelSize * o->resolution,
            log(o->lambdaMin) / log(2.0f),
            (o->nbWavesU->get() - 1.0f) / (log(o->lambdaMax) / log(2.0f) -  log(o->lambdaMin) / log(2.0f))));

    if (o->screenGrid == NULL || o->screenWidth != screen.z || o->screenHeight != screen.w) {
        o->screenWidth = screen.z;
        o->screenHeight = screen.w;
        o->screenGrid = new Mesh<vec2f, unsigned int>(TRIANGLES, GPU_STATIC);
        o->screenGrid->addAttributeType(0, 2, A32F, false);

        float f = 1.25f;
        int NX = int(f * screen.z / o->resolution);
        int NY = int(f * screen.w / o->resolution);
        for (int i = 0; i < NY; ++i) {
            for (int j = 0; j < NX; ++j) {
                o->screenGrid->addVertex(vec2f(2.0*f*j/(NX-1.0f)-f, 2.0*f*i/(NY-1.0f)-f));
            }
        }
        for (int i = 0; i < NY-1; ++i) {
            for (int j = 0; j < NX-1; ++j) {
                o->screenGrid->addIndice(i*NX+j);
                o->screenGrid->addIndice(i*NX+j+1);
                o->screenGrid->addIndice((i+1)*NX+j);
                o->screenGrid->addIndice((i+1)*NX+j);
                o->screenGrid->addIndice(i*NX+j+1);
                o->screenGrid->addIndice((i+1)*NX+j+1);
            }
        }
    }

    fb->draw(prog, *(o->screenGrid));

    return true;
}