AREXPORT void ArServerHandlerCamera::setCameraAbs(
	double pan, double tilt, double zoom, bool lockRobot)
{
 if (zoom > 100)
    zoom = 100;
 else if (zoom < 0)
   zoom = 0;
 
 if (lockRobot)
   myRobot->lock();
 
 cameraModePosition();
 
 myCamera->panTilt(pan, tilt);
 
 double absZoom = myCamera->getMinZoom() + ((zoom / 100.0) * getZoomRange());
 
 IFDEBUG(ArLog::log(ArLog::Normal,
		    "ArServerHandlerCamera::handleSetCameraAbs() p = %f, t = %f, z = %f, calc absZoom = %f",
		    pan, tilt, zoom, absZoom));
 
 myCamera->zoom(ArMath::roundInt(absZoom));
 
 if (lockRobot)
   myRobot->unlock();
 
} // end method doSetCameraAbs
void Source::Impl::updateTiles(const UpdateParameters& parameters) {
    if (!loaded) {
        return;
    }

    const uint16_t tileSize = getTileSize();
    const Range<uint8_t> zoomRange = getZoomRange();

    // Determine the overzooming/underzooming amounts and required tiles.
    int32_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize);
    int32_t tileZoom = overscaledZoom;

    std::vector<UnwrappedTileID> idealTiles;
    if (overscaledZoom >= zoomRange.min) {
        int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom);

        // Make sure we're not reparsing overzoomed raster tiles.
        if (type == SourceType::Raster) {
            tileZoom = idealZoom;
        }

        idealTiles = util::tileCover(parameters.transformState, idealZoom);
    }

    // Stores a list of all the tiles that we're definitely going to retain. There are two
    // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in
    // use because they're still loading. In addition to that, we also need to retain all tiles that
    // we're actively using, e.g. as a replacement for tile that aren't loaded yet.
    std::set<OverscaledTileID> retain;

    auto retainTileFn = [&retain](Tile& tile, Resource::Necessity necessity) -> void {
        retain.emplace(tile.id);
        tile.setNecessity(necessity);
    };
    auto getTileFn = [this](const OverscaledTileID& tileID) -> Tile* {
        auto it = tiles.find(tileID);
        return it == tiles.end() ? nullptr : it->second.get();
    };
    auto createTileFn = [this, &parameters](const OverscaledTileID& tileID) -> Tile* {
        std::unique_ptr<Tile> tile = cache.get(tileID);
        if (!tile) {
            tile = createTile(tileID, parameters);
            if (tile) {
                tile->setObserver(this);
            }
        }
        if (!tile) {
            return nullptr;
        }
        return tiles.emplace(tileID, std::move(tile)).first->second.get();
    };
    auto renderTileFn = [this](const UnwrappedTileID& tileID, Tile& tile) {
        renderTiles.emplace(tileID, RenderTile{ tileID, tile });
    };

    renderTiles.clear();
    algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn, renderTileFn,
                                 idealTiles, zoomRange, tileZoom);

    if (type != SourceType::Annotations && cache.getSize() == 0) {
        size_t conservativeCacheSize =
            std::max((float)parameters.transformState.getSize().width / util::tileSize, 1.0f) *
            std::max((float)parameters.transformState.getSize().height / util::tileSize, 1.0f) *
            (parameters.transformState.getMaxZoom() - parameters.transformState.getMinZoom() + 1) *
            0.5;
        cache.setSize(conservativeCacheSize);
    }

    removeStaleTiles(retain);

    const PlacementConfig config { parameters.transformState.getAngle(),
                                   parameters.transformState.getPitch(),
                                   parameters.debugOptions & MapDebugOptions::Collision };

    for (auto& pair : tiles) {
        pair.second->setPlacementConfig(config);
    }
}
AREXPORT void ArServerHandlerCamera::setCameraRel(
	double pan, double tilt, double zoom, bool lockRobot)
{
  if ((myRobot == NULL) || (myCamera == NULL)) {
    return;
  }

  IFDEBUG(ArLog::log(ArLog::Normal,
                    "ArServerHandlerCamera::doSetCameraRel() p = %f, t = %f, z = %f",
                    pan, tilt, zoom));

  // The camera mode is set to position only if the pan or tilt changed.

  double invAtZoom = 1;

  if (myCamera->getMaxZoom() - myCamera->getMinZoom() != 0)
  {
    // see what zoom we're at so we can modify total pan and tilt amounts by it
    invAtZoom = 1 - getCurrentZoomRatio();

    //totalPanAmount /= atZoom / (100 / (double) totalPanAmount);
    //totalTiltAmount /= atZoom / (100 / (double) totalTiltAmount);
    if (invAtZoom < .01)
      invAtZoom = .01;
    if (invAtZoom > 1.01)
      invAtZoom = 1;
  }
  else
  {
    invAtZoom = 1;
  }

  if (pan > 0)
    pan = ceil(pan * invAtZoom);
  else
    pan = floor(pan * invAtZoom);

  if (tilt > 0)
    tilt = ceil(tilt * invAtZoom);
  else
    tilt = floor(tilt * invAtZoom);

  if (lockRobot)
    myRobot->lock();

  if (zoom != 0)
  {
    double newZoomPct = (getCurrentZoomRatio() * 100.0) + zoom;

    double newAbsZoom = myCamera->getMinZoom() + ((newZoomPct / 100.0) * getZoomRange());

    IFDEBUG(ArLog::log(ArLog::Normal,
                      "ArServerHandlerCamera::handleSetCameraRel() newZoomPct = %f, newAbsZoom = %f",
                       newZoomPct, newAbsZoom));

    myCamera->zoom(ArMath::roundInt(newAbsZoom));

  } // end if zoom change
  
  // Only want to pan...
  if ((pan == 0) && (tilt != 0)) 
  {
    cameraModePosition();
    myCamera->tiltRel(tilt);
  }
  // Only want to tilt...
  else if ((pan != 0) && (tilt == 0)) 
  {
    cameraModePosition();
    myCamera->panRel(pan);
  }
  else if (pan != 0 && tilt != 0) 
  { // pan and tilt...
    cameraModePosition();
    myCamera->panTiltRel(pan, tilt);
  }

  if (lockRobot)
    myRobot->unlock();

} // end method doSetCameraRel