add text rendering code
This commit is contained in:
@@ -34,10 +34,12 @@ public:
|
||||
void removeGlyphs(uint64_t tile_id);
|
||||
void bind();
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
public:
|
||||
const uint16_t width = 0;
|
||||
const uint16_t height = 0;
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
BinPack<uint16_t> bin;
|
||||
std::map<std::string, std::map<uint32_t, GlyphValue>> index;
|
||||
char *const data = nullptr;
|
||||
|
||||
@@ -78,8 +78,8 @@ private:
|
||||
Transform transform;
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
Painter painter;
|
||||
GlyphAtlas glyphAtlas;
|
||||
Painter painter;
|
||||
|
||||
int32_t min_zoom;
|
||||
int32_t max_zoom;
|
||||
|
||||
29
include/llmr/renderer/frame_history.hpp
Normal file
29
include/llmr/renderer/frame_history.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef LLMR_RENDERER_FRAME_HISTORY
|
||||
#define LLMR_RENDERER_FRAME_HISTORY
|
||||
|
||||
#include <deque>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include <llmr/platform/platform.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
struct FrameSnapshot {
|
||||
explicit inline FrameSnapshot(float time, float z) : time(time), z(z) {}
|
||||
float time;
|
||||
float z;
|
||||
};
|
||||
|
||||
class FrameHistory {
|
||||
public:
|
||||
// Record frame history that will be used to calculate fading params
|
||||
void record(float zoom);
|
||||
|
||||
public:
|
||||
std::deque<FrameSnapshot> history;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <llmr/geometry/vertex_buffer.hpp>
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/renderer/frame_history.hpp>
|
||||
|
||||
#include <llmr/shader/plain_shader.hpp>
|
||||
#include <llmr/shader/outline_shader.hpp>
|
||||
@@ -30,7 +31,7 @@ class TextBucket;
|
||||
|
||||
class Painter : private util::noncopyable {
|
||||
public:
|
||||
Painter(Transform& transform, Settings& settings, Style& style);
|
||||
Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas);
|
||||
|
||||
void setup();
|
||||
void clear();
|
||||
@@ -68,8 +69,9 @@ private:
|
||||
Transform& transform;
|
||||
Settings& settings;
|
||||
Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
|
||||
|
||||
FrameHistory frameHistory;
|
||||
|
||||
uint32_t gl_program = 0;
|
||||
float gl_lineWidth = 0;
|
||||
|
||||
@@ -15,15 +15,15 @@ public:
|
||||
void setColor(const std::array<float, 4> &color);
|
||||
void setBuffer(float buffer);
|
||||
void setGamma(float gamma);
|
||||
void setExmatrix(const std::array<float, 16> &exmatrix);
|
||||
void setExtrudeMatrix(const std::array<float, 16> &exmatrix);
|
||||
void setAngle(float angle);
|
||||
void setZoom(float zoom);
|
||||
void setFlip(float flip);
|
||||
void setFadedist(float fadedist);
|
||||
void setMinfadezoom(float minfadezoom);
|
||||
void setMaxfadezoom(float maxfadezoom);
|
||||
void setFadezoom(float fadezoom);
|
||||
void setTexsize(const std::array<float, 2> &texsize);
|
||||
void setFadeDist(float fadedist);
|
||||
void setMinFadeZoom(float minfadezoom);
|
||||
void setMaxFadeZoom(float maxfadezoom);
|
||||
void setFadeZoom(float fadezoom);
|
||||
void setTextureSize(const std::array<float, 2> &texsize);
|
||||
|
||||
private:
|
||||
int32_t a_pos = -1;
|
||||
|
||||
@@ -113,11 +113,19 @@ struct FillProperties {
|
||||
};
|
||||
|
||||
struct TextClass {
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
FunctionProperty<bool> hidden;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color halo = {{ 1, 1, 1, 0.75 }};
|
||||
FunctionProperty<float> haloRadius = 0.25f;
|
||||
FunctionProperty<float> size = 12.0f;
|
||||
};
|
||||
|
||||
struct TextProperties {
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
bool hidden = false;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color halo = {{ 1, 1, 1, 0.75 }};
|
||||
float haloRadius = 0.25f;
|
||||
float size = 12.0f;
|
||||
};
|
||||
|
||||
struct BackgroundClass {
|
||||
|
||||
@@ -15,8 +15,8 @@ Map::Map(Settings& settings)
|
||||
transform(),
|
||||
texturepool(),
|
||||
style(),
|
||||
painter(transform, settings, style),
|
||||
glyphAtlas(1024, 1024),
|
||||
painter(transform, settings, style, glyphAtlas),
|
||||
min_zoom(0),
|
||||
max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) {
|
||||
}
|
||||
@@ -443,8 +443,6 @@ bool Map::render() {
|
||||
transform.updateAnimations();
|
||||
}
|
||||
|
||||
glyphAtlas.bind();
|
||||
|
||||
if (*style.sprite->raster && !style.sprite->raster->textured) {
|
||||
style.sprite->raster->setTexturepool(&texturepool);
|
||||
}
|
||||
|
||||
16
src/renderer/frame_history.cpp
Normal file
16
src/renderer/frame_history.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include <llmr/renderer/frame_history.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
// Record frame history that will be used to calculate fading params
|
||||
void FrameHistory::record(float zoom) {
|
||||
// first frame ever
|
||||
if (!history.size()) {
|
||||
history.emplace_back(FrameSnapshot{0.0f, zoom});
|
||||
history.emplace_back(FrameSnapshot{0.0f, zoom});
|
||||
}
|
||||
|
||||
if (history.size() == 2 || history.back().z != zoom) {
|
||||
history.emplace_back(FrameSnapshot{static_cast<float>(platform::time() * 1000), zoom});
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,11 @@ using namespace llmr;
|
||||
|
||||
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
|
||||
|
||||
Painter::Painter(Transform& transform, Settings& settings, Style& style)
|
||||
Painter::Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas)
|
||||
: transform(transform),
|
||||
settings(settings),
|
||||
style(style) {
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas) {
|
||||
}
|
||||
|
||||
void Painter::setup() {
|
||||
@@ -152,6 +153,8 @@ void Painter::render(const Tile& tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameHistory.record(transform.getZoom());
|
||||
|
||||
matrix = tile.matrix;
|
||||
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
|
||||
|
||||
@@ -477,9 +480,116 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
}
|
||||
|
||||
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
|
||||
// noop
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const TextProperties& properties = style.computed.texts[layer_name];
|
||||
if (properties.hidden) return;
|
||||
|
||||
mat4 exMatrix;
|
||||
matrix::copy(exMatrix, projMatrix);
|
||||
if (bucket.geom_desc.path == TextPathType::Curve) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, transform.getAngle());
|
||||
}
|
||||
|
||||
// var rotate = layerStyle.rotate || 0;
|
||||
float rotate = 0.0f;
|
||||
if (rotate != 0.0f) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, rotate);
|
||||
}
|
||||
|
||||
// If layerStyle.size > bucket.info.fontSize then labels may collide
|
||||
float fontSize = fmin(properties.size, bucket.geom_desc.font_size);
|
||||
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
|
||||
|
||||
useProgram(textShader->program);
|
||||
textShader->setMatrix(matrix);
|
||||
textShader->setExtrudeMatrix(exMatrix);
|
||||
|
||||
glyphAtlas.bind();
|
||||
textShader->setTextureSize({{ static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height) }});
|
||||
|
||||
// bucket.geometry.glyphVertex.bind(gl);
|
||||
|
||||
// var ubyte = gl.UNSIGNED_BYTE;
|
||||
|
||||
// gl.vertexAttribPointer(shader.a_pos, 2, gl.SHORT, false, 16, 0);
|
||||
// gl.vertexAttribPointer(shader.a_offset, 2, gl.SHORT, false, 16, 4);
|
||||
// gl.vertexAttribPointer(shader.a_tex, 2, ubyte, false, 16, 8);
|
||||
// gl.vertexAttribPointer(shader.a_labelminzoom, 1, ubyte, false, 16, 10);
|
||||
// gl.vertexAttribPointer(shader.a_minzoom, 1, ubyte, false, 16, 11);
|
||||
// gl.vertexAttribPointer(shader.a_maxzoom, 1, ubyte, false, 16, 12);
|
||||
// gl.vertexAttribPointer(shader.a_angle, 1, ubyte, false, 16, 13);
|
||||
// gl.vertexAttribPointer(shader.a_rangeend, 1, ubyte, false, 16, 14);
|
||||
// gl.vertexAttribPointer(shader.a_rangestart, 1, ubyte, false, 16, 15);
|
||||
|
||||
textShader->setGamma(2.5f / fontSize / transform.pixelRatio);
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = round((transform.getAngle() + rotate) / M_PI * 128);
|
||||
|
||||
// adjust min/max zooms for variable font sies
|
||||
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
|
||||
|
||||
textShader->setAngle((int32_t)(angle + 256) % 256);
|
||||
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
|
||||
textShader->setZoom((transform.getZoom() - zoomAdjust) * 10); // current zoom level
|
||||
|
||||
// Label fading
|
||||
const float duration = 300.0f;
|
||||
const float currentTime = platform::time() * 1000;
|
||||
|
||||
std::deque<FrameSnapshot> &history = frameHistory.history;
|
||||
|
||||
// Remove frames until only one is outside the duration, or until there are only three
|
||||
while (history.size() > 3 && history[1].time + duration < currentTime) {
|
||||
history.pop_front();
|
||||
}
|
||||
|
||||
if (history[1].time + duration < currentTime) {
|
||||
history[0].z = history[1].z;
|
||||
}
|
||||
|
||||
size_t frameLen = history.size();
|
||||
assert("there should never be less than three frames in the history" && frameLen >= 3);
|
||||
|
||||
// Find the range of zoom levels we want to fade between
|
||||
float startingZ = history.front().z;
|
||||
const FrameSnapshot lastFrame = history.back();
|
||||
float endingZ = lastFrame.z;
|
||||
float lowZ = fmin(startingZ, endingZ);
|
||||
float highZ = fmax(startingZ, endingZ);
|
||||
|
||||
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration
|
||||
float zoomDiff = endingZ - history[1].z,
|
||||
timeDiff = lastFrame.time - history[1].time;
|
||||
if (timeDiff > duration) timeDiff = 1;
|
||||
float fadedist = zoomDiff / (timeDiff / duration);
|
||||
|
||||
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN");
|
||||
|
||||
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
|
||||
// bump is how much farther it would have been if it had continued zooming at the same rate
|
||||
float bump = (currentTime - lastFrame.time) / duration * fadedist;
|
||||
|
||||
textShader->setFadeDist(fadedist * 10);
|
||||
textShader->setMinFadeZoom(floor(lowZ * 10));
|
||||
textShader->setMaxFadeZoom(floor(highZ * 10));
|
||||
textShader->setFadeZoom((transform.getZoom() + bump) * 10);
|
||||
|
||||
// We're drawing in the translucent pass which is bottom-to-top, so we need
|
||||
// to draw the halo first.
|
||||
if (properties.halo[3] > 0.0f) {
|
||||
textShader->setColor(properties.halo);
|
||||
textShader->setBuffer(properties.haloRadius);
|
||||
bucket.drawGlyphs(*textShader);
|
||||
}
|
||||
|
||||
// Then, we draw the text over the halo
|
||||
textShader->setColor(properties.color);
|
||||
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
|
||||
bucket.drawGlyphs(*textShader);
|
||||
}
|
||||
|
||||
void Painter::renderDebug(const TileData::Ptr& tile_data) {
|
||||
|
||||
@@ -150,5 +150,13 @@ void TextBucket::render(Painter &painter, const std::string &layer_name,
|
||||
bool TextBucket::empty() const { return triangleGroups.empty(); }
|
||||
|
||||
void TextBucket::drawGlyphs(TextShader &shader) {
|
||||
// noop
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index =
|
||||
BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize);
|
||||
for (triangle_group_type &group : triangleGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer->itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ TextShader::TextShader()
|
||||
}
|
||||
|
||||
void TextShader::bind(char *offset) {
|
||||
fprintf(stderr, "bind textshader\n");
|
||||
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 16, offset + 0);
|
||||
|
||||
@@ -123,7 +125,7 @@ void TextShader::setGamma(float new_gamma) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setExmatrix(const std::array<float, 16> &new_exmatrix) {
|
||||
void TextShader::setExtrudeMatrix(const std::array<float, 16> &new_exmatrix) {
|
||||
if (exmatrix != new_exmatrix) {
|
||||
glUniformMatrix4fv(u_exmatrix, 1, GL_FALSE, new_exmatrix.data());
|
||||
exmatrix = new_exmatrix;
|
||||
@@ -151,35 +153,35 @@ void TextShader::setFlip(float new_flip) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setFadedist(float new_fadedist) {
|
||||
void TextShader::setFadeDist(float new_fadedist) {
|
||||
if (fadedist != new_fadedist) {
|
||||
glUniform1f(u_fadedist, new_fadedist);
|
||||
fadedist = new_fadedist;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setMinfadezoom(float new_minfadezoom) {
|
||||
void TextShader::setMinFadeZoom(float new_minfadezoom) {
|
||||
if (minfadezoom != new_minfadezoom) {
|
||||
glUniform1f(u_minfadezoom, new_minfadezoom);
|
||||
minfadezoom = new_minfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setMaxfadezoom(float new_maxfadezoom) {
|
||||
void TextShader::setMaxFadeZoom(float new_maxfadezoom) {
|
||||
if (maxfadezoom != new_maxfadezoom) {
|
||||
glUniform1f(u_maxfadezoom, new_maxfadezoom);
|
||||
maxfadezoom = new_maxfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setFadezoom(float new_fadezoom) {
|
||||
void TextShader::setFadeZoom(float new_fadezoom) {
|
||||
if (fadezoom != new_fadezoom) {
|
||||
glUniform1f(u_fadezoom, new_fadezoom);
|
||||
fadezoom = new_fadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setTexsize(const std::array<float, 2> &new_texsize) {
|
||||
void TextShader::setTextureSize(const std::array<float, 2> &new_texsize) {
|
||||
if (texsize != new_texsize) {
|
||||
glUniform2fv(u_texsize, 1, new_texsize.data());
|
||||
texsize = new_texsize;
|
||||
|
||||
@@ -83,7 +83,11 @@ void Style::cascade(float z) {
|
||||
// TODO: This should be restricted to point styles that have actual
|
||||
// values so as to not override with default values.
|
||||
llmr::TextProperties& text = computed.texts[layer_name];
|
||||
text.hidden = layer.hidden(z);
|
||||
text.color = layer.color;
|
||||
text.size = layer.size(z);
|
||||
text.halo = layer.halo;
|
||||
text.haloRadius = layer.haloRadius(z);
|
||||
}
|
||||
|
||||
// Cascade background
|
||||
|
||||
@@ -268,24 +268,35 @@ std::string StyleParser::parseString(JSVal value) {
|
||||
}
|
||||
|
||||
Color StyleParser::parseColor(JSVal value) {
|
||||
if (!value.IsString()) {
|
||||
if (value.IsArray()) {
|
||||
// [ r, g, b, a] array
|
||||
if (value.Size() != 4) {
|
||||
throw Style::exception("color array must have four elements");
|
||||
}
|
||||
|
||||
JSVal r = value[(rapidjson::SizeType)0], g = value[1], b = value[2], a = value[3];
|
||||
if (!r.IsNumber() || !g.IsNumber() || !b.IsNumber() || !a.IsNumber()) {
|
||||
throw Style::exception("color values must be numbers");
|
||||
}
|
||||
|
||||
return {{static_cast<float>(r.GetDouble() / 255),
|
||||
static_cast<float>(g.GetDouble() / 255),
|
||||
static_cast<float>(b.GetDouble() / 255),
|
||||
static_cast<float>(a.GetDouble())}};
|
||||
|
||||
} else if (!value.IsString()) {
|
||||
throw Style::exception("color value must be a string");
|
||||
}
|
||||
|
||||
const std::string str { value.GetString(), value.GetStringLength() };
|
||||
const std::string str{value.GetString(), value.GetStringLength()};
|
||||
|
||||
auto it = constants.find(str);
|
||||
if (it != constants.end()) {
|
||||
return parseColor(*it->second);
|
||||
} else {
|
||||
CSSColorParser::Color css_color = CSSColorParser::parse(str);
|
||||
return {{
|
||||
(float)css_color.r / 255,
|
||||
(float)css_color.g / 255,
|
||||
(float)css_color.b / 255,
|
||||
css_color.a
|
||||
}
|
||||
};
|
||||
return {{(float)css_color.r / 255, (float)css_color.g / 255,
|
||||
(float)css_color.b / 255, css_color.a}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,10 +488,26 @@ PointClass StyleParser::parsePointClass(JSVal value) {
|
||||
TextClass StyleParser::parseTextClass(JSVal value) {
|
||||
TextClass klass;
|
||||
|
||||
if (value.HasMember("hidden")) {
|
||||
klass.hidden = parseBoolFunction(value["hidden"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("color")) {
|
||||
klass.color = parseColor(value["color"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("stroke")) {
|
||||
klass.halo = parseColor(value["stroke"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("strokeWidth")) {
|
||||
klass.haloRadius = parseFloatFunction(value["strokeWidth"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("size")) {
|
||||
klass.size = parseFloatFunction(value["size"]);
|
||||
}
|
||||
|
||||
return klass;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user