← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/editor-grid into lp:widelands

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/editor-grid into lp:widelands with lp:~nordfriese/widelands/workareas as a prerequisite.

Commit message:
Add a grid overlay to the editor, which can be toggled with a button or the hotkey 'G'

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1529261 in widelands: "Create an overlay which shows a grid"
  https://bugs.launchpad.net/widelands/+bug/1529261

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/editor-grid/+merge/366482
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/editor-grid into lp:widelands.
=== renamed file 'data/images/wui/overlays/workarea123.png' => 'data/images/wui/buildings/toggle_workarea.png'
=== added file 'data/images/wui/menus/menu_toggle_grid.png'
Binary files data/images/wui/menus/menu_toggle_grid.png	1970-01-01 00:00:00 +0000 and data/images/wui/menus/menu_toggle_grid.png	2019-04-24 17:17:26 +0000 differ
=== added file 'data/images/wui/overlays/grid_marker.png'
Binary files data/images/wui/overlays/grid_marker.png	1970-01-01 00:00:00 +0000 and data/images/wui/overlays/grid_marker.png	2019-04-24 17:17:26 +0000 differ
=== removed file 'data/images/wui/overlays/workarea1.png'
Binary files data/images/wui/overlays/workarea1.png	2019-03-30 06:46:25 +0000 and data/images/wui/overlays/workarea1.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'data/images/wui/overlays/workarea12.png'
Binary files data/images/wui/overlays/workarea12.png	2019-03-30 06:46:25 +0000 and data/images/wui/overlays/workarea12.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'data/images/wui/overlays/workarea2.png'
Binary files data/images/wui/overlays/workarea2.png	2019-03-30 06:46:25 +0000 and data/images/wui/overlays/workarea2.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'data/images/wui/overlays/workarea23.png'
Binary files data/images/wui/overlays/workarea23.png	2019-03-30 06:46:25 +0000 and data/images/wui/overlays/workarea23.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'data/images/wui/overlays/workarea3.png'
Binary files data/images/wui/overlays/workarea3.png	2014-12-03 20:13:06 +0000 and data/images/wui/overlays/workarea3.png	1970-01-01 00:00:00 +0000 differ
=== added file 'data/shaders/grid.fp'
--- data/shaders/grid.fp	1970-01-01 00:00:00 +0000
+++ data/shaders/grid.fp	2019-04-24 17:17:26 +0000
@@ -0,0 +1,5 @@
+#version 120
+
+void main() {
+	gl_FragColor = vec4(.0, .0, .0, .8);
+}

=== added file 'data/shaders/grid.vp'
--- data/shaders/grid.vp	1970-01-01 00:00:00 +0000
+++ data/shaders/grid.vp	2019-04-24 17:17:26 +0000
@@ -0,0 +1,10 @@
+#version 120
+
+// Attributes.
+attribute vec2 attr_position;
+
+uniform float u_z_value;
+
+void main() {
+	gl_Position = vec4(attr_position, u_z_value, 1.);
+}

=== added file 'data/shaders/workarea.fp'
--- data/shaders/workarea.fp	1970-01-01 00:00:00 +0000
+++ data/shaders/workarea.fp	2019-04-24 17:17:26 +0000
@@ -0,0 +1,7 @@
+#version 120
+
+varying vec4 var_overlay;
+
+void main() {
+	gl_FragColor = var_overlay;
+}

=== added file 'data/shaders/workarea.vp'
--- data/shaders/workarea.vp	1970-01-01 00:00:00 +0000
+++ data/shaders/workarea.vp	2019-04-24 17:17:26 +0000
@@ -0,0 +1,15 @@
+#version 120
+
+// Attributes.
+attribute vec2 attr_position;
+attribute vec4 attr_overlay;
+
+uniform float u_z_value;
+
+// Output of vertex shader.
+varying vec4 var_overlay;
+
+void main() {
+	var_overlay = attr_overlay;
+	gl_Position = vec4(attr_position, u_z_value, 1.);
+}

=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2019-04-24 07:09:29 +0000
+++ src/editor/editorinteractive.cc	2019-04-24 17:17:26 +0000
@@ -98,6 +98,10 @@
 	toggle_buildhelp_ = add_toolbar_button(
 	   "wui/menus/menu_toggle_buildhelp", "buildhelp", _("Show building spaces (on/off)"));
 	toggle_buildhelp_->sigclicked.connect(boost::bind(&EditorInteractive::toggle_buildhelp, this));
+	toggle_grid_ =
+	   add_toolbar_button("wui/menus/menu_toggle_grid", "grid", _("Show grid (on/off)"));
+	toggle_grid_->set_perm_pressed(true);
+	toggle_grid_->sigclicked.connect([this]() { toggle_grid(); });
 	toggle_immovables_ = add_toolbar_button(
 	   "wui/menus/menu_toggle_immovables", "immovables", _("Show immovables (on/off)"));
 	toggle_immovables_->set_perm_pressed(true);
@@ -262,7 +266,7 @@
 
 void EditorInteractive::draw(RenderTarget& dst) {
 	const auto& ebase = egbase();
-	auto* fields_to_draw = map_view()->draw_terrain(ebase, &dst);
+	auto* fields_to_draw = map_view()->draw_terrain(ebase, Workareas(), draw_grid_, &dst);
 
 	const float scale = 1.f / map_view()->view().zoom;
 	const uint32_t gametime = ebase.get_gametime();
@@ -430,6 +434,11 @@
 	toggle_bobs_->set_perm_pressed(draw_bobs_);
 }
 
+void EditorInteractive::toggle_grid() {
+	draw_grid_ = !draw_grid_;
+	toggle_grid_->set_perm_pressed(draw_grid_);
+}
+
 bool EditorInteractive::handle_key(bool const down, SDL_Keysym const code) {
 	if (down) {
 		switch (code.sym) {
@@ -504,6 +513,10 @@
 			toggle_buildhelp();
 			return true;
 
+		case SDLK_g:
+			toggle_grid();
+			return true;
+
 		case SDLK_c:
 			set_display_flag(
 			   InteractiveBase::dfShowCensus, !get_display_flag(InteractiveBase::dfShowCensus));

=== modified file 'src/editor/editorinteractive.h'
--- src/editor/editorinteractive.h	2019-04-24 07:09:29 +0000
+++ src/editor/editorinteractive.h	2019-04-24 17:17:26 +0000
@@ -149,6 +149,7 @@
 	void toggle_resources();
 	void toggle_immovables();
 	void toggle_bobs();
+	void toggle_grid();
 
 	//  state variables
 	bool need_save_;
@@ -170,6 +171,7 @@
 	UI::UniqueWindow::Registry helpmenu_;
 
 	UI::Button* toggle_buildhelp_;
+	UI::Button* toggle_grid_;
 	UI::Button* toggle_resources_;
 	UI::Button* toggle_immovables_;
 	UI::Button* toggle_bobs_;
@@ -182,6 +184,7 @@
 	bool draw_resources_ = true;
 	bool draw_immovables_ = true;
 	bool draw_bobs_ = true;
+	bool draw_grid_ = true;
 };
 
 #endif  // end of include guard: WL_EDITOR_EDITORINTERACTIVE_H

=== modified file 'src/graphic/CMakeLists.txt'
--- src/graphic/CMakeLists.txt	2019-04-08 13:32:28 +0000
+++ src/graphic/CMakeLists.txt	2019-04-24 17:17:26 +0000
@@ -225,12 +225,16 @@
 
 wl_library(graphic_terrain_programs
   SRCS
+    gl/grid_program.cc
+    gl/grid_program.h
     gl/road_program.cc
     gl/road_program.h
     gl/terrain_program.cc
     gl/terrain_program.h
     gl/dither_program.cc
     gl/dither_program.h
+    gl/workarea_program.cc
+    gl/workarea_program.h
   DEPENDS
     base_exceptions
     base_geometry

=== modified file 'src/graphic/game_renderer.cc'
--- src/graphic/game_renderer.cc	2019-04-24 06:01:37 +0000
+++ src/graphic/game_renderer.cc	2019-04-24 17:17:26 +0000
@@ -60,6 +60,8 @@
 void draw_terrain(const Widelands::EditorGameBase& egbase,
                   const FieldsToDraw& fields_to_draw,
                   const float scale,
+                  Workareas workarea,
+                  bool grid,
                   RenderTarget* dst) {
 	const Recti& bounding_rect = dst->get_rect();
 	const Surface& surface = dst->get_surface();
@@ -86,6 +88,20 @@
 	i.blend_mode = BlendMode::UseAlpha;
 	RenderQueue::instance().enqueue(i);
 
+	if (!workarea.empty()) {
+		// Enqueue the drawing of the workarea overlay layer.
+		i.program_id = RenderQueue::Program::kTerrainWorkarea;
+		i.terrain_arguments.workareas = workarea;
+		RenderQueue::instance().enqueue(i);
+	}
+
+	if (grid) {
+		// Enqueue the drawing of the grid layer.
+		i.program_id = RenderQueue::Program::kTerrainGrid;
+		i.blend_mode = BlendMode::UseAlpha;
+		RenderQueue::instance().enqueue(i);
+	}
+
 	// Enqueue the drawing of the road layer.
 	i.program_id = RenderQueue::Program::kTerrainRoad;
 	RenderQueue::instance().enqueue(i);

=== modified file 'src/graphic/game_renderer.h'
--- src/graphic/game_renderer.h	2019-02-23 11:00:49 +0000
+++ src/graphic/game_renderer.h	2019-04-24 17:17:26 +0000
@@ -33,6 +33,8 @@
 void draw_terrain(const Widelands::EditorGameBase& egbase,
                   const FieldsToDraw& fields_to_draw,
                   const float scale,
+                  Workareas workarea,
+                  bool grid,
                   RenderTarget* dst);
 
 // Draw the border stones for 'field' if it is a border and 'visibility' is

=== added file 'src/graphic/gl/grid_program.cc'
--- src/graphic/gl/grid_program.cc	1970-01-01 00:00:00 +0000
+++ src/graphic/gl/grid_program.cc	2019-04-24 17:17:26 +0000
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2006-2019 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.
+ *
+ */
+
+#include "graphic/gl/grid_program.h"
+
+#include "graphic/gl/coordinate_conversion.h"
+#include "graphic/gl/fields_to_draw.h"
+#include "graphic/gl/utils.h"
+#include "graphic/texture.h"
+
+GridProgram::GridProgram() {
+	gl_program_.build("grid");
+
+	attr_position_ = glGetAttribLocation(gl_program_.object(), "attr_position");
+
+	u_z_value_ = glGetUniformLocation(gl_program_.object(), "u_z_value");
+}
+
+void GridProgram::gl_draw(int gl_texture, float z_value) {
+	glUseProgram(gl_program_.object());
+
+	auto& gl_state = Gl::State::instance();
+
+	gl_array_buffer_.bind();
+	gl_array_buffer_.update(vertices_);
+
+	Gl::vertex_attrib_pointer(
+	   attr_position_, 2, sizeof(PerVertexData), offsetof(PerVertexData, gl_x));
+
+	gl_state.bind(GL_TEXTURE0, gl_texture);
+
+	glUniform1f(u_z_value_, z_value);
+
+	glDrawArrays(GL_LINES, 0, vertices_.size());
+}
+
+void GridProgram::add_vertex(const FieldsToDraw::Field& field) {
+	vertices_.emplace_back();
+	PerVertexData& back = vertices_.back();
+	back.gl_x = field.gl_position.x;
+	back.gl_y = field.gl_position.y;
+}
+
+void GridProgram::draw(uint32_t texture_id,
+                       const FieldsToDraw& fields_to_draw,
+                       float z_value) {
+	vertices_.clear();
+	vertices_.reserve(fields_to_draw.size() * 2);
+
+	for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
+		const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
+
+		// Southwestern edge
+		if (field.bln_index != FieldsToDraw::kInvalidIndex) {
+			add_vertex(fields_to_draw.at(current_index));
+			add_vertex(fields_to_draw.at(field.bln_index));
+		}
+
+		// Southeastern edge
+		if (field.brn_index != FieldsToDraw::kInvalidIndex) {
+			add_vertex(fields_to_draw.at(current_index));
+			add_vertex(fields_to_draw.at(field.brn_index));
+		}
+
+		// Eastern edge
+		if (field.rn_index != FieldsToDraw::kInvalidIndex) {
+			add_vertex(fields_to_draw.at(current_index));
+			add_vertex(fields_to_draw.at(field.rn_index));
+		}
+	}
+
+	gl_draw(texture_id, z_value);
+}

=== added file 'src/graphic/gl/grid_program.h'
--- src/graphic/gl/grid_program.h	1970-01-01 00:00:00 +0000
+++ src/graphic/gl/grid_program.h	2019-04-24 17:17:26 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2006-2019 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_GL_GRID_PROGRAM_H
+#define WL_GRAPHIC_GL_GRID_PROGRAM_H
+
+#include <vector>
+
+#include "base/vector.h"
+#include "graphic/gl/fields_to_draw.h"
+#include "graphic/gl/utils.h"
+#include "logic/description_maintainer.h"
+#include "logic/map_objects/world/terrain_description.h"
+
+class GridProgram {
+public:
+	// Compiles the program. Throws on errors.
+	GridProgram();
+
+	// Draws the grid layer
+	void draw(uint32_t texture_id,
+	          const FieldsToDraw& fields_to_draw,
+	          float z_value);
+
+private:
+	struct PerVertexData {
+		float gl_x;
+		float gl_y;
+	};
+	static_assert(sizeof(PerVertexData) == 8, "Wrong padding.");
+
+	void gl_draw(int gl_texture, float z_value);
+
+	// Adds a vertex to the end of vertices with data from 'field'.
+	void add_vertex(const FieldsToDraw::Field& field);
+
+	// The program used for drawing the grid layer
+	Gl::Program gl_program_;
+
+	// The buffer that will contain 'vertices_' for rendering.
+	Gl::Buffer<PerVertexData> gl_array_buffer_;
+
+	// Attributes.
+	GLint attr_position_;
+
+	// Uniforms.
+	GLint u_z_value_;
+
+	// Objects below are kept around to avoid memory allocations on each frame.
+	// They could theoretically also be recreated.
+	std::vector<PerVertexData> vertices_;
+
+	DISALLOW_COPY_AND_ASSIGN(GridProgram);
+};
+
+#endif  // end of include guard: WL_GRAPHIC_GL_GRID_PROGRAM_H

=== added file 'src/graphic/gl/workarea_program.cc'
--- src/graphic/gl/workarea_program.cc	1970-01-01 00:00:00 +0000
+++ src/graphic/gl/workarea_program.cc	2019-04-24 17:17:26 +0000
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2006-2019 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.
+ *
+ */
+
+#include "graphic/gl/workarea_program.h"
+
+#include "graphic/gl/coordinate_conversion.h"
+#include "graphic/gl/fields_to_draw.h"
+#include "graphic/gl/utils.h"
+#include "graphic/texture.h"
+
+WorkareaProgram::WorkareaProgram() {
+	gl_program_.build("workarea");
+
+	attr_position_ = glGetAttribLocation(gl_program_.object(), "attr_position");
+	attr_overlay_ = glGetAttribLocation(gl_program_.object(), "attr_overlay");
+
+	u_z_value_ = glGetUniformLocation(gl_program_.object(), "u_z_value");
+}
+
+void WorkareaProgram::gl_draw(int gl_texture, float z_value) {
+	glUseProgram(gl_program_.object());
+
+	auto& gl_state = Gl::State::instance();
+	gl_state.enable_vertex_attrib_array(
+	   {attr_position_, attr_overlay_});
+
+	gl_array_buffer_.bind();
+	gl_array_buffer_.update(vertices_);
+
+	Gl::vertex_attrib_pointer(
+	   attr_position_, 2, sizeof(PerVertexData), offsetof(PerVertexData, gl_x));
+	Gl::vertex_attrib_pointer(
+	   attr_overlay_, 4, sizeof(PerVertexData), offsetof(PerVertexData, overlay_r));
+
+	gl_state.bind(GL_TEXTURE0, gl_texture);
+
+	glUniform1f(u_z_value_, z_value);
+
+	glDrawArrays(GL_TRIANGLES, 0, vertices_.size());
+}
+
+constexpr uint8_t kWorkareaTransparency = 127;
+static RGBAColor workarea_colors[] {
+	RGBAColor(63, 31, 127, kWorkareaTransparency), // All three circles
+	RGBAColor(127, 63, 0, kWorkareaTransparency),  // Medium and outer circle
+	RGBAColor(0, 127, 0, kWorkareaTransparency),   // Outer circle
+	RGBAColor(63, 0, 127, kWorkareaTransparency),  // Inner and medium circle
+	RGBAColor(127, 0, 0, kWorkareaTransparency),   // Medium circle
+	RGBAColor(0, 0, 127, kWorkareaTransparency),   // Inner circle
+};
+static inline RGBAColor apply_color(RGBAColor c1, RGBAColor c2) {
+	uint8_t r = (c1.r * c1.a + c2.r * c2.a) / (c1.a + c2.a);
+	uint8_t g = (c1.g * c1.a + c2.g * c2.a) / (c1.a + c2.a);
+	uint8_t b = (c1.b * c1.a + c2.b * c2.a) / (c1.a + c2.a);
+	uint8_t a = (c1.a + c2.a) / 2;
+	return RGBAColor(r, g, b, a);
+}
+
+void WorkareaProgram::add_vertex(const FieldsToDraw::Field& field, RGBAColor overlay) {
+	vertices_.emplace_back();
+	PerVertexData& back = vertices_.back();
+
+	back.gl_x = field.gl_position.x;
+	back.gl_y = field.gl_position.y;
+	back.overlay_r = overlay.r / 255.f;
+	back.overlay_g = overlay.g / 255.f;
+	back.overlay_b = overlay.b / 255.f;
+	back.overlay_a = overlay.a / 255.f;
+}
+
+void WorkareaProgram::draw(uint32_t texture_id,
+             	           Workareas workarea,
+                           const FieldsToDraw& fields_to_draw,
+                           float z_value) {
+	vertices_.clear();
+	vertices_.reserve(fields_to_draw.size() * 3);
+
+	for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
+		const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
+
+		// The bottom right neighbor fields_to_draw is needed for both triangles
+		// associated with this field. If it is not in fields_to_draw, there is no need to
+		// draw any triangles.
+		if (field.brn_index == FieldsToDraw::kInvalidIndex) {
+			continue;
+		}
+
+		// Down triangle.
+		if (field.bln_index != FieldsToDraw::kInvalidIndex) {
+			RGBAColor color(0, 0, 0, 0);
+			for (const std::map<Widelands::TCoords<>, uint8_t>& wa_map : workarea) {
+				const auto it = wa_map.find(Widelands::TCoords<>(field.fcoords, Widelands::TriangleIndex::D));
+				if (it != wa_map.end()) {
+					color = apply_color(color, workarea_colors[it->second]);
+				}
+			}
+			add_vertex(fields_to_draw.at(current_index), color);
+			add_vertex(fields_to_draw.at(field.bln_index), color);
+			add_vertex(fields_to_draw.at(field.brn_index), color);
+		}
+
+		// Right triangle.
+		if (field.rn_index != FieldsToDraw::kInvalidIndex) {
+			RGBAColor color(0, 0, 0, 0);
+			for (const std::map<Widelands::TCoords<>, uint8_t>& wa_map : workarea) {
+				const auto it = wa_map.find(Widelands::TCoords<>(field.fcoords, Widelands::TriangleIndex::R));
+				if (it != wa_map.end()) {
+					color = apply_color(color, workarea_colors[it->second]);
+				}
+			}
+			add_vertex(fields_to_draw.at(current_index), color);
+			add_vertex(fields_to_draw.at(field.brn_index), color);
+			add_vertex(fields_to_draw.at(field.rn_index), color);
+		}
+	}
+
+	gl_draw(texture_id, z_value);
+}

=== added file 'src/graphic/gl/workarea_program.h'
--- src/graphic/gl/workarea_program.h	1970-01-01 00:00:00 +0000
+++ src/graphic/gl/workarea_program.h	2019-04-24 17:17:26 +0000
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2006-2019 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_GL_WORKAREA_PROGRAM_H
+#define WL_GRAPHIC_GL_WORKAREA_PROGRAM_H
+
+#include <vector>
+
+#include "base/vector.h"
+#include "graphic/gl/fields_to_draw.h"
+#include "graphic/gl/utils.h"
+#include "logic/description_maintainer.h"
+#include "logic/map_objects/world/terrain_description.h"
+
+class WorkareaProgram {
+public:
+	// Compiles the program. Throws on errors.
+	WorkareaProgram();
+
+	// Draws the workarea overlay.
+	void draw(uint32_t texture_id,
+	          Workareas workarea,
+	          const FieldsToDraw& fields_to_draw,
+	          float z_value);
+
+private:
+	struct PerVertexData {
+		float gl_x;
+		float gl_y;
+		float overlay_r;
+		float overlay_g;
+		float overlay_b;
+		float overlay_a;
+	};
+	static_assert(sizeof(PerVertexData) == 24, "Wrong padding.");
+
+	void gl_draw(int gl_texture, float z_value);
+
+	// Adds a vertex to the end of vertices with data from 'field' in order to apply the specified 'overlay'.
+	void add_vertex(const FieldsToDraw::Field& field, RGBAColor overlay);
+
+	// The program used for drawing the workarea overlay.
+	Gl::Program gl_program_;
+
+	// The buffer that will contain 'vertices_' for rendering.
+	Gl::Buffer<PerVertexData> gl_array_buffer_;
+
+	// Attributes.
+	GLint attr_position_;
+	GLint attr_overlay_;
+
+	// Uniforms.
+	GLint u_z_value_;
+
+	// Objects below are kept around to avoid memory allocations on each frame.
+	// They could theoretically also be recreated.
+	std::vector<PerVertexData> vertices_;
+
+	DISALLOW_COPY_AND_ASSIGN(WorkareaProgram);
+};
+
+#endif  // end of include guard: WL_GRAPHIC_GL_WORKAREA_PROGRAM_H

=== modified file 'src/graphic/render_queue.cc'
--- src/graphic/render_queue.cc	2019-02-23 11:00:49 +0000
+++ src/graphic/render_queue.cc	2019-04-24 17:17:26 +0000
@@ -28,8 +28,10 @@
 #include "graphic/gl/dither_program.h"
 #include "graphic/gl/draw_line_program.h"
 #include "graphic/gl/fill_rect_program.h"
+#include "graphic/gl/grid_program.h"
 #include "graphic/gl/road_program.h"
 #include "graphic/gl/terrain_program.h"
+#include "graphic/gl/workarea_program.h"
 
 namespace {
 
@@ -142,6 +144,8 @@
    : next_z_(1),
      terrain_program_(new TerrainProgram()),
      dither_program_(new DitherProgram()),
+     workarea_program_(new WorkareaProgram()),
+     grid_program_(new GridProgram()),
      road_program_(new RoadProgram()) {
 }
 
@@ -164,6 +168,8 @@
 	case Program::kRect:
 	case Program::kTerrainBase:
 	case Program::kTerrainDither:
+	case Program::kTerrainWorkarea:
+	case Program::kTerrainGrid:
 	case Program::kTerrainRoad:
 		/* all fallthroughs intended */
 		break;
@@ -251,6 +257,20 @@
 			++i;
 		} break;
 
+		case Program::kTerrainWorkarea: {
+			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
+			workarea_program_->draw(item.terrain_arguments.terrains->get(0).get_texture(0).blit_data().texture_id,
+					item.terrain_arguments.workareas, *item.terrain_arguments.fields_to_draw, item.z_value);
+			++i;
+		} break;
+
+		case Program::kTerrainGrid: {
+			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
+			grid_program_->draw(item.terrain_arguments.terrains->get(0).get_texture(0).blit_data().texture_id,
+					*item.terrain_arguments.fields_to_draw, item.z_value);
+			++i;
+		} break;
+
 		case Program::kTerrainRoad: {
 			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
 			road_program_->draw(item.terrain_arguments.renderbuffer_width,

=== modified file 'src/graphic/render_queue.h'
--- src/graphic/render_queue.h	2019-02-23 11:00:49 +0000
+++ src/graphic/render_queue.h	2019-04-24 17:17:26 +0000
@@ -36,8 +36,10 @@
 #include "logic/map_objects/world/terrain_description.h"
 
 class DitherProgram;
+class GridProgram;
 class RoadProgram;
 class TerrainProgram;
+class WorkareaProgram;
 
 // The RenderQueue is a singleton implementing the concept of deferred
 // rendering: Every rendering call that pretends to draw onto the screen will
@@ -82,6 +84,8 @@
 	enum Program {
 		kTerrainBase,
 		kTerrainDither,
+		kTerrainWorkarea,
+		kTerrainGrid,
 		kTerrainRoad,
 		kBlit,
 		kRect,
@@ -119,6 +123,7 @@
 		int renderbuffer_height = 0;
 		const DescriptionMaintainer<Widelands::TerrainDescription>* terrains = nullptr;
 		const FieldsToDraw* fields_to_draw = nullptr;
+		Workareas workareas;
 		float scale = 1.f;
 		Rectf destination_rect = Rectf(0.f, 0.f, 0.f, 0.f);
 	};
@@ -178,6 +183,8 @@
 
 	std::unique_ptr<TerrainProgram> terrain_program_;
 	std::unique_ptr<DitherProgram> dither_program_;
+	std::unique_ptr<WorkareaProgram> workarea_program_;
+	std::unique_ptr<GridProgram> grid_program_;
 	std::unique_ptr<RoadProgram> road_program_;
 
 	std::vector<Item> blended_items_;

=== modified file 'src/logic/widelands_geometry.h'
--- src/logic/widelands_geometry.h	2019-02-23 11:00:49 +0000
+++ src/logic/widelands_geometry.h	2019-04-24 17:17:26 +0000
@@ -21,6 +21,8 @@
 #define WL_LOGIC_WIDELANDS_GEOMETRY_H
 
 #include <cmath>
+#include <map>
+#include <set>
 #include <tuple>
 
 #include <stdint.h>
@@ -155,4 +157,6 @@
 };
 }  // namespace Widelands
 
+using Workareas = std::set<std::map<Widelands::TCoords<>, uint8_t>>;
+
 #endif  // end of include guard: WL_LOGIC_WIDELANDS_GEOMETRY_H

=== modified file 'src/wui/buildingwindow.cc'
--- src/wui/buildingwindow.cc	2019-03-22 18:52:17 +0000
+++ src/wui/buildingwindow.cc	2019-04-24 17:17:26 +0000
@@ -320,7 +320,7 @@
 		if (!wa_info->empty()) {
 			toggle_workarea_ =
 			   new UI::Button(capsbuttons, "workarea", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu,
-			                  g_gr->images().get("images/wui/overlays/workarea123.png"));
+			                  g_gr->images().get("images/wui/buildings/toggle_workarea.png"));
 			toggle_workarea_->sigclicked.connect(
 			   boost::bind(&BuildingWindow::toggle_workarea, boost::ref(*this)));
 

=== modified file 'src/wui/interactive_base.cc'
--- src/wui/interactive_base.cc	2019-04-24 06:01:37 +0000
+++ src/wui/interactive_base.cc	2019-04-24 17:17:26 +0000
@@ -112,14 +112,7 @@
      avg_usframetime_(0),
      buildroad_(nullptr),
      road_build_player_(0),
-     unique_window_handler_(new UniqueWindowHandler()),
-     // Start at idx 0 for 2 enhancements, idx 3 for 1, idx 5 if none
-     workarea_pics_{g_gr->images().get("images/wui/overlays/workarea123.png"),
-                    g_gr->images().get("images/wui/overlays/workarea23.png"),
-                    g_gr->images().get("images/wui/overlays/workarea3.png"),
-                    g_gr->images().get("images/wui/overlays/workarea12.png"),
-                    g_gr->images().get("images/wui/overlays/workarea2.png"),
-                    g_gr->images().get("images/wui/overlays/workarea1.png")} {
+     unique_window_handler_(new UniqueWindowHandler()) {
 
 	// Load the buildhelp icons.
 	{
@@ -200,8 +193,20 @@
 	return nullptr;
 }
 
-bool InteractiveBase::has_workarea_preview(const Widelands::Coords& coords) const {
-	return workarea_previews_.count(coords) == 1;
+bool InteractiveBase::has_workarea_preview(const Widelands::Coords& coords, const Widelands::Map* map) const {
+	if (!map) {
+		return workarea_previews_.count(coords) == 1;
+	}
+	for (const auto& pair : workarea_previews_) {
+		uint32_t radius = 0;
+		for (const auto& p : *pair.second) {
+			radius = std::max(radius, p.first);
+		}
+		if (map->calc_distance(coords, pair.first) <= radius) {
+			return true;
+		}
+	}
+	return false;
 }
 
 UniqueWindowHandler& InteractiveBase::unique_windows() {
@@ -303,12 +308,53 @@
 	workarea_previews_[coords] = &workarea_info;
 }
 
-std::map<Coords, const Image*>
-InteractiveBase::get_workarea_overlays(const Widelands::Map& map) const {
-	std::map<Coords, const Image*> result;
-	for (const auto& pair : workarea_previews_) {
-		const Coords& coords = pair.first;
-		const WorkareaInfo* workarea_info = pair.second;
+/* Helper function to get the correct index for graphic/gl/workarea_program.cc::workarea_colors .
+ * a, b, c are the indices for the three nodes bordering this triangle.
+ * This function returns the biggest workarea type that matches all three corners.
+ * The indices stand for:
+ * 0 – all three circles
+ * 1 – medium and outer circle
+ * 2 – outer circle
+ * 3 – inner and medium circle
+ * 4 – medium circle
+ * 5 – inner circle
+ * We currently assume that no building will have more than three workarea circles.
+ */
+static uint8_t workarea_max(uint8_t a, uint8_t b, uint8_t c) {
+	// Whether all nodes are part of the inner circle
+	bool inner = (a == 0 || a == 3 || a == 5) && (b == 0 || b == 3 || b == 5) && (c == 0 || c == 3 || c == 5);
+	// Whether all nodes are part of the medium circle
+	bool medium = (a == 0 || a == 1 || a == 3 || a == 4) && (b == 0 || b == 1 || b == 3 || b == 4) &&
+			(c == 0 || c == 1 || c == 3 || c == 4);
+	// Whether all nodes are part of the outer circle
+	bool outer = a <= 2 && b <= 2 && c <= 2;
+
+	if (medium) {
+		if (outer && inner) {
+			return 0;
+		} else if (inner) {
+			return 3;
+		} else if (outer) {
+			return 1;
+		} else {
+			return 4;
+		}
+	} else if (outer) {
+		assert(!inner);
+		return 2;
+	} else {
+		assert(inner);
+		return 5;
+	}
+}
+
+Workareas InteractiveBase::get_workarea_overlays(const Widelands::Map& map) const {
+	Workareas result_set;
+	for (const auto& wa_pair : workarea_previews_) {
+		std::map<Coords, uint8_t> intermediate_result;
+		const Coords& coords = wa_pair.first;
+		const WorkareaInfo* workarea_info = wa_pair.second;
+		intermediate_result[coords] = 0;
 		WorkareaInfo::size_type wa_index;
 		switch (workarea_info->size()) {
 		case 0:
@@ -335,13 +381,36 @@
 			hollow_area.radius = it->first;
 			Widelands::MapHollowRegion<> mr(map, hollow_area);
 			do {
-				result[mr.location()] = workarea_pics_[wa_index];
+				intermediate_result[mr.location()] = wa_index;
 			} while (mr.advance(map));
 			wa_index++;
 			hollow_area.hole_radius = hollow_area.radius;
 		}
+
+		std::map<TCoords<>, uint8_t> result;
+		for (const auto& pair : intermediate_result) {
+			Coords c;
+			map.get_brn(pair.first, &c);
+			const auto brn = intermediate_result.find(c);
+			if (brn == intermediate_result.end()) {
+				continue;
+			}
+			map.get_bln(pair.first, &c);
+			const auto bln = intermediate_result.find(c);
+			map.get_rn(pair.first, &c);
+			const auto rn = intermediate_result.find(c);
+			if (bln != intermediate_result.end()) {
+				result[TCoords<>(pair.first, Widelands::TriangleIndex::D)] = workarea_max(
+						pair.second, brn->second, bln->second);
+			}
+			if (rn != intermediate_result.end()) {
+				result[TCoords<>(pair.first, Widelands::TriangleIndex::R)] = workarea_max(
+						pair.second, brn->second, rn->second);
+			}
+		}
+		result_set.emplace(result);
 	}
-	return result;
+	return result_set;
 }
 
 void InteractiveBase::hide_workarea(const Widelands::Coords& coords) {

=== modified file 'src/wui/interactive_base.h'
--- src/wui/interactive_base.h	2019-04-19 07:27:15 +0000
+++ src/wui/interactive_base.h	2019-04-24 17:17:26 +0000
@@ -231,7 +231,7 @@
 	TextToDraw get_text_to_draw() const;
 
 	// Returns the current overlays for the work area previews.
-	std::map<Widelands::Coords, const Image*> get_workarea_overlays(const Widelands::Map& map) const;
+	Workareas get_workarea_overlays(const Widelands::Map& map) const;
 
 	// Returns the 'BuildhelpOverlay' for 'caps' or nullptr if there is no help
 	// to be displayed on this field.
@@ -241,8 +241,10 @@
 		return road_building_overlays_;
 	}
 
-	/// Returns true if there is a workarea preview being shown at the given coordinates
-	bool has_workarea_preview(const Widelands::Coords& coords) const;
+	/// Returns true if there is a workarea preview being shown at the given coordinates.
+	/// If 'map' is 0, checks only if the given coords are the center of a workarea;
+	/// otherwise checks if the coords are within any workarea.
+	bool has_workarea_preview(const Widelands::Coords& coords, const Widelands::Map* map = nullptr) const;
 
 	/// Returns true if the current player is allowed to hear sounds from map objects on this field
 	virtual bool player_hears_field(const Widelands::Coords& coords) const = 0;
@@ -304,7 +306,6 @@
 
 	UI::UniqueWindow::Registry debugconsole_;
 	std::unique_ptr<UniqueWindowHandler> unique_window_handler_;
-	std::vector<const Image*> workarea_pics_;
 	BuildhelpOverlay buildhelp_overlays_[Widelands::Field::Buildhelp_None];
 };
 

=== modified file 'src/wui/interactive_player.cc'
--- src/wui/interactive_player.cc	2019-04-24 06:01:37 +0000
+++ src/wui/interactive_player.cc	2019-04-24 17:17:26 +0000
@@ -160,7 +160,8 @@
                                      bool const multiplayer)
    : InteractiveGameBase(g, global_s, NONE, multiplayer),
      auto_roadbuild_mode_(global_s.get_bool("auto_roadbuild_mode", true)),
-     flag_to_connect_(Widelands::Coords::null()) {
+     flag_to_connect_(Widelands::Coords::null()),
+     grid_marker_pic_(g_gr->images().get("images/wui/overlays/grid_marker.png")) {
 	add_toolbar_button(
 	   "wui/menus/menu_options_menu", "options_menu", _("Main menu"), &options_, true);
 	options_.open_window = [this] { new GameOptionsMenu(*this, options_, main_windows_); };
@@ -286,9 +287,9 @@
 	const Widelands::Map& map = gbase.map();
 	const uint32_t gametime = gbase.get_gametime();
 
-	auto* fields_to_draw = given_map_view->draw_terrain(gbase, dst);
+	Workareas workareas = get_workarea_overlays(map);
+	auto* fields_to_draw = given_map_view->draw_terrain(gbase, workareas, false, dst);
 	const auto& road_building = road_building_overlays();
-	const std::map<Widelands::Coords, const Image*> workarea_overlays = get_workarea_overlays(map);
 
 	const float scale = 1.f / given_map_view->view().zoom;
 
@@ -329,13 +330,10 @@
 			}
 		}
 
-		// Draw work area previews.
-		{
-			const auto it = workarea_overlays.find(f->fcoords);
-			if (it != workarea_overlays.end()) {
-				blit_field_overlay(dst, *f, it->second,
-				                   Vector2i(it->second->width() / 2, it->second->height() / 2), scale);
-			}
+		// Draw work area markers.
+		if (has_workarea_preview(f->fcoords, &map)) {
+			blit_field_overlay(dst, *f, grid_marker_pic_,
+					Vector2i(grid_marker_pic_->width() / 2, grid_marker_pic_->height() / 2), scale);
 		}
 
 		if (f->vision > 0) {

=== modified file 'src/wui/interactive_player.h'
--- src/wui/interactive_player.h	2019-03-14 23:06:02 +0000
+++ src/wui/interactive_player.h	2019-04-24 17:17:26 +0000
@@ -91,6 +91,8 @@
 	UI::UniqueWindow::Registry objectives_;
 	UI::UniqueWindow::Registry encyclopedia_;
 	UI::UniqueWindow::Registry message_menu_;
+
+	const Image* grid_marker_pic_;
 };
 
 #endif  // end of include guard: WL_WUI_INTERACTIVE_PLAYER_H

=== modified file 'src/wui/interactive_spectator.cc'
--- src/wui/interactive_spectator.cc	2019-04-24 06:01:37 +0000
+++ src/wui/interactive_spectator.cc	2019-04-24 17:17:26 +0000
@@ -116,12 +116,11 @@
 
 	const Widelands::Game& the_game = game();
 	const Widelands::Map& map = the_game.map();
-	auto* fields_to_draw = given_map_view->draw_terrain(the_game, dst);
+	auto* fields_to_draw = given_map_view->draw_terrain(the_game, get_workarea_overlays(map), false, dst);
 	const float scale = 1.f / given_map_view->view().zoom;
 	const uint32_t gametime = the_game.get_gametime();
 
 	const auto text_to_draw = get_text_to_draw();
-	const std::map<Widelands::Coords, const Image*> workarea_overlays = get_workarea_overlays(map);
 	for (size_t idx = 0; idx < fields_to_draw->size(); ++idx) {
 		const FieldsToDraw::Field& field = fields_to_draw->at(idx);
 
@@ -137,13 +136,6 @@
 			bob->draw(the_game, text_to_draw, field.rendertarget_pixel, field.fcoords, scale, dst);
 		}
 
-		// Draw work area previews.
-		const auto it = workarea_overlays.find(field.fcoords);
-		if (it != workarea_overlays.end()) {
-			const Image* pic = it->second;
-			blit_field_overlay(dst, field, pic, Vector2i(pic->width() / 2, pic->height() / 2), scale);
-		}
-
 		// Draw build help.
 		if (buildhelp()) {
 			auto caps = Widelands::NodeCaps::CAPS_NONE;

=== modified file 'src/wui/mapview.cc'
--- src/wui/mapview.cc	2019-04-23 16:24:40 +0000
+++ src/wui/mapview.cc	2019-04-24 17:17:26 +0000
@@ -339,7 +339,8 @@
 	NEVER_HERE();
 }
 
-FieldsToDraw* MapView::draw_terrain(const Widelands::EditorGameBase& egbase, RenderTarget* dst) {
+FieldsToDraw* MapView::draw_terrain(const Widelands::EditorGameBase& egbase,
+		Workareas workarea, bool grid, RenderTarget* dst) {
 	uint32_t now = SDL_GetTicks();
 	while (!view_plans_.empty()) {
 		auto& plan = view_plans_.front();
@@ -383,7 +384,7 @@
 
 	fields_to_draw_.reset(egbase, view_.viewpoint, view_.zoom, dst);
 	const float scale = 1.f / view_.zoom;
-	::draw_terrain(egbase, fields_to_draw_, scale, dst);
+	::draw_terrain(egbase, fields_to_draw_, scale, workarea, grid, dst);
 	return &fields_to_draw_;
 }
 

=== modified file 'src/wui/mapview.h'
--- src/wui/mapview.h	2019-03-26 02:56:03 +0000
+++ src/wui/mapview.h	2019-04-24 17:17:26 +0000
@@ -172,7 +172,7 @@
 	// Schedules drawing of the terrain of this MapView. The returned value can
 	// be used to override contents of 'fields_to_draw' for player knowledge and
 	// visibility, and to correctly draw map objects, overlays and text.
-	FieldsToDraw* draw_terrain(const Widelands::EditorGameBase& egbase, RenderTarget* dst);
+	FieldsToDraw* draw_terrain(const Widelands::EditorGameBase& egbase, Workareas workarea, bool grid, RenderTarget* dst);
 
 	// Not overriden from UI::Panel, instead we expect to be passed the data through.
 	bool handle_mousepress(uint8_t btn, int32_t x, int32_t y);


Follow ups