1 /*
2 * Copyright (C) 2006-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 *
18 */
19
20 #include "graphic/gl/fields_to_draw.h"
21
22 #include "graphic/gl/coordinate_conversion.h"
23 #include "logic/map_objects/world/terrain_description.h"
24 #include "wui/mapviewpixelfunctions.h"
25
26 /*
27 * Explanation of how drawing works:
28 * Schematic of triangle neighborhood:
29 *
30 * *
31 * / \
32 * / u \
33 * (f)/ \
34 * *------*------* (r)
35 * \ l / \ r / \
36 * \ / \ / \
37 * \/ d \/ rr \
38 * *------*------* (br)
39 * \ dd /
40 * \ /
41 * \/
42 * *
43 *
44 * Each field (f) owns two triangles: (r)ight & (d)own. When we look at the
45 * field, we have to make sure to schedule drawing the triangles. This is done
46 * by TerrainProgram.
47 *
48 * To draw dithered edges, we have to look at the neighboring triangles for the
49 * two triangles too: If a neighboring triangle has another texture and our
50 * dither layer is smaller, we have to draw a dithering triangle too - this lets
51 * the neighboring texture bleed into our triangle.
52 *
53 * The dither triangle is the triangle that should be partially drawn (either r or
54 * d). Example: if r and d have different textures and r.dither_layer >
55 * d.dither_layer, then we will repaint d with the dither texture as mask.
56 */
57
58 namespace {
59
60 // Returns the brightness value in [0, 1.] for 'fcoords'.
field_brightness(const Widelands::FCoords & fcoords)61 float field_brightness(const Widelands::FCoords& fcoords) {
62 uint32_t brightness = 144 + fcoords.field->get_brightness();
63 brightness = std::min<uint32_t>(255, (brightness * 255) / 160);
64 return brightness / 255.;
65 }
66
67 } // namespace
68
reset(const Widelands::EditorGameBase & egbase,const Vector2f & viewpoint,const float zoom,RenderTarget * dst)69 void FieldsToDraw::reset(const Widelands::EditorGameBase& egbase,
70 const Vector2f& viewpoint,
71 const float zoom,
72 RenderTarget* dst) {
73 assert(viewpoint.x >= 0); // divisions involving negative numbers are bad
74 assert(viewpoint.y >= 0);
75 assert(dst->get_offset().x <= 0);
76 assert(dst->get_offset().y <= 0);
77
78 min_fx_ = std::floor(viewpoint.x / kTriangleWidth);
79 min_fy_ = std::floor(viewpoint.y / kTriangleHeight);
80
81 // If a view window is partially moved outside of the display, its 'rect' is
82 // adjusted to be fully contained on the screen - i.e. x = 0 and width is
83 // made smaller. Its offset is made negative to account for this change.
84 // To figure out which fields we need to draw, we have to add the absolute
85 // value of 'offset' to the actual dimension of the 'rect' to get to desired
86 // dimension of the 'rect'
87 const Vector2f br_map = MapviewPixelFunctions::panel_to_map(
88 viewpoint, zoom, Vector2f(dst->get_rect().w + std::abs(dst->get_offset().x),
89 dst->get_rect().h + std::abs(dst->get_offset().y)));
90 max_fx_ = std::ceil(br_map.x / kTriangleWidth);
91 max_fy_ = std::ceil(br_map.y / kTriangleHeight);
92
93 // Adjust for triangle boundary effects and for height differences.
94 min_fx_ -= 2;
95 max_fx_ += 2;
96 min_fy_ -= 2;
97 max_fy_ += 10;
98
99 const auto& surface = dst->get_surface();
100 const int surface_width = surface.width();
101 const int surface_height = surface.height();
102
103 const Widelands::Map& map = egbase.map();
104
105 w_ = max_fx_ - min_fx_ + 1;
106 h_ = max_fy_ - min_fy_ + 1;
107 assert(w_ > 0);
108 assert(h_ > 0);
109
110 // Ensure that there is enough memory for the resize operation
111 size_t dimension = w_ * h_;
112 const size_t max_dimension = fields_.max_size();
113 if (dimension > max_dimension) {
114 log("WARNING: Not enough memory allocated to redraw the whole map!\nWe recommend that you "
115 "restart Widelands\n");
116 dimension = max_dimension;
117 }
118 // Now resize the vector
119 if (fields_.size() != dimension) {
120 fields_.resize(dimension);
121 }
122
123 for (int32_t fy = min_fy_; fy <= max_fy_; ++fy) {
124 for (int32_t fx = min_fx_; fx <= max_fx_; ++fx) {
125 FieldsToDraw::Field& f = fields_[calculate_index(fx, fy)];
126
127 f.geometric_coords = Widelands::Coords(fx, fy);
128
129 f.ln_index = calculate_index(fx - 1, fy);
130 f.rn_index = calculate_index(fx + 1, fy);
131 f.trn_index = calculate_index(fx + (fy & 1), fy - 1);
132 f.bln_index = calculate_index(fx + (fy & 1) - 1, fy + 1);
133 f.brn_index = calculate_index(fx + (fy & 1), fy + 1);
134
135 // Texture coordinates for pseudo random tiling of terrain and road
136 // graphics. Since screen space X increases top-to-bottom and OpenGL
137 // increases bottom-to-top we flip the y coordinate to not have
138 // terrains and road graphics vertically mirrorerd.
139 Vector2f map_pixel =
140 MapviewPixelFunctions::to_map_pixel_ignoring_height(f.geometric_coords);
141 f.texture_coords.x = map_pixel.x / Widelands::kTextureSideLength;
142 f.texture_coords.y = -map_pixel.y / Widelands::kTextureSideLength;
143
144 Widelands::Coords normalized = f.geometric_coords;
145 map.normalize_coords(normalized);
146 f.fcoords = map.get_fcoords(normalized);
147
148 map_pixel.y -= f.fcoords.field->get_height() * kHeightFactor;
149
150 f.rendertarget_pixel = MapviewPixelFunctions::map_to_panel(viewpoint, zoom, map_pixel);
151 f.gl_position = f.surface_pixel = f.rendertarget_pixel +
152 dst->get_rect().origin().cast<float>() +
153 dst->get_offset().cast<float>();
154 pixel_to_gl_renderbuffer(
155 surface_width, surface_height, &f.gl_position.x, &f.gl_position.y);
156
157 f.brightness = field_brightness(f.fcoords);
158
159 const Widelands::PlayerNumber owned_by = f.fcoords.field->get_owned_by();
160 f.owner = owned_by != 0 ? egbase.get_player(owned_by) : nullptr;
161 f.is_border = f.fcoords.field->is_border();
162 f.vision = 2;
163 f.road_e = f.fcoords.field->get_road(Widelands::WALK_E);
164 f.road_se = f.fcoords.field->get_road(Widelands::WALK_SE);
165 f.road_sw = f.fcoords.field->get_road(Widelands::WALK_SW);
166 }
167 }
168 }
169