diff options
33 files changed, 666 insertions, 51 deletions
diff --git a/bin/convert-style.js b/bin/convert-style.js index 57aee0136f..a79f46cf7d 100755 --- a/bin/convert-style.js +++ b/bin/convert-style.js @@ -6,8 +6,6 @@ var style = require('./style.js'); var Protobuf = require('./protobuf.js'); var fs = require('fs'); -// var fs = require('fs'); - var pbf = new Protobuf(); // enum @@ -181,23 +179,47 @@ function createLineClass(layer, name) { return pbf; } +function createMarkerClass(layer, name) { + var pbf = new Protobuf(); + pbf.writeTaggedString(1 /* layer_name */, name); + + if ('color' in layer) { + var color = layer.color.match(/^#([0-9a-f]{6})$/i); + if (!color) { + console.warn('invalid color'); + } else { + pbf.writeTaggedUInt32(3 /* color */, parseInt(color[1] + 'ff', 16)); + } + } + + if ('size' in layer) { + pbf.writeMessage(4 /* size */, convertProperty(layer.size)); + } + + if ('opacity' in layer) { + pbf.writeMessage(6 /* opacity */, convertProperty(layer.opacity)); + } + + if ('image' in layer) { + pbf.writeTaggedString(8 /* image */, layer.image); + } + + return pbf; +} function createClass(klass) { var pbf = new Protobuf(); pbf.writeTaggedString(1 /* name */, klass.name); for (var name in klass.layers) { switch (klass.layers[name].type) { - case 'fill': pbf.writeMessage(2 /* fill */, createFillClass(klass.layers[name], name)); break; - case 'line': pbf.writeMessage(3 /* line */, createLineClass(klass.layers[name], name)); break; + case 'fill': pbf.writeMessage(2 /* fill */, createFillClass(klass.layers[name], name)); break; + case 'line': pbf.writeMessage(3 /* line */, createLineClass(klass.layers[name], name)); break; + case 'marker': pbf.writeMessage(4 /* marker */, createMarkerClass(klass.layers[name], name)); break; } } return pbf; } - - - - for (var name in style.buckets) { var bucket = style.buckets[name]; pbf.writeMessage(1 /* bucket */, createBucket(bucket, name)); diff --git a/bin/style.js b/bin/style.js index 1596e30a67..0383c3210b 100644 --- a/bin/style.js +++ b/bin/style.js @@ -140,6 +140,8 @@ module.exports = { }, "alcohol": { "type": "marker", + "color": "#999999", + "size": 24, "image": "alcohol-shop" } } diff --git a/include/llmr/geometry/point_buffer.hpp b/include/llmr/geometry/point_buffer.hpp new file mode 100644 index 0000000000..8c01a20ee2 --- /dev/null +++ b/include/llmr/geometry/point_buffer.hpp @@ -0,0 +1,19 @@ +#ifndef LLMR_GEOMETRY_POINT_BUFFER +#define LLMR_GEOMETRY_POINT_BUFFER + +#include "buffer.hpp" + +namespace llmr { + + class PointVertexBuffer : public Buffer< + 4 // 2 coordinates per vertex (== 4 bytes) + > { + public: + typedef int16_t vertex_type; + + void add(vertex_type x, vertex_type y); + }; + +} + +#endif diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index c82fcae476..768be14100 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -20,7 +20,7 @@ public: ~Map(); /* setup */ - void setup(); + void setup(float pixelRatio = 1); void loadStyle(const uint8_t *const data, uint32_t bytes); void loadSprite(const std::string& url); void loadSettings(); @@ -34,6 +34,8 @@ public: 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; + void startPanning(); + void stopPanning(); void resetPosition(); /* scale */ @@ -45,12 +47,16 @@ public: void setLonLatZoom(double lon, double lat, double zoom, double duration = 0); void getLonLatZoom(double &lon, double &lat, double &zoom) const; void resetZoom(); + void startScaling(); + void stopScaling(); /* rotation */ void rotateBy(double cx, double cy, double sx, double sy, double ex, double ey, double duration = 0); void setAngle(double angle, double cx = -1, double cy = -1, double duration = 0); double getAngle() const; void resetNorth(); + void startRotating(); + void stopRotating(); void toggleDebug(); @@ -73,6 +79,8 @@ private: int32_t min_zoom; int32_t max_zoom; + float pixel_ratio; + std::forward_list<std::shared_ptr<Tile>> tiles; std::forward_list<std::shared_ptr<Tile>> historic_tiles; }; diff --git a/include/llmr/map/tile.hpp b/include/llmr/map/tile.hpp index 99286a953e..dd107eb7b7 100644 --- a/include/llmr/map/tile.hpp +++ b/include/llmr/map/tile.hpp @@ -23,6 +23,7 @@ class VectorTile; class VectorTileLayer; class FillVertexBuffer; class LineVertexBuffer; +class PointVertexBuffer; class TriangleElementsBuffer; class LineElementsBuffer; class PointElementsBuffer; @@ -59,6 +60,7 @@ public: std::shared_ptr<Bucket> createBucket(const VectorTile& tile, const BucketDescription& bucket_desc); std::shared_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::shared_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); + std::shared_ptr<Bucket> createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); void cancel(); @@ -77,6 +79,7 @@ public: std::shared_ptr<FillVertexBuffer> fillVertexBuffer; std::shared_ptr<LineVertexBuffer> lineVertexBuffer; + std::shared_ptr<PointVertexBuffer> pointVertexBuffer; std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer; std::shared_ptr<LineElementsBuffer> lineElementsBuffer; diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index 56a5a8be9d..a628bf33d2 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -42,6 +42,14 @@ public: 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(); + // Temporary void mapCornersToBox(uint32_t z, box& b) const; @@ -61,6 +69,10 @@ public: float pixelRatio = 1; + 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; @@ -72,7 +84,10 @@ private: // cache values for spherical mercator math double zc, Bc, Cc; - std::forward_list<util::animation> animations; + std::forward_list<std::shared_ptr<util::animation>> animations; + std::shared_ptr<util::animation> scale_timeout; + std::shared_ptr<util::animation> rotate_timeout; + std::shared_ptr<util::animation> pan_timeout; }; } diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 41144fa632..4431a662cd 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -6,7 +6,7 @@ #include <string> #define kTileURL "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf" -#define kSpriteURL "http://mapbox-kkaefer.s3.amazonaws.com/static/sprite" +#define kSpriteURL "https://dl.dropboxusercontent.com/u/575564/sprite" namespace llmr { diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 47d6a500c5..165da37244 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -12,6 +12,7 @@ #include <llmr/renderer/shader-pattern.hpp> #include <llmr/renderer/shader-line.hpp> #include <llmr/renderer/shader-linejoin.hpp> +#include <llmr/renderer/shader-point.hpp> namespace llmr { @@ -23,6 +24,7 @@ class Tile; class FillBucket; class LineBucket; +class PointBucket; class Painter : private util::noncopyable { public: @@ -36,6 +38,7 @@ public: void renderBackground(); void renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& id); + void renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& id); private: void setupShaders(); @@ -63,6 +66,7 @@ private: std::unique_ptr<LineShader> lineShader; std::unique_ptr<LinejoinShader> linejoinShader; std::unique_ptr<PatternShader> patternShader; + std::unique_ptr<PointShader> pointShader; // Set up the stencil quad we're using to generate the stencil mask. VertexBuffer tileStencilBuffer = { diff --git a/include/llmr/renderer/point_bucket.hpp b/include/llmr/renderer/point_bucket.hpp new file mode 100644 index 0000000000..9c8de8f88c --- /dev/null +++ b/include/llmr/renderer/point_bucket.hpp @@ -0,0 +1,51 @@ +#ifndef LLMR_RENDERER_POINTBUCKET +#define LLMR_RENDERER_POINTBUCKET + +#include <llmr/renderer/bucket.hpp> +#include <llmr/style/bucket_description.hpp> +#include <llmr/geometry/elements_buffer.hpp> +#include <llmr/geometry/point_buffer.hpp> + +#include <vector> +#include <memory> + +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + +namespace llmr { + +class Style; +class PointVertexBuffer; +class BucketDescription; +class PointShader; +struct Coordinate; +struct pbf; + +class PointBucket : public Bucket { +public: + PointBucket(const std::shared_ptr<PointVertexBuffer>& vertexBuffer, + const BucketDescription& bucket_desc); + + virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + + void addGeometry(pbf& data); + + bool hasPoints() const; + + void drawPoints(PointShader& shader); + +public: + const BucketGeometryDescription geometry; + +private: + std::shared_ptr<PointVertexBuffer> vertexBuffer; + VertexArrayObject<PointShader> array; + + const uint32_t vertex_start; + uint32_t vertex_end = 0; +}; + +} + +#endif diff --git a/include/llmr/renderer/shader-point.hpp b/include/llmr/renderer/shader-point.hpp new file mode 100644 index 0000000000..1de38504cf --- /dev/null +++ b/include/llmr/renderer/shader-point.hpp @@ -0,0 +1,41 @@ +#ifndef LLMR_RENDERER_SHADER_POINT +#define LLMR_RENDERER_SHADER_POINT + +#include "shader.hpp" + +namespace llmr { + +class PointShader : public Shader { +public: + PointShader(); + + void bind(char *offset); + + void setImage(int32_t image); + void setColor(const std::array<float, 4>& color); + void setPointTopLeft(const std::array<float, 2>& point_tl); + void setPointBottomRight(const std::array<float, 2>& point_br); + void setSize(float size); + +private: + int32_t a_pos = -1; + + int32_t image = -1; + int32_t u_image = -1; + + std::array<float, 4> color = {}; + int32_t u_color = -1; + + std::array<float, 2> point_tl = {}; + int32_t u_point_tl = -1; + + std::array<float, 2> point_br = {}; + int32_t u_point_br = -1; + + float size = 0; + int32_t u_size = -1; +}; + +} + +#endif diff --git a/include/llmr/shader/shaders.hpp b/include/llmr/shader/shaders.hpp index c0f02337a0..6312cc6941 100644 --- a/include/llmr/shader/shaders.hpp +++ b/include/llmr/shader/shaders.hpp @@ -16,6 +16,7 @@ enum { OUTLINE_SHADER, PATTERN_SHADER, PLAIN_SHADER, + POINT_SHADER, SHADER_COUNT }; diff --git a/include/llmr/style/class_description.hpp b/include/llmr/style/class_description.hpp index ce5b172b23..eab7b46a95 100644 --- a/include/llmr/style/class_description.hpp +++ b/include/llmr/style/class_description.hpp @@ -12,6 +12,7 @@ class ClassDescription { public: std::map<std::string, FillClass> fill; std::map<std::string, LineClass> line; + std::map<std::string, PointClass> point; }; diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index 4d4ce68952..79b906cd53 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -60,8 +60,22 @@ struct FunctionProperty { inline T operator()(float z) const { return function(z, values); } }; +struct PointClass { + FunctionProperty<bool> hidden; + FunctionProperty<float> size; + Color color = {{ 0, 0, 0, 1 }}; + FunctionProperty<float> opacity = 1; + std::string image; +}; + +struct PointProperties { + bool hidden = false; + float size = 0; + Color color = {{ 0, 0, 0, 1 }}; + float opacity = 1.0; + std::string image; +}; -// LineClass is the information we parse from the stylesheet struct LineClass { FunctionProperty<bool> hidden; FunctionProperty<float> width; @@ -70,7 +84,6 @@ struct LineClass { FunctionProperty<float> opacity = 1; }; -// LineProperties is the one we resolve this to. struct LineProperties { bool hidden = false; float width = 0; @@ -79,9 +92,6 @@ struct LineProperties { float opacity = 1.0; }; - - - struct FillClass { FunctionProperty<bool> hidden; Winding winding = Winding::NonZero; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 0fcd15c054..b8b7b7a0b3 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -34,7 +34,7 @@ public: class Sprite : public std::enable_shared_from_this<Sprite> { public: - void load(const std::string& base_url); + void load(const std::string& base_url, float pixelRatio = 1); void bind(bool linear = false); ImagePosition getPosition(const std::string& name, bool repeating = false); diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 04b7e22163..d1212f9fe3 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -36,6 +36,7 @@ private: static std::pair<std::string, ClassDescription> parseClass(pbf data); static std::pair<std::string, FillClass> parseFillClass(pbf data); static std::pair<std::string, LineClass> parseLineClass(pbf data); + static std::pair<std::string, PointClass> parsePointClass(pbf data); template <typename T> static FunctionProperty<T> parseProperty(pbf data); static Color parseColor(pbf& data); @@ -53,6 +54,7 @@ public: struct { std::map<std::string, FillProperties> fills; std::map<std::string, LineProperties> lines; + std::map<std::string, PointProperties> points; } computed; }; diff --git a/include/llmr/util/animation.hpp b/include/llmr/util/animation.hpp index 4095fcba1e..2024fb8753 100644 --- a/include/llmr/util/animation.hpp +++ b/include/llmr/util/animation.hpp @@ -2,6 +2,7 @@ #define LLMR_UTIL_ANIMATION #include <llmr/util/noncopyable.hpp> +#include <llmr/platform/platform.hpp> namespace llmr { namespace util { @@ -12,14 +13,51 @@ public: running, complete }; + animation(double duration) + : start(platform::time()), + duration(duration) {} - animation(double from, double to, double &value, double duration); + double progress() const { + return (platform::time() - start) / duration; + } + + virtual state update() const = 0; + virtual ~animation(); + +protected: + const double start, duration; +}; + +class ease_animation : public animation { +public: + ease_animation(double from, double to, double& value, double duration); state update() const; private: - const double start, duration; const double from, to; - double &value; + double& value; +}; + +template <typename T> +class timeout : public animation { +public: + timeout(T final_value, T& value, double duration) + : animation(duration), + final_value(final_value), + value(value) {} + + state update() const { + if (progress() >= 1) { + value = final_value; + return complete; + } else { + return running; + } + } + +private: + const T final_value; + T& value; }; } diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index 8d734459cd..9900451082 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -49,7 +49,7 @@ class MBXMapView { settings.load(); - map.setup(); + map.setup([[UIScreen mainScreen] scale]); CGRect frame = [[UIScreen mainScreen] bounds]; map.resize(frame.size.width, frame.size.height, frame.size.width, frame.size.height); @@ -238,6 +238,8 @@ class MBXMapView if (pinch.state == UIGestureRecognizerStateBegan) { + mapView->map.startScaling(); + self.scale = mapView->map.getScale(); } else if (pinch.state == UIGestureRecognizerStateChanged) @@ -260,6 +262,8 @@ class MBXMapView } else if (pinch.state == UIGestureRecognizerStateEnded) { + mapView->map.stopScaling(); + if (fabsf(pinch.velocity) < 20) return; @@ -272,6 +276,10 @@ class MBXMapView mapView->map.scaleBy(new_scale / scale, [pinch locationInView:pinch.view].x, [pinch locationInView:pinch.view].y, duration); } + else if (pinch.state == UIGestureRecognizerStateCancelled) + { + mapView->map.stopScaling(); + } [self updateRender]; } @@ -282,12 +290,18 @@ class MBXMapView if (rotate.state == UIGestureRecognizerStateBegan) { + mapView->map.startRotating(); + self.angle = mapView->map.getAngle(); } else if (rotate.state == UIGestureRecognizerStateChanged) { mapView->map.setAngle(self.angle + rotate.rotation, [rotate locationInView:rotate.view].x, [rotate locationInView:rotate.view].y); } + else if (rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled) + { + mapView->map.stopRotating(); + } [self updateRender]; } diff --git a/macosx/main.mm b/macosx/main.mm index 18229f9f4f..1c1d77eeef 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -5,6 +5,8 @@ #include <llmr/platform/platform.hpp> #include "settings.hpp" +#include <cstdio> + #include <thread> NSString *const MBXNeedsRenderNotification = @"MBXNeedsRenderNotification"; @@ -39,8 +41,14 @@ public: glfwSetWindowUserPointer(window, this); glfwMakeContextCurrent(window); + + int width, height; + glfwGetWindowSize(window, &width, &height); + int fb_width, fb_height; + glfwGetFramebufferSize(window, &fb_width, &fb_height); + settings.load(); - map.setup(); + map.setup((double)fb_width / width); resize(window, 0, 0); @@ -110,6 +118,7 @@ public: scale = 1.0 / scale; } + mapView->map.startScaling(); mapView->map.scaleBy(scale, mapView->last_x, mapView->last_y); } @@ -132,11 +141,14 @@ public: if (mapView->rotating) { mapView->start_x = mapView->last_x; mapView->start_y = mapView->last_y; + } else { + mapView->map.stopRotating(); } } else if (button == GLFW_MOUSE_BUTTON_LEFT) { mapView->tracking = action == GLFW_PRESS; if (action == GLFW_RELEASE) { + mapView->map.stopPanning(); double now = glfwGetTime(); if (now - mapView->last_click < 0.4) { mapView->map.scaleBy(2.0, mapView->last_x, mapView->last_y); @@ -149,8 +161,14 @@ public: static void mousemove(GLFWwindow *window, double x, double y) { MapView *mapView = (MapView *)glfwGetWindowUserPointer(window); if (mapView->tracking) { - mapView->map.moveBy(x - mapView->last_x, y - mapView->last_y); + double dx = x - mapView->last_x; + double dy = y - mapView->last_y; + if (dx || dy) { + mapView->map.startPanning(); + mapView->map.moveBy(dx, dy); + } } else if (mapView->rotating) { + mapView->map.startRotating(); mapView->map.rotateBy(mapView->start_x, mapView->start_y, mapView->last_x, mapView->last_y, x, y); } mapView->last_x = x; diff --git a/resources/style.pbf b/resources/style.pbf Binary files differindex 1b991386c7..46e117e52d 100644 --- a/resources/style.pbf +++ b/resources/style.pbf diff --git a/src/geometry/point_buffer.cpp b/src/geometry/point_buffer.cpp new file mode 100644 index 0000000000..00137eae38 --- /dev/null +++ b/src/geometry/point_buffer.cpp @@ -0,0 +1,12 @@ +#include <llmr/geometry/point_buffer.hpp> +#include <llmr/platform/gl.hpp> + +#include <cmath> + +using namespace llmr; + +void PointVertexBuffer::add(vertex_type x, vertex_type y) { + vertex_type *vertices = static_cast<vertex_type *>(addElement()); + vertices[0] = x; + vertices[1] = y; +} diff --git a/src/map/map.cpp b/src/map/map.cpp index 5e31c97c1d..429b98e2b5 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -15,19 +15,20 @@ Map::Map(Settings& settings) painter(transform, settings, style), min_zoom(0), max_zoom(14) { - - // TODO: Extract that information from the stylesheet instead of hard coding - style.sprite = std::make_shared<Sprite>(); - style.sprite->load(kSpriteURL); } Map::~Map() { settings.sync(); } -void Map::setup() { +void Map::setup(float pixelRatio) { painter.setup(); + pixel_ratio = pixelRatio; + + style.sprite = std::make_shared<Sprite>(); + style.sprite->load(kSpriteURL, pixel_ratio); + style.load(resources::style, resources::style_size); // style.loadJSON((const char *)resources::style, resources::style_size); } @@ -54,7 +55,7 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb transform.height = height; transform.fb_width = fb_width; transform.fb_height = fb_height; - transform.pixelRatio = (double)fb_width / (double)width; + transform.pixelRatio = pixel_ratio; update(); } @@ -66,6 +67,16 @@ void Map::moveBy(double dx, double dy, double duration) { settings.persist(); } +void Map::startPanning() { + transform.startPanning(); + platform::restart(this); +} + +void Map::stopPanning() { + transform.stopPanning(); + platform::restart(this); +} + void Map::scaleBy(double ds, double cx, double cy, double duration) { transform.scaleBy(ds, cx, cy, duration); style.cascade(transform.getZoom()); @@ -76,6 +87,16 @@ void Map::scaleBy(double ds, double cx, double cy, double duration) { settings.persist(); } +void Map::startScaling() { + transform.startScaling(); + platform::restart(this); +} + +void Map::stopScaling() { + transform.stopScaling(); + platform::restart(this); +} + void Map::rotateBy(double cx, double cy, double sx, double sy, double ex, double ey, double duration) { transform.rotateBy(cx, cy, sx, sy, ex, ey, duration); update(); @@ -84,6 +105,16 @@ void Map::rotateBy(double cx, double cy, double sx, double sy, double ex, double settings.persist(); } +void Map::startRotating() { + transform.startRotating(); + platform::restart(this); +} + +void Map::stopRotating() { + transform.stopRotating(); + platform::restart(this); +} + void Map::setLonLat(double lon, double lat, double duration) { transform.setLonLat(lon, lat, duration); update(); @@ -92,7 +123,7 @@ void Map::setLonLat(double lon, double lat, double duration) { settings.persist(); } -void Map::getLonLat(double &lon, double &lat) const { +void Map::getLonLat(double& lon, double& lat) const { transform.getLonLat(lon, lat); } @@ -106,7 +137,7 @@ void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) { settings.persist(); } -void Map::getLonLatZoom(double &lon, double &lat, double &zoom) const { +void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const { transform.getLonLatZoom(lon, lat, zoom); } diff --git a/src/map/tile.cpp b/src/map/tile.cpp index 902c2c03bb..938024d47d 100644 --- a/src/map/tile.cpp +++ b/src/map/tile.cpp @@ -4,9 +4,11 @@ #include <llmr/map/vector_tile.hpp> #include <llmr/geometry/fill_buffer.hpp> #include <llmr/geometry/line_buffer.hpp> +#include <llmr/geometry/point_buffer.hpp> #include <llmr/geometry/elements_buffer.hpp> #include <llmr/renderer/fill_bucket.hpp> #include <llmr/renderer/line_bucket.hpp> +#include <llmr/renderer/point_bucket.hpp> #include <llmr/platform/platform.hpp> #include <llmr/util/pbf.hpp> #include <llmr/util/string.hpp> @@ -45,6 +47,7 @@ Tile::Tile(ID id, const Style& style) state(initial), fillVertexBuffer(std::make_shared<FillVertexBuffer>()), lineVertexBuffer(std::make_shared<LineVertexBuffer>()), + pointVertexBuffer(std::make_shared<PointVertexBuffer>()), triangleElementsBuffer(std::make_shared<TriangleElementsBuffer>()), lineElementsBuffer(std::make_shared<LineElementsBuffer>()), pointElementsBuffer(std::make_shared<PointElementsBuffer>()), @@ -159,6 +162,8 @@ std::shared_ptr<Bucket> Tile::createBucket(const VectorTile& tile, const BucketD return createFillBucket(layer, bucket_desc); } else if (bucket_desc.type == BucketType::Line) { return createLineBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Point) { + return createPointBucket(layer, bucket_desc); } else { // TODO: create other bucket types. } @@ -200,3 +205,19 @@ std::shared_ptr<Bucket> Tile::createLineBucket(const VectorTileLayer& layer, con return bucket; } + +std::shared_ptr<Bucket> Tile::createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { + std::shared_ptr<PointBucket> bucket = std::make_shared<PointBucket>(pointVertexBuffer, bucket_desc); + + FilteredVectorTileLayer filtered_layer(layer, bucket_desc); + for (pbf feature : filtered_layer) { + while (feature.next(4)) { // geometry + pbf geometry_pbf = feature.message(); + if (geometry_pbf) { + bucket->addGeometry(geometry_pbf); + } + } + } + + return bucket; +} diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 446cc23e24..12c55e1f93 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -1,6 +1,7 @@ #include <llmr/map/transform.hpp> #include <llmr/util/constants.hpp> #include <llmr/util/mat4.hpp> +#include <llmr/util/std.hpp> #include <llmr/util/math.hpp> #include <cstdio> @@ -24,8 +25,8 @@ bool Transform::needsAnimation() const { } void Transform::updateAnimations() { - animations.remove_if([](const util::animation& animation) { - return animation.update() == util::animation::complete; + animations.remove_if([](const std::shared_ptr<util::animation>& animation) { + return animation->update() == util::animation::complete; }); } @@ -40,8 +41,25 @@ void Transform::moveBy(double dx, double dy, double duration) { x = xn; y = yn; } else { - animations.emplace_front(x, xn, x, duration); - animations.emplace_front(y, yn, y, duration); + animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, duration)); + animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, duration)); + } +} + +void Transform::startPanning() { + stopPanning(); + + // Add a 200ms timeout for resetting this to false + panning = true; + pan_timeout = std::make_shared<util::timeout<bool>>(false, panning, 0.2); + animations.emplace_front(pan_timeout); +} + +void Transform::stopPanning() { + panning = false; + if (pan_timeout) { + animations.remove(pan_timeout); + pan_timeout.reset(); } } @@ -59,6 +77,22 @@ void Transform::scaleBy(double ds, double cx, double cy, double duration) { setScale(new_scale, cx, cy, duration); } +void Transform::startScaling() { + stopScaling(); + + // Add a 200ms timeout for resetting this to false + scaling = true; + scale_timeout = std::make_shared<util::timeout<bool>>(false, scaling, 0.2); + animations.emplace_front(scale_timeout); +} + +void Transform::stopScaling() { + scaling = false; + if (scale_timeout) { + animations.remove(scale_timeout); + scale_timeout.reset(); + } +} void Transform::rotateBy(double anchor_x, double anchor_y, double start_x, double start_y, double end_x, double end_y, double duration) { double center_x = width / 2, center_y = height / 2; @@ -94,7 +128,24 @@ void Transform::setAngle(double new_angle, double duration) { if (duration == 0) { angle = new_angle; } else { - animations.emplace_front(angle, new_angle, angle, duration); + animations.emplace_front(std::make_shared<util::ease_animation>(angle, new_angle, angle, duration)); + } +} + +void Transform::startRotating() { + stopRotating(); + + // Add a 200ms timeout for resetting this to false + rotating = true; + rotate_timeout = std::make_shared<util::timeout<bool>>(false, rotating, 0.2); + animations.emplace_front(rotate_timeout); +} + +void Transform::stopRotating() { + rotating = false; + if (rotate_timeout) { + animations.remove(rotate_timeout); + rotate_timeout.reset(); } } @@ -104,9 +155,9 @@ void Transform::setScaleXY(double new_scale, double xn, double yn, double durati x = xn; y = yn; } else { - animations.emplace_front(scale, new_scale, scale, duration); - animations.emplace_front(x, xn, x, duration); - animations.emplace_front(y, yn, y, duration); + animations.emplace_front(std::make_shared<util::ease_animation>(scale, new_scale, scale, duration)); + animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, duration)); + animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, duration)); } const double s = scale * util::tileSize; diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 73f96a55f6..8d362ca29e 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -7,6 +7,7 @@ #include <llmr/renderer/fill_bucket.hpp> #include <llmr/renderer/line_bucket.hpp> +#include <llmr/renderer/point_bucket.hpp> #include <llmr/map/transform.hpp> #include <llmr/map/settings.hpp> @@ -29,6 +30,7 @@ Painter::Painter(Transform& transform, Settings& settings, Style& style) void Painter::setup() { setupShaders(); + assert(pointShader); assert(plainShader); assert(outlineShader); assert(lineShader); @@ -52,6 +54,7 @@ void Painter::setupShaders() { lineShader = std::make_unique<LineShader>(); linejoinShader = std::make_unique<LinejoinShader>(); patternShader = std::make_unique<PatternShader>(); + pointShader = std::make_unique<PointShader>(); } void Painter::useProgram(uint32_t program) { @@ -377,6 +380,44 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons } } +void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& id) { + const PointProperties& properties = style.computed.points[layer_name]; + + // Abort early. + if (!bucket.hasPoints()) return; + if (properties.hidden) return; + + Color color = properties.color; + color[0] *= properties.opacity; + color[1] *= properties.opacity; + color[2] *= properties.opacity; + color[3] *= properties.opacity; + + std::string sized_image = properties.image; + sized_image.append("-"); + sized_image.append(std::to_string(static_cast<int>(std::round(properties.size)))); + + ImagePosition imagePos = style.sprite->getPosition(sized_image, false); + + // fprintf(stderr, "%f/%f => %f/%f\n", imagePos.tl.x, imagePos.tl.y, imagePos.br.x, imagePos.br.y); + + useProgram(pointShader->program); + pointShader->setMatrix(matrix); + pointShader->setImage(0); + pointShader->setColor(color); + const float pointSize = properties.size * 1.4142135623730951; + #if defined(GL_ES_VERSION_2_0) + pointShader->setSize(pointSize); + #else + glPointSize(pointSize); + glEnable(GL_POINT_SPRITE); + #endif + pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); + pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); + style.sprite->bind(transform.rotating || transform.scaling || transform.panning); + bucket.drawPoints(*pointShader); +} + void Painter::renderDebug(const Tile::Ptr& tile) { // Blend to the front, not the back. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/renderer/point_bucket.cpp b/src/renderer/point_bucket.cpp new file mode 100644 index 0000000000..1f786a0cbd --- /dev/null +++ b/src/renderer/point_bucket.cpp @@ -0,0 +1,52 @@ +#include <llmr/renderer/point_bucket.hpp> +#include <llmr/geometry/point_buffer.hpp> +#include <llmr/geometry/elements_buffer.hpp> +#include <llmr/geometry/geometry.hpp> + +#include <llmr/renderer/painter.hpp> +#include <llmr/style/style.hpp> +#include <llmr/map/vector_tile.hpp> + +#include <llmr/platform/gl.hpp> + +#include <cassert> + +struct geometry_too_long_exception : std::exception {}; + +using namespace llmr; + +PointBucket::PointBucket(const std::shared_ptr<PointVertexBuffer>& vertexBuffer, + const BucketDescription& bucket_desc) + : geometry(bucket_desc.geometry), + vertexBuffer(vertexBuffer), + vertex_start(vertexBuffer->index()) { +} + +void PointBucket::addGeometry(pbf& geom) { + Geometry::command cmd; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + vertexBuffer->add(x, y); + } else { + fprintf(stderr, "other command than move_to in point geometry\n"); + } + } + + vertex_end = vertexBuffer->index(); +} + +void PointBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) { + painter.renderPoint(*this, layer_name, id); +} + +bool PointBucket::hasPoints() const { + return vertex_end > 0; +} + +void PointBucket::drawPoints(PointShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize); + array.bind(shader, *vertexBuffer, vertex_index); + glDrawArrays(GL_POINTS, 0, vertex_end - vertex_start); +} diff --git a/src/renderer/shader-point.cpp b/src/renderer/shader-point.cpp new file mode 100644 index 0000000000..835728417d --- /dev/null +++ b/src/renderer/shader-point.cpp @@ -0,0 +1,74 @@ +#include <llmr/renderer/shader-point.hpp> +#include <llmr/shader/shaders.hpp> +#include <llmr/platform/gl.hpp> + +#include <cstdio> + +using namespace llmr; + +PointShader::PointShader() + : Shader( + shaders[POINT_SHADER].vertex, + shaders[POINT_SHADER].fragment + ) { + if (!valid) { + fprintf(stderr, "invalid point shader\n"); + return; + } + + a_pos = glGetAttribLocation(program, "a_pos"); + + u_matrix = glGetUniformLocation(program, "u_matrix"); + u_color = glGetUniformLocation(program, "u_color"); + u_size = glGetUniformLocation(program, "u_size"); + u_point_tl = glGetUniformLocation(program, "u_tl"); + u_point_br = glGetUniformLocation(program, "u_br"); + + // fprintf(stderr, "PointShader:\n"); + // fprintf(stderr, " - u_matrix: %d\n", u_matrix); + // fprintf(stderr, " - u_color: %d\n", u_color); + // fprintf(stderr, " - u_size: %d\n", u_size); + // fprintf(stderr, " - u_point_tl: %d\n", u_point_tl); + // fprintf(stderr, " - u_point_br: %d\n", u_point_br); + // fprintf(stderr, " - u_image: %d\n", u_image); +} + +void PointShader::bind(char *offset) { + glEnableVertexAttribArray(a_pos); + glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset); +} + +void PointShader::setImage(int32_t new_image) { + if (image != new_image) { + glUniform1i(u_image, new_image); + image = new_image; + } +} + +void PointShader::setColor(const std::array<float, 4>& new_color) { + if (color != new_color) { + glUniform4fv(u_color, 1, new_color.data()); + color = new_color; + } +} + +void PointShader::setSize(float new_size) { + if (size != new_size) { + glUniform1f(u_size, new_size); + size = new_size; + } +} + +void PointShader::setPointTopLeft(const std::array<float, 2>& new_point_tl) { + if (point_tl != new_point_tl) { + glUniform2fv(u_point_tl, 1, new_point_tl.data()); + point_tl = new_point_tl; + } +} + +void PointShader::setPointBottomRight(const std::array<float, 2>& new_point_br) { + if (point_br != new_point_br) { + glUniform2fv(u_point_br, 1, new_point_br.data()); + point_br = new_point_br; + } +} diff --git a/src/shader/point.fragment.glsl b/src/shader/point.fragment.glsl new file mode 100644 index 0000000000..2343f7db09 --- /dev/null +++ b/src/shader/point.fragment.glsl @@ -0,0 +1,21 @@ +#define root2 1.4142135623730951 + +uniform sampler2D u_image; +uniform vec2 u_tl; +uniform vec2 u_br; +uniform vec4 u_color; + +uniform vec2 pos; +uniform float inbounds; +uniform vec4 color; + +void main() { + vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5; + + float inbounds = step(0.0, pos.x) * step(0.0, pos.y) * + (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y)); + + vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds; + + gl_FragColor = color; +} diff --git a/src/shader/point.vertex.glsl b/src/shader/point.vertex.glsl new file mode 100644 index 0000000000..4ccc52663b --- /dev/null +++ b/src/shader/point.vertex.glsl @@ -0,0 +1,9 @@ +attribute vec2 a_pos; + +uniform mat4 u_matrix; +uniform float u_size; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + gl_PointSize = u_size; +} diff --git a/src/shader/shaders.cpp b/src/shader/shaders.cpp index 203f143ca1..b6ee1b46bc 100644 --- a/src/shader/shaders.cpp +++ b/src/shader/shaders.cpp @@ -23,5 +23,9 @@ const shader_source llmr::shaders[SHADER_COUNT] = { { "attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", "uniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n", + }, + { + "attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", + "#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color;\n}\n", } }; diff --git a/src/style/resources.cpp b/src/style/resources.cpp index 15b6aa040a..bf0eb207e3 100644 --- a/src/style/resources.cpp +++ b/src/style/resources.cpp @@ -47,7 +47,7 @@ const unsigned char resources::style[] = { 12, 114, 111, 97, 100, 95, 114, 101, 103, 117, 108, 97, 114, 18, 24, 10, 10, 114, 111, 97, 100, 95, 108, 97, 114, 103, 101, 18, 10, 114, 111, 97, 100, 95, 108, 97, 114, 103, 101, 18, 18, 10, 7, 97, 108, 99, 111, 104, - 111, 108, 18, 7, 97, 108, 99, 111, 104, 111, 108, 26, 220, 2, 10, 7, + 111, 108, 18, 7, 97, 108, 99, 111, 104, 111, 108, 26, 132, 3, 10, 7, 100, 101, 102, 97, 117, 108, 116, 18, 27, 10, 4, 112, 97, 114, 107, 34, 8, 8, 2, 18, 4, 0, 0, 128, 63, 45, 255, 159, 223, 200, 66, 4, 112, 97, 114, 107, 18, 31, 10, 4, 119, 111, 111, 100, 34, 8, 8, 2, @@ -69,6 +69,9 @@ const unsigned char resources::style[] = { 255, 102, 102, 102, 34, 52, 8, 3, 18, 48, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 48, 65, 0, 0, 0, 63, 0, 0, 80, 65, 0, 0, 128, 63, 0, 0, 128, 65, 0, 0, 128, 64, 0, 0, 160, 65, 0, 0, - 128, 66, 0, 0, 240, 65, 0, 0, 128, 66 + 128, 66, 0, 0, 240, 65, 0, 0, 128, 66, 34, 38, 10, 7, 97, 108, + 99, 111, 104, 111, 108, 29, 255, 153, 153, 153, 34, 8, 8, 2, 18, 4, + 0, 0, 192, 65, 66, 12, 97, 108, 99, 111, 104, 111, 108, 45, 115, 104, + 111, 112 }; const unsigned long resources::style_size = sizeof(resources::style); diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index ceef267392..a3e5d145cd 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -29,7 +29,7 @@ Sprite::operator bool() const { return loaded; } -void Sprite::load(const std::string& base_url) { +void Sprite::load(const std::string& base_url, float pixelRatio) { std::shared_ptr<Sprite> sprite = shared_from_this(); auto complete = [sprite]() { @@ -41,7 +41,9 @@ void Sprite::load(const std::string& base_url) { } }; - platform::request_http(base_url + ".json", [sprite](const platform::Response & res) { + std::string suffix = (pixelRatio > 1 ? "@2x" : ""); + + platform::request_http(base_url + suffix + ".json", [sprite](const platform::Response & res) { if (res.code == 200) { sprite->parseJSON(res.body); } else { @@ -49,7 +51,7 @@ void Sprite::load(const std::string& base_url) { } }, complete); - platform::request_http(base_url + ".png", [sprite](const platform::Response & res) { + platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response & res) { if (res.code == 200) { sprite->loadImage(res.body); } else { @@ -234,11 +236,13 @@ void Sprite::bind(bool linear) { if (!texture) { glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data()); } else { + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); } diff --git a/src/style/style.cpp b/src/style/style.cpp index 0e8e9f3b69..2aac0c3843 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -12,6 +12,7 @@ Style::Style() { void Style::reset() { computed.fills.clear(); computed.lines.clear(); + computed.points.clear(); } void Style::load(const uint8_t *const data, uint32_t bytes) { @@ -89,10 +90,12 @@ std::pair<std::string, ClassDescription> Style::parseClass(pbf data) { while (data.next()) { if (data.tag == 1) { // name name = data.string(); - } else if (data.tag == 2) { // fill_style + } else if (data.tag == 2) { // fill style klass.fill.insert(parseFillClass(data.message())); - } else if (data.tag == 3) { // stroke_style + } else if (data.tag == 3) { // line style klass.line.insert(parseLineClass(data.message())); + } else if (data.tag == 4) { // point style + klass.point.insert(parsePointClass(data.message())); } else { data.skip(); } @@ -159,6 +162,30 @@ std::pair<std::string, LineClass> Style::parseLineClass(pbf data) { return { name, stroke }; } +std::pair<std::string, PointClass> Style::parsePointClass(pbf data) { + PointClass point; + std::string name; + + while (data.next()) { + if (data.tag == 1) { // name + name = data.string(); + } else if (data.tag == 2) { // hidden + point.hidden = parseProperty<bool>(data.message()); + } else if (data.tag == 3) { // color + point.color = parseColor(data); + } else if (data.tag == 4) { // size + point.size = parseProperty<float>(data.message()); + } else if (data.tag == 6) { // opacity + point.opacity = parseProperty<float>(data.message()); + } else if (data.tag == 8) { // image + point.image = data.string(); + } else { + data.skip(); + } + } + + return { name, point }; +} Color Style::parseColor(pbf& data) { uint32_t rgba = data.fixed<uint32_t, 4>(); @@ -240,6 +267,21 @@ void Style::cascade(float z) { stroke.color = layer.color; stroke.opacity = layer.opacity(z); } + + // Cascade point classes + for (const auto& point_pair : sheetClass.point) { + const std::string& layer_name = point_pair.first; + const llmr::PointClass& layer = point_pair.second; + + // TODO: This should be restricted to point styles that have actual + // values so as to not override with default values. + llmr::PointProperties& point = computed.points[layer_name]; + point.hidden = layer.hidden(z); + point.color = layer.color; + point.size = layer.size(z); + point.opacity = layer.opacity(z); + point.image = layer.image; + } } } diff --git a/src/util/animation.cpp b/src/util/animation.cpp index a2c3e778e8..8624fc22ab 100644 --- a/src/util/animation.cpp +++ b/src/util/animation.cpp @@ -6,16 +6,17 @@ using namespace llmr::util; UnitBezier ease(0.25, 0.1, 0.25, 1); -animation::animation(double from, double to, double &value, double duration) - : start(platform::time()), - duration(duration), +animation::~animation() {} + +ease_animation::ease_animation(double from, double to, double &value, double duration) + : animation(duration), from(from), to(to), value(value) { } -animation::state animation::update() const { - double t = (platform::time() - start) / duration; +animation::state ease_animation::update() const { + double t = progress(); if (t >= 1) { value = to; return complete; |