move to libuv for the rendering loop
This commit is contained in:
@@ -88,10 +88,9 @@ static std::queue<CURL *> curl_handle_cache;
|
||||
class CURLRequest : public llmr::platform::Request {
|
||||
public:
|
||||
CURLRequest(const std::string &url,
|
||||
std::function<void(llmr::platform::Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(llmr::platform::Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
: Request(url, background_function, foreground_callback, loop) {}
|
||||
: Request(url, callback, loop) {}
|
||||
|
||||
CURL *curl = nullptr;
|
||||
};
|
||||
@@ -169,16 +168,7 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) {
|
||||
// We're currently in the CURL request thread. We're going to schedule a uv_work request
|
||||
// that executes the background function in a threadpool, and tell it to call the
|
||||
// after callback back in the main uv loop.
|
||||
|
||||
uv_work_t *work = new uv_work_t();
|
||||
|
||||
// We're passing on the pointer we created to the work structure.
|
||||
// It is going to be deleted in the after_work_cb;
|
||||
work->data = req;
|
||||
|
||||
// Executes the background_function in a libuv thread pool, and the after_work_cb back
|
||||
// in the *main* event loop.
|
||||
uv_queue_work((*req)->loop, work, Request::work_callback, Request::after_work_callback);
|
||||
(*req)->complete();
|
||||
|
||||
CURL *handle = message->easy_handle;
|
||||
remove_curl_handle(handle);
|
||||
@@ -187,6 +177,8 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) {
|
||||
// be cancelled.
|
||||
((CURLRequest *)req->get())->curl = nullptr;
|
||||
|
||||
// Delete the shared_ptr pointer we created earlier.
|
||||
delete req;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -312,8 +304,6 @@ void async_add_cb(uv_async_t * /*async*/) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(*req)->res = std::make_unique<Response>();
|
||||
|
||||
// Obtain a curl handle (and try to reuse existing handles before creating new ones).
|
||||
CURL *handle = nullptr;
|
||||
if (!curl_handle_cache.empty()) {
|
||||
@@ -367,12 +357,11 @@ void thread_init_cb() {
|
||||
|
||||
std::shared_ptr<platform::Request>
|
||||
platform::request_http(const std::string &url,
|
||||
std::function<void(Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop) {
|
||||
using namespace request;
|
||||
init_thread_once(thread_init_cb);
|
||||
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, background_function, foreground_callback, loop);
|
||||
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop);
|
||||
|
||||
// Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store
|
||||
// objects with nontrivial destructors. We have to make absolutely sure that we manually delete
|
||||
|
||||
@@ -40,10 +40,9 @@ void request_initialize_cb() {
|
||||
class FoundationRequest : public llmr::platform::Request {
|
||||
public:
|
||||
FoundationRequest(const std::string &url,
|
||||
std::function<void(llmr::platform::Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(llmr::platform::Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
: Request(url, background_function, foreground_callback, loop) {
|
||||
: Request(url, callback, loop) {
|
||||
#if TARGET_OS_IPHONE
|
||||
active_tasks++;
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
||||
@@ -68,13 +67,12 @@ public:
|
||||
|
||||
std::shared_ptr<llmr::platform::Request>
|
||||
llmr::platform::request_http(const std::string &url,
|
||||
std::function<void(Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop) {
|
||||
uv_once(&request_initialize, request_initialize_cb);
|
||||
|
||||
std::shared_ptr<FoundationRequest> req =
|
||||
std::make_shared<FoundationRequest>(url, background_function, foreground_callback, loop);
|
||||
std::make_shared<FoundationRequest>(url, callback, loop);
|
||||
|
||||
// Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one
|
||||
// shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the
|
||||
@@ -93,8 +91,6 @@ llmr::platform::request_http(const std::string &url,
|
||||
return;
|
||||
}
|
||||
|
||||
(*req_ptr)->res = std::make_unique<Response>();
|
||||
|
||||
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
(*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode];
|
||||
(*req_ptr)->res->body = {(const char *)[data bytes], [data length]};
|
||||
@@ -102,19 +98,9 @@ llmr::platform::request_http(const std::string &url,
|
||||
(*req_ptr)->res->error_message = [[error localizedDescription] UTF8String];
|
||||
}
|
||||
|
||||
// We're currently in the request thread. We're going to schedule a uv_work request that
|
||||
// executes the background function in a threadpool, and tell it to call the after callback
|
||||
// back in the main uv loop.
|
||||
uv_work_t *work = new uv_work_t();
|
||||
|
||||
// We're passing on the pointer we created to the work structure. It is going to be deleted
|
||||
// in the after_work_cb;
|
||||
work->data = req_ptr;
|
||||
|
||||
// Executes the background_function in a libuv thread pool, and the after_work_cb back in
|
||||
// the *main* event loop.
|
||||
uv_queue_work((*req_ptr)->loop, work, Request::work_callback,
|
||||
Request::after_work_callback);
|
||||
(*req_ptr)->complete();
|
||||
|
||||
delete req_ptr;
|
||||
}];
|
||||
|
||||
req->task = task;
|
||||
|
||||
@@ -1,39 +1,7 @@
|
||||
#include "glfw_view.hpp"
|
||||
|
||||
|
||||
#ifndef HAVE_KQUEUE
|
||||
# if defined(__APPLE__) || \
|
||||
defined(__DragonFly__) || \
|
||||
defined(__FreeBSD__) || \
|
||||
defined(__OpenBSD__) || \
|
||||
defined(__NetBSD__)
|
||||
# define HAVE_KQUEUE 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_EPOLL
|
||||
# if defined(__linux__)
|
||||
# define HAVE_EPOLL 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
|
||||
|
||||
#if defined(HAVE_KQUEUE)
|
||||
# include <sys/types.h>
|
||||
# include <sys/event.h>
|
||||
# include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_EPOLL)
|
||||
# include <sys/epoll.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
MapView::MapView(llmr::Settings &settings, bool fullscreen)
|
||||
: fullscreen(fullscreen), settings(settings), map(settings) {}
|
||||
: fullscreen(fullscreen), settings(settings), map(*this) {}
|
||||
|
||||
MapView::~MapView() { glfwTerminate(); }
|
||||
|
||||
@@ -77,20 +45,17 @@ void MapView::init() {
|
||||
glfwGetFramebufferSize(window, &fb_width, &fb_height);
|
||||
|
||||
settings.load();
|
||||
map.setup();
|
||||
|
||||
resize(window, 0, 0);
|
||||
|
||||
glfwSwapInterval(1);
|
||||
|
||||
map.loadSettings();
|
||||
|
||||
glfwSetCursorPosCallback(window, mousemove);
|
||||
glfwSetMouseButtonCallback(window, mouseclick);
|
||||
glfwSetWindowSizeCallback(window, resize);
|
||||
glfwSetFramebufferSizeCallback(window, resize);
|
||||
glfwSetScrollCallback(window, scroll);
|
||||
glfwSetKeyCallback(window, key);
|
||||
|
||||
glfwMakeContextCurrent(nullptr);
|
||||
}
|
||||
|
||||
void MapView::key(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) {
|
||||
@@ -152,7 +117,6 @@ void MapView::resize(GLFWwindow *window, int, int) {
|
||||
glfwGetFramebufferSize(window, &fb_width, &fb_height);
|
||||
|
||||
mapView->map.resize(width, height, (float)fb_width / (float)width, fb_width, fb_height);
|
||||
mapView->map.update();
|
||||
}
|
||||
|
||||
void MapView::mouseclick(GLFWwindow *window, int button, int action, int modifiers) {
|
||||
@@ -199,17 +163,11 @@ int MapView::run() {
|
||||
map.start();
|
||||
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
if (map.clean.test_and_set() == false) {
|
||||
// This branch is executed when the previous value of the "clean"
|
||||
// flag is false (i.e. it is unclean == dirty).
|
||||
try {
|
||||
map.render();
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
fprintf(stderr, "exception: %s\n", ex.what());
|
||||
}
|
||||
if (map.swapped.test_and_set() == false) {
|
||||
glfwSwapBuffers(window);
|
||||
map.rendered.clear();
|
||||
fps();
|
||||
map.rerender();
|
||||
}
|
||||
|
||||
glfwWaitEvents();
|
||||
@@ -220,6 +178,14 @@ int MapView::run() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MapView::make_active() {
|
||||
glfwMakeContextCurrent(window);
|
||||
}
|
||||
|
||||
void MapView::swap() {
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
|
||||
void MapView::fps() {
|
||||
static int frames = 0;
|
||||
static double time_elapsed = 0;
|
||||
@@ -267,9 +233,5 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
void restart() {
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <uv.h>
|
||||
|
||||
class MapView {
|
||||
class MapView : public llmr::View {
|
||||
public:
|
||||
MapView(llmr::Settings &settings, bool fullscreen = false);
|
||||
~MapView();
|
||||
|
||||
void init();
|
||||
|
||||
void swap();
|
||||
void make_active();
|
||||
|
||||
static void key(GLFWwindow *window, int key, int scancode, int action, int mods);
|
||||
static void scroll(GLFWwindow *window, double xoffset, double yoffset);
|
||||
static void resize(GLFWwindow *window, int, int);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define LLMR_MAIN
|
||||
|
||||
#include "map/map.hpp"
|
||||
#include "map/view.hpp"
|
||||
#include "map/settings.hpp"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <llmr/map/tile.hpp>
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/map/view.hpp>
|
||||
// #include <llmr/map/tile.hpp>
|
||||
// #include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
@@ -18,31 +19,34 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Settings;
|
||||
class Source;
|
||||
|
||||
class Map : private util::noncopyable {
|
||||
public:
|
||||
explicit Map(Settings& settings);
|
||||
explicit Map(View &view);
|
||||
~Map();
|
||||
|
||||
// Start/stop the map render thread
|
||||
void start();
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
/* setup */
|
||||
void setup();
|
||||
void loadStyle(const uint8_t *const data, uint32_t bytes);
|
||||
void loadSettings();
|
||||
// Runs the map event loop.
|
||||
void run();
|
||||
|
||||
// Triggers a lazy rerender: only performs a render when the map is not clean.
|
||||
void rerender();
|
||||
|
||||
// Forces a map update: always triggers a rerender.
|
||||
void update();
|
||||
|
||||
// Size
|
||||
void resize(uint16_t width, uint16_t height, float ratio = 1);
|
||||
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
|
||||
|
||||
/* callback */
|
||||
void update();
|
||||
void render();
|
||||
// Animations
|
||||
void cancelAnimations();
|
||||
|
||||
/* position */
|
||||
// Position
|
||||
void moveBy(double dx, double dy, double duration = 0);
|
||||
void setLonLat(double lon, double lat, double duration = 0);
|
||||
void getLonLat(double &lon, double &lat) const;
|
||||
@@ -50,7 +54,7 @@ public:
|
||||
void stopPanning();
|
||||
void resetPosition();
|
||||
|
||||
/* scale */
|
||||
// Scale
|
||||
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
|
||||
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
|
||||
double getScale() const;
|
||||
@@ -62,39 +66,63 @@ public:
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
|
||||
/* rotation */
|
||||
// Rotation
|
||||
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
|
||||
void setAngle(double angle, double cx = -1, double cy = -1, double duration = 0);
|
||||
void setAngle(double angle, double cx = -1, double cy = -1);
|
||||
double getAngle() const;
|
||||
void resetNorth();
|
||||
void startRotating();
|
||||
void stopRotating();
|
||||
|
||||
|
||||
void redraw();
|
||||
|
||||
// Toggles
|
||||
void setDebug(bool value);
|
||||
void toggleDebug();
|
||||
bool getDebug() const;
|
||||
void toggleRaster();
|
||||
|
||||
box cornersToBox(uint32_t z) const;
|
||||
|
||||
|
||||
public: // Getters
|
||||
float getPixelRatio() const;
|
||||
Style& getStyle();
|
||||
GlyphAtlas& getGlyphAtlas();
|
||||
uv_loop_t *getLoop();
|
||||
public:
|
||||
inline const TransformState &getState() const { return state; }
|
||||
inline const Style &getStyle() const { return style; }
|
||||
inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; }
|
||||
inline uv_loop_t *getLoop() { return loop; }
|
||||
|
||||
private:
|
||||
bool updateTiles();
|
||||
static void eventloop(void *arg);
|
||||
// uv async callbacks
|
||||
static void render(uv_async_t *async);
|
||||
static void terminate(uv_async_t *async);
|
||||
static void delete_async(uv_handle_t *handle);
|
||||
|
||||
// Setup
|
||||
void setup();
|
||||
void loadStyle(const uint8_t *const data, uint32_t bytes);
|
||||
|
||||
void updateTiles();
|
||||
|
||||
// Prepares a map render by updating the tiles we need for the current view, as well as updating
|
||||
// the stylesheet.
|
||||
void prepare();
|
||||
|
||||
// Unconditionally performs a render with the current map state.
|
||||
void render();
|
||||
|
||||
public:
|
||||
// If cleared, the next time the render thread attempts to render the map, it will *actually*
|
||||
// render the map.
|
||||
std::atomic_flag clean = ATOMIC_FLAG_INIT;
|
||||
|
||||
// If this flag is cleared, the current back buffer is ready for being swapped with the front
|
||||
// buffer (i.e. it has rendered data).
|
||||
std::atomic_flag swapped = ATOMIC_FLAG_INIT;
|
||||
|
||||
// This is cleared once the current front buffer has been presented and the back buffer is
|
||||
// ready for rendering.
|
||||
std::atomic_flag rendered = ATOMIC_FLAG_INIT;
|
||||
|
||||
private:
|
||||
Settings& settings;
|
||||
View &view;
|
||||
Transform transform;
|
||||
TransformState state;
|
||||
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
GlyphAtlas glyphAtlas;
|
||||
@@ -102,10 +130,15 @@ private:
|
||||
|
||||
std::map<std::string, const std::unique_ptr<Source>> sources;
|
||||
|
||||
bool debug = false;
|
||||
double animationTime = 0;
|
||||
|
||||
private:
|
||||
bool async = false;
|
||||
uv_loop_t *loop = nullptr;
|
||||
uv_thread_t thread;
|
||||
uv_async_t async_terminate;
|
||||
uv_async_t *async_terminate = nullptr;
|
||||
uv_async_t *async_render = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace llmr {
|
||||
|
||||
class Map;
|
||||
class Transform;
|
||||
class TransformState;
|
||||
class Painter;
|
||||
class Texturepool;
|
||||
|
||||
@@ -25,15 +25,18 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
Source(Map &map, Transform &transform, Painter &painter, Texturepool &texturepool,
|
||||
Source(Map &map, Painter &painter, Texturepool &texturepool,
|
||||
const char *url = "", Type type = Type::vector, std::vector<uint32_t> zooms = {0},
|
||||
uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14,
|
||||
bool enabled = true);
|
||||
|
||||
|
||||
|
||||
bool update();
|
||||
void prepare_render(bool is_baselayer = false);
|
||||
void prepare_render(const TransformState &transform, bool is_baselayer = false);
|
||||
void render(double animationTime);
|
||||
|
||||
|
||||
public:
|
||||
bool enabled;
|
||||
|
||||
@@ -51,7 +54,6 @@ private:
|
||||
|
||||
private:
|
||||
Map& map;
|
||||
Transform& transform;
|
||||
Painter& painter;
|
||||
Texturepool& texturepool;
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ public:
|
||||
~TileData();
|
||||
|
||||
void request();
|
||||
bool parse();
|
||||
void scheduleParsing();
|
||||
void parse();
|
||||
void cancel();
|
||||
|
||||
const std::string toString() const;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef LLMR_MAP_TRANSFORM
|
||||
#define LLMR_MAP_TRANSFORM
|
||||
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/util/animation.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/map/tile.hpp>
|
||||
#include <llmr/util/uv.hpp>
|
||||
|
||||
#include "transform_state.hpp"
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
@@ -17,82 +17,72 @@ struct box;
|
||||
public:
|
||||
Transform();
|
||||
|
||||
// Map view
|
||||
// Note: width * ratio does not necessarily equal fb_width
|
||||
bool resize(uint16_t width, uint16_t height, float ratio,
|
||||
uint16_t fb_width, uint16_t fb_height);
|
||||
|
||||
// Position
|
||||
void moveBy(double dx, double dy, double duration = 0);
|
||||
void setLonLat(double lon, double lat, double duration = 0);
|
||||
void setLonLatZoom(double lon, double lat, double zoom, double duration = 0);
|
||||
void getLonLat(double& lon, double& lat) const;
|
||||
void getLonLatZoom(double& lon, double& lat, double& zoom) const;
|
||||
void startPanning();
|
||||
void stopPanning();
|
||||
|
||||
// Zoom
|
||||
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
|
||||
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
|
||||
void setZoom(double zoom, double duration = 0);
|
||||
double getZoom() const;
|
||||
double getScale() const;
|
||||
void startRotating();
|
||||
void stopRotating();
|
||||
|
||||
// Angle
|
||||
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
|
||||
void setAngle(double angle, double duration = 0);
|
||||
void setAngle(double angle, double cx, double cy);
|
||||
double getAngle() const;
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
|
||||
// Animations
|
||||
bool needsAnimation() const;
|
||||
void updateAnimations(double time);
|
||||
void cancelAnimations();
|
||||
|
||||
// Map view changes.
|
||||
// Note: width * ratio does not necessarily equal fb_width
|
||||
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
|
||||
|
||||
// Relative changes
|
||||
void moveBy(double dx, double dy, double duration = 0);
|
||||
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
|
||||
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
|
||||
|
||||
// Absolute changes
|
||||
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
|
||||
void setAngle(double angle, double duration = 0);
|
||||
void setZoom(double zoom, double duration = 0);
|
||||
void setLonLat(double lon, double lat, double duration = 0);
|
||||
void setLonLatZoom(double lon, double lat, double zoom, double duration = 0);
|
||||
|
||||
// Getters
|
||||
void matrixFor(mat4& matrix, const Tile::ID& id) const;
|
||||
float getZoom() const;
|
||||
float getNormalizedZoom() const;
|
||||
int32_t getIntegerZoom() const;
|
||||
double getScale() const;
|
||||
double getAngle() const;
|
||||
void getLonLat(double& lon, double& lat) const;
|
||||
void getLonLatZoom(double& lon, double& lat, double& zoom) const;
|
||||
|
||||
// Animations
|
||||
void startPanning();
|
||||
void stopPanning();
|
||||
void startRotating();
|
||||
void stopRotating();
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
|
||||
box cornersToBox(uint32_t z) const;
|
||||
|
||||
// More getters
|
||||
inline uint16_t getWidth() const { return width; }
|
||||
inline uint16_t getHeight() const { return height; }
|
||||
inline uint16_t getFramebufferWidth() const { return fb_width; }
|
||||
inline uint16_t getFramebufferHeight() const { return fb_height; }
|
||||
inline float getPixelRatio() const { return pixelRatio; }
|
||||
// Transform state
|
||||
const TransformState currentState() const;
|
||||
const TransformState finalState() const;
|
||||
|
||||
private:
|
||||
void setScaleXY(double new_scale, double xn, double yn, double duration = 0);
|
||||
double pixel_x() const;
|
||||
double pixel_y() const;
|
||||
// Functions prefixed with underscores will *not* perform any locks. It is the caller's
|
||||
// responsibility to lock this object.
|
||||
void _moveBy(double dx, double dy, double duration = 0);
|
||||
void _setScale(double scale, double cx, double cy, double duration = 0);
|
||||
void _setScaleXY(double new_scale, double xn, double yn, double duration = 0);
|
||||
void _setAngle(double angle, double duration = 0);
|
||||
void _clearPanning();
|
||||
void _clearRotating();
|
||||
void _clearScaling();
|
||||
|
||||
private:
|
||||
// logical dimensions
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
mutable uv::rwlock mtx;
|
||||
|
||||
// physical (framebuffer) dimensions
|
||||
uint16_t fb_width = 0;
|
||||
uint16_t fb_height = 0;
|
||||
// This reflects the current state of the transform, representing the actual position of the
|
||||
// map. After calling a transform function with a timer, this will likely remain the same until
|
||||
// you render a new frame.
|
||||
TransformState current;
|
||||
|
||||
float pixelRatio = 1;
|
||||
// This reflects the final position of the transform, after all possible animations took place.
|
||||
TransformState final;
|
||||
|
||||
public:
|
||||
bool rotating = false;
|
||||
bool scaling = false;
|
||||
bool panning = false;
|
||||
|
||||
private:
|
||||
double x = 0, y = 0; // pixel values of the map center in the current scale
|
||||
double angle = 0;
|
||||
double scale = 1;
|
||||
|
||||
double min_scale = pow(2, 0);
|
||||
double max_scale = pow(2, 20);
|
||||
// Limit the amount of zooming possible on the map.
|
||||
// TODO: make these modifiable from outside.
|
||||
const double min_scale = pow(2, 0);
|
||||
const double max_scale = pow(2, 20);
|
||||
|
||||
// cache values for spherical mercator math
|
||||
double zc, Bc, Cc;
|
||||
|
||||
68
include/llmr/map/transform_state.hpp
Normal file
68
include/llmr/map/transform_state.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef LLMR_MAP_TRANSFORM_STATE
|
||||
#define LLMR_MAP_TRANSFORM_STATE
|
||||
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/map/tile.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Transform;
|
||||
|
||||
class TransformState {
|
||||
friend class Transform;
|
||||
|
||||
public:
|
||||
// Matrix
|
||||
void matrixFor(mat4& matrix, const Tile::ID& id) const;
|
||||
box cornersToBox(uint32_t z) const;
|
||||
|
||||
// Dimensions
|
||||
uint16_t getWidth() const;
|
||||
uint16_t getHeight() const;
|
||||
uint16_t getFramebufferWidth() const;
|
||||
uint16_t getFramebufferHeight() const;
|
||||
const std::array<uint16_t, 2> getFramebufferDimensions() const;
|
||||
float getPixelRatio() const;
|
||||
|
||||
// Zoom
|
||||
float getNormalizedZoom() const;
|
||||
int32_t getIntegerZoom() const;
|
||||
double getZoom() const;
|
||||
double getScale() const;
|
||||
|
||||
// Rotation
|
||||
float getAngle() const;
|
||||
|
||||
// Changing
|
||||
bool isChanging() const;
|
||||
|
||||
private:
|
||||
double pixel_x() const;
|
||||
double pixel_y() const;
|
||||
|
||||
private:
|
||||
// logical dimensions
|
||||
uint16_t width = 0, height = 0;
|
||||
|
||||
// physical (framebuffer) dimensions
|
||||
std::array<uint16_t, 2> framebuffer = {{ 0, 0 }};
|
||||
|
||||
// map scale factor
|
||||
float pixelRatio = 0;
|
||||
|
||||
// animation state
|
||||
bool rotating = false;
|
||||
bool scaling = false;
|
||||
bool panning = false;
|
||||
|
||||
// map position
|
||||
double x = 0, y = 0;
|
||||
double angle = 0;
|
||||
double scale = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
19
include/llmr/map/view.hpp
Normal file
19
include/llmr/map/view.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef LLMR_MAP_VIEW
|
||||
#define LLMR_MAP_VIEW
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class View {
|
||||
public:
|
||||
// Called from the render (=GL) thread. Signals that the context should
|
||||
// swap the front and the back buffer.
|
||||
virtual void swap() = 0;
|
||||
|
||||
// Called from the render thread. Makes the GL context active in the current
|
||||
// thread. This is typically just called once at the beginning of the
|
||||
// renderer setup since the render thread doesn't switch the contexts.
|
||||
virtual void make_active() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -13,34 +13,27 @@ extern const char *kSpriteURL;
|
||||
|
||||
namespace platform {
|
||||
|
||||
// Restarts painting. This could for example trigger the event loop of the controlling application.
|
||||
void restart();
|
||||
|
||||
class Request;
|
||||
|
||||
|
||||
struct Response {
|
||||
|
||||
Response(std::function<void(Response *)> callback) : callback(callback) {}
|
||||
int16_t code = -1;
|
||||
std::string body;
|
||||
|
||||
std::string error_message;
|
||||
std::function<void(Response *)> callback;
|
||||
};
|
||||
|
||||
// Makes an HTTP request of a URL on a background thread, calls a function with the results in
|
||||
// another thread, then calls the foreground callback in the original main thread.
|
||||
// Returns a cancellable request.
|
||||
std::shared_ptr<Request> request_http(const std::string &url,
|
||||
std::function<void(Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop);
|
||||
|
||||
// Cancels an HTTP request.
|
||||
void cancel_request_http(const std::shared_ptr<Request> &req);
|
||||
|
||||
// Returns a relative timestamp in seconds. This value must be monotonic.
|
||||
double elapsed();
|
||||
|
||||
// Shows an RGBA image with the specified dimensions in a named window.
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height);
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
|
||||
// Forward definition.
|
||||
typedef struct uv_work_s uv_work_t;
|
||||
typedef struct uv_check_s uv_check_t;
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
typedef struct uv_async_s uv_async_t;
|
||||
|
||||
namespace llmr {
|
||||
namespace platform {
|
||||
@@ -20,24 +20,22 @@ struct Response;
|
||||
class Request : public std::enable_shared_from_this<Request>, private util::noncopyable {
|
||||
public:
|
||||
Request(const std::string &url,
|
||||
std::function<void(Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop);
|
||||
~Request();
|
||||
|
||||
public:
|
||||
static void work_callback(uv_work_t *work);
|
||||
static void after_work_callback(uv_work_t *work, int status);
|
||||
void complete();
|
||||
|
||||
private:
|
||||
static void complete(uv_async_t *async);
|
||||
|
||||
public:
|
||||
const std::string url;
|
||||
const std::function<void(Response *)> background_function;
|
||||
const std::function<void()> foreground_callback;
|
||||
std::unique_ptr<Response> res;
|
||||
bool cancelled = false;
|
||||
std::atomic<bool> cancelled;
|
||||
|
||||
public:
|
||||
uv_check_t *check = nullptr;
|
||||
uv_async_t *async = nullptr;
|
||||
uv_loop_t *loop = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <llmr/shader/raster_shader.hpp>
|
||||
#include <llmr/shader/text_shader.hpp>
|
||||
|
||||
#include <llmr/map/transform_state.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Transform;
|
||||
@@ -32,7 +34,8 @@ class RasterBucket;
|
||||
|
||||
class Painter : private util::noncopyable {
|
||||
public:
|
||||
Painter(Transform& transform, Style& style, GlyphAtlas& glyphAtlas);
|
||||
Painter(Map &map);
|
||||
|
||||
|
||||
void setup();
|
||||
void clear();
|
||||
@@ -72,9 +75,7 @@ public:
|
||||
mat4 extrudeMatrix;
|
||||
|
||||
private:
|
||||
Transform& transform;
|
||||
Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
Map& map;
|
||||
|
||||
FrameHistory frameHistory;
|
||||
|
||||
@@ -83,6 +84,7 @@ private:
|
||||
uint32_t gl_program = 0;
|
||||
float gl_lineWidth = 0;
|
||||
bool gl_depthMask = true;
|
||||
std::array<uint16_t, 2> gl_viewport = {{ 0, 0 }};
|
||||
float strata = 0;
|
||||
const float strata_epsilon = 1.0f / (1 << 16);
|
||||
enum { Opaque, Translucent } pass = Opaque;
|
||||
|
||||
@@ -41,18 +41,22 @@ public:
|
||||
|
||||
ImagePosition getPosition(const std::string& name, bool repeating = false);
|
||||
|
||||
operator bool() const;
|
||||
bool isLoaded() const;
|
||||
|
||||
public:
|
||||
const float pixelRatio;
|
||||
std::shared_ptr<Raster> raster;
|
||||
|
||||
private:
|
||||
void parseJSON(const std::string& data);
|
||||
void asyncParseJSON();
|
||||
|
||||
static void parseJSON(std::shared_ptr<Sprite> &sprite);
|
||||
static void complete(std::shared_ptr<Sprite> &sprite);
|
||||
|
||||
private:
|
||||
Map ↦
|
||||
mutable std::mutex mtx;
|
||||
std::string body;
|
||||
bool loaded = false;
|
||||
std::map<std::string, SpritePosition> pos;
|
||||
};
|
||||
|
||||
42
include/llmr/util/time.hpp
Normal file
42
include/llmr/util/time.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef LLMR_UTIL_TIME
|
||||
#define LLMR_UTIL_TIME
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
typedef uint64_t time;
|
||||
|
||||
namespace util {
|
||||
|
||||
|
||||
// Returns time in nanoseconds since an arbitrary point in the past. This has
|
||||
// no relation to the current time, but is continuously increasing, so
|
||||
// comparisons between different timestamps produce meaningful values.
|
||||
inline time now() {
|
||||
return uv_hrtime();
|
||||
}
|
||||
}
|
||||
|
||||
// used for time conversions
|
||||
constexpr time operator"" _nanoseconds (long double ns) { return ns; }
|
||||
constexpr time operator"" _nanosecond (long double ns) { return ns; }
|
||||
constexpr time operator"" _microseconds (long double us) { return us * 1e3; }
|
||||
constexpr time operator"" _microsecond (long double us) { return us * 1e3; }
|
||||
constexpr time operator"" _milliseconds (long double ms) { return ms * 1e6; }
|
||||
constexpr time operator"" _millisecond (long double ms) { return ms * 1e6; }
|
||||
constexpr time operator"" _seconds (long double s) { return s * 1e9; }
|
||||
constexpr time operator"" _second (long double s) { return s * 1e9; }
|
||||
|
||||
constexpr time operator"" _nanoseconds (unsigned long long ns) { return ns; }
|
||||
constexpr time operator"" _nanosecond (unsigned long long ns) { return ns; }
|
||||
constexpr time operator"" _microseconds (unsigned long long us) { return us * 1e3; }
|
||||
constexpr time operator"" _microsecond (unsigned long long us) { return us * 1e3; }
|
||||
constexpr time operator"" _milliseconds (unsigned long long ms) { return ms * 1e6; }
|
||||
constexpr time operator"" _millisecond (unsigned long long ms) { return ms * 1e6; }
|
||||
constexpr time operator"" _seconds (unsigned long long s) { return s * 1e9; }
|
||||
constexpr time operator"" _second (unsigned long long s) { return s * 1e9; }
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
namespace uv {
|
||||
|
||||
typedef void (*callback)();
|
||||
|
||||
class mutex {
|
||||
public:
|
||||
@@ -16,7 +15,6 @@ public:
|
||||
inline ~mutex() { uv_mutex_destroy(&mtx); }
|
||||
inline void lock() { uv_mutex_lock(&mtx); }
|
||||
inline void unlock() { uv_mutex_unlock(&mtx); }
|
||||
inline bool trylock() { return uv_mutex_trylock(&mtx); }
|
||||
|
||||
private:
|
||||
uv_mutex_t mtx;
|
||||
@@ -31,8 +29,40 @@ private:
|
||||
mutex &mtx;
|
||||
};
|
||||
|
||||
class rwlock {
|
||||
public:
|
||||
inline rwlock() { assert(uv_rwlock_init(&mtx) == 0); }
|
||||
inline ~rwlock() { uv_rwlock_destroy(&mtx); }
|
||||
inline void rdlock() { uv_rwlock_rdlock(&mtx); }
|
||||
inline void wrlock() { uv_rwlock_wrlock(&mtx); }
|
||||
inline void rdunlock() { uv_rwlock_rdunlock(&mtx); }
|
||||
inline void wrunlock() { uv_rwlock_wrunlock(&mtx); }
|
||||
|
||||
private:
|
||||
uv_rwlock_t mtx;
|
||||
};
|
||||
|
||||
class readlock {
|
||||
public:
|
||||
inline readlock(rwlock &mtx) : mtx(mtx) { mtx.rdlock(); }
|
||||
inline ~readlock() { mtx.rdunlock(); }
|
||||
|
||||
private:
|
||||
rwlock &mtx;
|
||||
};
|
||||
|
||||
class writelock {
|
||||
public:
|
||||
inline writelock(rwlock &mtx) : mtx(mtx) { mtx.wrlock(); }
|
||||
inline ~writelock() { mtx.wrunlock(); }
|
||||
|
||||
private:
|
||||
rwlock &mtx;
|
||||
};
|
||||
|
||||
class once {
|
||||
public:
|
||||
typedef void (*callback)();
|
||||
void operator()(void (*callback)(void)) {
|
||||
uv_once(&o, callback);
|
||||
}
|
||||
@@ -41,6 +71,40 @@ private:
|
||||
uv_once_t o = UV_ONCE_INIT;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class work {
|
||||
public:
|
||||
typedef void (*work_callback)(T &object);
|
||||
typedef void (*after_work_callback)(T &object);
|
||||
|
||||
template<typename... Args>
|
||||
work(uv_loop_t *loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args)
|
||||
: data(std::forward<Args>(args)...),
|
||||
work_cb(work_cb),
|
||||
after_work_cb(after_work_cb) {
|
||||
req.data = this;
|
||||
uv_queue_work(loop, &req, do_work, after_work);
|
||||
}
|
||||
|
||||
private:
|
||||
static void do_work(uv_work_t *req) {
|
||||
work<T> *w = static_cast<work<T> *>(req->data);
|
||||
w->work_cb(w->data);
|
||||
}
|
||||
|
||||
static void after_work(uv_work_t *req, int) {
|
||||
work<T> *w = static_cast<work<T> *>(req->data);
|
||||
w->after_work_cb(w->data);
|
||||
delete w;
|
||||
}
|
||||
|
||||
private:
|
||||
uv_work_t req;
|
||||
T data;
|
||||
work_callback work_cb;
|
||||
after_work_callback after_work_cb;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <GLKit/GLKit.h>
|
||||
|
||||
@interface MBXViewController : GLKViewController
|
||||
@interface MBXViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#import <OpenGLES/EAGL.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <GLKit/GLKit.h>
|
||||
|
||||
#include <llmr/llmr.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
@@ -25,19 +26,18 @@
|
||||
|
||||
@implementation MBXViewController
|
||||
|
||||
class View;
|
||||
|
||||
llmr::Map *map = nullptr;
|
||||
View *viewObj = nullptr;
|
||||
llmr::Settings_iOS *settings = nullptr;
|
||||
MBXViewController *viewController = nullptr;
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
- (void)viewDidLoad
|
||||
- (void)loadView
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
viewController = self;
|
||||
|
||||
self.preferredFramesPerSecond = 60;
|
||||
[super loadView];
|
||||
|
||||
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
||||
|
||||
@@ -46,29 +46,34 @@ MBXViewController *viewController = nullptr;
|
||||
NSLog(@"Failed to create OpenGL ES context");
|
||||
}
|
||||
|
||||
GLKView *view = (GLKView *)self.view;
|
||||
|
||||
view.context = self.context;
|
||||
GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds] context:self.context];
|
||||
view.enableSetNeedsDisplay = NO;
|
||||
view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
|
||||
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
|
||||
|
||||
[view bindDrawable];
|
||||
|
||||
self.view = view;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupMap];
|
||||
[self setupInteraction];
|
||||
[self setupDebugUI];
|
||||
|
||||
map->start();
|
||||
}
|
||||
|
||||
- (void)setupMap
|
||||
{
|
||||
[EAGLContext setCurrentContext:self.context];
|
||||
|
||||
settings = new llmr::Settings_iOS();
|
||||
settings->load();
|
||||
|
||||
map = new llmr::Map(*settings);
|
||||
map->setup();
|
||||
map->loadSettings();
|
||||
viewObj = new View(self);
|
||||
|
||||
map = new llmr::Map(*viewObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -146,26 +151,23 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
#pragma mark - Rendering delegates
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
- (void)swap
|
||||
{
|
||||
// This callback is called whenever the view dimensions change. We trigger an update to have a fresh render to display
|
||||
// while the view change is transitioned.
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
// TODO: Pause all current animations during animation. We don't want other events to fire while the view is rotated.
|
||||
self.paused = NO;
|
||||
if (map->swapped.test_and_set() == false)
|
||||
{
|
||||
[(GLKView *)self.view display];
|
||||
map->rendered.clear();
|
||||
map->rerender();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
// Render the map
|
||||
map->resize(rect.size.width, rect.size.height, view.contentScaleFactor, view.drawableWidth, view.drawableHeight);
|
||||
[super viewWillLayoutSubviews];
|
||||
|
||||
if (map->render() == false)
|
||||
{
|
||||
self.paused = YES;
|
||||
settings->sync();
|
||||
}
|
||||
GLKView *view = (GLKView *)self.view;
|
||||
CGRect rect = [view bounds];
|
||||
map->resize(rect.size.width, rect.size.height, view.contentScaleFactor, view.drawableWidth, view.drawableHeight);
|
||||
}
|
||||
|
||||
#pragma mark - Debugging UI
|
||||
@@ -211,7 +213,7 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
- (void)toggleRaster
|
||||
{
|
||||
map->toggleRaster();
|
||||
map->toggleRaster();
|
||||
}
|
||||
|
||||
#pragma mark - Destruction
|
||||
@@ -224,6 +226,12 @@ MBXViewController *viewController = nullptr;
|
||||
map = nullptr;
|
||||
}
|
||||
|
||||
if (viewObj)
|
||||
{
|
||||
delete viewObj;
|
||||
viewObj = nullptr;
|
||||
}
|
||||
|
||||
if (settings)
|
||||
{
|
||||
settings->sync();
|
||||
@@ -270,8 +278,6 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
map->moveBy(finalCenter.x - self.center.x, finalCenter.y - self.center.y, duration);
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
|
||||
@@ -324,8 +330,6 @@ MBXViewController *viewController = nullptr;
|
||||
{
|
||||
map->stopScaling();
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
|
||||
@@ -346,8 +350,6 @@ MBXViewController *viewController = nullptr;
|
||||
{
|
||||
map->stopRotating();
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
|
||||
@@ -366,8 +368,6 @@ MBXViewController *viewController = nullptr;
|
||||
{
|
||||
map->scaleBy(2, [doubleTap locationInView:doubleTap.view].x, [doubleTap locationInView:doubleTap.view].y, 0.5);
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap
|
||||
@@ -378,8 +378,6 @@ MBXViewController *viewController = nullptr;
|
||||
{
|
||||
map->scaleBy(0.5, [twoFingerTap locationInView:twoFingerTap.view].x, [twoFingerTap locationInView:twoFingerTap.view].y, 0.5);
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
|
||||
@@ -400,8 +398,6 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
map->scaleBy(powf(2, newZoom) / map->getScale(), self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
|
||||
}
|
||||
|
||||
self.paused = NO;
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
||||
@@ -411,40 +407,21 @@ MBXViewController *viewController = nullptr;
|
||||
return ([validSimultaneousGestures containsObject:[gestureRecognizer class]] && [validSimultaneousGestures containsObject:[otherGestureRecognizer class]]);
|
||||
}
|
||||
|
||||
@end
|
||||
class View : public llmr::View {
|
||||
public:
|
||||
View(MBXViewController *controller) : controller(controller) {}
|
||||
virtual ~View() {}
|
||||
|
||||
|
||||
namespace llmr
|
||||
{
|
||||
namespace platform
|
||||
{
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
int16_t identifier;
|
||||
std::string original_url;
|
||||
};
|
||||
|
||||
void restart()
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
{
|
||||
viewController.paused = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
[viewController performSelectorOnMainThread:@selector(setPaused:) withObject:@(NO) waitUntilDone:NO];
|
||||
}
|
||||
}
|
||||
|
||||
double time()
|
||||
{
|
||||
return [viewController timeSinceFirstResume];
|
||||
}
|
||||
|
||||
double elapsed()
|
||||
{
|
||||
return CACurrentMediaTime();
|
||||
}
|
||||
void make_active() {
|
||||
[EAGLContext setCurrentContext:controller.context];
|
||||
}
|
||||
}
|
||||
|
||||
void swap() {
|
||||
[controller performSelectorOnMainThread:@selector(swap) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
private:
|
||||
MBXViewController *controller = nullptr;
|
||||
};
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
MapView *mapView = nullptr;
|
||||
|
||||
void quit_handler(int s) {
|
||||
void quit_handler(int) {
|
||||
if (mapView) {
|
||||
fprintf(stderr, "waiting for quit...\n");
|
||||
glfwSetWindowShouldClose(mapView->window, true);
|
||||
llmr::platform::restart();
|
||||
glfwPostEmptyEvent();
|
||||
} else {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
426
src/map/map.cpp
426
src/map/map.cpp
@@ -1,65 +1,130 @@
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/settings.hpp>
|
||||
#include <llmr/map/source.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/style/resources.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/util/animation.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
Map::Map(Settings& settings)
|
||||
: settings(settings),
|
||||
Map::Map(View& view)
|
||||
: view(view),
|
||||
transform(),
|
||||
texturepool(),
|
||||
style(),
|
||||
glyphAtlas(1024, 1024),
|
||||
painter(transform, style, glyphAtlas),
|
||||
painter(*this),
|
||||
loop(uv_loop_new()) {
|
||||
|
||||
// Make sure that we're doing an initial drawing in all cases.
|
||||
clean.clear();
|
||||
rendered.clear();
|
||||
swapped.test_and_set();
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
void Map::start() {
|
||||
uv_async_init(loop, &async_terminate, [](uv_async_t *async) {
|
||||
uv_stop(static_cast<uv_loop_t *>(async->data));
|
||||
});
|
||||
async_terminate.data = loop;
|
||||
|
||||
uv_thread_create(&thread, eventloop, this);
|
||||
}
|
||||
|
||||
void Map::stop() {
|
||||
uv_async_send(&async_terminate);
|
||||
uv_thread_join(&thread);
|
||||
|
||||
uv_loop_delete(loop);
|
||||
loop = nullptr;
|
||||
}
|
||||
|
||||
void Map::eventloop(void *arg) {
|
||||
Map *map = static_cast<Map *>(arg);
|
||||
map->run();
|
||||
void Map::start() {
|
||||
// When starting map rendering in another thread, we perform async/continuously
|
||||
// updated rendering. Only in these cases, we attach the async handlers.
|
||||
async = true;
|
||||
|
||||
// Setup async notifications
|
||||
async_terminate = new uv_async_t();
|
||||
uv_async_init(loop, async_terminate, terminate);
|
||||
async_terminate->data = loop;
|
||||
|
||||
async_render = new uv_async_t();
|
||||
uv_async_init(loop, async_render, render);
|
||||
async_render->data = this;
|
||||
|
||||
uv_thread_create(&thread, [](void *arg) {
|
||||
Map *map = static_cast<Map *>(arg);
|
||||
map->run();
|
||||
}, this);
|
||||
}
|
||||
|
||||
void Map::stop() {
|
||||
uv_async_send(async_terminate);
|
||||
uv_thread_join(&thread);
|
||||
|
||||
uv_close((uv_handle_t *)async_terminate, delete_async);
|
||||
async_terminate = nullptr;
|
||||
uv_close((uv_handle_t *)async_render, delete_async);
|
||||
async_render = nullptr;
|
||||
|
||||
// Run the event loop once to make sure our async delete handlers are called.
|
||||
uv_run(loop, UV_RUN_ONCE);
|
||||
|
||||
async = false;
|
||||
}
|
||||
|
||||
void Map::delete_async(uv_handle_t *handle) {
|
||||
delete (uv_async_t *)handle;
|
||||
}
|
||||
|
||||
void Map::run() {
|
||||
setup();
|
||||
prepareRender();
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
// If the map rendering wasn't started asynchronously, we perform one render
|
||||
// *after* all events have been processed.
|
||||
if (!async) {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::setup() {
|
||||
painter.setup();
|
||||
void Map::rerender() {
|
||||
// We only send render events if we want to continuously update the map
|
||||
// (== async rendering).
|
||||
if (async) {
|
||||
uv_async_send(async_render);
|
||||
}
|
||||
}
|
||||
|
||||
style.loadJSON(resources::style, resources::style_size);
|
||||
void Map::update() {
|
||||
clean.clear();
|
||||
rerender();
|
||||
}
|
||||
|
||||
void Map::render(uv_async_t *async) {
|
||||
Map *map = static_cast<Map *>(async->data);
|
||||
|
||||
map->prepare();
|
||||
|
||||
if (map->rendered.test_and_set() == false) {
|
||||
if (map->clean.test_and_set() == false) {
|
||||
map->swapped.clear();
|
||||
map->view.swap();
|
||||
} else {
|
||||
// We set the rendered flag in the test above, so we have to reset it
|
||||
// now that we're not actually rendering because the map is clean.
|
||||
map->rendered.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Map::terminate(uv_async_t *async) {
|
||||
uv_stop(static_cast<uv_loop_t *>(async->data));
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
void Map::setup() {
|
||||
view.make_active();
|
||||
|
||||
painter.setup();
|
||||
|
||||
sources.emplace("mapbox streets",
|
||||
std::unique_ptr<Source>(new Source(*this,
|
||||
transform,
|
||||
painter,
|
||||
texturepool,
|
||||
"http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf",
|
||||
@@ -72,7 +137,6 @@ void Map::setup() {
|
||||
|
||||
sources.emplace("satellite",
|
||||
std::unique_ptr<Source>(new Source(*this,
|
||||
transform,
|
||||
painter,
|
||||
texturepool,
|
||||
"https://a.tiles.mapbox.com/v3/justin.hh0gkdfm/%d/%d/%d%s.png256",
|
||||
@@ -82,6 +146,8 @@ void Map::setup() {
|
||||
0,
|
||||
21,
|
||||
false)));
|
||||
|
||||
loadStyle(resources::style, resources::style_size);
|
||||
}
|
||||
|
||||
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
|
||||
@@ -89,176 +155,120 @@ void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::loadSettings() {
|
||||
transform.setAngle(settings.angle);
|
||||
transform.setScale(settings.scale);
|
||||
transform.setLonLat(settings.longitude, settings.latitude);
|
||||
painter.setDebug(settings.debug);
|
||||
update();
|
||||
}
|
||||
#pragma mark - Size
|
||||
|
||||
void Map::resize(uint16_t width, uint16_t height, float ratio) {
|
||||
resize(width, height, ratio, width * ratio, height * ratio);
|
||||
}
|
||||
|
||||
void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) {
|
||||
transform.resize(width, height, ratio, fb_width, fb_height);
|
||||
painter.resize();
|
||||
|
||||
if (!style.sprite || style.sprite->pixelRatio != transform.getPixelRatio()) {
|
||||
style.sprite = std::make_shared<Sprite>(*this, transform.getPixelRatio());
|
||||
style.sprite->load(kSpriteURL);
|
||||
if (transform.resize(width, height, ratio, fb_width, fb_height)) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
box Map::cornersToBox(uint32_t z) const {
|
||||
return transform.cornersToBox(z);
|
||||
#pragma mark - Animations
|
||||
|
||||
void Map::cancelAnimations() {
|
||||
transform.cancelAnimations();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
float Map::getPixelRatio() const {
|
||||
return transform.getPixelRatio();
|
||||
}
|
||||
|
||||
Style& Map::getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
GlyphAtlas& Map::getGlyphAtlas() {
|
||||
return glyphAtlas;
|
||||
}
|
||||
|
||||
uv_loop_t *Map::getLoop() {
|
||||
return loop;
|
||||
}
|
||||
#pragma mark - Position
|
||||
|
||||
void Map::moveBy(double dx, double dy, double duration) {
|
||||
transform.moveBy(dx, dy, duration);
|
||||
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::startPanning() {
|
||||
transform.startPanning();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::stopPanning() {
|
||||
transform.stopPanning();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::scaleBy(double ds, double cx, double cy, double duration) {
|
||||
transform.scaleBy(ds, cx, cy, duration);
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.scale = transform.getScale();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::startScaling() {
|
||||
transform.startScaling();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::stopScaling() {
|
||||
transform.stopScaling();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
|
||||
transform.rotateBy(sx, sy, ex, ey, duration);
|
||||
update();
|
||||
|
||||
settings.angle = transform.getAngle();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::startRotating() {
|
||||
transform.startRotating();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::stopRotating() {
|
||||
transform.stopRotating();
|
||||
redraw();
|
||||
}
|
||||
|
||||
|
||||
void Map::setLonLat(double lon, double lat, double duration) {
|
||||
transform.setLonLat(lon, lat, duration);
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::getLonLat(double& lon, double& lat) const {
|
||||
transform.getLonLat(lon, lat);
|
||||
}
|
||||
|
||||
void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
|
||||
transform.setLonLatZoom(lon, lat, zoom, duration);
|
||||
void Map::startPanning() {
|
||||
transform.startPanning();
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.scale = transform.getScale();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
|
||||
transform.getLonLatZoom(lon, lat, zoom);
|
||||
void Map::stopPanning() {
|
||||
transform.stopPanning();
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::resetPosition() {
|
||||
transform.setAngle(0);
|
||||
transform.setLonLat(0, 0);
|
||||
transform.setZoom(0);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Scale
|
||||
|
||||
void Map::scaleBy(double ds, double cx, double cy, double duration) {
|
||||
transform.scaleBy(ds, cx, cy, duration);
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::setScale(double scale, double cx, double cy, double duration) {
|
||||
transform.setScale(scale, cx, cy, duration);
|
||||
update();
|
||||
}
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.scale = transform.getScale();
|
||||
settings.persist();
|
||||
double Map::getScale() const {
|
||||
return transform.getScale();
|
||||
}
|
||||
|
||||
void Map::setZoom(double zoom, double duration) {
|
||||
transform.setZoom(zoom, duration);
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.scale = transform.getScale();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
double Map::getZoom() const {
|
||||
return transform.getZoom();
|
||||
}
|
||||
|
||||
void Map::setAngle(double angle, double x, double y, double duration) {
|
||||
double dx = 0, dy = 0;
|
||||
|
||||
if (x >= 0 && y >= 0) {
|
||||
dx = (transform.getWidth() / 2) - x;
|
||||
dy = (transform.getWidth() / 2) - y;
|
||||
transform.moveBy(dx, dy, duration);
|
||||
}
|
||||
|
||||
transform.setAngle(angle, duration);
|
||||
|
||||
if (x >= 0 && y >= 0) {
|
||||
transform.moveBy(-dx, -dy, duration);
|
||||
}
|
||||
|
||||
void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
|
||||
transform.setLonLatZoom(lon, lat, zoom, duration);
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.angle = transform.getAngle();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
double Map::getScale() const {
|
||||
return transform.getScale();
|
||||
void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
|
||||
transform.getLonLatZoom(lon, lat, zoom);
|
||||
}
|
||||
|
||||
void Map::resetZoom() {
|
||||
setZoom(0);
|
||||
}
|
||||
|
||||
void Map::startScaling() {
|
||||
transform.startScaling();
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::stopScaling() {
|
||||
transform.stopScaling();
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
|
||||
transform.rotateBy(sx, sy, ex, ey, duration);
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::setAngle(double angle, double cx, double cy) {
|
||||
transform.setAngle(angle, cx, cy);
|
||||
update();
|
||||
}
|
||||
|
||||
double Map::getAngle() const {
|
||||
@@ -268,33 +278,33 @@ double Map::getAngle() const {
|
||||
void Map::resetNorth() {
|
||||
transform.setAngle(0, 0.5);
|
||||
update();
|
||||
|
||||
settings.angle = transform.getAngle();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::resetPosition() {
|
||||
transform.setAngle(0);
|
||||
transform.setLonLat(0, 0);
|
||||
transform.setZoom(0);
|
||||
void Map::startRotating() {
|
||||
transform.startRotating();
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
settings.scale = transform.getScale();
|
||||
settings.angle = transform.getAngle();
|
||||
settings.persist();
|
||||
}
|
||||
|
||||
void Map::resetZoom() {
|
||||
setZoom(0);
|
||||
void Map::stopRotating() {
|
||||
transform.stopRotating();
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Toggles
|
||||
|
||||
void Map::setDebug(bool value) {
|
||||
debug = value;
|
||||
painter.setDebug(debug);
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::toggleDebug() {
|
||||
settings.debug = !settings.debug;
|
||||
painter.setDebug(settings.debug);
|
||||
update();
|
||||
setDebug(!debug);
|
||||
}
|
||||
|
||||
settings.persist();
|
||||
bool Map::getDebug() const {
|
||||
return debug;
|
||||
}
|
||||
|
||||
void Map::toggleRaster() {
|
||||
@@ -309,64 +319,45 @@ void Map::toggleRaster() {
|
||||
satellite_source.enabled = true;
|
||||
style.appliedClasses.insert("satellite");
|
||||
}
|
||||
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void Map::cancelAnimations() {
|
||||
transform.cancelAnimations();
|
||||
}
|
||||
|
||||
void Map::update() {
|
||||
// Loads new tiles and removes old ones.
|
||||
updateTiles();
|
||||
|
||||
// Updates the stylesheet to the new zoom level
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
|
||||
// Schedules a repaint
|
||||
redraw();
|
||||
}
|
||||
|
||||
void Map::redraw() {
|
||||
clean.clear();
|
||||
platform::restart();
|
||||
}
|
||||
|
||||
bool Map::updateTiles() {
|
||||
bool changed = false;
|
||||
|
||||
void Map::updateTiles() {
|
||||
for (auto &pair : sources) {
|
||||
Source &source = *pair.second;
|
||||
if (source.enabled) {
|
||||
changed = source.update() || changed;
|
||||
const std::unique_ptr<Source> &source = pair.second;
|
||||
if (source->enabled) {
|
||||
source->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
void Map::prepare() {
|
||||
view.make_active();
|
||||
|
||||
// Update animations
|
||||
animationTime = (double)util::now() / 1e9;
|
||||
bool animating = transform.needsAnimation();
|
||||
if (animating) {
|
||||
transform.updateAnimations(animationTime);
|
||||
}
|
||||
|
||||
state = transform.currentState();
|
||||
style.cascade(state.getNormalizedZoom());
|
||||
if (!style.sprite || style.sprite->pixelRatio != state.getPixelRatio()) {
|
||||
style.sprite = std::make_shared<Sprite>(*this, state.getPixelRatio());
|
||||
style.sprite->load(kSpriteURL);
|
||||
}
|
||||
|
||||
updateTiles();
|
||||
}
|
||||
|
||||
void Map::render() {
|
||||
double animationTime = platform::elapsed();
|
||||
|
||||
if (transform.needsAnimation()) {
|
||||
transform.updateAnimations(animationTime);
|
||||
|
||||
// Animations change the transform which affects the stylesheet.
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
}
|
||||
|
||||
if (*style.sprite->raster && !style.sprite->raster->textured) {
|
||||
style.sprite->raster->setTexturepool(&texturepool);
|
||||
}
|
||||
|
||||
bool changed = updateTiles();
|
||||
|
||||
painter.clear();
|
||||
|
||||
painter.resize();
|
||||
|
||||
painter.changeMatrix();
|
||||
|
||||
// First, update the sources' tile matrices with the new
|
||||
@@ -379,7 +370,7 @@ void Map::render() {
|
||||
|
||||
for (auto &pair : sources) {
|
||||
Source &source = *pair.second;
|
||||
source.prepare_render(is_baselayer);
|
||||
source.prepare_render(state, is_baselayer);
|
||||
is_baselayer = source.enabled ? false : (true && is_baselayer);
|
||||
}
|
||||
|
||||
@@ -395,11 +386,8 @@ void Map::render() {
|
||||
|
||||
painter.renderMatte();
|
||||
|
||||
|
||||
bool transition = transform.needsAnimation();
|
||||
bool animation = painter.needsAnimation();
|
||||
|
||||
if (changed || transition || animation) {
|
||||
redraw();
|
||||
// Schedule another rerender when we definitely need a next frame.
|
||||
if (transform.needsAnimation()) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
Source::Source(Map &map, Transform &transform, Painter &painter, Texturepool &texturepool,
|
||||
Source::Source(Map &map, Painter &painter, Texturepool &texturepool,
|
||||
const char *url, Source::Type type, std::vector<uint32_t> zooms, uint32_t tile_size,
|
||||
uint32_t min_zoom, uint32_t max_zoom, bool enabled)
|
||||
: enabled(enabled),
|
||||
map(map),
|
||||
transform(transform),
|
||||
painter(painter),
|
||||
texturepool(texturepool),
|
||||
type(type),
|
||||
@@ -30,7 +29,7 @@ bool Source::update() {
|
||||
return updateTiles();
|
||||
}
|
||||
|
||||
void Source::prepare_render(bool is_baselayer) {
|
||||
void Source::prepare_render(const TransformState &transform, bool is_baselayer) {
|
||||
if (!enabled) return;
|
||||
|
||||
uint8_t i = 1;
|
||||
@@ -113,7 +112,7 @@ TileData::State Source::addTile(const Tile::ID& id) {
|
||||
if (type == Source::Type::vector) {
|
||||
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y);
|
||||
} else {
|
||||
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getPixelRatio() > 1.0 ? "@2x" : ""));
|
||||
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getState().getPixelRatio() > 1.0 ? "@2x" : ""));
|
||||
}
|
||||
|
||||
new_tile.data = std::make_shared<TileData>(normalized_id, map, formed_url, (type == Source::Type::raster));
|
||||
@@ -179,7 +178,7 @@ bool Source::updateTiles() {
|
||||
bool changed = false;
|
||||
|
||||
// Figure out what tiles we need to load
|
||||
int32_t zoom = map.getZoom();
|
||||
int32_t zoom = map.getState().getIntegerZoom();
|
||||
if (zoom > max_zoom) zoom = max_zoom;
|
||||
if (zoom < min_zoom) zoom = min_zoom;
|
||||
|
||||
@@ -190,7 +189,7 @@ bool Source::updateTiles() {
|
||||
if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom;
|
||||
|
||||
// Map four viewport corners to pixel coordinates
|
||||
box box = map.cornersToBox(zoom);
|
||||
box box = map.getState().cornersToBox(zoom);
|
||||
|
||||
// Performs a scanline algorithm search that covers the rectangle of the box
|
||||
// and sorts them by proximity to the center.
|
||||
@@ -263,7 +262,7 @@ bool Source::updateTiles() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -331,8 +330,8 @@ void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const
|
||||
|
||||
double Source::getZoom() const {
|
||||
double offset = log(util::tileSize / tile_size) / log(2);
|
||||
offset += (map.getPixelRatio() > 1.0 ? 1 :0);
|
||||
return map.getZoom() + offset;
|
||||
offset += (map.getState().getPixelRatio() > 1.0 ? 1 :0);
|
||||
return map.getState().getZoom() + offset;
|
||||
}
|
||||
|
||||
std::forward_list<llmr::Tile::ID> Source::covering_tiles(int32_t zoom, const box& points) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <llmr/util/raster.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
|
||||
#include <uv.h>
|
||||
#include <llmr/util/uv.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -47,16 +47,14 @@ void TileData::request() {
|
||||
// to drop to zero for destruction.
|
||||
} else if (res->code == 200) {
|
||||
tile->state = State::loaded;
|
||||
|
||||
tile->data.swap(res->body);
|
||||
tile->parse();
|
||||
|
||||
// Schedule tile parsing in another thread
|
||||
tile->scheduleParsing();
|
||||
} else {
|
||||
fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str());
|
||||
}
|
||||
}, [weak_tile]() {
|
||||
std::shared_ptr<TileData> tile = weak_tile.lock();
|
||||
if (tile) {
|
||||
tile->map.redraw();
|
||||
}
|
||||
}, map.getLoop());
|
||||
}
|
||||
|
||||
@@ -67,9 +65,19 @@ void TileData::cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
bool TileData::parse() {
|
||||
void TileData::scheduleParsing() {
|
||||
// We're creating a new work request. The work request deletes itself after it executed
|
||||
// the after work handler
|
||||
new uv::work<std::shared_ptr<TileData>>(
|
||||
map.getLoop(),
|
||||
[](std::shared_ptr<TileData> &tile) { tile->parse(); },
|
||||
[](std::shared_ptr<TileData> &tile) { tile->map.update(); },
|
||||
shared_from_this());
|
||||
}
|
||||
|
||||
void TileData::parse() {
|
||||
if (state != State::loaded) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -80,14 +88,10 @@ bool TileData::parse() {
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
|
||||
cancel();
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == State::obsolete) {
|
||||
return false;
|
||||
} else {
|
||||
if (state != State::obsolete) {
|
||||
state = State::parsed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,113 +3,280 @@
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
const double D2R = M_PI / 180.0;
|
||||
const double R2D = 180.0 / M_PI;
|
||||
const double M2PI = 2 * M_PI;
|
||||
|
||||
|
||||
Transform::Transform() {
|
||||
setScale(scale);
|
||||
setAngle(angle);
|
||||
setScale(current.scale);
|
||||
setAngle(current.angle);
|
||||
}
|
||||
|
||||
bool Transform::needsAnimation() const {
|
||||
return !animations.empty();
|
||||
}
|
||||
#pragma mark - Map View
|
||||
|
||||
void Transform::updateAnimations(double time) {
|
||||
animations.remove_if([time](const std::shared_ptr<util::animation>& animation) {
|
||||
return animation->update(time) == util::animation::complete;
|
||||
});
|
||||
}
|
||||
bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
|
||||
const uint16_t fb_w, const uint16_t fb_h) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
void Transform::cancelAnimations() {
|
||||
animations.clear();
|
||||
}
|
||||
if (final.width != w || final.height != h || final.pixelRatio != ratio ||
|
||||
final.framebuffer[0] != fb_w || final.framebuffer[1] != fb_h) {
|
||||
|
||||
void Transform::resize(uint16_t w, uint16_t h, float ratio, uint16_t fb_w, uint16_t fb_h) {
|
||||
width = w;
|
||||
height = h;
|
||||
pixelRatio = ratio;
|
||||
fb_width = fb_w;
|
||||
fb_height = fb_h;
|
||||
}
|
||||
|
||||
void Transform::moveBy(double dx, double dy, double duration) {
|
||||
double xn = x + cos(angle) * dx + sin(angle) * dy;
|
||||
double yn = y + cos(angle) * dy + sin(-angle) * dx;
|
||||
if (duration == 0) {
|
||||
x = xn;
|
||||
y = yn;
|
||||
current.width = final.width = w;
|
||||
current.height = final.height = h;
|
||||
current.pixelRatio = final.pixelRatio = ratio;
|
||||
current.framebuffer[0] = final.framebuffer[0] = fb_w;
|
||||
current.framebuffer[1] = final.framebuffer[1] = fb_h;
|
||||
return true;
|
||||
} else {
|
||||
// Use a common start time for all of the animations to avoid divergent animations.
|
||||
double start = platform::elapsed();
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, start, duration));
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, start, duration));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Position
|
||||
|
||||
void Transform::moveBy(const double dx, const double dy, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_moveBy(dx, dy, duration);
|
||||
}
|
||||
|
||||
void Transform::_moveBy(const double dx, const double dy, const double duration) {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
final.x = current.x + cos(current.angle) * dx + sin(current.angle) * dy;
|
||||
final.y = current.y + cos(current.angle) * dy + sin(-current.angle) * dx;
|
||||
|
||||
if (duration == 0) {
|
||||
current.x = final.x;
|
||||
current.y = final.y;
|
||||
} else {
|
||||
// Use a common start time for all of the animations to avoid divergent animations.
|
||||
double start = (double)util::now() / 1_second;
|
||||
animations.emplace_front(
|
||||
std::make_shared<util::ease_animation>(current.x, final.x, current.x, start, duration));
|
||||
animations.emplace_front(
|
||||
std::make_shared<util::ease_animation>(current.y, final.y, current.y, start, duration));
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::setLonLat(const double lon, const double lat, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
|
||||
double xn = -round(lon * Bc);
|
||||
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
|
||||
|
||||
_setScaleXY(current.scale, xn, yn, duration);
|
||||
}
|
||||
|
||||
void Transform::setLonLatZoom(const double lon, const double lat, const double zoom,
|
||||
const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
double new_scale = pow(2.0, zoom);
|
||||
|
||||
const double s = new_scale * util::tileSize;
|
||||
zc = s / 2;
|
||||
Bc = s / 360;
|
||||
Cc = s / (2 * M_PI);
|
||||
|
||||
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
|
||||
double xn = -round(lon * Bc);
|
||||
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
|
||||
|
||||
_setScaleXY(new_scale, xn, yn, duration);
|
||||
}
|
||||
|
||||
void Transform::getLonLat(double &lon, double &lat) const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
lon = -final.x / Bc;
|
||||
lat = R2D * (2 * atan(exp(final.y / Cc)) - 0.5 * M_PI);
|
||||
}
|
||||
|
||||
void Transform::getLonLatZoom(double &lon, double &lat, double &zoom) const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
getLonLat(lon, lat);
|
||||
zoom = getZoom();
|
||||
}
|
||||
|
||||
void Transform::startPanning() {
|
||||
stopPanning();
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearPanning();
|
||||
|
||||
// Add a 200ms timeout for resetting this to false
|
||||
panning = true;
|
||||
double start = platform::elapsed();
|
||||
pan_timeout = std::make_shared<util::timeout<bool>>(false, panning, start, 0.2);
|
||||
current.panning = true;
|
||||
double start = (double)util::now() / 1_second;
|
||||
pan_timeout = std::make_shared<util::timeout<bool>>(false, current.panning, start, 0.2);
|
||||
animations.emplace_front(pan_timeout);
|
||||
}
|
||||
|
||||
void Transform::stopPanning() {
|
||||
panning = false;
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearPanning();
|
||||
}
|
||||
|
||||
void Transform::_clearPanning() {
|
||||
current.panning = false;
|
||||
if (pan_timeout) {
|
||||
animations.remove(pan_timeout);
|
||||
pan_timeout.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::scaleBy(double ds, double cx, double cy, double duration) {
|
||||
#pragma mark - Zoom
|
||||
|
||||
void Transform::scaleBy(const double ds, const double cx, const double cy, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
// clamp scale to min/max values
|
||||
double new_scale = scale * ds;
|
||||
double new_scale = current.scale * ds;
|
||||
if (new_scale < min_scale) {
|
||||
new_scale = min_scale;
|
||||
} else if (new_scale > max_scale) {
|
||||
new_scale = max_scale;
|
||||
}
|
||||
|
||||
setScale(new_scale, cx, cy, duration);
|
||||
_setScale(new_scale, cx, cy, duration);
|
||||
}
|
||||
|
||||
void Transform::setScale(const double scale, const double cx, const double cy,
|
||||
const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_setScale(scale, cx, cy, duration);
|
||||
}
|
||||
|
||||
void Transform::setZoom(const double zoom, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_setScale(pow(2.0, zoom), -1, -1, duration);
|
||||
}
|
||||
|
||||
double Transform::getZoom() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
return log(final.scale) / M_LN2;
|
||||
}
|
||||
|
||||
double Transform::getScale() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
return final.scale;
|
||||
}
|
||||
|
||||
void Transform::startScaling() {
|
||||
stopScaling();
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearScaling();
|
||||
|
||||
// Add a 200ms timeout for resetting this to false
|
||||
scaling = true;
|
||||
double start = platform::elapsed();
|
||||
scale_timeout = std::make_shared<util::timeout<bool>>(false, scaling, start, 0.2);
|
||||
current.scaling = true;
|
||||
double start = (double)util::now() / 1_second;
|
||||
scale_timeout = std::make_shared<util::timeout<bool>>(false, current.scaling, start, 0.2);
|
||||
animations.emplace_front(scale_timeout);
|
||||
}
|
||||
|
||||
void Transform::stopScaling() {
|
||||
scaling = false;
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearScaling();
|
||||
}
|
||||
|
||||
void Transform::_clearScaling() {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
current.scaling = false;
|
||||
if (scale_timeout) {
|
||||
animations.remove(scale_timeout);
|
||||
scale_timeout.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::rotateBy(double start_x, double start_y, double end_x, double end_y, double duration) {
|
||||
double center_x = width / 2, center_y = height / 2;
|
||||
void Transform::_setScale(double new_scale, double cx, double cy, const double duration) {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
// Ensure that we don't zoom in further than the maximum allowed.
|
||||
if (new_scale < min_scale) {
|
||||
new_scale = min_scale;
|
||||
} else if (new_scale > max_scale) {
|
||||
new_scale = max_scale;
|
||||
}
|
||||
|
||||
// Zoom in on the center if we don't have click or gesture anchor coordinates.
|
||||
if (cx < 0 || cy < 0) {
|
||||
cx = current.width / 2;
|
||||
cy = current.height / 2;
|
||||
}
|
||||
|
||||
// Account for the x/y offset from the center (= where the user clicked or pinched)
|
||||
const double factor = new_scale / current.scale;
|
||||
const double dx = (cx - current.width / 2) * (1.0 - factor);
|
||||
const double dy = (cy - current.height / 2) * (1.0 - factor);
|
||||
|
||||
// Account for angle
|
||||
const double angle_sin = sin(-current.angle);
|
||||
const double angle_cos = cos(-current.angle);
|
||||
const double ax = angle_cos * dx - angle_sin * dy;
|
||||
const double ay = angle_sin * dx + angle_cos * dy;
|
||||
|
||||
const double xn = current.x * factor + ax;
|
||||
const double yn = current.y * factor + ay;
|
||||
|
||||
_setScaleXY(new_scale, xn, yn, duration);
|
||||
}
|
||||
|
||||
void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
|
||||
const double duration) {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
final.scale = new_scale;
|
||||
final.x = xn;
|
||||
final.y = yn;
|
||||
|
||||
if (duration == 0) {
|
||||
current.scale = final.scale;
|
||||
current.x = final.x;
|
||||
current.y = final.y;
|
||||
} else {
|
||||
// Use a common start time for all of the animations to avoid divergent animations.
|
||||
double start = (double)util::now() / 1_second;
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(
|
||||
current.scale, final.scale, current.scale, start, duration));
|
||||
animations.emplace_front(
|
||||
std::make_shared<util::ease_animation>(current.x, final.x, current.x, start, duration));
|
||||
animations.emplace_front(
|
||||
std::make_shared<util::ease_animation>(current.y, final.y, current.y, start, duration));
|
||||
}
|
||||
|
||||
const double s = final.scale * util::tileSize;
|
||||
zc = s / 2;
|
||||
Bc = s / 360;
|
||||
Cc = s / (2 * M_PI);
|
||||
}
|
||||
|
||||
#pragma mark - Angle
|
||||
|
||||
void Transform::rotateBy(const double start_x, const double start_y, const double end_x,
|
||||
const double end_y, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
double center_x = current.width / 2, center_y = current.height / 2;
|
||||
|
||||
const double begin_center_x = start_x - center_x;
|
||||
const double begin_center_y = start_y - center_y;
|
||||
|
||||
const double beginning_center_dist = sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
|
||||
const double beginning_center_dist =
|
||||
sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
|
||||
|
||||
// If the first click was too close to the center, move the center of rotation by 200 pixels
|
||||
// in the direction of the click.
|
||||
@@ -125,206 +292,120 @@ void Transform::rotateBy(double start_x, double start_y, double end_x, double en
|
||||
const double first_x = start_x - center_x, first_y = start_y - center_y;
|
||||
const double second_x = end_x - center_x, second_y = end_y - center_y;
|
||||
|
||||
const double ang = angle + util::angle_between(first_x, first_y, second_x, second_y);
|
||||
const double ang = current.angle + util::angle_between(first_x, first_y, second_x, second_y);
|
||||
|
||||
setAngle(ang, duration);
|
||||
_setAngle(ang, duration);
|
||||
}
|
||||
|
||||
void Transform::setAngle(double new_angle, double duration) {
|
||||
while (new_angle > M_PI) new_angle -= M2PI;
|
||||
while (new_angle <= -M_PI) new_angle += M2PI;
|
||||
void Transform::setAngle(const double new_angle, const double duration) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
if (duration == 0) {
|
||||
angle = new_angle;
|
||||
} else {
|
||||
double start = platform::elapsed();
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(angle, new_angle, angle, start, duration));
|
||||
_setAngle(new_angle, duration);
|
||||
}
|
||||
|
||||
void Transform::setAngle(const double new_angle, const double cx, const double cy) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
double dx = 0, dy = 0;
|
||||
|
||||
if (cx >= 0 && cy >= 0) {
|
||||
dx = (final.width / 2) - cx;
|
||||
dy = (final.height / 2) - cy;
|
||||
_moveBy(dx, dy, 0);
|
||||
}
|
||||
|
||||
_setAngle(new_angle, 0);
|
||||
|
||||
if (cx >= 0 && cy >= 0) {
|
||||
_moveBy(-dx, -dy, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::_setAngle(double new_angle, const double duration) {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
while (new_angle > M_PI)
|
||||
new_angle -= M2PI;
|
||||
while (new_angle <= -M_PI)
|
||||
new_angle += M2PI;
|
||||
|
||||
final.angle = new_angle;
|
||||
|
||||
if (duration == 0) {
|
||||
current.angle = final.angle;
|
||||
} else {
|
||||
double start = (double)util::now() / 1_second;
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(
|
||||
current.angle, final.angle, current.angle, start, duration));
|
||||
}
|
||||
}
|
||||
|
||||
double Transform::getAngle() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
return final.angle;
|
||||
}
|
||||
|
||||
void Transform::startRotating() {
|
||||
stopRotating();
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearRotating();
|
||||
|
||||
// Add a 200ms timeout for resetting this to false
|
||||
rotating = true;
|
||||
double start = platform::elapsed();
|
||||
rotate_timeout = std::make_shared<util::timeout<bool>>(false, rotating, start, 0.2);
|
||||
current.rotating = true;
|
||||
double start = (double)util::now() / 1_second;
|
||||
rotate_timeout = std::make_shared<util::timeout<bool>>(false, current.rotating, start, 0.2);
|
||||
animations.emplace_front(rotate_timeout);
|
||||
}
|
||||
|
||||
void Transform::stopRotating() {
|
||||
rotating = false;
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
_clearRotating();
|
||||
}
|
||||
|
||||
void Transform::_clearRotating() {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
current.rotating = false;
|
||||
if (rotate_timeout) {
|
||||
animations.remove(rotate_timeout);
|
||||
rotate_timeout.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::setScaleXY(double new_scale, double xn, double yn, double duration) {
|
||||
if (duration == 0) {
|
||||
scale = new_scale;
|
||||
x = xn;
|
||||
y = yn;
|
||||
} else {
|
||||
// Use a common start time for all of the animations to avoid divergent animations.
|
||||
double start = platform::elapsed();
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(scale, new_scale, scale, start, duration));
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, start, duration));
|
||||
animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, start, duration));
|
||||
}
|
||||
#pragma mark - Animation
|
||||
|
||||
const double s = scale * util::tileSize;
|
||||
zc = s / 2;
|
||||
Bc = s / 360;
|
||||
Cc = s / (2 * M_PI);
|
||||
bool Transform::needsAnimation() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
return !animations.empty();
|
||||
}
|
||||
|
||||
void Transform::setScale(double new_scale, double cx, double cy, double duration) {
|
||||
// Ensure that we don't zoom in further than the maximum allowed.
|
||||
if (new_scale < min_scale) {
|
||||
new_scale = min_scale;
|
||||
} else if (new_scale > max_scale) {
|
||||
new_scale = max_scale;
|
||||
}
|
||||
void Transform::updateAnimations(const double time) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
// Zoom in on the center if we don't have click or gesture anchor coordinates.
|
||||
if (cx < 0 || cy < 0) {
|
||||
cx = width / 2;
|
||||
cy = height / 2;
|
||||
}
|
||||
|
||||
// Account for the x/y offset from the center (= where the user clicked or pinched)
|
||||
const double factor = new_scale / scale;
|
||||
const double dx = (cx - width / 2) * (1.0 - factor);
|
||||
const double dy = (cy - height / 2) * (1.0 - factor);
|
||||
|
||||
// Account for angle
|
||||
const double angle_sin = sin(-angle);
|
||||
const double angle_cos = cos(-angle);
|
||||
const double ax = angle_cos * dx - angle_sin * dy;
|
||||
const double ay = angle_sin * dx + angle_cos * dy;
|
||||
|
||||
const double xn = x * factor + ax;
|
||||
const double yn = y * factor + ay;
|
||||
|
||||
setScaleXY(new_scale, xn, yn, duration);
|
||||
animations.remove_if([time](const std::shared_ptr<util::animation> &animation) {
|
||||
return animation->update(time) == util::animation::complete;
|
||||
});
|
||||
}
|
||||
|
||||
void Transform::setZoom(double zoom, double duration) {
|
||||
setScale(pow(2.0, zoom), -1, -1, duration);
|
||||
void Transform::cancelAnimations() {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
animations.clear();
|
||||
}
|
||||
|
||||
void Transform::setLonLat(double lon, double lat, double duration) {
|
||||
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
|
||||
double xn = -round(lon * Bc);
|
||||
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
|
||||
#pragma mark - Transform state
|
||||
|
||||
setScaleXY(scale, xn, yn, duration);
|
||||
const TransformState Transform::currentState() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void Transform::setLonLatZoom(double lon, double lat, double zoom, double duration) {
|
||||
double new_scale = pow(2.0, zoom);
|
||||
const TransformState Transform::finalState() const {
|
||||
uv::readlock lock(mtx);
|
||||
|
||||
const double s = new_scale * util::tileSize;
|
||||
zc = s / 2;
|
||||
Bc = s / 360;
|
||||
Cc = s / (2 * M_PI);
|
||||
|
||||
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
|
||||
double xn = -round(lon * Bc);
|
||||
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
|
||||
|
||||
setScaleXY(new_scale, xn, yn, duration);
|
||||
return final;
|
||||
}
|
||||
|
||||
void Transform::getLonLat(double &lon, double &lat) const {
|
||||
lon = -x / Bc;
|
||||
lat = R2D * (2 * atan(exp(y / Cc)) - 0.5 * M_PI);
|
||||
}
|
||||
|
||||
void Transform::getLonLatZoom(double& lon, double& lat, double& zoom) const {
|
||||
getLonLat(lon, lat);
|
||||
zoom = getZoom();
|
||||
}
|
||||
|
||||
double Transform::pixel_x() const {
|
||||
const double center = (width - scale * util::tileSize) / 2;
|
||||
return center + x;
|
||||
}
|
||||
|
||||
double Transform::pixel_y() const {
|
||||
const double center = (height - scale * util::tileSize) / 2;
|
||||
return center + y;
|
||||
}
|
||||
|
||||
void Transform::matrixFor(mat4& matrix, const Tile::ID& id) const {
|
||||
const double tile_scale = pow(2, id.z);
|
||||
const double tile_size = scale * util::tileSize / tile_scale;
|
||||
|
||||
matrix::identity(matrix);
|
||||
|
||||
matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
|
||||
matrix::rotate_z(matrix, matrix, angle);
|
||||
matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
|
||||
|
||||
matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
|
||||
|
||||
// TODO: Get rid of the 8 (scaling from 4096 to tile size);
|
||||
float factor = scale / tile_scale / (4096.0f / util::tileSize);
|
||||
matrix::scale(matrix, matrix, factor, factor, 1);
|
||||
}
|
||||
|
||||
float Transform::getZoom() const {
|
||||
return log(scale) / M_LN2;
|
||||
}
|
||||
|
||||
float Transform::getNormalizedZoom() const {
|
||||
return log(scale * util::tileSize / 256.0f) / M_LN2;
|
||||
}
|
||||
|
||||
int32_t Transform::getIntegerZoom() const {
|
||||
return floor(log(scale) / M_LN2);
|
||||
}
|
||||
|
||||
double Transform::getScale() const {
|
||||
return scale;
|
||||
}
|
||||
|
||||
double Transform::getAngle() const {
|
||||
return angle;
|
||||
}
|
||||
|
||||
box Transform::cornersToBox(uint32_t z) const {
|
||||
const double ref_scale = pow(2, z);
|
||||
|
||||
const double angle_sin = sin(-angle);
|
||||
const double angle_cos = cos(-angle);
|
||||
|
||||
const double w_2 = width / 2;
|
||||
const double h_2 = height / 2;
|
||||
const double ss_0 = scale * util::tileSize;
|
||||
const double ss_1 = ref_scale / ss_0;
|
||||
const double ss_2 = ss_0 / 2.0;
|
||||
|
||||
// Calculate the corners of the map view. The resulting coordinates will be
|
||||
// in fractional tile coordinates.
|
||||
box b;
|
||||
|
||||
b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.center.x = (ss_2 - x) * ss_1;
|
||||
b.center.y = (ss_2 - y) * ss_1;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
132
src/map/transform_state.cpp
Normal file
132
src/map/transform_state.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include <llmr/map/transform_state.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
#pragma mark - Matrix
|
||||
|
||||
void TransformState::matrixFor(mat4& matrix, const Tile::ID& id) const {
|
||||
const double tile_scale = pow(2, id.z);
|
||||
const double tile_size = scale * util::tileSize / tile_scale;
|
||||
|
||||
matrix::identity(matrix);
|
||||
|
||||
matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
|
||||
matrix::rotate_z(matrix, matrix, angle);
|
||||
matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
|
||||
|
||||
matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
|
||||
|
||||
// TODO: Get rid of the 8 (scaling from 4096 to tile size);
|
||||
float factor = scale / tile_scale / (4096.0f / util::tileSize);
|
||||
matrix::scale(matrix, matrix, factor, factor, 1);
|
||||
}
|
||||
|
||||
box TransformState::cornersToBox(uint32_t z) const {
|
||||
const double ref_scale = pow(2, z);
|
||||
|
||||
const double angle_sin = sin(-angle);
|
||||
const double angle_cos = cos(-angle);
|
||||
|
||||
const double w_2 = width / 2;
|
||||
const double h_2 = height / 2;
|
||||
const double ss_0 = scale * util::tileSize;
|
||||
const double ss_1 = ref_scale / ss_0;
|
||||
const double ss_2 = ss_0 / 2.0;
|
||||
|
||||
// Calculate the corners of the map view. The resulting coordinates will be
|
||||
// in fractional tile coordinates.
|
||||
box b;
|
||||
|
||||
b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
|
||||
b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
|
||||
|
||||
b.center.x = (ss_2 - x) * ss_1;
|
||||
b.center.y = (ss_2 - y) * ss_1;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Dimensions
|
||||
|
||||
uint16_t TransformState::getWidth() const {
|
||||
return width;
|
||||
}
|
||||
|
||||
uint16_t TransformState::getHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
uint16_t TransformState::getFramebufferWidth() const {
|
||||
return framebuffer[0];
|
||||
}
|
||||
|
||||
uint16_t TransformState::getFramebufferHeight() const {
|
||||
return framebuffer[1];
|
||||
}
|
||||
|
||||
const std::array<uint16_t, 2> TransformState::getFramebufferDimensions() const {
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
|
||||
float TransformState::getPixelRatio() const {
|
||||
return pixelRatio;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Zoom
|
||||
|
||||
float TransformState::getNormalizedZoom() const {
|
||||
return log(scale * util::tileSize / 256.0f) / M_LN2;
|
||||
}
|
||||
|
||||
int32_t TransformState::getIntegerZoom() const {
|
||||
return floor(getZoom());
|
||||
}
|
||||
|
||||
double TransformState::getZoom() const {
|
||||
return log(scale) / M_LN2;
|
||||
}
|
||||
|
||||
double TransformState::getScale() const {
|
||||
return scale;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
float TransformState::getAngle() const {
|
||||
return angle;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
bool TransformState::isChanging() const {
|
||||
return rotating || scaling || panning;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - (private helper functions)
|
||||
|
||||
|
||||
double TransformState::pixel_x() const {
|
||||
const double center = (width - scale * util::tileSize) / 2;
|
||||
return center + x;
|
||||
}
|
||||
|
||||
double TransformState::pixel_y() const {
|
||||
const double center = (height - scale * util::tileSize) / 2;
|
||||
return center + y;
|
||||
}
|
||||
@@ -1,46 +1,46 @@
|
||||
#include <llmr/platform/request.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <uv.h>
|
||||
|
||||
using namespace llmr::platform;
|
||||
|
||||
Request::Request(const std::string &url,
|
||||
std::function<void(Response *)> background_function,
|
||||
std::function<void()> foreground_callback,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
: url(url),
|
||||
background_function(background_function),
|
||||
foreground_callback(foreground_callback),
|
||||
res(std::make_unique<Response>(callback)),
|
||||
cancelled(false),
|
||||
loop(loop) {
|
||||
|
||||
// Add a check handle without a callback to keep the default loop running.
|
||||
// We don't have a real handler attached to the default loop right from the
|
||||
// beginning, because we're using asynchronous messaging to perform the actual
|
||||
// request in the request thread. Only after the request is complete, we
|
||||
// create an actual work request that is attached to the default loop.
|
||||
check = new uv_check_t();
|
||||
uv_check_init(loop, check);
|
||||
uv_check_start(check, [](uv_check_t *) {});
|
||||
async = new uv_async_t();
|
||||
async->data = new std::unique_ptr<Response>();
|
||||
uv_async_init(loop, async, complete);
|
||||
}
|
||||
|
||||
Request::~Request() {
|
||||
// We need to remove our no-op handle again to allow the main event loop to exit.
|
||||
uv_check_stop(check);
|
||||
}
|
||||
|
||||
void Request::work_callback(uv_work_t *work) {
|
||||
std::shared_ptr<Request> *req = static_cast<std::shared_ptr<Request> *>(work->data);
|
||||
(*req)->background_function((*req)->res.get());
|
||||
void Request::complete() {
|
||||
// We're scheduling the response callback to be invoked in the event loop.
|
||||
// Since the Request object will be deleted before the callback is invoked,
|
||||
// we move over the Response object to be owned by the async handle.
|
||||
((std::unique_ptr<Response> *)async->data)->swap(res);
|
||||
uv_async_send(async);
|
||||
}
|
||||
|
||||
// This callback is executed in the main loop.
|
||||
void Request::after_work_callback(uv_work_t *work, int /*status*/) {
|
||||
std::shared_ptr<Request> *req = static_cast<std::shared_ptr<Request> *>(work->data);
|
||||
void Request::complete(uv_async_t *async) {
|
||||
Response *res = static_cast<std::unique_ptr<Response> *>(async->data)->get();
|
||||
|
||||
(*req)->foreground_callback();
|
||||
res->callback(res);
|
||||
|
||||
// This finally deletes the *pointer* to the shared pointer we've been holding on since we
|
||||
// pushed it on the add_queue on request creation.
|
||||
delete req;
|
||||
delete work;
|
||||
// We need to remove our async handle again to allow the main event loop to exit.
|
||||
uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
|
||||
delete static_cast<std::unique_ptr<Response> *>(handle->data);
|
||||
delete (uv_async_t *)handle;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <llmr/renderer/frame_history.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -11,7 +12,7 @@ void FrameHistory::record(float zoom) {
|
||||
}
|
||||
|
||||
if (history.size() > 0 || history.back().z != zoom) {
|
||||
history.emplace_back(FrameSnapshot{static_cast<float>(platform::elapsed() * 1000), zoom});
|
||||
history.emplace_back(FrameSnapshot{static_cast<float>((double) util::now() / 1_millisecond), zoom});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
#include <llmr/renderer/raster_bucket.hpp>
|
||||
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/geometry/debug_font_buffer.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
@@ -20,15 +21,14 @@
|
||||
#include <llmr/util/raster.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/timer.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
|
||||
|
||||
Painter::Painter(Transform& transform, Style& style, GlyphAtlas& glyphAtlas)
|
||||
: transform(transform),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas) {
|
||||
Painter::Painter(Map &map)
|
||||
: map(map) {
|
||||
}
|
||||
|
||||
bool Painter::needsAnimation() const {
|
||||
@@ -79,8 +79,12 @@ void Painter::setupShaders() {
|
||||
}
|
||||
|
||||
void Painter::resize() {
|
||||
assert(transform.getFramebufferWidth() > 0 && transform.getFramebufferHeight() > 0);
|
||||
glViewport(0, 0, transform.getFramebufferWidth(), transform.getFramebufferHeight());
|
||||
const TransformState &state = map.getState();
|
||||
if (gl_viewport != state.getFramebufferDimensions()) {
|
||||
gl_viewport = state.getFramebufferDimensions();
|
||||
assert(gl_viewport[0] > 0 && gl_viewport[1] > 0);
|
||||
glViewport(0, 0, gl_viewport[0], gl_viewport[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::setDebug(bool enabled) {
|
||||
@@ -110,12 +114,12 @@ void Painter::depthMask(bool value) {
|
||||
|
||||
void Painter::changeMatrix() {
|
||||
// Initialize projection matrix
|
||||
matrix::ortho(projMatrix, 0, transform.getWidth(), transform.getHeight(), 0, 0, 1);
|
||||
matrix::ortho(projMatrix, 0, map.getState().getWidth(), map.getState().getHeight(), 0, 0, 1);
|
||||
|
||||
// The extrusion matrix.
|
||||
matrix::identity(extrudeMatrix);
|
||||
matrix::multiply(extrudeMatrix, projMatrix, extrudeMatrix);
|
||||
matrix::rotate_z(extrudeMatrix, extrudeMatrix, transform.getAngle());
|
||||
matrix::rotate_z(extrudeMatrix, extrudeMatrix, map.getState().getAngle());
|
||||
|
||||
// The native matrix is a 1:1 matrix that paints the coordinates at the
|
||||
// same screen position as the vertex specifies.
|
||||
@@ -137,11 +141,11 @@ void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) {
|
||||
gl::group group("mask");
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
Color background = style.computed.background.color;
|
||||
background[0] *= style.computed.background.opacity;
|
||||
background[1] *= style.computed.background.opacity;
|
||||
background[2] *= style.computed.background.opacity;
|
||||
background[3] *= style.computed.background.opacity;
|
||||
Color background = map.getStyle().computed.background.color;
|
||||
background[0] *= map.getStyle().computed.background.opacity;
|
||||
background[1] *= map.getStyle().computed.background.opacity;
|
||||
background[2] *= map.getStyle().computed.background.opacity;
|
||||
background[3] *= map.getStyle().computed.background.opacity;
|
||||
|
||||
plainShader->setColor(background);
|
||||
|
||||
@@ -177,12 +181,12 @@ void Painter::render(const Tile& tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameHistory.record(transform.getNormalizedZoom());
|
||||
frameHistory.record(map.getState().getNormalizedZoom());
|
||||
|
||||
matrix = tile.matrix;
|
||||
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
|
||||
|
||||
renderLayers(tile.data, style.layers);
|
||||
renderLayers(tile.data, map.getStyle().layers);
|
||||
|
||||
if (debug) {
|
||||
renderDebug(tile.data);
|
||||
@@ -234,15 +238,18 @@ void Painter::renderLayer(const std::shared_ptr<TileData>& tile_data, const Laye
|
||||
} else {
|
||||
// This is a singular layer. Try to find the bucket associated with
|
||||
// this layer and render it.
|
||||
if (style.buckets[layer_desc.bucket_name].type == BucketType::Raster) {
|
||||
if (tile_data && tile_data->raster) {
|
||||
renderRaster(layer_desc.name, tile_data);
|
||||
}
|
||||
} else {
|
||||
auto bucket_it = tile_data->buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it != tile_data->buckets.end()) {
|
||||
assert(bucket_it->second);
|
||||
bucket_it->second->render(*this, layer_desc.name, tile_data->id);
|
||||
auto bucket_it = map.getStyle().buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it != map.getStyle().buckets.end()) {
|
||||
if (bucket_it->second.type == BucketType::Raster) {
|
||||
if (tile_data && tile_data->raster) {
|
||||
renderRaster(layer_desc.name, tile_data);
|
||||
}
|
||||
} else {
|
||||
auto databucket_it = tile_data->buckets.find(layer_desc.bucket_name);
|
||||
if (databucket_it != tile_data->buckets.end()) {
|
||||
assert(databucket_it->second);
|
||||
databucket_it->second->render(*this, layer_desc.name, tile_data->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,7 +258,11 @@ void Painter::renderLayer(const std::shared_ptr<TileData>& tile_data, const Laye
|
||||
void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr<TileData>& tile_data) {
|
||||
if (pass == Opaque) return;
|
||||
|
||||
const RasterProperties& properties = style.computed.rasters[layer_name];
|
||||
auto raster_properties = map.getStyle().computed.rasters;
|
||||
auto raster_properties_it = raster_properties.find(layer_name);
|
||||
if (raster_properties_it == raster_properties.end()) return;
|
||||
|
||||
const RasterProperties& properties = raster_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
gl::group group(layer_name + " (raster)");
|
||||
@@ -271,7 +282,11 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
// Abort early.
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const FillProperties& properties = style.computed.fills[layer_name];
|
||||
auto fill_properties = map.getStyle().computed.fills;
|
||||
auto fill_properties_it = fill_properties.find(layer_name);
|
||||
if (fill_properties_it == fill_properties.end()) return;
|
||||
|
||||
const FillProperties& properties = fill_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color fill_color = properties.fill_color;
|
||||
@@ -305,8 +320,8 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(transform.getFramebufferWidth()),
|
||||
static_cast<float>(transform.getFramebufferHeight())
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
@@ -329,12 +344,13 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
}
|
||||
|
||||
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
|
||||
if (properties.image.size() && *style.sprite) {
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
if (properties.image.size() && sprite && sprite->isLoaded()) {
|
||||
// Draw texture fill
|
||||
ImagePosition imagePos = style.sprite->getPosition(properties.image, true);
|
||||
ImagePosition imagePos = sprite->getPosition(properties.image, true);
|
||||
|
||||
float factor = 8.0 / pow(2, transform.getIntegerZoom() - id.z);
|
||||
float mix = fmod(transform.getZoom(), 1.0);
|
||||
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
|
||||
float mix = fmod(map.getState().getZoom(), 1.0);
|
||||
|
||||
std::array<float, 2> imageSize = {{
|
||||
imagePos.size.x * factor,
|
||||
@@ -356,7 +372,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
patternShader->setColor(fill_color);
|
||||
patternShader->setMix(mix);
|
||||
style.sprite->raster->bind(true);
|
||||
sprite->raster->bind(true);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
@@ -387,8 +403,8 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(transform.getFramebufferWidth()),
|
||||
static_cast<float>(transform.getFramebufferHeight())
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
@@ -401,7 +417,11 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
if (pass == Opaque) return;
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const LineProperties& properties = style.computed.lines[layer_name];
|
||||
auto line_properties = map.getStyle().computed.lines;
|
||||
auto line_properties_it = line_properties.find(layer_name);
|
||||
if (line_properties_it == line_properties.end()) return;
|
||||
|
||||
const LineProperties& properties = line_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
float width = properties.width;
|
||||
@@ -428,17 +448,17 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
linejoinShader->setMatrix(matrix);
|
||||
linejoinShader->setColor(color);
|
||||
linejoinShader->setWorld({{
|
||||
transform.getFramebufferWidth() * 0.5f,
|
||||
transform.getFramebufferHeight() * 0.5f
|
||||
map.getState().getFramebufferWidth() * 0.5f,
|
||||
map.getState().getFramebufferHeight() * 0.5f
|
||||
}
|
||||
});
|
||||
linejoinShader->setLineWidth({{
|
||||
((outset - 0.25f) * transform.getPixelRatio()),
|
||||
((inset - 0.25f) * transform.getPixelRatio())
|
||||
((outset - 0.25f) * map.getState().getPixelRatio()),
|
||||
((inset - 0.25f) * map.getState().getPixelRatio())
|
||||
}
|
||||
});
|
||||
|
||||
float pointSize = ceil(transform.getPixelRatio() * outset * 2.0);
|
||||
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
linejoinShader->setSize(pointSize);
|
||||
#else
|
||||
@@ -468,7 +488,7 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
lineShader->setExtrudeMatrix(extrudeMatrix);
|
||||
|
||||
// TODO: Move this to transform?
|
||||
const float tilePixelRatio = transform.getScale() / (1 << transform.getIntegerZoom()) / 8;
|
||||
const float tilePixelRatio = map.getState().getScale() / (1 << map.getState().getIntegerZoom()) / 8;
|
||||
|
||||
lineShader->setDashArray({{ 1, -1 }});
|
||||
lineShader->setLineWidth({{ outset, inset }});
|
||||
@@ -483,9 +503,16 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
if (!bucket.hasPoints()) return;
|
||||
if (pass == Opaque) return;
|
||||
|
||||
const PointProperties& properties = style.computed.points[layer_name];
|
||||
auto point_properties = map.getStyle().computed.points;
|
||||
auto point_properties_it = point_properties.find(layer_name);
|
||||
if (point_properties_it == point_properties.end()) return;
|
||||
|
||||
const PointProperties& properties = point_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
if (!sprite || !sprite->isLoaded()) return;
|
||||
|
||||
gl::group group(layer_name + " (point)");
|
||||
|
||||
Color color = properties.color;
|
||||
@@ -498,7 +525,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
sized_image.append("-");
|
||||
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
|
||||
|
||||
ImagePosition imagePos = style.sprite->getPosition(sized_image, false);
|
||||
ImagePosition imagePos = sprite->getPosition(sized_image, false);
|
||||
|
||||
// fprintf(stderr, "%f/%f => %f/%f\n", imagePos.tl.x, imagePos.tl.y, imagePos.br.x, imagePos.br.y);
|
||||
|
||||
@@ -506,7 +533,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
pointShader->setMatrix(matrix);
|
||||
pointShader->setImage(0);
|
||||
pointShader->setColor(color);
|
||||
const float pointSize = properties.size * 1.4142135623730951 * transform.getPixelRatio();
|
||||
const float pointSize = properties.size * 1.4142135623730951 * map.getState().getPixelRatio();
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
pointShader->setSize(pointSize);
|
||||
#else
|
||||
@@ -515,8 +542,8 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
#endif
|
||||
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
if (*style.sprite->raster) {
|
||||
style.sprite->raster->bind(transform.rotating || transform.scaling || transform.panning);
|
||||
if (*sprite->raster) {
|
||||
sprite->raster->bind(map.getState().isChanging());
|
||||
}
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawPoints(*pointShader);
|
||||
@@ -527,7 +554,11 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
if (pass == Opaque) return;
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const TextProperties& properties = style.computed.texts[layer_name];
|
||||
auto text_properties = map.getStyle().computed.texts;
|
||||
auto text_properties_it = text_properties.find(layer_name);
|
||||
if (text_properties_it == text_properties.end()) return;
|
||||
|
||||
const TextProperties& properties = text_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
gl::group group(layer_name + " (text)");
|
||||
@@ -535,7 +566,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
mat4 exMatrix;
|
||||
matrix::copy(exMatrix, projMatrix);
|
||||
if (bucket.geom_desc.path == TextPathType::Curve) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, transform.getAngle());
|
||||
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
|
||||
}
|
||||
|
||||
const float rotate = properties.rotate;
|
||||
@@ -551,24 +582,25 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
textShader->setMatrix(matrix);
|
||||
textShader->setExtrudeMatrix(exMatrix);
|
||||
|
||||
glyphAtlas.bind();
|
||||
textShader->setTextureSize({{ static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height) }});
|
||||
map.getGlyphAtlas().bind();
|
||||
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
|
||||
static_cast<float>(map.getGlyphAtlas().height)}});
|
||||
|
||||
textShader->setGamma(2.5f / fontSize / transform.getPixelRatio());
|
||||
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = round((transform.getAngle() + rotate) / M_PI * 128);
|
||||
float angle = round((map.getState().getAngle() + rotate) / M_PI * 128);
|
||||
|
||||
// adjust min/max zooms for variable font sies
|
||||
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
|
||||
|
||||
textShader->setAngle((int32_t)(angle + 256) % 256);
|
||||
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
|
||||
textShader->setZoom((transform.getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
|
||||
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
|
||||
|
||||
// Label fading
|
||||
const float duration = 300.0f;
|
||||
const float currentTime = platform::elapsed() * 1000;
|
||||
const float currentTime = util::now() / 1_millisecond;
|
||||
|
||||
std::deque<FrameSnapshot> &history = frameHistory.history;
|
||||
|
||||
@@ -606,7 +638,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
textShader->setFadeDist(fadedist * 10);
|
||||
textShader->setMinFadeZoom(floor(lowZ * 10));
|
||||
textShader->setMaxFadeZoom(floor(highZ * 10));
|
||||
textShader->setFadeZoom((transform.getZoom() + bump) * 10);
|
||||
textShader->setFadeZoom((map.getState().getZoom() + bump) * 10);
|
||||
|
||||
// We're drawing in the translucent pass which is bottom-to-top, so we need
|
||||
// to draw the halo first.
|
||||
@@ -638,16 +670,16 @@ void Painter::renderDebug(const TileData::Ptr& tile_data) {
|
||||
// draw tile outline
|
||||
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(4.0f * transform.getPixelRatio());
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
|
||||
|
||||
// draw debug info
|
||||
tile_data->debugFontArray.bind(*plainShader, tile_data->debugFontBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
lineWidth(4.0f * transform.getPixelRatio());
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index());
|
||||
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(2.0f * transform.getPixelRatio());
|
||||
lineWidth(2.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index());
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/uv.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
@@ -24,46 +25,56 @@ ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<fl
|
||||
Sprite::Sprite(Map &map, float pixelRatio) : pixelRatio(pixelRatio), raster(std::make_shared<Raster>()), map(map) {}
|
||||
|
||||
void Sprite::load(const std::string& base_url) {
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
|
||||
auto complete = [sprite]() {
|
||||
std::lock_guard<std::mutex> lock(sprite->mtx);
|
||||
if (*sprite->raster && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.redraw();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
};
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->body.swap(res->body);
|
||||
sprite->asyncParseJSON();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](const platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->parseJSON(res->body);
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, complete, map.getLoop());
|
||||
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
// TODO: move raster loading to other thread
|
||||
// TODO: call complete handler
|
||||
sprite->raster->load(res->body);
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, complete, map.getLoop());
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
}
|
||||
|
||||
Sprite::operator bool() const {
|
||||
void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
|
||||
std::lock_guard<std::mutex> lock(sprite->mtx);
|
||||
if (*sprite->raster && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool Sprite::isLoaded() const {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
void Sprite::parseJSON(const std::string& data) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
void Sprite::asyncParseJSON() {
|
||||
new uv::work<std::shared_ptr<Sprite>>(map.getLoop(),
|
||||
parseJSON,
|
||||
complete,
|
||||
shared_from_this());
|
||||
}
|
||||
|
||||
void Sprite::parseJSON(std::shared_ptr<Sprite> &sprite) {
|
||||
std::lock_guard<std::mutex> lock(sprite->mtx);
|
||||
|
||||
rapidjson::Document d;
|
||||
d.Parse<0>(data.c_str());
|
||||
d.Parse<0>(sprite->body.c_str());
|
||||
|
||||
if (d.IsObject()) {
|
||||
for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
|
||||
@@ -83,7 +94,7 @@ void Sprite::parseJSON(const std::string& data) {
|
||||
if (value.HasMember("height")) height = value["height"].GetInt();
|
||||
if (value.HasMember("pixelRatio")) pixelRatio = value["height"].GetInt();
|
||||
|
||||
pos.insert({ name, { x, y, width, height, pixelRatio } });
|
||||
sprite->pos.insert({ name, { x, y, width, height, pixelRatio } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
@@ -182,7 +183,7 @@ void Raster::bind(bool linear) {
|
||||
}
|
||||
|
||||
void Raster::beginFadeInAnimation() {
|
||||
double start = platform::elapsed();
|
||||
double start = (double)util::now() / 1_second;
|
||||
fade_animation = std::make_shared<util::ease_animation>(opacity, 1.0, opacity, start, 0.25);
|
||||
}
|
||||
|
||||
|
||||
35
src/util/timer.cpp
Normal file
35
src/util/timer.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef DISABLE_TIMER
|
||||
#include <llmr/util/timer.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
|
||||
using namespace llmr::util;
|
||||
|
||||
std::atomic<int> indent;
|
||||
|
||||
timer::timer() : start(now()) {
|
||||
indent++;
|
||||
}
|
||||
|
||||
timer::timer(const std::string &name) : name(name), start(now()) {
|
||||
indent++;
|
||||
}
|
||||
|
||||
void timer::report(const std::string &name) {
|
||||
time duration = now() - start;
|
||||
std::cerr << std::string((indent - 1) * 4, ' ')
|
||||
<< name << ": "
|
||||
<< (double)(duration) / 1_millisecond
|
||||
<< "ms" << std::endl;
|
||||
start += duration;
|
||||
}
|
||||
|
||||
timer::~timer() {
|
||||
if (name.size()) {
|
||||
report(name);
|
||||
}
|
||||
indent--;
|
||||
}
|
||||
#endif
|
||||
@@ -11,23 +11,24 @@
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
double llmr::platform::elapsed() {
|
||||
return 0;
|
||||
}
|
||||
class View : public llmr::View {
|
||||
public:
|
||||
void make_active() {
|
||||
CGLError error = CGLSetCurrentContext(gl_context);
|
||||
if (error) {
|
||||
fprintf(stderr, "Switching OpenGL context failed\n");
|
||||
}
|
||||
}
|
||||
void swap() {}
|
||||
|
||||
void llmr::platform::restart() {
|
||||
// noop
|
||||
}
|
||||
CGLContextObj gl_context;
|
||||
};
|
||||
|
||||
TEST(Headless, initialize) {
|
||||
llmr::Settings settings;
|
||||
|
||||
llmr::Map map(settings);
|
||||
|
||||
llmr::util::timer timer;
|
||||
|
||||
// Setup OpenGL
|
||||
CGLContextObj gl_context;
|
||||
View view;
|
||||
|
||||
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
|
||||
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
|
||||
@@ -46,18 +47,14 @@ TEST(Headless, initialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
error = CGLCreateContext(pixelFormat, NULL, &gl_context);
|
||||
error = CGLCreateContext(pixelFormat, NULL, &view.gl_context);
|
||||
CGLDestroyPixelFormat(pixelFormat);
|
||||
if (error) {
|
||||
fprintf(stderr, "Error creating GL context object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
error = CGLSetCurrentContext(gl_context);
|
||||
if (error) {
|
||||
fprintf(stderr, "Switching OpenGL context failed\n");
|
||||
return;
|
||||
}
|
||||
view.make_active();
|
||||
|
||||
timer.report("gl setup");
|
||||
|
||||
@@ -102,31 +99,18 @@ TEST(Headless, initialize) {
|
||||
|
||||
timer.report("gl framebuffer");
|
||||
|
||||
map.setup();
|
||||
|
||||
timer.report("map setup");
|
||||
llmr::Map map(view);
|
||||
|
||||
map.resize(width, height);
|
||||
|
||||
timer.report("map resize");
|
||||
|
||||
map.loadSettings();
|
||||
|
||||
timer.report("map settings");
|
||||
|
||||
map.update();
|
||||
|
||||
timer.report("map update");
|
||||
|
||||
// Run the loop. It will terminate when we don't have any further listeners.
|
||||
map.run();
|
||||
|
||||
timer.report("map loop");
|
||||
|
||||
map.render();
|
||||
|
||||
timer.report("map render");
|
||||
|
||||
uint32_t *pixels = new uint32_t[width * height];
|
||||
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
@@ -149,7 +133,7 @@ TEST(Headless, initialize) {
|
||||
glDeleteTextures(1, &fbo_color);
|
||||
glDeleteRenderbuffersEXT(1, &fbo_depth_stencil);
|
||||
|
||||
CGLDestroyContext(gl_context);
|
||||
CGLDestroyContext(view.gl_context);
|
||||
|
||||
timer.report("destruct");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user