Esempio n. 1
0
float CPUElevationProducer::getHeight(ptr<TileProducer> producer, int level, float x, float y)
{
    float levelTileSize = producer->getRootQuadSize() / (1 << level);
    float s = producer->getRootQuadSize() / 2.0f;
    if (x <= -s || x >= s || y <= -s || y >= s) {
        return 0;
    }
    x += s;
    y += s;

    int tx = (int) floor(x / levelTileSize);
    int ty = (int) floor(y / levelTileSize);

    int tileWidth = producer->getCache()->getStorage()->getTileSize();
    int tileSize = tileWidth - 5;

    TileCache::Tile *t = producer->findTile(level, tx, ty);;

    if (t == NULL) {
        if (Logger::INFO_LOGGER != NULL) {
            Logger::INFO_LOGGER->logf("DEM", "Missing CPUElevation tile [%d:%d:%d] (coord %f:%f)", level, tx, ty, x, y);
        }
        return 0;
    }
    assert(t != NULL);
    CPUTileStorage<float>::CPUSlot *slot = dynamic_cast<CPUTileStorage<float>::CPUSlot*>(t->getData());
    assert(slot != NULL);
    float *tile = slot->data;
    x = 2.0f + (fmod(x, levelTileSize) / levelTileSize) * tileSize;
    y = 2.0f + (fmod(y, levelTileSize) / levelTileSize) * tileSize;
    int sx = (int) floor(x);
    int sy = (int) floor(y);

    return tile[sx + sy * tileWidth];
}
Esempio n. 2
0
    virtual bool doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
    {
        if (Logger::DEBUG_LOGGER != NULL) {
            ostringstream oss;
            oss << "Contour tile " << getId() << " " << level << " " << tx << " " << ty;
            Logger::DEBUG_LOGGER->log("ORTHO", oss.str());
        }

        GPUTileStorage::GPUSlot *gpuData = dynamic_cast<GPUTileStorage::GPUSlot*>(data);
        assert(gpuData != NULL);

        // don't forget this!
        getCache()->getStorage().cast<GPUTileStorage> ()->notifyChange(gpuData);

        int tileWidth = data->getOwner()->getTileSize();

        ptr<Texture> storage = getCache()->getStorage().cast<GPUTileStorage>()->getTexture(0);

        TileCache::Tile *t = elevationTiles->findTile(level, tx, ty);
        assert(t != NULL);
        GPUTileStorage::GPUSlot *elevationGpuData = dynamic_cast<GPUTileStorage::GPUSlot*>(t->getData());
        assert(elevationGpuData != NULL);

        int zTileWidth = elevationGpuData->getWidth();
        float scale = ((zTileWidth - 5.0) / zTileWidth) * (tileWidth / (tileWidth - 4.0));
        float offset = (1.0 - scale) / 2.0;

        elevationSamplerU->set(elevationGpuData->t);
        elevationOSLU->set(vec4f(offset, offset, scale, elevationGpuData->l));

        frameBuffer->drawQuad(contourProgram);
        gpuData->copyPixels(frameBuffer, 0, 0, tileWidth, tileWidth);
        return true;
    }
Esempio n. 3
0
bool ForestOrthoLayer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "OrthoForest tile " << getProducerId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("ORTHO", oss.str());
    }
    if (level >= displayLevel) {
        ptr<FrameBuffer> fb  = SceneManager::getCurrentFrameBuffer();

        TileCache::Tile * t = graphProducer->findTile(level, tx, ty);
        assert(t != NULL);
        ObjectTileStorage::ObjectSlot *graphData = dynamic_cast<ObjectTileStorage::ObjectSlot*>(t->getData());
        GraphPtr g = graphData->data.cast<Graph>();
        if (g != NULL) {

            vec3d q = getTileCoords(level, tx, ty);
            float scale = 2.0f * (1.0f - getTileBorder() * 2.0f / getTileSize()) / q.z;
            vec3d tileOffset = vec3d(q.x + q.z / 2.0f, q.y + q.z / 2.0f, scale);
            //offsetU->set(vec3f(q.x + q.z / 2.0f, q.y + q.z / 2.0f, scale));
            offsetU->set(vec3f(0.0, 0.0, 1.0));
            colorU->set(vec4f(color.x, color.y, color.z, color.w));

            mesh->setMode(TRIANGLES);
            mesh->clear();
            ptr<Graph::AreaIterator> ai = g->getAreas();
            while (ai->hasNext()) {
                AreaPtr a = ai->next();
                tess->beginPolygon(mesh);
                drawArea(tileOffset, a, *tess);
                tess->endPolygon();
            }
            fb->draw(layerProgram, *mesh);
        } else {
            if (Logger::DEBUG_LOGGER != NULL) {
                ostringstream oss;
                oss << "NULL Graph : " << level << " " << tx << " " << ty;
                Logger::DEBUG_LOGGER->log("GRAPH", oss.str());
            }
        }
    }
    return true;
}
Esempio n. 4
0
bool OrthoGPUProducer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "GPU tile " << getId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("ORTHO", oss.str());
    }

    GPUTileStorage::GPUSlot *gpuData = dynamic_cast<GPUTileStorage::GPUSlot*> (data);
    assert(gpuData != NULL);
    getCache()->getStorage().cast<GPUTileStorage> ()->notifyChange(gpuData);

    CPUTileStorage<unsigned char>::CPUSlot *cpuData = NULL;

    TileCache::Tile* coarseTile = NULL;
    GPUTileStorage::GPUSlot *coarseGpuData = NULL;

    if (orthoTiles != NULL) {
        if (hasLayers() && !orthoTiles->hasTile(level, tx, ty)) {
            int l = level;
            int x = tx;
            int y = ty;
            while (!coarseGpuTiles->hasTile(l, x, y)) {
                l -= 1;
                x /= 2;
                y /= 2;
            }

            coarseTile = coarseGpuTiles->findTile(l, x, y);
            assert(coarseTile != NULL);
            coarseGpuData = dynamic_cast<GPUTileStorage::GPUSlot*>(coarseTile->getData());
            assert(coarseGpuData != NULL);
        } else {
            TileCache::Tile *t = orthoTiles->findTile(level, tx, ty);
            assert(t != NULL);
            cpuData = dynamic_cast<CPUTileStorage<unsigned char>::CPUSlot*>(t->getData());
            assert(cpuData != NULL);
        }
    }

    TextureFormat f;
    switch (channels) {
    case 1:
        f = RED;
        break;
    case 2:
        f = RG;
        break;
    case 3:
        f = RGB;
        break;
    default:
        f = RGBA;
        break;
    }
    if (compressedTexture == NULL && !hasLayers()) {
        assert(cpuData != NULL);
        if (channels != 2 || tileSize%2 == 0) {
            gpuData->setSubImage(0, 0, tileSize, tileSize, f, UNSIGNED_BYTE, Buffer::Parameters(), CPUBuffer(cpuData->data));
        } else {
            // TODO better way to fix this "OpenGL bug" (?) with odd texture sizes?
            unsigned char *tmp = new unsigned char[tileSize*tileSize*4];
            for (int i = 0; i < tileSize*tileSize; ++i) {
                tmp[4*i] = cpuData->data[2*i];
                tmp[4*i+1] = cpuData->data[2*i+1];
                tmp[4*i+2] = 0;
                tmp[4*i+3] = 0;
            }
            gpuData->setSubImage(0, 0, tileSize, tileSize, RGBA, UNSIGNED_BYTE, Buffer::Parameters(), CPUBuffer(tmp));
            delete[] tmp;
        }
    } else {
        if (cpuData != NULL) {
            if (compressedTexture != NULL) {
                compressedTexture->setCompressedSubImage(0, 0, 0, tileSize, tileSize, cpuData->size, CPUBuffer(cpuData->data));
                uncompressSourceU->set(compressedTexture);
                frameBuffer->drawQuad(uncompress);
            } else {
                uncompressedTexture->setSubImage(0, 0, 0, tileSize, tileSize, f, UNSIGNED_BYTE, Buffer::Parameters(), CPUBuffer(cpuData->data));
            }
        }
        if (coarseGpuData != NULL) {
            vec4f coords = coarseGpuTiles->getGpuTileCoords(level, tx, ty, &coarseTile);
            float b = float(getBorder()) / (1 << (level - coarseTile->level));
            float s = (float) getCache()->getStorage()->getTileSize();
            float S = s / (s - 2 * getBorder());
            coords.x -= b / coarseGpuData->getWidth();
            coords.y -= b / coarseGpuData->getHeight();
            coords.w *= S;

            upsampleSourceU->set(coarseGpuData->t);
            tileU->set(coords);
            frameBuffer->drawQuad(upsample);
        }
        if (hasLayers()) {
            TileProducer::doCreateTile(level, tx, ty, data);
        }
        gpuData->copyPixels(frameBuffer, 0, 0, tileSize, tileSize);
    }

    return true;
}
Esempio n. 5
0
bool TextureLayer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "Texture tile " << getProducerId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("ORTHO", oss.str());
    }

    if (level < minDisplayLevel) {
        return true;
    }

    int l = level;
    int x = tx;
    int y = ty;
    while (!tiles->hasTile(l, x, y)) {
        l -= 1;
        x /= 2;
        y /= 2;
        assert(l >= 0);
    }

    ptr<FrameBuffer> fb = SceneManager::getCurrentFrameBuffer();

    TileCache::Tile *t = tiles->findTile(l, x, y);
    assert(t != NULL);
    GPUTileStorage::GPUSlot *gput = dynamic_cast<GPUTileStorage::GPUSlot*>(t->getData());
    assert(gput != NULL);

    vec4f coords = tiles->getGpuTileCoords(level, tx, ty, &t);
    int s = tiles->getCache()->getStorage()->getTileSize();
    float b = float(tiles->getBorder()) / (1 << (level - l));
    float S = s / (s - 2.0f * tiles->getBorder());

    // correct border
    vec4f coordsCorrected = coords;
    coordsCorrected.x -= b / gput->getWidth();
    coordsCorrected.y -= b / gput->getHeight();
    coordsCorrected.w *= S;

    samplerU->set(gput->t);
    coordsU->set(vec3f(coordsCorrected.x, coordsCorrected.y, coords.z));
    sizeU->set(vec3f(coordsCorrected.w, coordsCorrected.w, (s / 2) * 2.0f - 2.0f * b));

    if (blend.buffer != (BufferId) -1) {
        fb->setBlend(blend.buffer, true, blend.rgb, blend.srgb, blend.drgb,
            blend.alpha, blend.salpha, blend.dalpha);
    } else {
        fb->setBlend(true, blend.rgb, blend.srgb, blend.drgb,
            blend.alpha, blend.salpha, blend.dalpha);
    }

    fb->drawQuad(program);

    if (blend.buffer != (BufferId) -1) {
        fb->setBlend(blend.buffer, false);
    } else {
        fb->setBlend(false);
    }

    return true;
}
Esempio n. 6
0
bool CPUElevationProducer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "CPUElevation tile " << getId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("DEM", oss.str());
    }

    CPUTileStorage<float>::CPUSlot *cpuData = dynamic_cast<CPUTileStorage<float>::CPUSlot*>(data);
    assert(cpuData != NULL);

    int tileWidth = data->getOwner()->getTileSize();
    int tileSize = tileWidth - 5;

    CPUTileStorage<float>::CPUSlot *parentCpuData = NULL;
    if (level > 0) {
        TileCache::Tile *t = findTile(level - 1, tx / 2, ty / 2);
        assert(t != NULL);
        parentCpuData = dynamic_cast<CPUTileStorage<float>::CPUSlot*>(t->getData());
        assert(parentCpuData != NULL);
    }

    int residualTileWidth = residualTiles->getCache()->getStorage()->getTileSize();
    int mod = (residualTileWidth - 2 * residualTiles->getBorder() - 1) / tileSize;

    int rx = (tx % mod) * tileSize; // select the xy coord in residual
    int ry = (ty % mod) * tileSize;
    TileCache::Tile *t = residualTiles->findTile(level, tx / mod, ty / mod);

    CPUTileStorage<float>::CPUSlot *cpuTile = NULL;

    bool hasResidual = residualTiles->hasTile(level, tx / mod, ty / mod);
    if (hasResidual) {
        assert(t != NULL);
        cpuTile = dynamic_cast<CPUTileStorage<float>::CPUSlot*>(t->getData());
        assert(cpuTile != NULL);
    }

    int px = 1 + (tx % 2) * tileSize / 2; //select the xy coord in the parent tile
    int py = 1 + (ty % 2) * tileSize / 2;

    float *parentTile = NULL;
    if (level > 0) {
        parentTile = parentCpuData->data;
    }

    for (int j = 0; j < tileWidth; ++j) {
        for (int i = 0; i < tileWidth; ++i) {
            float z;
            float r = 0.0f;

            if (level == 0) {
                z = 0.0f;
            } else {
                if (j % 2 == 0) {
                    if (i % 2 == 0) {
                        z = parentTile[i / 2 + px + (j / 2 + py) * tileWidth];
                    } else {
                        float z0 = parentTile[i / 2 + px - 1 + (j / 2 + py) * tileWidth];
                        float z1 = parentTile[i / 2 + px + (j / 2 + py) * tileWidth];
                        float z2 = parentTile[i / 2 + px + 1 + (j / 2 + py) * tileWidth];
                        float z3 = parentTile[i / 2 + px + 2 + (j / 2 + py) * tileWidth];
                        z = ((z1 + z2) * 9 - (z0 + z3)) / 16;
                    }
                } else {
                    if (i % 2 == 0) {
                        float z0 = parentTile[i / 2  + px + (j / 2 - 1 + py) * tileWidth];
                        float z1 = parentTile[i / 2 + px + (j / 2 + py) * tileWidth];
                        float z2 = parentTile[i / 2 + px + (j / 2 + 1 + py) * tileWidth];
                        float z3 = parentTile[i / 2 + px + (j / 2 + 2 + py) * tileWidth];
                        z = ((z1 + z2) * 9 - (z0 + z3)) / 16;
                    } else {
                        int di, dj;
                        z = 0;
                        for (dj = -1; dj <= 2; ++dj) {
                            float f = dj == -1 || dj == 2 ? -1/16.0f : 9/16.0f;
                            for (di = -1; di <= 2; ++di) {
                                float g = di == -1 || di == 2 ? -1/16.0f : 9/16.0f;
                                z += f * g * parentTile[i / 2 + di + px + (j / 2 + dj + py) * tileWidth];
                            }
                        }
                    }
                }
            }
            if (hasResidual) {
                r = cpuTile->data[(int)(i + rx + (j + ry) * residualTileWidth)];
            }
            cpuData->data[i + j * tileWidth] = z + r;
        }
    }

    return true;
}
bool WaterElevationLayer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "ElevationRoad tile " << getProducerId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("DEM", oss.str());
    }
    if (level >= displayLevel) {
        TileCache::Tile *t = graphProducer->findTile(level, tx, ty);
        assert(t != NULL);

        ObjectTileStorage::ObjectSlot *graphData = dynamic_cast<ObjectTileStorage::ObjectSlot*>(t->getData());
        GraphPtr g = graphData->data.cast<Graph>();

        if (g->getCurveCount() == 0) {
            return false;
        }

        ptr<FrameBuffer> fb = SceneManager::getCurrentFrameBuffer();

        vec3d q = getTileCoords(level, tx, ty);
        vec2d nx, ny, lx, ly;
        getDeformParameters(q, nx, ny, lx, ly);

        float scale = 2.0f * (getTileSize() - 1.0f - (2.0f * getTileBorder())) / q.z;

        vec3d tileOffset = vec3d(q.x + q.z / 2.0f, q.y + q.z / 2.0f, scale / getTileSize());
        //tileOffsetU->set(vec3f(q.x + q.z / 2.0f, q.y + q.z / 2.0f, scale / getTileSize()));
        tileOffsetU->set(vec3f(0.0, 0.0, 1.0));

        fb->clear(false, false, true);

        if (g->getAreaCount() > 0) {
            //fillOffsetU->set(vec3f(q.x + q.z / 2.0f, q.y + q.z / 2.0f, scale / getTileSize()));
            fillOffsetU->set(vec3f(0.0, 0.0, 1.0));
            depthU->set(0.02f);
            colorU->set(vec4f(0.0f, 0.0f, 0.0f, 0.0f));

            fb->setDepthTest(true, ALWAYS);
            fb->setColorMask(false, false, false, true);
            fb->setDepthMask(true);

            mesh->setMode(TRIANGLES);
            mesh->clear();
            tess->beginPolygon(mesh);
            ptr<Graph::AreaIterator> ai = g->getAreas();
            while (ai->hasNext()) {
                AreaPtr a = ai->next();
                GraphLayer::drawArea(tileOffset, a, *tess);
            }
            tess->endPolygon();
            fb->draw(fillProg, *mesh);

            riverU->set(1);

            ai = g->getAreas();
            while (ai->hasNext()) {
                AreaPtr a = ai->next();
                bool island = true;
                for (int j = 0; j < a->getCurveCount(); ++j) {
                    int o;
                    island &= (a->getCurve(j, o)->getType() == WaterElevationCurveData::ISLAND);
                    if (!island) {
                        break;
                    }
                }
                for (int j = 0; j < a->getCurveCount(); ++j) {
                    int orientation;
                    CurvePtr p = a->getCurve(j, orientation);
                    if (island) {
                        orientation = 1 - orientation;
                    } else {
                        if (p->getType() == WaterElevationCurveData::ISLAND) {
                            continue;
                        }
                    }
                    if (orientation != 0) {
                        GraphLayer::drawCurve(tileOffset, p, vec4f(0, 12, 1, 2), fb, layerProgram, *(meshuv), &nx, &ny, &lx, &ly);
                    } else {
                        GraphLayer::drawCurve(tileOffset, p, vec4f(0, -12, 1, 2), fb, layerProgram, *(meshuv), &nx, &ny, &lx, &ly);
                    }
                }
            }

            fb->setDepthTest(true, NOTEQUAL);
            fb->setColorMask(false, false, true, false);
            fb->setDepthMask(false);

            riverU->set(2);

            ptr<Graph::CurveIterator> ci = g->getCurves();
            while (ci->hasNext()) {
                CurvePtr c = ci->next();
                float w = c->getWidth();
                float tw = w;
                if (w * scale <= 1 || (c->getParent() == NULL && level != 0) || c->getType() == WaterElevationCurveData::LAKE || c->getType() == WaterElevationCurveData::RIVER || c->getArea1() != NULL) {//== WaterElevationCurveData::RIVER) {
                    continue;
                }
                ElevationCurveData *cData = dynamic_cast<ElevationCurveData*>(findCurveData(c));
                ElevationGraphLayer::drawCurveAltitude(tileOffset, c, cData, tw, tw / w, max(1.0f, 1.0f / scale), false, fb, layerProgram, *meshuv, &nx, &ny, &lx, &ly);
            }
        }

        fb->setColorMask(false, false, true, true);
        fb->setDepthTest(true, LESS);
        fb->setDepthMask(true);

        riverU->set(1);

        ptr<Graph::CurveIterator> ci = g->getCurves();
        while (ci->hasNext()) {
            CurvePtr c = ci->next();
            float cwidth = c->getWidth();
            if (cwidth * scale <= 1 || c->getType() != WaterElevationCurveData::RIVER || (c->getParent() == NULL && level != 0)) {
                continue;
            }

            float w = BASEWIDTH(cwidth, scale);
            float tw = TOTALWIDTH(w);

            ElevationCurveData *cData = dynamic_cast<ElevationCurveData*>(findCurveData(c));
            ElevationGraphLayer::drawCurveAltitude(tileOffset, c, cData, tw, tw / w, max(1.0f, 1.0f / scale), true, fb, layerProgram, *meshuv, &nx, &ny, &lx, &ly);
        }
        fb->setColorMask(true, true, true, true);
    }
    return true;
}
Esempio n. 8
0
bool HydroFlowProducer::doCreateTile(int level, int tx, int ty, TileStorage::Slot *data)
{
    if (Logger::DEBUG_LOGGER != NULL) {
        ostringstream oss;
        oss << "Hydro tile " << getId() << " " << level << " " << tx << " " << ty;
        Logger::DEBUG_LOGGER->log("RIVER", oss.str());
    }
    swHYDRODATA->start();
    bool res = false;

    ObjectTileStorage::ObjectSlot *objectData = dynamic_cast<ObjectTileStorage::ObjectSlot*>(data);
    assert(objectData != NULL);
    TileCache::Tile::TId id = TileCache::Tile::getTId(getId(), level, tx, ty);

    TileCache::Tile *graphTile = graphs->findTile(level, tx, ty);
    assert(graphTile != NULL);
    ptr<Graph> graphData = dynamic_cast<ObjectTileStorage::ObjectSlot*>(graphTile->getData())->data.cast<Graph>();

    float quadSize = getRootQuadSize() / (1 << level);

    bool diffVersion = false;
    if (objectData->data != NULL) {
        diffVersion = !objectData->data.cast<HydroFlowTile>()->equals(graphData->version, slipParameter, min((int) (quadSize / potentialDelta), displayTileSize), searchRadiusFactor);//objectData->data.cast<HydroFlowTile>()->version != graphData->version;
    }

    if (diffVersion || id != objectData->id || objectData->data == NULL ) {
        ptr<HydroFlowTile> hydroData;

        double ox = getRootQuadSize() * (double(tx) / (1 << level) - 0.5f);
        double oy = getRootQuadSize() * (double(ty) / (1 << level) - 0.5f);

        if (level >= minLevel) {
            if (graphData.cast<HydroGraph>() == NULL && graphData.cast<LazyHydroGraph>() == NULL) { // if the type of graph is wrong
                if (Logger::ERROR_LOGGER != NULL) {
                    ostringstream oss;
                    oss << "Bad Graph Type : Should be a [Lazy]HydroGraph.";
                    Logger::ERROR_LOGGER->log("RIVER", oss.str());
                }
                return false;
            }

            if (quadSize / potentialDelta < displayTileSize / 2 && level - 1 >=minLevel) { //maxLevel
                objectData->data = dynamic_cast<ObjectTileStorage::ObjectSlot*>(findTile(level - 1, tx / 2, ty / 2)->getData())->data;
                return true;
            }

            float scale = displayTileSize == -1 ? 1 : displayTileSize / quadSize;

            ptr<Graph::CurveIterator> ci = graphData->getCurves();
            vector<ptr<HydroCurve> > banks;
            float width = 0.f;
            while(ci->hasNext()) {
                ptr<HydroCurve> c = ci->next().cast<HydroCurve>();
                bool display = false;
                if (c->getType() != HydroCurve::BANK && c->getWidth() * scale > 1.0f) {
                    display = true;
                    if (width < c->getWidth()) {
                        width = c->getWidth();
                    }
                } else if (c->getType() == HydroCurve::BANK && c->getRiver().id != NULL_ID) {
                    if (c->getOwner()->getAncestor()->getCurve(c->getRiver()).cast<HydroCurve>()->getWidth() * scale > 1.0f) {
                        display = true;
                    }
                }
                if (display) {
                    banks.push_back(c);
                }
            }
            hydroData = new HydroFlowTile(ox, oy, quadSize, slipParameter, min((int) (quadSize / potentialDelta), displayTileSize), searchRadiusFactor);
            hydroData->addBanks(banks, width);
        } else {
            hydroData = new HydroFlowTile(ox, oy, quadSize, slipParameter, min((int) (quadSize / potentialDelta), displayTileSize), searchRadiusFactor);
        }

        objectData->data = hydroData;
        hydroData->version = graphData->version;
        res = true;
    }
    swHYDRODATA->end();

    TileProducer::doCreateTile(level, tx, ty, data);
    return res;
}