widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #05722
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
SirVer has proposed merging lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands.
Commit message:
Fixes line drawing by replacing the broken use of GL_LINES with a tessellation algorithm for drawing lines.
We are drawing a line segment as two triangles. The width of the lines is found by calculating the normal of a line and pushing the vertices of the triangles along this normal. At line segment joints, we construct a proper miter joint now, which makes line segments look really nice. We also antialias lines now, so that they are not as harsh as they were before.
Reference I used for the implementation:
https://www.mapbox.com/blog/drawing-antialiased-lines
https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1296889 in widelands: "Soldiers' healthbar misses pixel"
https://bugs.launchpad.net/widelands/+bug/1296889
Bug #1531114 in widelands: "Lines are not drawn correctly "
https://bugs.launchpad.net/widelands/+bug/1531114
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/beautiful_correct_lines/+merge/284517
See commit message.
Testers: see the statistics windows and the solider boxes and lines in general. Should be all fixed now.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands.
=== modified file 'src/base/point.h'
--- src/base/point.h 2014-11-27 18:27:31 +0000
+++ src/base/point.h 2016-01-30 15:36:48 +0000
@@ -20,10 +20,13 @@
#ifndef WL_BASE_POINT_H
#define WL_BASE_POINT_H
+#include <cmath>
#include <limits>
#include <stdint.h>
+// This class is our 2D vector class. We use it as Points, but also as Vectors
+// in some cases.
template <typename T> struct GenericPoint {
GenericPoint(const T& px, const T& py) : x(px), y(py) {
}
@@ -76,4 +79,16 @@
/// values).
Point middle(const Point& a, const Point& b);
+// Returns a normalized version of 'v'.
+template <typename Vector> FloatPoint normalize(const Vector& v) {
+ const float len = std::hypot(v.x, v.y);
+ return FloatPoint(v.x / len, v.y / len);
+}
+
+// Returns the dot product of v1 with v2.
+inline float dot(const FloatPoint& v1, const FloatPoint& v2) {
+ return v1.x * v2.x + v1.y * v2.y;
+}
+
+
#endif // end of include guard: WL_BASE_POINT_H
=== modified file 'src/graphic/CMakeLists.txt'
--- src/graphic/CMakeLists.txt 2016-01-24 12:43:26 +0000
+++ src/graphic/CMakeLists.txt 2016-01-30 15:36:48 +0000
@@ -127,6 +127,7 @@
SRCS
blend_mode.h
blit_mode.h
+ line_strip_mode.h
gl/blit_program.cc
gl/blit_program.h
gl/draw_line_program.cc
=== modified file 'src/graphic/game_renderer.cc'
--- src/graphic/game_renderer.cc 2016-01-23 11:42:26 +0000
+++ src/graphic/game_renderer.cc 2016-01-30 15:36:48 +0000
@@ -218,7 +218,7 @@
RenderQueue::Item i;
i.program_id = RenderQueue::Program::kTerrainBase;
i.blend_mode = BlendMode::Copy;
- i.destination_rect =
+ i.terrain_arguments.destination_rect =
FloatRect(bounding_rect.x,
surface_height - bounding_rect.y - bounding_rect.h,
bounding_rect.w,
=== modified file 'src/graphic/gl/draw_line_program.cc'
--- src/graphic/gl/draw_line_program.cc 2016-01-09 15:27:05 +0000
+++ src/graphic/gl/draw_line_program.cc 2016-01-30 15:36:48 +0000
@@ -33,11 +33,14 @@
// Attributes.
attribute vec3 attr_position;
attribute vec3 attr_color;
+attribute float attr_distance_from_center;
varying vec3 var_color;
+varying float var_distance_from_center;
void main() {
var_color = attr_color;
+ var_distance_from_center = attr_distance_from_center;
gl_Position = vec4(attr_position, 1.);
}
)";
@@ -46,18 +49,22 @@
#version 120
varying vec3 var_color;
+varying float var_distance_from_center;
+
+// The percentage of the line that should be solid, i.e. not feathered into
+// alpha = 0.
+#define SOLID 0.3
void main() {
- gl_FragColor = vec4(var_color.rgb, 1.);
+ // This means till SOLID we want full alpha, then a (1 - t**2) with t [0, 1]
+ // falloff.
+ float d = abs(var_distance_from_center);
+ float opaqueness =
+ step(SOLID, d) * ((d - SOLID) / (1. - SOLID));
+ gl_FragColor = vec4(var_color.rgb, 1. - pow(opaqueness, 2));
}
)";
-struct DrawBatch {
- int offset;
- int count;
- int line_width;
-};
-
} // namespace
// static
@@ -71,76 +78,52 @@
attr_position_ = glGetAttribLocation(gl_program_.object(), "attr_position");
attr_color_ = glGetAttribLocation(gl_program_.object(), "attr_color");
-}
-
-void DrawLineProgram::draw(const FloatPoint& start,
- const FloatPoint& end,
- const float z_value,
- const RGBColor& color,
- int line_width) {
- draw({Arguments{FloatRect(start.x, start.y, end.x - start.x, end.y - start.y),
- z_value,
- color,
- static_cast<uint8_t>(line_width),
- BlendMode::Copy}});
-}
-
-void DrawLineProgram::draw(std::vector<Arguments> arguments) {
- size_t i = 0;
-
+ attr_distance_from_center_ = glGetAttribLocation(gl_program_.object(), "attr_distance_from_center");
+}
+
+void DrawLineProgram::draw(const std::vector<Arguments>& arguments) {
glUseProgram(gl_program_.object());
auto& gl_state = Gl::State::instance();
gl_state.enable_vertex_attrib_array({
- attr_position_, attr_color_,
+ attr_position_, attr_color_, attr_distance_from_center_
});
gl_array_buffer_.bind();
Gl::vertex_attrib_pointer(attr_position_, 3, sizeof(PerVertexData), offsetof(PerVertexData, gl_x));
- Gl::vertex_attrib_pointer(attr_color_, 3, sizeof(PerVertexData), offsetof(PerVertexData, color_r));
+ Gl::vertex_attrib_pointer(
+ attr_color_, 3, sizeof(PerVertexData), offsetof(PerVertexData, color_r));
+ Gl::vertex_attrib_pointer(attr_distance_from_center_, 1, sizeof(PerVertexData),
+ offsetof(PerVertexData, distance_from_center));
vertices_.clear();
- std::vector<DrawBatch> draw_batches;
- int offset = 0;
- while (i < arguments.size()) {
- const Arguments& template_args = arguments[i];
-
- while (i < arguments.size()) {
- const Arguments& current_args = arguments[i];
- if (current_args.line_width != template_args.line_width) {
- break;
- }
- // We do not support anything else for drawing lines, really.
- assert(current_args.blend_mode == BlendMode::Copy);
-
- vertices_.emplace_back(current_args.destination_rect.x,
- current_args.destination_rect.y,
- current_args.z_value,
- current_args.color.r / 255.,
- current_args.color.g / 255.,
- current_args.color.b / 255.);
-
- vertices_.emplace_back(current_args.destination_rect.x + current_args.destination_rect.w,
- current_args.destination_rect.y + current_args.destination_rect.h,
- current_args.z_value,
- current_args.color.r / 255.,
- current_args.color.g / 255.,
- current_args.color.b / 255.);
- ++i;
+ for (const Arguments& current_args : arguments) {
+ // We do not support anything else for drawing lines, really.
+ assert(current_args.blend_mode == BlendMode::UseAlpha);
+ assert(current_args.points.size() % 4 == 0);
+
+ const float r = current_args.color.r / 255.;
+ const float g = current_args.color.g / 255.;
+ const float b = current_args.color.b / 255.;
+ const float z_value = current_args.z_value;
+
+ const auto& p = current_args.points;
+ for (size_t i = 0; i < p.size(); i += 4) {
+ vertices_.emplace_back(
+ PerVertexData{p[i].x, p[i].y, z_value, r, g, b, 1.f});
+ vertices_.emplace_back(
+ PerVertexData{p[i + 1].x, p[i + 1].y, z_value, r, g, b, 1.f});
+ vertices_.emplace_back(
+ PerVertexData{p[i + 2].x, p[i + 2].y, z_value, r, g, b, -1.f});
+ vertices_.emplace_back(vertices_[vertices_.size() - 2]);
+ vertices_.emplace_back(vertices_[vertices_.size() - 2]);
+ vertices_.emplace_back(
+ PerVertexData{p[i + 3].x, p[i + 3].y, z_value, r, g, b, -1.f});
}
-
- draw_batches.emplace_back(
- DrawBatch{offset, static_cast<int>(vertices_.size() - offset), template_args.line_width});
- offset = vertices_.size();
}
-
gl_array_buffer_.update(vertices_);
- // Now do the draw calls.
- for (const auto& draw_arg : draw_batches) {
- glLineWidth(draw_arg.line_width);
- glDrawArrays(GL_LINES, draw_arg.offset, draw_arg.count);
- }
+ glDrawArrays(GL_TRIANGLES, 0, vertices_.size());
}
=== modified file 'src/graphic/gl/draw_line_program.h'
--- src/graphic/gl/draw_line_program.h 2016-01-05 11:28:54 +0000
+++ src/graphic/gl/draw_line_program.h 2016-01-30 15:36:48 +0000
@@ -31,51 +31,29 @@
class DrawLineProgram {
public:
struct Arguments {
- // The line is drawn from the top left to the bottom right of
- // this rectangle.
- FloatRect destination_rect;
+ // Vertices of the quads that make up the lines plus. That means
+ // points.size() == <number of lines> * 4.
+ std::vector<FloatPoint> points;
+ RGBColor color;
float z_value;
- RGBAColor color;
- uint8_t line_width;
- BlendMode blend_mode;
+ BlendMode blend_mode; // Always BlendMode::kUseAlpha.
};
// Returns the (singleton) instance of this class.
static DrawLineProgram& instance();
- // Draws a line from (x1, y1) to (x2, y2) which are in gl
- // coordinates in 'color' with a 'line_width' in pixels.
- void draw(const FloatPoint& start,
- const FloatPoint& end,
- const float z_value,
- const RGBColor& color,
- const int line_width);
-
- void draw(std::vector<Arguments> arguments);
-
+ void draw(const std::vector<Arguments>& arguments);
private:
- DrawLineProgram();
-
struct PerVertexData {
- PerVertexData(float init_gl_x,
- float init_gl_y,
- float init_gl_z,
- float init_color_r,
- float init_color_g,
- float init_color_b)
- : gl_x(init_gl_x),
- gl_y(init_gl_y),
- gl_z(init_gl_z),
- color_r(init_color_r),
- color_g(init_color_g),
- color_b(init_color_b) {
- }
-
float gl_x, gl_y, gl_z;
float color_r, color_g, color_b;
+ // This value is changing from -1 to 1 and is used for alpha blending.
+ float distance_from_center;
};
- static_assert(sizeof(PerVertexData) == 24, "Wrong padding.");
+ static_assert(sizeof(PerVertexData) == 28, "Wrong padding.");
+
+ DrawLineProgram();
// This is only kept around so that we do not constantly
// allocate memory for it.
@@ -90,6 +68,7 @@
// Attributes.
GLint attr_position_;
GLint attr_color_;
+ GLint attr_distance_from_center_;
DISALLOW_COPY_AND_ASSIGN(DrawLineProgram);
};
=== added file 'src/graphic/line_strip_mode.h'
--- src/graphic/line_strip_mode.h 1970-01-01 00:00:00 +0000
+++ src/graphic/line_strip_mode.h 2016-01-30 15:36:48 +0000
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006-2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_LINE_STRIP_MODE_H
+#define WL_GRAPHIC_LINE_STRIP_MODE_H
+
+// A line strip can either be closed, which draws a line between the last and
+// the first line again or open which does not do that.
+enum class LineStripMode {
+ kClose,
+ kOpen,
+};
+
+#endif // end of include guard: WL_GRAPHIC_LINE_STRIP_MODE_H
=== modified file 'src/graphic/render_queue.cc'
--- src/graphic/render_queue.cc 2016-01-20 20:04:52 +0000
+++ src/graphic/render_queue.cc 2016-01-30 15:36:48 +0000
@@ -77,6 +77,7 @@
inline void from_item(const RenderQueue::Item& item, FillRectProgram::Arguments* args) {
args->color = item.rect_arguments.color;
+ args->destination_rect = item.rect_arguments.destination_rect;
}
inline void from_item(const RenderQueue::Item& item, BlitProgram::Arguments* args) {
@@ -84,11 +85,12 @@
args->blend = item.blit_arguments.blend;
args->mask = item.blit_arguments.mask;
args->blit_mode = item.blit_arguments.mode;
+ args->destination_rect = item.blit_arguments.destination_rect;
}
inline void from_item(const RenderQueue::Item& item, DrawLineProgram::Arguments* args) {
+ args->points = item.line_arguments.points;
args->color = item.line_arguments.color;
- args->line_width = item.line_arguments.line_width;
}
// Batches up as many items from 'items' that have the same 'program_id'.
@@ -106,7 +108,6 @@
}
all_args.emplace_back();
T& args = all_args.back();
- args.destination_rect = current_item.destination_rect;
args.z_value = current_item.z_value;
args.blend_mode = current_item.blend_mode;
from_item(current_item, &args);
@@ -159,10 +160,6 @@
extra_value = given_item.blit_arguments.texture.texture_id;
break;
- case Program::kLine:
- extra_value = given_item.line_arguments.line_width;
- break;
-
case Program::kRect:
case Program::kTerrainBase:
case Program::kTerrainDither:
@@ -238,7 +235,7 @@
break;
case Program::kTerrainBase: {
- ScopedScissor scoped_scissor(item.destination_rect);
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
terrain_program_->draw(item.terrain_arguments.gametime,
*item.terrain_arguments.terrains,
*item.terrain_arguments.fields_to_draw,
@@ -247,7 +244,7 @@
} break;
case Program::kTerrainDither: {
- ScopedScissor scoped_scissor(item.destination_rect);
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
dither_program_->draw(item.terrain_arguments.gametime,
*item.terrain_arguments.terrains,
*item.terrain_arguments.fields_to_draw,
@@ -256,7 +253,7 @@
} break;
case Program::kTerrainRoad: {
- ScopedScissor scoped_scissor(item.destination_rect);
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
road_program_->draw(item.terrain_arguments.renderbuffer_width,
item.terrain_arguments.renderbuffer_height,
*item.terrain_arguments.fields_to_draw,
=== modified file 'src/graphic/render_queue.h'
--- src/graphic/render_queue.h 2016-01-13 07:27:55 +0000
+++ src/graphic/render_queue.h 2016-01-30 15:36:48 +0000
@@ -98,15 +98,17 @@
BlitData texture;
BlitData mask;
RGBAColor blend;
+ FloatRect destination_rect;
};
struct RectArguments {
RGBAColor color;
+ FloatRect destination_rect;
};
struct LineArguments {
+ std::vector<FloatPoint> points;
RGBColor color;
- uint8_t line_width;
};
struct TerrainArguments {
@@ -117,14 +119,13 @@
int renderbuffer_height;
const DescriptionMaintainer<Widelands::TerrainDescription>* terrains;
FieldsToDraw* fields_to_draw;
+ FloatRect destination_rect;
};
// The union of all possible program arguments represents an Item that is
// enqueued in the Queue. This is on purpose not done with OOP so that the
// queue is more cache friendly.
struct Item {
- Item() {}
-
inline bool operator<(const Item& other) const {
return key < other.key;
}
@@ -136,9 +137,6 @@
// The z-value in GL space that will be used for drawing.
float z_value;
- // The bounding box in the renderbuffer where this draw will change pixels.
- FloatRect destination_rect;
-
// The key for sorting this item in the queue. It depends on the type of
// item how this is calculated, but it will contain at least the program,
// the z-layer, if it is opaque or transparent and program specific
@@ -148,12 +146,12 @@
// If this is opaque or, if not, which blend_mode to use.
BlendMode blend_mode;
- union {
- BlitArguments blit_arguments;
- TerrainArguments terrain_arguments;
- RectArguments rect_arguments;
- LineArguments line_arguments;
- };
+ // This is a logical union, i.e. only one of these members will be filled
+ // with useful data. It cannot be because some items are non POD.
+ BlitArguments blit_arguments;
+ TerrainArguments terrain_arguments;
+ RectArguments rect_arguments;
+ LineArguments line_arguments;
};
static RenderQueue& instance();
=== modified file 'src/graphic/rendertarget.cc'
--- src/graphic/rendertarget.cc 2016-01-27 08:06:40 +0000
+++ src/graphic/rendertarget.cc 2016-01-30 15:36:48 +0000
@@ -111,14 +111,16 @@
/**
* This functions draws a line in the target
*/
-void RenderTarget::draw_line(const Point& start,
- const Point& end,
- const RGBColor& color,
- uint8_t line_width) {
- m_surface->draw_line(Point(start.x + m_offset.x + m_rect.x, start.y + m_offset.y + m_rect.y),
- Point(end.x + m_offset.x + m_rect.x, end.y + m_offset.y + m_rect.y),
- color,
- line_width);
+void RenderTarget::draw_line_strip(const LineStripMode& line_strip_mode,
+ const std::vector<Point>& points,
+ const RGBColor& color,
+ float line_width) {
+ std::vector<Point> adjusted_points;
+ adjusted_points.reserve(points.size());
+ for (const Point& p : points) {
+ adjusted_points.emplace_back(p.x + m_offset.x + m_rect.x, p.y + m_offset.y + m_rect.y);
+ }
+ m_surface->draw_line_strip(line_strip_mode, adjusted_points, color, line_width);
}
/**
=== modified file 'src/graphic/rendertarget.h'
--- src/graphic/rendertarget.h 2016-01-28 21:27:04 +0000
+++ src/graphic/rendertarget.h 2016-01-30 15:36:48 +0000
@@ -27,6 +27,7 @@
#include "graphic/blend_mode.h"
#include "graphic/color.h"
#include "graphic/image.h"
+#include "graphic/line_strip_mode.h"
class Animation;
class Surface;
@@ -57,7 +58,10 @@
int32_t width() const;
int32_t height() const;
- void draw_line(const Point& start, const Point& end, const RGBColor& color, uint8_t width = 1);
+ void draw_line_strip(const LineStripMode& line_strip_mode,
+ const std::vector<Point>& points,
+ const RGBColor& color,
+ float width);
void draw_rect(const Rect&, const RGBColor&);
void fill_rect(const Rect&, const RGBAColor&);
void brighten_rect(const Rect&, int32_t factor);
=== modified file 'src/graphic/screen.cc'
--- src/graphic/screen.cc 2016-01-08 15:50:48 +0000
+++ src/graphic/screen.cc 2016-01-30 15:36:48 +0000
@@ -65,13 +65,13 @@
float opacity,
BlendMode blend_mode) {
RenderQueue::Item i;
- i.destination_rect = dst_rect;
i.program_id = RenderQueue::Program::kBlit;
i.blend_mode = blend_mode;
- i.blit_arguments.texture = texture;
- i.blit_arguments.mask.texture_id = 0;
i.blit_arguments.blend = RGBAColor(0, 0, 0, 255 * opacity);
+ i.blit_arguments.destination_rect = dst_rect;
+ i.blit_arguments.mask.texture_id = 0;
i.blit_arguments.mode = BlitMode::kDirect;
+ i.blit_arguments.texture = texture;
RenderQueue::instance().enqueue(i);
}
@@ -80,13 +80,13 @@
const BlitData& mask,
const RGBColor& blend) {
RenderQueue::Item i;
- i.destination_rect = dst_rect;
i.program_id = RenderQueue::Program::kBlit;
i.blend_mode = BlendMode::UseAlpha;
- i.blit_arguments.texture = texture;
+ i.blit_arguments.blend = blend;
+ i.blit_arguments.destination_rect = dst_rect;
i.blit_arguments.mask = mask;
- i.blit_arguments.blend = blend;
i.blit_arguments.mode = BlitMode::kBlendedWithMask;
+ i.blit_arguments.texture = texture;
RenderQueue::instance().enqueue(i);
}
@@ -94,34 +94,31 @@
const BlitData& texture,
const RGBAColor& blend) {
RenderQueue::Item i;
- i.destination_rect = dst_rect;
i.program_id = RenderQueue::Program::kBlit;
i.blend_mode = BlendMode::UseAlpha;
- i.blit_arguments.texture = texture;
+ i.blit_arguments.blend = blend;
+ i.blit_arguments.destination_rect = dst_rect;
i.blit_arguments.mask.texture_id = 0;
- i.blit_arguments.blend = blend;
i.blit_arguments.mode = BlitMode::kMonochrome;
+ i.blit_arguments.texture = texture;
RenderQueue::instance().enqueue(i);
}
-void Screen::do_draw_line(const FloatPoint& start,
- const FloatPoint& end,
- const RGBColor& color,
- const int line_width) {
+void Screen::do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+ const RGBColor& color) {
RenderQueue::Item i;
i.program_id = RenderQueue::Program::kLine;
- i.blend_mode = BlendMode::Copy;
- i.destination_rect = FloatRect(start.x, start.y, end.x - start.x, end.y - start.y);
+ i.blend_mode = BlendMode::UseAlpha;
+ i.line_arguments.points = gl_points;
i.line_arguments.color = color;
- i.line_arguments.line_width = line_width;
RenderQueue::instance().enqueue(i);
}
void Screen::do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) {
RenderQueue::Item i;
+ i.blend_mode = blend_mode;
i.program_id = RenderQueue::Program::kRect;
- i.blend_mode = blend_mode;
- i.destination_rect = dst_rect;
i.rect_arguments.color = color;
+ i.rect_arguments.destination_rect = dst_rect;
RenderQueue::instance().enqueue(i);
}
=== modified file 'src/graphic/screen.h'
--- src/graphic/screen.h 2016-01-04 20:54:08 +0000
+++ src/graphic/screen.h 2016-01-30 15:36:48 +0000
@@ -53,10 +53,8 @@
void do_blit_monochrome(const FloatRect& dst_rect,
const BlitData& texture,
const RGBAColor& blend) override;
- void do_draw_line(const FloatPoint& start,
- const FloatPoint& end,
- const RGBColor& color,
- int width) override;
+ void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+ const RGBColor& color) override;
void
do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) override;
=== modified file 'src/graphic/surface.cc'
--- src/graphic/surface.cc 2016-01-04 20:54:08 +0000
+++ src/graphic/surface.cc 2016-01-30 15:36:48 +0000
@@ -26,9 +26,11 @@
#include <SDL.h>
#include "base/macros.h"
+#include "base/point.h"
#include "base/rect.h"
#include "graphic/gl/coordinate_conversion.h"
#include "graphic/gl/utils.h"
+#include "graphic/line_strip_mode.h"
namespace {
@@ -41,13 +43,35 @@
return blit_data;
}
+// Get the normal of the line between 'start' and 'end'.
+template <typename PointType>
+FloatPoint calculate_line_normal(const PointType& start, const PointType& end) {
+ const float dx = end.x - start.x;
+ const float dy = end.y - start.y;
+ const float len = std::hypot(dx, dy);
+ return FloatPoint(-dy / len, dx / len);
+}
+
+// Finds the pseudo-normal of a point at the join of two lines. We construct
+// this like a miter joint from woodworks. The best explanation I found for
+// this algorithm is here
+// https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127
+FloatPoint calculate_pseudo_normal(const Point& p0, const Point& p1, const Point& p2) {
+ FloatPoint tangent = normalize(normalize(p2 - p1) + normalize(p1 - p0));
+ FloatPoint miter(-tangent.y, tangent.x);
+ float len = 1. / dot(miter, calculate_line_normal(p0, p1));
+ return FloatPoint(miter.x * len, miter.y * len);
+}
+
} // namespace
void draw_rect(const Rect& rc, const RGBColor& clr, Surface* surface) {
- surface->draw_line(Point(rc.x, rc.y), Point(rc.x + rc.w, rc.y), clr, 1);
- surface->draw_line(Point(rc.x + rc.w, rc.y), Point(rc.x + rc.w, rc.y + rc.h), clr, 1);
- surface->draw_line(Point(rc.x + rc.w, rc.y + rc.h), Point(rc.x, rc.y + rc.h), clr, 1);
- surface->draw_line(Point(rc.x, rc.y + rc.h), Point(rc.x, rc.y), clr, 1);
+ surface->draw_line_strip(LineStripMode::kClose, {
+ Point(rc.x, rc.y),
+ Point(rc.x + rc.w, rc.y),
+ Point(rc.x + rc.w, rc.y + rc.h),
+ Point(rc.x, rc.y + rc.h),
+ }, clr, 1);
}
void Surface::fill_rect(const Rect& rc, const RGBAColor& clr) {
@@ -68,19 +92,81 @@
do_fill_rect(rect, color, blend_mode);
}
-void Surface::draw_line
- (const Point& start, const Point& end, const RGBColor& color, int line_width)
-{
- float gl_x1 = start.x;
- float gl_y1 = start.y;
-
- // Include the end pixel.
- float gl_x2 = end.x + 1.;
- float gl_y2 = end.y + 1.;
- pixel_to_gl_renderbuffer(width(), height(), &gl_x1, &gl_y1);
- pixel_to_gl_renderbuffer(width(), height(), &gl_x2, &gl_y2);
-
- do_draw_line(FloatPoint(gl_x1, gl_y1), FloatPoint(gl_x2, gl_y2), color, line_width);
+void Surface::draw_line_strip(const LineStripMode& line_strip_mode,
+ std::vector<Point> points,
+ const RGBColor& color,
+ float line_width) {
+ assert(points.size() > 1);
+
+ // Figure out the tesselation for the line. The normal for a point is the
+ // average of the up to two lines it is part of.
+ std::vector<FloatPoint> normals;
+ for (size_t i = 0; i < points.size(); ++i) {
+ if (i == 0) {
+ switch (line_strip_mode) {
+ case LineStripMode::kOpen:
+ normals.push_back(calculate_line_normal(points.front(), points[1]));
+ break;
+
+ case LineStripMode::kClose:
+ normals.push_back(calculate_pseudo_normal(points.back(), points.front(), points[1]));
+ break;
+ }
+ } else if (i == points.size() - 1) {
+ switch (line_strip_mode) {
+ case LineStripMode::kOpen:
+ normals.push_back(calculate_line_normal(points[points.size() - 2], points.back()));
+ break;
+
+ case LineStripMode::kClose:
+ normals.push_back(
+ calculate_pseudo_normal(points[points.size() - 2], points.back(), points.front()));
+ break;
+ }
+ } else {
+ normals.push_back(calculate_pseudo_normal(points[i - 1], points[i], points[i + 1]));
+ }
+ }
+ if (line_strip_mode == LineStripMode::kClose) {
+ // Push the first point as the last point again. For drawing we do not
+ // need to special case closed or open lines anymore.
+ normals.push_back(normals.front());
+ points.push_back(points.front());
+ }
+ assert(points.size() == normals.size());
+ assert(!points.empty());
+
+ std::vector<FloatPoint> gl_points;
+
+ // Iterate over each line segment, i.e. all points but the last, convert
+ // them from pixel space to gl space and draw them.
+ const auto w = width();
+ const auto h = height();
+ for (size_t i = 0; i < points.size() - 1; ++i) {
+ const FloatPoint p1 = FloatPoint(points[i].x, points[i].y);
+ const FloatPoint p2 = FloatPoint(points[i + 1].x, points[i + 1].y);
+ const FloatPoint scaled_n1(0.5 * line_width * normals[i].x, 0.5 * line_width * normals[i].y);
+ const FloatPoint scaled_n2(
+ 0.5 * line_width * normals[i + 1].x, 0.5 * line_width * normals[i + 1].y);
+
+ // Quad points are created in rendering order for OpenGL.
+ FloatPoint quad_a = p1 - scaled_n1;
+ pixel_to_gl_renderbuffer(w, h, &quad_a.x, &quad_a.y);
+ gl_points.emplace_back(quad_a);
+
+ FloatPoint quad_b = p2 - scaled_n2;
+ pixel_to_gl_renderbuffer(w, h, &quad_b.x, &quad_b.y);
+ gl_points.emplace_back(quad_b);
+
+ FloatPoint quad_c = p1 + scaled_n1 + scaled_n1;
+ pixel_to_gl_renderbuffer(w, h, &quad_c.x, &quad_c.y);
+ gl_points.emplace_back(quad_c);
+
+ FloatPoint quad_d = p2 + scaled_n2 + scaled_n2;
+ pixel_to_gl_renderbuffer(w, h, &quad_d.x, &quad_d.y);
+ gl_points.emplace_back(quad_d);
+ }
+ do_draw_line_strip(gl_points, color);
}
void Surface::blit_monochrome(const Rect& dst_rect,
=== modified file 'src/graphic/surface.h'
--- src/graphic/surface.h 2016-01-08 15:50:48 +0000
+++ src/graphic/surface.h 2016-01-30 15:36:48 +0000
@@ -21,12 +21,14 @@
#define WL_GRAPHIC_SURFACE_H
#include <memory>
+#include <vector>
#include "base/macros.h"
#include "base/rect.h"
#include "graphic/blend_mode.h"
#include "graphic/color.h"
#include "graphic/image.h"
+#include "graphic/line_strip_mode.h"
class Texture;
@@ -63,8 +65,13 @@
// in the target are just replaced (i.e. / BlendMode would be BlendMode::Copy).
void fill_rect(const Rect&, const RGBAColor&);
- /// draw a line to the destination
- void draw_line(const Point& start, const Point& end, const RGBColor& color, int width);
+ // Draw a 'width' pixel wide line to the destination. If 'line_strip_mode' is
+ // kClosed, the last point will be connected to the first one again.
+ // 'points' are taken by value on purpose.
+ void draw_line_strip(const LineStripMode& line_strip_mode,
+ std::vector<Point> points,
+ const RGBColor& color,
+ float width);
/// makes a rectangle on the destination brighter (or darker).
void brighten_rect(const Rect&, int factor);
@@ -85,8 +92,8 @@
const BlitData& texture,
const RGBAColor& blend) = 0;
- virtual void
- do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int width) = 0;
+ virtual void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+ const RGBColor& color) = 0;
virtual void
do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) = 0;
=== modified file 'src/graphic/texture.cc'
--- src/graphic/texture.cc 2016-01-28 06:31:40 +0000
+++ src/graphic/texture.cc 2016-01-30 15:36:48 +0000
@@ -304,13 +304,14 @@
BlitProgram::instance().draw_monochrome(dst_rect, 0.f, texture, blend);
}
-void
-Texture::do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int line_width) {
+void Texture::do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+ const RGBColor& color) {
if (m_blit_data.texture_id == 0) {
return;
}
setup_gl();
- DrawLineProgram::instance().draw(start, end, 0.f, color, line_width);
+ DrawLineProgram::instance().draw({
+ DrawLineProgram::Arguments{gl_points, color, 0.f, BlendMode::UseAlpha}});
}
void
=== modified file 'src/graphic/texture.h'
--- src/graphic/texture.h 2016-01-04 20:54:08 +0000
+++ src/graphic/texture.h 2016-01-30 15:36:48 +0000
@@ -94,8 +94,8 @@
void do_blit_monochrome(const FloatRect& dst_rect,
const BlitData& texture,
const RGBAColor& blend) override;
- void
- do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int width) override;
+ void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+ const RGBColor& color) override;
void
do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) override;
=== modified file 'src/wui/plot_area.cc'
--- src/wui/plot_area.cc 2016-01-28 21:27:04 +0000
+++ src/wui/plot_area.cc 2016-01-30 15:36:48 +0000
@@ -48,7 +48,6 @@
const int32_t space_left_of_label = 15;
const uint32_t nr_samples = 30; // How many samples per diagramm when relative plotting
-
const uint32_t time_in_ms[] = {
15 * minutes,
30 * minutes,
@@ -60,8 +59,10 @@
};
const char BG_PIC[] = "pics/plot_area_bg.png";
-const RGBColor LINE_COLOR(0, 0, 0);
-const RGBColor ZERO_LINE_COLOR(255, 255, 255);
+const RGBColor kAxisLineColor(0, 0, 0);
+constexpr float kAxisLinesWidth = 1.5f;
+constexpr float kPlotLinesWidth = 2.5f;
+const RGBColor kZeroLineColor(255, 255, 255);
enum UNIT {
UNIT_MIN,
@@ -184,34 +185,34 @@
// Draw coordinate system
// X Axis
- dst.draw_line(Point(spacing, inner_h - space_at_bottom),
- Point(inner_w - space_at_right, inner_h - space_at_bottom),
- LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen, {
+ Point(spacing, inner_h - space_at_bottom),
+ Point(inner_w - space_at_right, inner_h - space_at_bottom)},
+ kAxisLineColor, kAxisLinesWidth);
// Arrow
- dst.draw_line(Point(spacing, inner_h - space_at_bottom),
- Point(spacing + 5, inner_h - space_at_bottom - 3),
- LINE_COLOR,
- 2);
- dst.draw_line(Point(spacing, inner_h - space_at_bottom),
- Point(spacing + 5, inner_h - space_at_bottom + 3),
- LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen,
+ {
+ Point(spacing + 5, inner_h - space_at_bottom - 3),
+ Point(spacing, inner_h - space_at_bottom),
+ Point(spacing + 5, inner_h - space_at_bottom + 3),
+ },
+ kAxisLineColor, kAxisLinesWidth);
+
// Y Axis
- dst.draw_line(Point(inner_w - space_at_right, spacing),
- Point(inner_w - space_at_right, inner_h - space_at_bottom),
- LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen, {
+ Point(inner_w - space_at_right, spacing),
+ Point(inner_w - space_at_right, inner_h - space_at_bottom)},
+ kAxisLineColor, kAxisLinesWidth);
// No Arrow here, since this doesn't continue.
float sub = (xline_length - space_left_of_label) / how_many_ticks;
float posx = inner_w - space_at_right;
for (uint32_t i = 0; i <= how_many_ticks; ++i) {
- dst.draw_line(Point(static_cast<int32_t>(posx), inner_h - space_at_bottom),
- Point(static_cast<int32_t>(posx), inner_h - space_at_bottom + 3),
- LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen, {
+ Point(static_cast<int32_t>(posx), inner_h - space_at_bottom),
+ Point(static_cast<int32_t>(posx), inner_h - space_at_bottom + 3)},
+ kAxisLineColor, kAxisLinesWidth);
// The space at the end is intentional to have the tick centered
// over the number, not to the left
@@ -225,15 +226,14 @@
}
// draw yticks, one at full, one at half
- dst.draw_line(Point(inner_w - space_at_right, spacing),
- Point(inner_w - space_at_right - 3, spacing),
- LINE_COLOR,
- 2);
- dst.draw_line(
- Point(inner_w - space_at_right, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
- Point(inner_w - space_at_right - 3, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
- LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen, {Point(inner_w - space_at_right, spacing),
+ Point(inner_w - space_at_right - 3, spacing)},
+ kAxisLineColor, kAxisLinesWidth);
+ dst.draw_line_strip(
+ LineStripMode::kOpen, {
+ Point(inner_w - space_at_right, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
+ Point(inner_w - space_at_right - 3, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
+ }, kAxisLineColor, kAxisLinesWidth);
// print the used unit
const Image* xtick = UI::g_fh1->render(xtick_text_style((boost::format(get_unit_name(unit)) % "").str()));
@@ -410,17 +410,18 @@
(RenderTarget & dst, std::vector<uint32_t> const * dataset, float const yline_length,
uint32_t const highest_scale, float const sub, RGBColor const color, int32_t const yoffset)
{
-
float posx = get_inner_w() - space_at_right;
-
- int32_t lx = get_inner_w() - space_at_right;
- int32_t ly = yoffset;
- //init start point of the plot line with the first data value.
- //this prevent that the plot start always at zero
- if (int32_t value = (*dataset)[dataset->size() - 1]) {
+ const int lx = get_inner_w() - space_at_right;
+ int ly = yoffset;
+ // init start point of the plot line with the first data value.
+ // this prevent that the plot start always at zero
+ if (int value = (*dataset)[dataset->size() - 1]) {
ly -= static_cast<int32_t>(scale_value(yline_length, highest_scale, value));
}
+ std::vector<Point> points;
+ points.emplace_back(lx, ly);
+
for (int32_t i = dataset->size() - 1; i > 0 && posx > spacing; --i) {
int32_t const curx = static_cast<int32_t>(posx);
int32_t cury = yoffset;
@@ -428,17 +429,12 @@
//scale the line to the available space
if (int32_t value = (*dataset)[i]) {
const float length_y = scale_value(yline_length, highest_scale, value);
-
cury -= static_cast<int32_t>(length_y);
}
-
- dst.draw_line(Point(lx, ly), Point(curx, cury), color, 2);
-
+ points.emplace_back(curx, cury);
posx -= sub;
-
- lx = curx;
- ly = cury;
}
+ dst.draw_line_strip(LineStripMode::kOpen, points, color, kPlotLinesWidth);
}
/*
@@ -511,10 +507,10 @@
draw_diagram(time_ms, get_inner_w(), get_inner_h(), xline_length, dst);
// draw zero line
- dst.draw_line(Point(get_inner_w() - space_at_right, yoffset),
- Point(get_inner_w() - space_at_right - xline_length, yoffset),
- ZERO_LINE_COLOR,
- 2);
+ dst.draw_line_strip(LineStripMode::kOpen, {
+ Point(get_inner_w() - space_at_right, yoffset),
+ Point(get_inner_w() - space_at_right - xline_length, yoffset)},
+ kZeroLineColor, kPlotLinesWidth);
// How many do we take together when relative ploting
const int32_t how_many = calc_how_many(time_ms, sample_rate_);
Follow ups
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: noreply, 2016-03-09
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-03-09
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-03-09
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-03-08
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-03-08
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-03-08
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-03-07
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-02-22
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-02-22
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-02-22
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-19
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-19
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-19
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-19
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: kaputtnik, 2016-02-18
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-11
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-11
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: Klaus Halfmann, 2016-02-06
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-06
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: Klaus Halfmann, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: kaputtnik, 2016-02-05
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-05
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-05
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: kaputtnik, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: kaputtnik, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-02
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-02
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-02-02
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-02-01
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: Tino, 2016-02-01
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: kaputtnik, 2016-01-31
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-01-31
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-01-31
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-01-31
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-01-31
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-01-31
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-01-31
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-01-30
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-01-30
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-01-30
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: SirVer, 2016-01-30
-
Re: [Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: GunChleoc, 2016-01-30
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-01-30
-
[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands
From: bunnybot, 2016-01-30