OpenClonk
C4TextureShape.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Textures used by the landscape */
19 
20 #include "C4Include.h"
22 
23 #include "c4group/C4Group.h"
24 #include "graphics/StdPNG.h"
25 #include "landscape/C4Landscape.h"
26 
27 // -------------------------------------- C4TextureShape
28 
30 {
31  data.Clear();
32  num_shapes = 0;
33  shape_border_x.clear();
34  shape_border_y.clear();
35  shape_pixnum.clear();
36  shape_pixnum.reserve(128);
37 }
38 
39 bool C4TextureShape::Load(C4Group &group, const char *filename, int32_t base_tex_wdt, int32_t base_tex_hgt)
40 {
41  Clear();
42  // Material shapes loading
43  StdBuf png_data;
44  if (!group.LoadEntry(filename, &png_data)) return false;
45  CPNGFile png;
46  if (!png.Load(static_cast<BYTE *>(png_data.getMData()), png_data.getSize())) return false;
47  assert(base_tex_wdt > 0);
48  int32_t zoom = png.iWdt / base_tex_wdt;
49  if (base_tex_wdt * zoom != static_cast<int32_t>(png.iWdt) || base_tex_hgt * zoom != static_cast<int32_t>(png.iHgt))
50  {
51  LogF("ERROR: Material shape texture %s size (%d,%d) not a multiple of associated texture size (%d,%d). Not loading.", filename, int(png.iWdt), int(png.iHgt), int(base_tex_wdt), int(base_tex_hgt));
52  return false;
53  }
54  // Prepare wrap info
55  shape_border_x.resize(255, false);
56  shape_border_y.resize(255, false);
57  // Create shape data surface as downscaled version where equal pixel colors are assigned the same index
58  std::map<uint32_t, uint8_t> clr2shape;
59  std::vector<int32_t> first_px_pos_x(255), first_px_pos_y(255);
60  if (!data.Create(base_tex_wdt, base_tex_hgt)) return false;
61  for (int32_t y = 0; y < data.Hgt; ++y)
62  {
63  for (int32_t x = 0; x < data.Wdt; ++x)
64  {
65  uint32_t px = png.GetPix(x * zoom, y * zoom);
66  uint8_t px_data;
67  if ((px & 0xff000000u) == 0u)
68  {
69  // Fully transparent pixels are not part of a shape
70  px_data = Shape_None;
71  }
72  else
73  {
74  // Otherwise, ignore alpha and lookup
75  px &= 0xffffff;
76  auto shape_idx = clr2shape.find(px);
77  if (shape_idx == clr2shape.end())
78  {
79  // New shape
80  px_data = num_shapes;
81  clr2shape[px] = num_shapes;
82  shape_pixnum.push_back(1);
83  first_px_pos_x[num_shapes] = x;
84  first_px_pos_y[num_shapes] = y;
85  ++num_shapes;
86  if (num_shapes >= 255)
87  {
88  LogF("Material shape texture %s invalid: Too many shape colors (>=255).", filename);
89  Clear();
90  return false;
91  }
92  }
93  else
94  {
95  // Another pixel of previous shape
96  px_data = shape_idx->second;
97  ++shape_pixnum[px_data];
98  }
99  // Remember wrapping
100  if (!x || x == data.Wdt - 1) shape_border_x[px_data] = true;
101  if (!y || y == data.Hgt - 1) shape_border_y[px_data] = true;
102  }
103  data._SetPix(x, y, px_data);
104  }
105  }
106  // Show a summary about found shapes in this texture
108  {
109  LogF("Shape texture summary for %s (%d x %d downscaled to %d x %d):", filename, (int)png.iWdt, (int)png.iHgt, (int)base_tex_wdt, (int)base_tex_wdt);
110  for (auto iter : clr2shape)
111  {
112  unsigned int clr = iter.first;
113  int i = iter.second;
114  LogF(" Color 0x%08x: %d pixels. First seen pos: %d/%d", clr, int(shape_pixnum[i]), int(first_px_pos_x[i]), int(first_px_pos_y[i]));
115  }
116  }
117 
118  return true;
119 }
120 
121 // Activation map: Temp storage for whether shapes are activated in each covered block
122 // Contains Act_None if not active and background pixel color to be used otherwise
124 {
125 private:
126  std::vector<int32_t> activation; // number of pixels covered
127  std::vector<uint8_t> back_pix; // last encountered background pixel
128  int32_t block_x0, block_y0;
129  int32_t n_blocks_x, n_blocks_y;
130  int32_t num_shapes;
131 
132 private:
133  int32_t Idx(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart) const; // get index into 5D activation and back_pix arrays
134 
135 public:
136  C4TextureShapeActivationMap(int32_t block_x0, int32_t block_y0, int32_t n_blocks_x, int32_t n_blocks_y, int32_t num_shapes) : block_x0(block_x0), block_y0(block_y0), n_blocks_x(n_blocks_x), n_blocks_y(n_blocks_y), num_shapes(num_shapes)
137  {
138  activation.resize(n_blocks_x * n_blocks_y * num_shapes * 4, 0);
139  back_pix.resize(n_blocks_x * n_blocks_y * num_shapes * 4, 0u);
140  }
141 
142  int32_t Get(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t *bg_pix) const;
143  void Add(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t back_pix);
144 };
145 
146 int32_t C4TextureShapeActivationMap::Idx(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart) const
147 {
148  // return index from 5D coords. -1 for out of bounds.
149  block_x -= block_x0; block_y -= block_y0;
150  if (!Inside(block_x, 0, n_blocks_x - 1) || !Inside(block_y, 0, n_blocks_y - 1)) return -1;
151  return ypart + 2 * (xpart + 2 * (shape_idx + num_shapes * (block_x + n_blocks_x * block_y)));
152 }
153 
154 int32_t C4TextureShapeActivationMap::Get(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t *bg_pix) const
155 {
156  // activation map access
157  int32_t idx = Idx(block_x, block_y, shape_idx, xpart, ypart);
158  assert(idx >= 0);
159  if (idx < 0) return 0;
160  *bg_pix = back_pix[idx];
161  return activation[idx];
162 }
163 
164 void C4TextureShapeActivationMap::Add(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t bg_pix)
165 {
166  // activation map access
167  int32_t idx = Idx(block_x, block_y, shape_idx, xpart, ypart);
168  if (idx < 0)
169  {
170  // This can happen for maps in which the shape is too large or simply at landscape borders. So ignore.
171  return;
172  }
173  ++activation[idx];
174  back_pix[idx] = bg_pix;
175 }
176 
177 
178 void C4TextureShape::Draw(const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint8_t iTexture, int32_t iOffX, int32_t iOffY, int32_t MapZoom, int32_t min_overlap_ratio)
179 {
180  // Safety
181  if (!num_shapes) return;
182  // Get affected range of shapes in pixels
183  // Add max polygon size because polygons may extent far onto outside pixels
184  int32_t x0 = std::max<int32_t>(0, iMapX*MapZoom + iOffX - GetMaxPolyWidth()),
185  y0 = std::max<int32_t>(0, iMapY*MapZoom + iOffY - GetMaxPolyHeight());
186  int32_t x1 = std::min<int32_t>(::Landscape.GetWidth(), x0 + iMapWdt*MapZoom + GetMaxPolyWidth() * 2),
187  y1 = std::min<int32_t>(::Landscape.GetHeight(), y0 + iMapHgt*MapZoom + GetMaxPolyHeight() * 2);
188  // Range in shape blocks.
189  // A shape block is the coverage of the size of one loaded shape data surface
190  int32_t rblock_x0 = x0 / data.Wdt;
191  int32_t rblock_y0 = y0 / data.Hgt;
192  int32_t rblock_x1 = (x1 - 1) / data.Wdt + 1;
193  int32_t rblock_y1 = (y1 - 1) / data.Hgt + 1;
194  int32_t n_blocks_x = rblock_x1 - rblock_x0 + 1;
195  int32_t n_blocks_y = rblock_y1 - rblock_y0 + 1;
196 
197  // Step 1: Find all active shapes and store them in activation map
198  // The activation map covers all repeated shape blocks in the updated areas and tiles each block into 2x2 sub-blocks.
199  // Sub-blocks handle the case where shapes wrap out of the side of a block into the next block.
200  C4TextureShapeActivationMap activation(rblock_x0, rblock_y0, n_blocks_x, n_blocks_y, num_shapes);
201  for (int32_t map_y = iMapY; map_y < iMapY + iMapHgt; ++map_y)
202  {
203  for (int32_t map_x = iMapX; map_x < iMapX + iMapWdt; ++map_x)
204  {
205  if (sfcMap.GetPix(map_x, map_y) == iTexture)
206  {
207  // Here we have a pixel of the texture drawn in this shape
208  // Find all shapes covered by this map pixel and remember background pixel for them
209  const BYTE pixBkg = sfcMapBkg.GetPix(map_x, map_y);
210  // Find all shape blocks to be checked
211  int32_t px_check_rate = 1; // sample rate to check coverage, in pixels. Could also increase this if it turns out to be a bottleneck
212  for (int32_t pxo_y = 0; pxo_y < MapZoom; pxo_y += px_check_rate)
213  {
214  int32_t px_y = map_y * MapZoom + pxo_y + iOffY;
215  int32_t spx_y = px_y % data.Hgt;
216  int32_t block_y = px_y / data.Hgt;
217  for (int32_t pxo_x = 0; pxo_x < MapZoom; pxo_x += px_check_rate)
218  {
219  int32_t px_x = map_x * MapZoom + pxo_x + iOffX;
220  int32_t spx_x = px_x % data.Wdt;
221  int32_t block_x = px_x / data.Wdt;
222  BYTE shape_idx = data._GetPix(spx_x, spx_y);
223  // No shape here or already handled?
224  if (shape_idx == Shape_None) continue;
225  // We touched a new shape! Activate it in the proper activation vectors
226  int32_t block_x1 = block_x, block_x2 = block_x;
227  // Is this shape wrapping along x?
228  if (shape_border_x[shape_idx])
229  {
230  // It is! Are we on the left or right side of the shape block?
231  if (spx_x < data.Wdt / 2)
232  {
233  // Left side. Activate left sub-block of current block and right sub-block of block left to that
234  --block_x2;
235  }
236  else
237  {
238  // Right side. Activate right sub-block of current block and left sub-block of block right to that
239  ++block_x1;
240  }
241  }
242  // Same checks for vertical
243  int32_t block_y1 = block_y, block_y2 = block_y;
244  if (shape_border_y[shape_idx]) { if (spx_y < data.Hgt / 2) --block_y2; else ++block_y1; }
245  // Store activation (performs additional border checks)
246  // This may overwrite the previous pixBkg when multiple chunks are covered
247  // So effectively, it will always have the background of the bottom right map coverage
248  // Could manage priorities here and ensure the center determines the background
249  // - but it's just for the corner case of map designers switching background material within a single patch.
250  activation.Add(block_x1, block_y1, shape_idx, 0, 0, pixBkg);
251  activation.Add(block_x2, block_y1, shape_idx, 1, 0, pixBkg);
252  activation.Add(block_x1, block_y2, shape_idx, 0, 1, pixBkg);
253  activation.Add(block_x2, block_y2, shape_idx, 1, 1, pixBkg);
254  }
255  }
256  }
257  }
258  }
259 
260  // Step 2: Draw texture on all active shapes
261  for (int32_t y = y0; y < y1; ++y)
262  {
263  int32_t block_y = y / data.Hgt;
264  int32_t blockpos_y = y % data.Hgt;
265  int32_t subblock_y = (blockpos_y >= data.Hgt / 2) ? 1 : 0;
266  for (int32_t x = x0; x < x1; ++x)
267  {
268  int32_t block_x = x / data.Wdt;
269  int32_t blockpos_x = x % data.Wdt;
270  int32_t subblock_x = (blockpos_x >= data.Wdt / 2) ? 1 : 0;
271  int32_t shape_idx = data._GetPix(blockpos_x, blockpos_y);
272  if (shape_idx == Shape_None) continue;
273  uint8_t pixBg = 0;
274  int32_t act = activation.Get(block_x, block_y, shape_idx, subblock_x, subblock_y, &pixBg);
275  if (!act || act < shape_pixnum[shape_idx] * min_overlap_ratio / 100) continue;
276  // Shape active at this pixel. Draw it.
277  ::Landscape._SetPix2(x, y, iTexture, pixBg);
278  }
279  }
280 }
C4Config Config
Definition: C4Config.cpp:930
C4Landscape Landscape
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
uint8_t BYTE
bool Inside(T ival, U lbound, V rbound)
Definition: Standard.h:43
int32_t DebugShapeTextures
Definition: C4Config.h:88
C4ConfigDeveloper Developer
Definition: C4Config.h:256
bool LoadEntry(const char *entry_name, char **buffer, size_t *size_info=nullptr, int zeros_to_append=0)
Definition: C4Group.cpp:2375
int32_t GetWidth() const
bool _SetPix2(int32_t x, int32_t y, BYTE fgPix, BYTE bgPix)
int32_t GetHeight() const
void Add(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t back_pix)
C4TextureShapeActivationMap(int32_t block_x0, int32_t block_y0, int32_t n_blocks_x, int32_t n_blocks_y, int32_t num_shapes)
int32_t Get(int32_t block_x, int32_t block_y, int32_t shape_idx, int32_t xpart, int32_t ypart, uint8_t *bg_pix) const
void Draw(const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint8_t iTexture, int32_t iOffX, int32_t iOffY, int32_t MapZoom, int32_t min_overlap_ratio)
bool Load(C4Group &group, const char *filename, int32_t base_tex_wdt, int32_t base_tex_hgt)
int32_t GetMaxPolyHeight() const
int32_t GetMaxPolyWidth() const
unsigned long iHgt
Definition: StdPNG.h:44
bool Load(BYTE *pFile, int iSize)
Definition: StdPNG.cpp:155
unsigned long iWdt
Definition: StdPNG.h:44
DWORD GetPix(int iX, int iY)
Definition: StdPNG.cpp:175
BYTE _GetPix(int x, int y) const
Definition: CSurface8.h:54
BYTE GetPix(int iX, int iY) const
Definition: CSurface8.h:49
bool Create(int iWdt, int iHgt)
Definition: CSurface8.cpp:78
void Clear()
Definition: CSurface8.cpp:48
void _SetPix(int iX, int iY, BYTE byCol)
Definition: CSurface8.h:44
int Wdt
Definition: CSurface8.h:28
int Hgt
Definition: CSurface8.h:28
Definition: StdBuf.h:30
size_t getSize() const
Definition: StdBuf.h:101
void * getMData()
Definition: StdBuf.h:100