drawing lines with triangles
This commit is contained in:
5
Makefile
5
Makefile
@@ -19,6 +19,11 @@ app: config.gypi src macosx/llmr-app.gyp
|
||||
xcode: config.gypi llmr.gyp
|
||||
deps/run_gyp llmr.gyp -Goutput_dir=./out/ --depth=. --generator-output=./ -f xcode
|
||||
|
||||
# build OS X app with Xcode
|
||||
xproj: config.gypi src macosx/llmr-app.gyp
|
||||
deps/run_gyp macosx/llmr-app.gyp -Goutput_dir=./out/ --depth=. --generator-output=./ -f xcode
|
||||
open ./macosx/llmr-app.xcodeproj
|
||||
|
||||
# build OS X app with xcodebuild
|
||||
xapp: config.gypi src macosx/llmr-app.gyp
|
||||
deps/run_gyp macosx/llmr-app.gyp -Goutput_dir=./out/ --depth=. --generator-output=./ -f xcode
|
||||
|
||||
@@ -25,7 +25,7 @@ var cap_type = {
|
||||
|
||||
// enum
|
||||
var join_type = {
|
||||
butt: 1,
|
||||
miter: 1,
|
||||
bevel: 2,
|
||||
round: 3
|
||||
};
|
||||
|
||||
@@ -10,17 +10,17 @@ module.exports = {
|
||||
"road_large": {
|
||||
"datasource": "streets",
|
||||
"layer": "road", "field": "class", "value": ["motorway", "main"],
|
||||
"type": "line", "cap": "round", "join": "bevel"
|
||||
"type": "line", "cap": "round", "join": "round"
|
||||
},
|
||||
"road_regular": {
|
||||
"datasource": "streets",
|
||||
"layer": "road", "field": "class", "value": "street",
|
||||
"type": "line", "cap": "round", "join": "bevel"
|
||||
"type": "line", "cap": "round", "join": "round"
|
||||
},
|
||||
"road_limited": {
|
||||
"datasource": "streets",
|
||||
"layer": "road", "field": "class", "value": "street_limited",
|
||||
"type": "line", "cap": "round", "join": "bevel"
|
||||
"type": "line", "cap": "round", "join": "round"
|
||||
},
|
||||
"park": {
|
||||
"datasource": "streets",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace llmr {
|
||||
|
||||
template <
|
||||
size_t itemSize,
|
||||
size_t item_size,
|
||||
int bufferType = GL_ARRAY_BUFFER,
|
||||
size_t defaultLength = 8192
|
||||
>
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
// increase the buffer size by at least /required/ bytes.
|
||||
void *addElement() {
|
||||
@@ -57,6 +58,9 @@ protected:
|
||||
return static_cast<char *>(array) + (pos - itemSize);
|
||||
}
|
||||
|
||||
public:
|
||||
const size_t itemSize = item_size;
|
||||
|
||||
private:
|
||||
// CPU buffer
|
||||
void *array = nullptr;
|
||||
|
||||
30
include/llmr/geometry/elements_buffer.hpp
Normal file
30
include/llmr/geometry/elements_buffer.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef LLMR_GEOMETRY_TRIANGLE_ELEMENTS_BUFFER
|
||||
#define LLMR_GEOMETRY_TRIANGLE_ELEMENTS_BUFFER
|
||||
|
||||
#include "buffer.hpp"
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class TriangleElementsBuffer : public Buffer<
|
||||
6, // bytes per triangle (3 * unsigned short == 6 bytes)
|
||||
GL_ELEMENT_ARRAY_BUFFER
|
||||
> {
|
||||
public:
|
||||
typedef uint16_t element_type;
|
||||
|
||||
void add(element_type a, element_type b, element_type c);
|
||||
};
|
||||
|
||||
class PointElementsBuffer : public Buffer<
|
||||
2, // bytes per point (1 unsigned short)
|
||||
GL_ELEMENT_ARRAY_BUFFER
|
||||
> {
|
||||
public:
|
||||
typedef uint16_t element_type;
|
||||
|
||||
void add(element_type a);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
|
||||
class FillVertexBuffer : public Buffer<
|
||||
4 // bytes per coordinates (2 * unsigned short == 4 bytes)
|
||||
> {
|
||||
@@ -18,17 +17,6 @@ public:
|
||||
void add(vertex_type x, vertex_type y);
|
||||
};
|
||||
|
||||
|
||||
class FillElementsBuffer : public Buffer<
|
||||
6, // bytes per triangle (3 * unsigned short == 6 bytes)
|
||||
GL_ELEMENT_ARRAY_BUFFER
|
||||
> {
|
||||
public:
|
||||
typedef uint16_t element_type;
|
||||
|
||||
void add(element_type a, element_type b, element_type c);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
|
||||
class LineBuffer : public Buffer<
|
||||
class LineVertexBuffer : public Buffer<
|
||||
8 // 2 coordinates per vertex + 1 linesofar + 1 extrude coord pair == 4 (== 8 bytes)
|
||||
> {
|
||||
public:
|
||||
@@ -31,20 +30,10 @@ public:
|
||||
* @param {number} tx texture normal
|
||||
* @param {number} ty texture normal
|
||||
*/
|
||||
void add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar = 0);
|
||||
|
||||
/*
|
||||
* Add a degenerate triangle to the buffer
|
||||
*
|
||||
* > So we need a way to get from the end of one triangle strip
|
||||
* to the beginning of the next strip without actually filling triangles
|
||||
* on the way. We can do this with "degenerate" triangles: We simply
|
||||
* repeat the last coordinate of the first triangle strip and the first
|
||||
* coordinate of the next triangle strip.
|
||||
*/
|
||||
void addDegenerate();
|
||||
size_t add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar = 0);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,19 +10,47 @@ namespace llmr {
|
||||
template <typename Shader>
|
||||
class VertexArrayObject {
|
||||
public:
|
||||
template <typename Buffer>
|
||||
void bind(Shader& shader, Buffer& buffer, char *offset) {
|
||||
template <typename VertexBuffer>
|
||||
void bind(Shader& shader, VertexBuffer& vertex_buffer, char *offset) {
|
||||
if (!vao) {
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
buffer.bind();
|
||||
vertex_buffer.bind();
|
||||
shader.bind(offset);
|
||||
|
||||
buffer_ptr = &buffer;
|
||||
vertex_buffer_ptr = &vertex_buffer;
|
||||
elements_buffer_ptr = nullptr;
|
||||
offset_ptr = offset;
|
||||
} else if (buffer_ptr != &buffer) {
|
||||
throw std::runtime_error("trying to bind VAO to another buffer");
|
||||
} else if (vertex_buffer_ptr != &vertex_buffer) {
|
||||
throw std::runtime_error("trying to bind VAO to another vertex buffer");
|
||||
} else if (elements_buffer_ptr != nullptr) {
|
||||
throw std::runtime_error("trying to bind VAO to another elements buffer");
|
||||
} else if (offset_ptr != offset) {
|
||||
throw std::runtime_error("trying to bind VAO to another offset");
|
||||
} else {
|
||||
// We have been given the correct information.
|
||||
glBindVertexArray(vao);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VertexBuffer, typename ElementsBuffer>
|
||||
void bind(Shader& shader, VertexBuffer& vertex_buffer, ElementsBuffer& elements_buffer, char *offset) {
|
||||
if (!vao) {
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
vertex_buffer.bind();
|
||||
elements_buffer.bind();
|
||||
shader.bind(offset);
|
||||
|
||||
vertex_buffer_ptr = &vertex_buffer;
|
||||
elements_buffer_ptr = &elements_buffer;
|
||||
offset_ptr = offset;
|
||||
} else if (vertex_buffer_ptr != &vertex_buffer) {
|
||||
throw std::runtime_error("trying to bind VAO to another vertex buffer");
|
||||
} else if (elements_buffer_ptr != &elements_buffer) {
|
||||
throw std::runtime_error("trying to bind VAO to another elements buffer");
|
||||
} else if (offset_ptr != offset) {
|
||||
throw std::runtime_error("trying to bind VAO to another offset");
|
||||
} else {
|
||||
@@ -42,7 +70,8 @@ private:
|
||||
|
||||
// For debug reasons, we're storing the bind information so that we can
|
||||
// detect errors and report
|
||||
void *buffer_ptr = nullptr;
|
||||
void *vertex_buffer_ptr = nullptr;
|
||||
void *elements_buffer_ptr = nullptr;
|
||||
char *offset_ptr = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@ class BucketDescription;
|
||||
class VectorTile;
|
||||
class VectorTileLayer;
|
||||
class FillVertexBuffer;
|
||||
class FillElementsBuffer;
|
||||
class LineBuffer;
|
||||
class LineVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class PointElementsBuffer;
|
||||
class PlainShader;
|
||||
|
||||
class Tile : public std::enable_shared_from_this<Tile>,
|
||||
@@ -74,8 +75,9 @@ public:
|
||||
VertexArrayObject<PlainShader> debugFontArray;
|
||||
|
||||
std::shared_ptr<FillVertexBuffer> fillVertexBuffer;
|
||||
std::shared_ptr<FillElementsBuffer> fillElementsBuffer;
|
||||
std::shared_ptr<LineBuffer> lineBuffer;
|
||||
std::shared_ptr<LineVertexBuffer> lineVertexBuffer;
|
||||
std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer;
|
||||
std::shared_ptr<PointElementsBuffer> pointElementsBuffer;
|
||||
|
||||
// Holds the buckets of this tile.
|
||||
// They contain the location offsets in the buffers stored above
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace llmr {
|
||||
|
||||
class Style;
|
||||
class FillVertexBuffer;
|
||||
class FillElementsBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class BucketDescription;
|
||||
class OutlineShader;
|
||||
class PlainShader;
|
||||
@@ -27,7 +27,7 @@ struct pbf;
|
||||
class FillBucket : public Bucket {
|
||||
public:
|
||||
FillBucket(const std::shared_ptr<FillVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<FillElementsBuffer>& elementsBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& elementsBuffer,
|
||||
const BucketDescription& bucket_desc);
|
||||
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
|
||||
private:
|
||||
std::shared_ptr<FillVertexBuffer> vertexBuffer;
|
||||
std::shared_ptr<FillElementsBuffer> elementsBuffer;
|
||||
std::shared_ptr<TriangleElementsBuffer> elementsBuffer;
|
||||
|
||||
struct group {
|
||||
VertexArrayObject<PlainShader> array;
|
||||
|
||||
@@ -10,34 +10,50 @@
|
||||
namespace llmr {
|
||||
|
||||
class Style;
|
||||
class LineBuffer;
|
||||
class LineVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class LineShader;
|
||||
class LinejoinShader;
|
||||
struct Coordinate;
|
||||
struct pbf;
|
||||
|
||||
class LineBucket : public Bucket {
|
||||
public:
|
||||
LineBucket(const std::shared_ptr<LineBuffer>& buffer, const BucketDescription& bucket_desc);
|
||||
LineBucket(const std::shared_ptr<LineVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<PointElementsBuffer>& pointElementsBuffer,
|
||||
const BucketDescription& bucket_desc);
|
||||
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
|
||||
void addGeometry(pbf& data);
|
||||
void addGeometry(const std::vector<Coordinate>& line);
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
bool hasPoints() const;
|
||||
|
||||
void bind(LineShader& shader);
|
||||
void drawLines();
|
||||
void drawPoints();
|
||||
void drawLines(LineShader& shader);
|
||||
void drawDebugLines(LineShader& shader);
|
||||
void drawPoints(LinejoinShader& shader);
|
||||
|
||||
public:
|
||||
const BucketGeometryDescription geometry;
|
||||
|
||||
private:
|
||||
std::shared_ptr<LineBuffer> buffer;
|
||||
uint32_t start;
|
||||
uint32_t length;
|
||||
VertexArrayObject<LineShader> array;
|
||||
std::shared_ptr<LineVertexBuffer> vertexBuffer;
|
||||
const uint32_t vertex_start;
|
||||
uint32_t vertex_length;
|
||||
|
||||
std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer;
|
||||
const uint32_t triangle_elements_start;
|
||||
uint32_t triangle_length;
|
||||
|
||||
std::shared_ptr<PointElementsBuffer> pointElementsBuffer;
|
||||
const uint32_t point_elements_start;
|
||||
uint32_t point_length;
|
||||
|
||||
VertexArrayObject<LineShader> lineArray;
|
||||
VertexArrayObject<LinejoinShader> pointArray;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <llmr/renderer/shader-outline.hpp>
|
||||
#include <llmr/renderer/shader-pattern.hpp>
|
||||
#include <llmr/renderer/shader-line.hpp>
|
||||
#include <llmr/renderer/shader-linejoin.hpp>
|
||||
|
||||
|
||||
namespace llmr {
|
||||
@@ -54,6 +55,7 @@ private:
|
||||
std::unique_ptr<PlainShader> plainShader;
|
||||
std::unique_ptr<OutlineShader> outlineShader;
|
||||
std::unique_ptr<LineShader> lineShader;
|
||||
std::unique_ptr<LinejoinShader> linejoinShader;
|
||||
std::unique_ptr<PatternShader> patternShader;
|
||||
|
||||
// Set up the stencil quad we're using to generate the stencil mask.
|
||||
|
||||
@@ -19,11 +19,9 @@ public:
|
||||
int32_t u_exmatrix;
|
||||
int32_t u_linewidth;
|
||||
int32_t u_color;
|
||||
int32_t u_debug;
|
||||
int32_t u_ratio;
|
||||
int32_t u_dasharray;
|
||||
int32_t u_point;
|
||||
int32_t u_gamma;
|
||||
int32_t u_debug;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
24
include/llmr/renderer/shader-linejoin.hpp
Normal file
24
include/llmr/renderer/shader-linejoin.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef LLMR_RENDERER_SHADER_LINEJOIN
|
||||
#define LLMR_RENDERER_SHADER_LINEJOIN
|
||||
|
||||
#include "shader.hpp"
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class LinejoinShader : public Shader {
|
||||
public:
|
||||
LinejoinShader();
|
||||
|
||||
void bind(char *offset);
|
||||
|
||||
int32_t a_pos;
|
||||
|
||||
int32_t u_matrix;
|
||||
int32_t u_world;
|
||||
int32_t u_linewidth;
|
||||
int32_t u_color;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@ struct shader_source {
|
||||
|
||||
enum {
|
||||
LINE_SHADER,
|
||||
LINEJOIN_SHADER,
|
||||
OUTLINE_SHADER,
|
||||
PATTERN_SHADER,
|
||||
PLAIN_SHADER,
|
||||
|
||||
@@ -24,7 +24,7 @@ enum class CapType {
|
||||
|
||||
enum class JoinType {
|
||||
None = 0,
|
||||
Butt = 1,
|
||||
Miter = 1,
|
||||
Bevel = 2,
|
||||
Round = 3
|
||||
};
|
||||
@@ -46,7 +46,7 @@ inline CapType capType(const std::string& cap) {
|
||||
|
||||
|
||||
inline JoinType joinType(const std::string& join) {
|
||||
if (join == "butt") return JoinType::Butt;
|
||||
if (join == "miter") return JoinType::Miter;
|
||||
else if (join == "bevel") return JoinType::Bevel;
|
||||
else if (join == "round") return JoinType::Round;
|
||||
else return JoinType::None;
|
||||
|
||||
@@ -161,7 +161,11 @@ public:
|
||||
int run() {
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
if (dirty) {
|
||||
dirty = render();
|
||||
try {
|
||||
dirty = render();
|
||||
} catch(std::exception& ex) {
|
||||
fprintf(stderr, "exception: %s\n", ex.what());
|
||||
}
|
||||
glfwSwapBuffers(window);
|
||||
fps();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ enum cap_type {
|
||||
}
|
||||
|
||||
enum join_type {
|
||||
butt_join = 1;
|
||||
miter_join = 1;
|
||||
bevel_join = 2;
|
||||
round_join = 3;
|
||||
}
|
||||
|
||||
Binary file not shown.
15
src/geometry/elements_buffer.cpp
Normal file
15
src/geometry/elements_buffer.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void TriangleElementsBuffer::add(element_type a, element_type b, element_type c) {
|
||||
element_type *elements = static_cast<element_type *>(addElement());
|
||||
elements[0] = a;
|
||||
elements[1] = b;
|
||||
elements[2] = c;
|
||||
}
|
||||
|
||||
void PointElementsBuffer::add(element_type a) {
|
||||
uint16_t *data = static_cast<element_type *>(addElement());
|
||||
data[0] = a;
|
||||
}
|
||||
@@ -16,10 +16,3 @@ void FillVertexBuffer::add(vertex_type x, vertex_type y) {
|
||||
vertices[0] = x;
|
||||
vertices[1] = y;
|
||||
}
|
||||
|
||||
void FillElementsBuffer::add(element_type a, element_type b, element_type c) {
|
||||
element_type *elements = static_cast<element_type *>(addElement());
|
||||
elements[0] = a;
|
||||
elements[1] = b;
|
||||
elements[2] = c;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void LineBuffer::add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar) {
|
||||
size_t LineVertexBuffer::add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar) {
|
||||
size_t idx = index();
|
||||
void *data = addElement();
|
||||
|
||||
int16_t *coords = static_cast<int16_t *>(data);
|
||||
@@ -16,8 +17,6 @@ void LineBuffer::add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx
|
||||
int8_t *extrude = static_cast<int8_t *>(data);
|
||||
extrude[6] = round(extrudeScale * ex);
|
||||
extrude[7] = round(extrudeScale * ey);
|
||||
};
|
||||
|
||||
void LineBuffer::addDegenerate() {
|
||||
add(16383, 16383, 0, 0, 1, 1, 0);
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/geometry/fill_buffer.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
@@ -43,8 +44,9 @@ Tile::Tile(ID id, const Style& style)
|
||||
: id(id),
|
||||
state(initial),
|
||||
fillVertexBuffer(std::make_shared<FillVertexBuffer>()),
|
||||
fillElementsBuffer(std::make_shared<FillElementsBuffer>()),
|
||||
lineBuffer(std::make_shared<LineBuffer>()),
|
||||
lineVertexBuffer(std::make_shared<LineVertexBuffer>()),
|
||||
triangleElementsBuffer(std::make_shared<TriangleElementsBuffer>()),
|
||||
pointElementsBuffer(std::make_shared<PointElementsBuffer>()),
|
||||
style(style) {
|
||||
|
||||
// Initialize tile debug coordinates
|
||||
@@ -167,7 +169,7 @@ std::shared_ptr<Bucket> Tile::createBucket(const VectorTile& tile, const BucketD
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> Tile::createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::shared_ptr<FillBucket> bucket = std::make_shared<FillBucket>(fillVertexBuffer, fillElementsBuffer, bucket_desc);
|
||||
std::shared_ptr<FillBucket> bucket = std::make_shared<FillBucket>(fillVertexBuffer, triangleElementsBuffer, bucket_desc);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
@@ -183,7 +185,7 @@ std::shared_ptr<Bucket> Tile::createFillBucket(const VectorTileLayer& layer, con
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> Tile::createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::shared_ptr<LineBucket> bucket = std::make_shared<LineBucket>(lineBuffer, bucket_desc);
|
||||
std::shared_ptr<LineBucket> bucket = std::make_shared<LineBucket>(lineVertexBuffer, triangleElementsBuffer, pointElementsBuffer, bucket_desc);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/geometry/fill_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/geometry.hpp>
|
||||
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
@@ -15,7 +16,7 @@ struct geometry_too_long_exception : std::exception {};
|
||||
using namespace llmr;
|
||||
|
||||
FillBucket::FillBucket(const std::shared_ptr<FillVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<FillElementsBuffer>& elementsBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& elementsBuffer,
|
||||
const BucketDescription& bucket_desc)
|
||||
: geom_desc(bucket_desc.geometry),
|
||||
vertexBuffer(vertexBuffer),
|
||||
@@ -102,14 +103,11 @@ uint32_t FillBucket::size() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FillBucket::drawElements(PlainShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * 2 * sizeof(int16_t));
|
||||
char *elements_index = BUFFER_OFFSET(elements_start * 3 * sizeof(int16_t));
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(elements_start * elementsBuffer->itemSize);
|
||||
for (group& group : groups) {
|
||||
group.array.bind(shader, *vertexBuffer, vertex_index);
|
||||
elementsBuffer->bind();
|
||||
group.array.bind(shader, *vertexBuffer, *elementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3 - 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * 2 * sizeof(uint16_t);
|
||||
elements_index += group.elements_length * 3 * sizeof(uint16_t);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/geometry.hpp>
|
||||
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/shader-line.hpp>
|
||||
#include <llmr/renderer/shader-linejoin.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
|
||||
@@ -18,11 +20,20 @@ struct geometry_too_long_exception : std::exception {};
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
LineBucket::LineBucket(const std::shared_ptr<LineBuffer>& buffer, const BucketDescription& bucket_desc)
|
||||
LineBucket::LineBucket(const std::shared_ptr<LineVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<PointElementsBuffer>& pointElementsBuffer,
|
||||
const BucketDescription& bucket_desc)
|
||||
: geometry(bucket_desc.geometry),
|
||||
buffer(buffer),
|
||||
start(buffer->index()),
|
||||
length(0) {
|
||||
vertexBuffer(vertexBuffer),
|
||||
vertex_start(vertexBuffer->index()),
|
||||
vertex_length(0),
|
||||
triangleElementsBuffer(triangleElementsBuffer),
|
||||
triangle_elements_start(triangleElementsBuffer->index()),
|
||||
triangle_length(0),
|
||||
pointElementsBuffer(pointElementsBuffer),
|
||||
point_elements_start(pointElementsBuffer->index()),
|
||||
point_length(0) {
|
||||
}
|
||||
|
||||
void LineBucket::addGeometry(pbf& geom) {
|
||||
@@ -52,15 +63,8 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
const float miterLimit = geometry.miter_limit;
|
||||
const float roundLimit = geometry.round_limit;
|
||||
|
||||
if (vertices.size() < 1) {
|
||||
// alert('a line must have at least one vertex');
|
||||
return;
|
||||
}
|
||||
|
||||
// Point
|
||||
if (vertices.size() == 1) {
|
||||
Coordinate coord = vertices.front();
|
||||
buffer->add(coord.x, coord.y, 1, 0, 0, 0);
|
||||
if (vertices.size() < 2) {
|
||||
// fprintf(stderr, "a line must have at least two vertices\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,19 +73,23 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y;
|
||||
|
||||
if (vertices.size() == 2 && closed) {
|
||||
// alert('a line may not have coincident points');
|
||||
// fprintf(stderr, "a line may not have coincident points\n");
|
||||
return;
|
||||
}
|
||||
|
||||
CapType beginCap = cap;
|
||||
CapType endCap = closed ? CapType::Butt : cap;
|
||||
|
||||
JoinType currentJoin = JoinType::None;
|
||||
|
||||
Coordinate currentVertex = Coordinate::null(),
|
||||
prevVertex = Coordinate::null(),
|
||||
nextVertex = Coordinate::null();
|
||||
vec2<double> prevNormal = vec2<double>::null(),
|
||||
nextNormal = vec2<double>::null();
|
||||
|
||||
int32_t e1 = -1, e2 = -1, e3 = -1;
|
||||
|
||||
int8_t flip = 1;
|
||||
double distance = 0;
|
||||
|
||||
@@ -90,14 +98,12 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
nextNormal = util::normal<double>(currentVertex, lastVertex);
|
||||
}
|
||||
|
||||
// Start all lines with a degenerate vertex
|
||||
buffer->addDegenerate();
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y };
|
||||
if (currentVertex) prevVertex = currentVertex;
|
||||
|
||||
currentVertex = vertices[i];
|
||||
currentJoin = join;
|
||||
|
||||
if (prevVertex) distance += util::dist<double>(currentVertex, prevVertex);
|
||||
|
||||
@@ -108,18 +114,11 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
nextVertex = Coordinate::null();
|
||||
}
|
||||
|
||||
bool segment = false;
|
||||
|
||||
if (closed && i >= 2 && (i + 1 < vertices.size())) {
|
||||
segment = true;
|
||||
}
|
||||
|
||||
// If the line is closed, we treat the last vertex like the first vertex.
|
||||
if (!nextVertex && closed) {
|
||||
nextVertex = vertices[1];
|
||||
}
|
||||
|
||||
|
||||
if (nextVertex) {
|
||||
// if two consecutive vertices exist, skip one
|
||||
if (currentVertex.x == nextVertex.x && currentVertex.y == nextVertex.y) continue;
|
||||
@@ -155,67 +154,53 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
double roundness = fmax(abs(joinNormal.x), abs(joinNormal.y));
|
||||
|
||||
|
||||
bool roundJoin = false;
|
||||
// Determine whether we should actually draw a round/bevelled/butt line join. It looks
|
||||
// better if we do, but we can get away with drawing a mitered join for
|
||||
// joins that have a very small angle. For this, we have the "roundLimit"
|
||||
// parameter. We do this to reduce the number of vertices we have to
|
||||
// write into the line vertex buffer. Note that joinAngularity may be 0,
|
||||
// so the roundness grows to infinity. This is intended.
|
||||
if ((join == JoinType::Round || join == JoinType::Bevel || join == JoinType::Butt) && roundness > roundLimit) {
|
||||
roundJoin = true;
|
||||
}
|
||||
|
||||
// Close up the previous line for a round join
|
||||
if (roundJoin && prevVertex && nextVertex) {
|
||||
// Add first vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * prevNormal.y, -flip * prevNormal.x, // extrude normal
|
||||
0, 0, distance); // texture normal
|
||||
|
||||
// Add second vertex.
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * prevNormal.y, flip * prevNormal.x, // extrude normal
|
||||
0, 1, distance); // texture normal
|
||||
|
||||
// Degenerate triangle
|
||||
if (join == JoinType::Round || join == JoinType::Butt) {
|
||||
buffer->addDegenerate();
|
||||
// Switch to miter joins if the angle is very low.
|
||||
if (currentJoin != JoinType::Miter) {
|
||||
if (fabs(joinAngularity) < 0.5 && roundness < miterLimit) {
|
||||
currentJoin = JoinType::Miter;
|
||||
}
|
||||
|
||||
if (join == JoinType::Round) prevVertex = Coordinate::null();
|
||||
|
||||
prevNormal = { -nextNormal.x, -nextNormal.y };
|
||||
flip = 1;
|
||||
}
|
||||
|
||||
// Add a cap.
|
||||
if (!prevVertex && (beginCap == CapType::Round || beginCap == CapType::Square || (roundJoin && join == JoinType::Round))) {
|
||||
int8_t tex = beginCap == CapType::Round || roundJoin ? 1 : 0;
|
||||
|
||||
// Add offset square begin cap.
|
||||
if (!prevVertex && beginCap == CapType::Square) {
|
||||
// Add first vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal
|
||||
tex, 0, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal
|
||||
0, 0, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal
|
||||
tex, 1, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal
|
||||
0, 1, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
}
|
||||
|
||||
if (roundJoin) {
|
||||
// ROUND JOIN
|
||||
// Add offset square end cap.
|
||||
else if (!nextVertex && endCap == CapType::Square) {
|
||||
// Add first vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * nextNormal.y, flip * nextNormal.x, // extrude normal
|
||||
0, 0, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
0, 0, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * nextNormal.y, -flip * nextNormal.x, // extrude normal
|
||||
0, 1, distance); // texture normal
|
||||
} else if ((nextVertex || endCap != CapType::Square) && (prevVertex || beginCap != CapType::Square)) {
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
0, 1, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
}
|
||||
|
||||
else
|
||||
if (currentJoin == JoinType::Miter) {
|
||||
// MITER JOIN
|
||||
if (fabs(joinAngularity) < 0.01) {
|
||||
// The two normals are almost parallel.
|
||||
@@ -226,58 +211,126 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
// bevel join.
|
||||
joinNormal.x = (prevNormal.x - nextNormal.x) / joinAngularity;
|
||||
joinNormal.y = (prevNormal.y - nextNormal.y) / joinAngularity;
|
||||
}
|
||||
|
||||
if (roundness > miterLimit) {
|
||||
flip = -flip;
|
||||
}
|
||||
|
||||
// Add first vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * joinNormal.x, flip * joinNormal.y, // extrude normal
|
||||
0, 0, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * joinNormal.x, flip * joinNormal.y, // extrude normal
|
||||
0, 0, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * joinNormal.x, -flip * joinNormal.y, // extrude normal
|
||||
0, 1, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * joinNormal.x, -flip * joinNormal.y, // extrude normal
|
||||
0, 1, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
if ((!prevVertex && beginCap == CapType::Round) ||
|
||||
(!nextVertex && endCap == CapType::Round)) {
|
||||
pointElementsBuffer->add(e1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the end cap, but only if this vertex is distinct from the begin
|
||||
// vertex.
|
||||
if (!nextVertex && (endCap == CapType::Round || endCap == CapType::Square)) {
|
||||
int8_t capTex = endCap == CapType::Round ? 1 : 0;
|
||||
|
||||
else {
|
||||
// Close up the previous line
|
||||
// Add first vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
capTex, 0, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * prevNormal.y, -flip * prevNormal.x, // extrude normal
|
||||
0, 0, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex.
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * prevNormal.y, flip * prevNormal.x, // extrude normal
|
||||
0, 1, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
prevNormal = { -nextNormal.x, -nextNormal.y };
|
||||
flip = 1;
|
||||
|
||||
|
||||
// begin/end caps
|
||||
if ((!prevVertex && beginCap == CapType::Round) ||
|
||||
(!nextVertex && endCap == CapType::Round)) {
|
||||
pointElementsBuffer->add(e1);
|
||||
}
|
||||
|
||||
|
||||
if (currentJoin == JoinType::Round) {
|
||||
if (prevVertex && nextVertex && (!closed || i > 0)) {
|
||||
pointElementsBuffer->add(e1);
|
||||
}
|
||||
|
||||
// Reset the previous vertices so that we don't accidentally create
|
||||
// any triangles.
|
||||
e1 = -1; e2 = -1; e3 = -1;
|
||||
}
|
||||
|
||||
// Start the new quad.
|
||||
// Add first vertex
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * nextNormal.y, flip * nextNormal.x, // extrude normal
|
||||
0, 0, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
buffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
capTex, 1, distance); // texture normal
|
||||
e3 = vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * nextNormal.y, -flip * nextNormal.x, // extrude normal
|
||||
0, 1, distance) - vertex_start; // texture normal
|
||||
|
||||
if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangleElementsBuffer->add(e1, e2, e3);
|
||||
e1 = e2; e2 = e3;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the length.
|
||||
length = buffer->index() - start;
|
||||
// Update the lengths.
|
||||
vertex_length = vertexBuffer->index() - vertex_start;
|
||||
triangle_length = triangleElementsBuffer->index() - triangle_elements_start;
|
||||
point_length = pointElementsBuffer->index() - point_elements_start;
|
||||
}
|
||||
|
||||
void LineBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) {
|
||||
painter.renderLine(*this, layer_name, id);
|
||||
}
|
||||
|
||||
size_t LineBucket::size() const {
|
||||
return length;
|
||||
bool LineBucket::empty() const {
|
||||
return vertex_length == 0;
|
||||
}
|
||||
|
||||
void LineBucket::bind(LineShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(start * 4 * sizeof(int16_t));
|
||||
array.bind(shader, *buffer, vertex_index);
|
||||
bool LineBucket::hasPoints() const {
|
||||
return point_length > 0;
|
||||
}
|
||||
|
||||
void LineBucket::drawLines() {
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, length);
|
||||
void LineBucket::drawLines(LineShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize);
|
||||
lineArray.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, triangle_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
}
|
||||
|
||||
void LineBucket::drawPoints() {
|
||||
glDrawArrays(GL_POINTS, 0, length);
|
||||
void LineBucket::drawDebugLines(LineShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
lineArray.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
glDrawArrays(GL_LINE_STRIP, 0, vertex_length);
|
||||
}
|
||||
|
||||
void LineBucket::drawPoints(LinejoinShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer->itemSize);
|
||||
pointArray.bind(shader, *vertexBuffer, *pointElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_POINTS, point_length, GL_UNSIGNED_SHORT, elements_index);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ void Painter::setup() {
|
||||
assert(plainShader);
|
||||
assert(outlineShader);
|
||||
assert(lineShader);
|
||||
assert(linejoinShader);
|
||||
assert(patternShader);
|
||||
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
@@ -46,6 +47,7 @@ void Painter::setupShaders() {
|
||||
plainShader = std::make_unique<PlainShader>();
|
||||
outlineShader = std::make_unique<OutlineShader>();
|
||||
lineShader = std::make_unique<LineShader>();
|
||||
linejoinShader = std::make_unique<LinejoinShader>();
|
||||
patternShader = std::make_unique<PatternShader>();
|
||||
}
|
||||
|
||||
@@ -213,7 +215,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
if (properties.antialias) {
|
||||
glUseProgram(outlineShader->program);
|
||||
glUniformMatrix4fv(outlineShader->u_matrix, 1, GL_FALSE, matrix.data());
|
||||
glLineWidth(2);
|
||||
glLineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
|
||||
|
||||
if (properties.stroke_color != properties.fill_color) {
|
||||
// If we defined a different color for the fill outline, we are
|
||||
@@ -233,7 +235,6 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
// Draw the entire line
|
||||
glUniform2f(outlineShader->u_world, transform.fb_width, transform.fb_height);
|
||||
glLineWidth(2.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
}
|
||||
|
||||
@@ -286,13 +287,16 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
const LineProperties& properties = style.computed.lines[layer_name];
|
||||
|
||||
// Abort early.
|
||||
if (!bucket.size()) return;
|
||||
if (bucket.empty()) return;
|
||||
if (properties.hidden) return;
|
||||
|
||||
double width = properties.width;
|
||||
double offset = (properties.offset || 0) / 2;
|
||||
double inset = fmax(-1, offset - width / 2 - 0.5) + 1;
|
||||
double outset = offset + width / 2 + 0.5;
|
||||
double offset = properties.offset / 2;
|
||||
|
||||
// These are the radii of the line. We are limiting it to 16, which will result
|
||||
// in a point size of 64 on retina.
|
||||
double inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f);
|
||||
double outset = fmin(offset + width / 2 + 0.5, 16.0f);
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
@@ -300,10 +304,19 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
|
||||
// var shader;
|
||||
bool imagePos = false;
|
||||
// We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
|
||||
if (bucket.hasPoints() && outset > 1.0f) {
|
||||
glUseProgram(linejoinShader->program);
|
||||
glUniformMatrix4fv(linejoinShader->u_matrix, 1, GL_FALSE, matrix.data());
|
||||
glUniform4fv(linejoinShader->u_color, 1, color.data());
|
||||
glUniform2f(linejoinShader->u_world, transform.fb_width / 2, transform.fb_height / 2);
|
||||
glUniform2f(linejoinShader->u_linewidth, (outset - 0.25) * transform.pixelRatio, (inset - 0.25) * transform.pixelRatio);
|
||||
glPointSize(ceil(transform.pixelRatio * outset * 2.0));
|
||||
bucket.drawPoints(*linejoinShader);
|
||||
}
|
||||
|
||||
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
|
||||
bool imagePos = false;
|
||||
if (imagePos) {
|
||||
// var factor = 8 / Math.pow(2, painter.transform.zoom - params.z);
|
||||
|
||||
@@ -321,38 +334,17 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
glUseProgram(lineShader->program);
|
||||
glUniformMatrix4fv(lineShader->u_matrix, 1, GL_FALSE, matrix.data());
|
||||
glUniformMatrix4fv(lineShader->u_exmatrix, 1, GL_FALSE, exMatrix.data());
|
||||
// glUniform2fv(painter.lineShader.u_dasharray, properties.dasharray || [1, -1]);
|
||||
glUniform2f(lineShader->u_dasharray, 1, -1);
|
||||
|
||||
|
||||
// TODO: Move this to transform?
|
||||
const double tilePixelRatio = transform.getScale() / (1 << transform.getIntegerZoom()) / 8;
|
||||
|
||||
glUniform2f(lineShader->u_dasharray, 1, -1);
|
||||
glUniform2f(lineShader->u_linewidth, outset, inset);
|
||||
glUniform1f(lineShader->u_ratio, tilePixelRatio);
|
||||
glUniform1f(lineShader->u_gamma, transform.pixelRatio);
|
||||
|
||||
// const Color& color = properties.color;
|
||||
// if (!params.antialiasing) {
|
||||
// color[3] = Infinity;
|
||||
// glUniform4fv(lineShader->u_color, color);
|
||||
// } else {
|
||||
glUniform4fv(lineShader->u_color, 1, color.data());
|
||||
// }
|
||||
|
||||
|
||||
glUniform1f(lineShader->u_point, 0);
|
||||
bucket.bind(*lineShader);
|
||||
bucket.drawLines();
|
||||
|
||||
if (bucket.geometry.join == JoinType::Round) {
|
||||
glUniform1f(lineShader->u_point, 1);
|
||||
bucket.drawPoints();
|
||||
}
|
||||
glUniform1f(lineShader->u_debug, 0);
|
||||
bucket.drawLines(*lineShader);
|
||||
}
|
||||
|
||||
// statistics
|
||||
// stats.lines += count;
|
||||
}
|
||||
|
||||
void Painter::renderDebug(const Tile::Ptr& tile) {
|
||||
@@ -364,16 +356,16 @@ void Painter::renderDebug(const Tile::Ptr& tile) {
|
||||
// draw tile outline
|
||||
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
|
||||
glUniform4f(plainShader->u_color, 1.0f, 0.0f, 0.0f, 1.0f);
|
||||
glLineWidth(4.0f);
|
||||
glLineWidth(4.0f * transform.pixelRatio);
|
||||
glDrawArrays(GL_LINE_STRIP, 0, tileBorderBuffer.index());
|
||||
|
||||
// draw debug info
|
||||
tile->debugFontArray.bind(*plainShader, tile->debugFontBuffer, BUFFER_OFFSET(0));
|
||||
glUniform4f(plainShader->u_color, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glLineWidth(4.0f);
|
||||
glLineWidth(4.0f * transform.pixelRatio);
|
||||
glDrawArrays(GL_LINES, 0, tile->debugFontBuffer.index());
|
||||
glUniform4f(plainShader->u_color, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glLineWidth(2.0f);
|
||||
glLineWidth(2.0f * transform.pixelRatio);
|
||||
glDrawArrays(GL_LINES, 0, tile->debugFontBuffer.index());
|
||||
|
||||
// Revert blending mode to blend to the back.
|
||||
|
||||
@@ -24,20 +24,18 @@ LineShader::LineShader()
|
||||
u_exmatrix = glGetUniformLocation(program, "u_exmatrix");
|
||||
u_linewidth = glGetUniformLocation(program, "u_linewidth");
|
||||
u_color = glGetUniformLocation(program, "u_color");
|
||||
u_debug = glGetUniformLocation(program, "u_debug");
|
||||
u_ratio = glGetUniformLocation(program, "u_ratio");
|
||||
u_dasharray = glGetUniformLocation(program, "u_dasharray");
|
||||
u_point = glGetUniformLocation(program, "u_point");
|
||||
u_gamma = glGetUniformLocation(program, "u_gamma");
|
||||
u_debug = glGetUniformLocation(program, "u_debug");
|
||||
}
|
||||
|
||||
void LineShader::bind(char *offset) {
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
|
||||
|
||||
glEnableVertexAttribArray(a_extrude);
|
||||
glVertexAttribPointer(a_extrude, 2, GL_BYTE, false, 8, offset + 6);
|
||||
|
||||
glEnableVertexAttribArray(a_linesofar);
|
||||
glVertexAttribPointer(a_linesofar, 1, GL_SHORT, false, 8, offset + 4);
|
||||
|
||||
glEnableVertexAttribArray(a_extrude);
|
||||
glVertexAttribPointer(a_extrude, 2, GL_BYTE, false, 8, offset + 6);
|
||||
}
|
||||
31
src/renderer/shader-linejoin.cpp
Normal file
31
src/renderer/shader-linejoin.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include <llmr/renderer/shader-linejoin.hpp>
|
||||
#include <llmr/shader/shaders.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
LinejoinShader::LinejoinShader()
|
||||
: Shader(
|
||||
shaders[LINEJOIN_SHADER].vertex,
|
||||
shaders[LINEJOIN_SHADER].fragment
|
||||
) {
|
||||
if (!valid) {
|
||||
fprintf(stderr, "invalid line shader\n");
|
||||
return;
|
||||
}
|
||||
|
||||
a_pos = glGetAttribLocation(program, "a_pos");
|
||||
|
||||
u_matrix = glGetUniformLocation(program, "u_matrix");
|
||||
u_world = glGetUniformLocation(program, "u_world");
|
||||
u_linewidth = glGetUniformLocation(program, "u_linewidth");
|
||||
u_color = glGetUniformLocation(program, "u_color");
|
||||
}
|
||||
|
||||
void LinejoinShader::bind(char *offset) {
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
// Note: We're referring to the vertices in a line array, which are 8 bytes long!
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// shared
|
||||
uniform float u_debug;
|
||||
uniform vec2 u_linewidth;
|
||||
uniform vec4 u_color;
|
||||
uniform float u_point;
|
||||
uniform float u_gamma;
|
||||
|
||||
uniform vec2 u_dasharray;
|
||||
|
||||
@@ -12,23 +9,21 @@ varying float v_linesofar;
|
||||
|
||||
void main() {
|
||||
// Calculate the distance of the pixel from the line in pixels.
|
||||
float dist = length(v_normal) * (1.0 - u_point) + u_point * length(gl_PointCoord * 2.0 - 1.0);
|
||||
|
||||
dist *= u_linewidth.s;
|
||||
float dist = length(v_normal) * u_linewidth.s;
|
||||
|
||||
// Calculate the antialiasing fade factor. This is either when fading in
|
||||
// the line in case of an offset line (v_linewidth.t) or when fading out
|
||||
// (v_linewidth.s)
|
||||
float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist) * u_gamma, 0.0, 1.0);
|
||||
float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);
|
||||
|
||||
// Calculate the antialiasing fade factor based on distance to the dash.
|
||||
// Only affects alpha when line is dashed
|
||||
float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);
|
||||
alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));
|
||||
|
||||
gl_FragColor = u_color * alpha;
|
||||
|
||||
if (u_debug > 0.0) {
|
||||
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
gl_FragColor = u_color;
|
||||
} else {
|
||||
gl_FragColor = u_color * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
// these are the shaders for rendering antialiased lines
|
||||
|
||||
// floor(127 / 2) == 63.0
|
||||
// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is
|
||||
// stored in a byte (-128..127). we scale regular normals up to length 63, but
|
||||
// there are also "special" normals that have a bigger length (of up to 126 in
|
||||
// this case).
|
||||
#define scale 63.0
|
||||
// #define scale 63.0
|
||||
#define scale 0.015873016
|
||||
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_extrude;
|
||||
attribute float a_linesofar;
|
||||
|
||||
// matrix is for the vertex position, exmatrix is for rotating and projecting
|
||||
// posmatrix is for the vertex position, exmatrix is for rotating and projecting
|
||||
// the extrusion vector.
|
||||
uniform mat4 u_matrix;
|
||||
uniform mat4 u_exmatrix;
|
||||
|
||||
|
||||
uniform float u_debug;
|
||||
|
||||
// shared
|
||||
uniform float u_ratio;
|
||||
uniform vec2 u_linewidth;
|
||||
uniform vec4 u_color;
|
||||
uniform float u_point;
|
||||
|
||||
varying vec2 v_normal;
|
||||
varying float v_linesofar;
|
||||
@@ -39,24 +36,18 @@ void main() {
|
||||
|
||||
// Scale the extrusion vector down to a normal and then up by the line width
|
||||
// of this vertex.
|
||||
vec2 extrude = a_extrude / scale;
|
||||
vec2 dist = u_linewidth.s * extrude * (1.0 - u_point);
|
||||
|
||||
// If the x coordinate is the maximum integer, we move the z coordinates out
|
||||
// of the view plane so that the triangle gets clipped. This makes it easier
|
||||
// for us to create degenerate triangle strips.
|
||||
float z = step(32767.0, a_pos.x) * 2.0;
|
||||
|
||||
// When drawing points, skip every other vertex
|
||||
z += u_point * step(1.0, v_normal.y);
|
||||
vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);
|
||||
|
||||
// Remove the texture normal bit of the position before scaling it with the
|
||||
// model/view matrix. Add the extrusion vector *after* the model/view matrix
|
||||
// because we're extruding the line in pixel space, regardless of the current
|
||||
// tile's zoom level.
|
||||
gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0) + u_exmatrix * vec4(dist, z, 0.0);
|
||||
gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;
|
||||
v_linesofar = a_linesofar * u_ratio;
|
||||
|
||||
gl_PointSize = 8.0;
|
||||
|
||||
gl_PointSize = 2.0 * u_linewidth.s - 1.0;
|
||||
}
|
||||
if (u_debug > 1.5) {
|
||||
gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
14
src/shader/linejoin.fragment.glsl
Normal file
14
src/shader/linejoin.fragment.glsl
Normal file
@@ -0,0 +1,14 @@
|
||||
uniform vec4 u_color;
|
||||
uniform vec2 u_linewidth;
|
||||
|
||||
varying vec2 v_pos;
|
||||
|
||||
void main() {
|
||||
float dist = length(v_pos - gl_FragCoord.xy);
|
||||
|
||||
// Calculate the antialiasing fade factor. This is either when fading in
|
||||
// the line in case of an offset line (v_linewidth.t) or when fading out
|
||||
// (v_linewidth.s)
|
||||
float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);
|
||||
gl_FragColor = u_color * alpha;
|
||||
}
|
||||
12
src/shader/linejoin.vertex.glsl
Normal file
12
src/shader/linejoin.vertex.glsl
Normal file
@@ -0,0 +1,12 @@
|
||||
attribute vec2 a_pos;
|
||||
|
||||
uniform mat4 u_matrix;
|
||||
uniform vec2 u_world;
|
||||
|
||||
varying vec2 v_pos;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);
|
||||
v_pos = (gl_Position.xy + 1.0) * u_world;
|
||||
gl_PointSize = u_linewidth.s * 2.0;
|
||||
}
|
||||
@@ -5,8 +5,12 @@ using namespace llmr;
|
||||
|
||||
const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
{
|
||||
"// these are the shaders for rendering antialiased lines\n\n// floor(127 / 2) == 63.0\n// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is\n// stored in a byte (-128..127). we scale regular normals up to length 63, but\n// there are also \"special\" normals that have a bigger length (of up to 126 in\n// this case).\n#define scale 63.0\n\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\n\n// matrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\n\n\nuniform float u_debug;\n\n// shared\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\nuniform float u_point;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // We store the texture normals in the most insignificant bit\n // transform y so that 0 => -1 and 1 => 1\n // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap\n // y is 1 if the normal points up, and -1 if it points down\n vec2 normal = mod(a_pos, 2.0);\n normal.y = sign(normal.y - 0.5);\n v_normal = normal;\n\n // Scale the extrusion vector down to a normal and then up by the line width\n // of this vertex.\n vec2 extrude = a_extrude / scale;\n vec2 dist = u_linewidth.s * extrude * (1.0 - u_point);\n\n // If the x coordinate is the maximum integer, we move the z coordinates out\n // of the view plane so that the triangle gets clipped. This makes it easier\n // for us to create degenerate triangle strips.\n float z = step(32767.0, a_pos.x) * 2.0;\n\n // When drawing points, skip every other vertex\n z += u_point * step(1.0, v_normal.y);\n\n // Remove the texture normal bit of the position before scaling it with the\n // model/view matrix. Add the extrusion vector *after* the model/view matrix\n // because we're extruding the line in pixel space, regardless of the current\n // tile's zoom level.\n gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0) + u_exmatrix * vec4(dist, z, 0.0);\n v_linesofar = a_linesofar * u_ratio;\n\n\n gl_PointSize = 2.0 * u_linewidth.s - 1.0;\n}\n",
|
||||
"// shared\nuniform float u_debug;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\nuniform float u_point;\nuniform float u_gamma;\n\nuniform vec2 u_dasharray;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // Calculate the distance of the pixel from the line in pixels.\n float dist = length(v_normal) * (1.0 - u_point) + u_point * length(gl_PointCoord * 2.0 - 1.0);\n\n dist *= u_linewidth.s;\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist) * u_gamma, 0.0, 1.0);\n\n // Calculate the antialiasing fade factor based on distance to the dash.\n // Only affects alpha when line is dashed\n float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);\n alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));\n\n gl_FragColor = u_color * alpha;\n\n if (u_debug > 0.0) {\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n }\n}\n",
|
||||
"// floor(127 / 2) == 63.0\n// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is\n// stored in a byte (-128..127). we scale regular normals up to length 63, but\n// there are also \"special\" normals that have a bigger length (of up to 126 in\n// this case).\n// #define scale 63.0\n#define scale 0.015873016\n\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\n\nuniform float u_debug;\n\n// shared\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // We store the texture normals in the most insignificant bit\n // transform y so that 0 => -1 and 1 => 1\n // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap\n // y is 1 if the normal points up, and -1 if it points down\n vec2 normal = mod(a_pos, 2.0);\n normal.y = sign(normal.y - 0.5);\n v_normal = normal;\n\n // Scale the extrusion vector down to a normal and then up by the line width\n // of this vertex.\n vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);\n\n // Remove the texture normal bit of the position before scaling it with the\n // model/view matrix. Add the extrusion vector *after* the model/view matrix\n // because we're extruding the line in pixel space, regardless of the current\n // tile's zoom level.\n gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;\n v_linesofar = a_linesofar * u_ratio;\n\n gl_PointSize = 8.0;\n\n if (u_debug > 1.5) {\n gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0);\n }\n}",
|
||||
"uniform float u_debug;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nuniform vec2 u_dasharray;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // Calculate the distance of the pixel from the line in pixels.\n float dist = length(v_normal) * u_linewidth.s;\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n\n // Calculate the antialiasing fade factor based on distance to the dash.\n // Only affects alpha when line is dashed\n float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);\n alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));\n\n if (u_debug > 0.0) {\n gl_FragColor = u_color;\n } else {\n gl_FragColor = u_color * alpha;\n }\n}",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform vec2 u_world;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);\n v_pos = (gl_Position.xy + 1.0) * u_world;\n}\n",
|
||||
"uniform vec4 u_color;\nuniform vec2 u_linewidth;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n gl_FragColor = u_color * alpha;\n}\n",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\n\nvarying vec2 v_pos;\n\nvoid main() {\n // If the x coordinate is the maximum integer, we move the z coordinates out\n // of the view plane so that the triangle gets clipped. This makes it easier\n // for us to create degenerate triangle strips.\n float z = step(32767.0, a_pos.x) * 2.0;\n gl_Position = u_matrix * vec4(a_pos, z, 1);\n\n v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world;\n}\n",
|
||||
|
||||
@@ -9,14 +9,14 @@ const unsigned char resources::style[] = {
|
||||
111, 97, 100, 95, 108, 97, 114, 103, 101, 16, 2, 26, 7, 115, 116, 114,
|
||||
101, 101, 116, 115, 34, 4, 114, 111, 97, 100, 42, 5, 99, 108, 97, 115,
|
||||
115, 50, 10, 10, 8, 109, 111, 116, 111, 114, 119, 97, 121, 50, 6, 10,
|
||||
4, 109, 97, 105, 110, 56, 1, 64, 2, 10, 52, 10, 12, 114, 111, 97,
|
||||
4, 109, 97, 105, 110, 56, 1, 64, 3, 10, 52, 10, 12, 114, 111, 97,
|
||||
100, 95, 114, 101, 103, 117, 108, 97, 114, 16, 2, 26, 7, 115, 116, 114,
|
||||
101, 101, 116, 115, 34, 4, 114, 111, 97, 100, 42, 5, 99, 108, 97, 115,
|
||||
115, 50, 8, 10, 6, 115, 116, 114, 101, 101, 116, 56, 1, 64, 2, 10,
|
||||
115, 50, 8, 10, 6, 115, 116, 114, 101, 101, 116, 56, 1, 64, 3, 10,
|
||||
60, 10, 12, 114, 111, 97, 100, 95, 108, 105, 109, 105, 116, 101, 100, 16,
|
||||
2, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 4, 114, 111, 97, 100,
|
||||
42, 5, 99, 108, 97, 115, 115, 50, 16, 10, 14, 115, 116, 114, 101, 101,
|
||||
116, 95, 108, 105, 109, 105, 116, 101, 100, 56, 1, 64, 2, 10, 41, 10,
|
||||
116, 95, 108, 105, 109, 105, 116, 101, 100, 56, 1, 64, 3, 10, 41, 10,
|
||||
4, 112, 97, 114, 107, 16, 1, 26, 7, 115, 116, 114, 101, 101, 116, 115,
|
||||
34, 7, 108, 97, 110, 100, 117, 115, 101, 42, 5, 99, 108, 97, 115, 115,
|
||||
50, 6, 10, 4, 112, 97, 114, 107, 10, 41, 10, 4, 119, 111, 111, 100,
|
||||
|
||||
Reference in New Issue
Block a user