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"
21 #include "landscape/C4Texture.h"
22
23 #include "c4group/C4Components.h"
24 #include "c4group/C4Group.h"
25 #include "landscape/C4Landscape.h"
26 #include "lib/C4Random.h"
27 #include "lib/StdColors.h"
28
C4Texture()29 C4Texture::C4Texture()
30 {
31 Surface32=nullptr;
32 AvgColor = 0x00000000;
33 Next=nullptr;
34 }
35
~C4Texture()36 C4Texture::~C4Texture()
37 {
38 delete Surface32;
39 }
40
C4TexMapEntry()41 C4TexMapEntry::C4TexMapEntry()
42 : iMaterialIndex(MNone)
43 {
44 }
45
Clear()46 void C4TexMapEntry::Clear()
47 {
48 Material.Clear(); Texture.Clear();
49 iMaterialIndex = MNone;
50 pMaterial = nullptr;
51 MatPattern.Clear();
52 }
53
Create(const char * szMaterial,const char * szTexture)54 bool C4TexMapEntry::Create(const char *szMaterial, const char *szTexture)
55 {
56 // Clear previous data
57 Clear();
58 // Save names
59 Material = szMaterial; Texture = szTexture;
60 return true;
61 }
62
Init()63 bool C4TexMapEntry::Init()
64 {
65 // Find material
66 iMaterialIndex = ::MaterialMap.Get(Material.getData());
67 if (!MatValid(iMaterialIndex))
68 {
69 DebugLogF("Error initializing material %s-%s: Invalid material!", Material.getData(), Texture.getData());
70 return false;
71 }
72 pMaterial = &::MaterialMap.Map[iMaterialIndex];
73 // Find texture
74 StdStrBuf FirstTexture;
75 FirstTexture.CopyUntil(Texture.getData(), '-');
76 C4Texture * sfcTexture = ::TextureMap.GetTexture(FirstTexture.getData());
77 if (!sfcTexture)
78 {
79 DebugLogF("Error initializing material %s-%s: Invalid texture!", Material.getData(), FirstTexture.getData());
80 Clear();
81 return false;
82 }
83 // Get overlay properties
84 int32_t iOverlayType=pMaterial->OverlayType;
85 int32_t iZoom=0;
86 if (iOverlayType & C4MatOv_Exact) iZoom=1;
87 if (iOverlayType & C4MatOv_HugeZoom) iZoom=4;
88 // Create pattern
89 MatPattern.Set(sfcTexture->Surface32, iZoom);
90 return true;
91 }
92
C4TextureMap()93 C4TextureMap::C4TextureMap()
94 {
95 Order.reserve(C4M_MaxTexIndex);
96 }
97
~C4TextureMap()98 C4TextureMap::~C4TextureMap()
99 {
100 Clear();
101 }
102
AddEntry(BYTE byIndex,const char * szMaterial,const char * szTexture)103 bool C4TextureMap::AddEntry(BYTE byIndex, const char *szMaterial, const char *szTexture)
104 {
105 // Security
106 if (byIndex <= 0 || byIndex >= C4M_MaxTexIndex)
107 return false;
108 // Set entry and initialize
109 Entry[byIndex].Create(szMaterial, szTexture);
110 if (fInitialized)
111 {
112 if (!Entry[byIndex].Init())
113 {
114 // Clear entry if it could not be initialized
115 Entry[byIndex].Clear();
116 return false;
117 }
118 // Landscape must be notified (new valid pixel clr)
119 ::Landscape.HandleTexMapUpdate();
120 }
121 // Add last in order list
122 Order.push_back(byIndex);
123 return true;
124 }
125
AddTexture(const char * szTexture,C4Surface * sfcSurface)126 bool C4TextureMap::AddTexture(const char *szTexture, C4Surface * sfcSurface)
127 {
128 C4Texture *pTexture;
129 if (!(pTexture=new C4Texture)) return false;
130 pTexture->Name.Copy(szTexture);
131 pTexture->Surface32=sfcSurface;
132 pTexture->Next=FirstTexture;
133 FirstTexture=pTexture;
134
135 // Compute average texture color
136 if(sfcSurface)
137 {
138 sfcSurface->Lock();
139 uint32_t avg_c[4] = { 0, 0, 0, 0 };
140 for(int32_t y = 0; y < sfcSurface->Hgt; ++y)
141 {
142 for(int32_t x = 0; x < sfcSurface->Wdt; ++x)
143 {
144 DWORD c = sfcSurface->GetPixDw(x, y, false);
145 avg_c[0] += c & 0xff;
146 avg_c[1] += (c >> 8) & 0xff;
147 avg_c[2] += (c >> 16) & 0xff;
148 avg_c[3] += (c >> 24) & 0xff;
149 }
150 }
151 sfcSurface->Unlock();
152
153 double Size = sfcSurface->Wdt * sfcSurface->Hgt;
154 avg_c[0] = static_cast<uint32_t>(avg_c[0] / Size + 0.5);
155 avg_c[1] = static_cast<uint32_t>(avg_c[1] / Size + 0.5);
156 avg_c[2] = static_cast<uint32_t>(avg_c[2] / Size + 0.5);
157 avg_c[3] = static_cast<uint32_t>(avg_c[3] / Size + 0.5);
158 pTexture->SetAverageColor(avg_c[0] | (avg_c[1] << 8) | (avg_c[2] << 16) | (avg_c[3] << 24));
159 }
160 else
161 {
162 pTexture->SetAverageColor(0x00000000);
163 }
164
165 return true;
166 }
167
Clear()168 void C4TextureMap::Clear()
169 {
170 for (int32_t i = 1; i < C4M_MaxTexIndex; i++)
171 Entry[i].Clear();
172 C4Texture *ctex,*next2;
173 for (ctex=FirstTexture; ctex; ctex=next2)
174 {
175 next2=ctex->Next;
176 delete ctex;
177 }
178 FirstTexture=nullptr;
179 fInitialized = false;
180 fEntriesAdded = false;
181 fOverloadMaterials = false;
182 fOverloadTextures = false;
183 Order.clear();
184 Order.reserve(C4M_MaxTexIndex);
185 }
186
LoadFlags(C4Group & hGroup,const char * szEntryName,bool * pOverloadMaterials,bool * pOverloadTextures)187 bool C4TextureMap::LoadFlags(C4Group &hGroup, const char *szEntryName, bool *pOverloadMaterials, bool *pOverloadTextures)
188 {
189 // Load the file
190 StdStrBuf TexMap;
191 if (!hGroup.LoadEntryString(szEntryName, &TexMap))
192 return false;
193 // Reset flags
194 if (pOverloadMaterials) *pOverloadMaterials = false;
195 if (pOverloadTextures) *pOverloadTextures = false;
196 // Check if there are flags in there
197 for (const char *pPos = TexMap.getData(); pPos && *pPos; pPos = SSearch(pPos + 1, "\n"))
198 {
199 // Go over newlines
200 while (*pPos == '\r' || *pPos == '\n') pPos++;
201 // Flag?
202 if (pOverloadMaterials && SEqual2(pPos, "OverloadMaterials"))
203 *pOverloadMaterials = true;
204 if (pOverloadTextures && SEqual2(pPos, "OverloadTextures"))
205 *pOverloadTextures = true;
206 }
207 // Done
208 return true;
209 }
210
LoadMap(C4Group & hGroup,const char * szEntryName,bool * pOverloadMaterials,bool * pOverloadTextures)211 int32_t C4TextureMap::LoadMap(C4Group &hGroup, const char *szEntryName, bool *pOverloadMaterials, bool *pOverloadTextures)
212 {
213 static std::regex line_terminator("\r?\n", static_cast<std::regex::flag_type>(std::regex_constants::optimize | std::regex_constants::ECMAScript));
214
215 char *bpMap;
216 size_t map_size;
217 int32_t iTextures = 0;
218 // Load text file into memory
219 if (!hGroup.LoadEntry(szEntryName,&bpMap,&map_size,1)) return 0;
220
221 char *begin = bpMap;
222 char *end = begin + map_size;
223
224 size_t line = 1; // Counter for error messages
225 for (auto it = std::cregex_token_iterator(begin, end, line_terminator, -1); it != std::cregex_token_iterator(); ++it, ++line)
226 {
227 if (it->compare("OverloadMaterials") == 0)
228 {
229 fOverloadMaterials = true;
230 if (pOverloadMaterials)
231 *pOverloadMaterials = true;
232 }
233 else if (it->compare("OverloadTextures") == 0)
234 {
235 fOverloadTextures = true;
236 if (pOverloadTextures)
237 *pOverloadTextures = true;
238 }
239 else if (it->length() == 0 || it->first[0] == '#' || std::all_of(it->first, it->second, &isspace))
240 {
241 // Skip empty lines, comments, and all-whitespace lines
242 continue;
243 }
244 else
245 {
246 // This must be a texmap entry now
247 std::string value;
248
249 // Read index
250 unsigned long index;
251 try
252 {
253 size_t separator;
254 index = std::stoul(it->str(), &separator, 10);
255 if (index >= C4M_MaxTexIndex)
256 throw std::out_of_range("Texture index out of range");
257 value.assign(it->first + separator + 1, it->second);
258 }
259 catch (std::invalid_argument &)
260 {
261 DebugLogF("TexMap line %u: Texture index is not numeric", static_cast<unsigned>(line));
262 continue;
263 }
264 catch (std::out_of_range &)
265 {
266 DebugLogF("TexMap line %u: Texture index is out of range", static_cast<unsigned>(line));
267 continue;
268 }
269
270 // Split material/texture combination
271 std::string::const_iterator separator = std::find(value.cbegin(), value.cend(), '-');
272 if (separator == value.cend())
273 {
274 DebugLogF(R"(TexMap line %u: Texture name "%s" is invalid (missing "-"))", static_cast<unsigned>(line), value.c_str());
275 continue;
276 }
277
278 std::string material(value.cbegin(), separator);
279 std::string texture(separator + 1, value.cend());
280
281 if (AddEntry(index, material.c_str(), texture.c_str()))
282 ++iTextures;
283 }
284 }
285
286 // Delete buffer, return entry count
287 delete [] bpMap;
288 fEntriesAdded=false;
289 return iTextures;
290 }
291
Init()292 int32_t C4TextureMap::Init()
293 {
294 int32_t iRemoved = 0;
295 // Initialize texture mappings
296 int32_t i;
297 for (i = 0; i < C4M_MaxTexIndex; i++)
298 if (!Entry[i].isNull())
299 if (!Entry[i].Init())
300 {
301 LogF("Error in TextureMap initialization at entry %d", (int) i);
302 Entry[i].Clear();
303 iRemoved++;
304 }
305 fInitialized = true;
306 return iRemoved;
307 }
308
SaveMap(C4Group & hGroup,const char * szEntryName)309 bool C4TextureMap::SaveMap(C4Group &hGroup, const char *szEntryName)
310 {
311 // build file in memory
312 StdStrBuf sTexMapFile;
313 // add desc
314 sTexMapFile.Append("# Automatically generated texture map" LineFeed);
315 sTexMapFile.Append("# Contains material-texture-combinations added at runtime" LineFeed);
316 // add overload-entries
317 if (fOverloadMaterials) sTexMapFile.Append("# Import materials from global file as well" LineFeed "OverloadMaterials" LineFeed);
318 if (fOverloadTextures) sTexMapFile.Append("# Import textures from global file as well" LineFeed "OverloadTextures" LineFeed);
319 sTexMapFile.Append(LineFeed);
320 // add entries
321 for (auto i : Order)
322 {
323 if (!Entry[i].isNull())
324 {
325 // compose line
326 sTexMapFile.AppendFormat("%d=%s-%s" LineFeed, i, Entry[i].GetMaterialName(), Entry[i].GetTextureName());
327 }
328 }
329 // create new buffer allocated with new [], because C4Group cannot handle StdStrBuf-buffers
330 size_t iBufSize = sTexMapFile.getLength();
331 BYTE *pBuf = new BYTE[iBufSize];
332 memcpy(pBuf, sTexMapFile.getData(), iBufSize);
333 // add to group
334 bool fSuccess = !!hGroup.Add(szEntryName, pBuf, iBufSize, false, true);
335 if (!fSuccess) delete [] pBuf;
336 // done
337 return fSuccess;
338 }
339
LoadTextures(C4Group & hGroup,C4Group * OverloadFile)340 int32_t C4TextureMap::LoadTextures(C4Group &hGroup, C4Group* OverloadFile)
341 {
342 int32_t texnum=0;
343 // overload: load from other file
344 if (OverloadFile) texnum+=LoadTextures(*OverloadFile);
345
346 char texname[256+1];
347 C4Surface *ctex;
348 size_t binlen;
349
350 hGroup.ResetSearch();
351 while (hGroup.AccessNextEntry("*",&binlen,texname))
352 {
353 // check if it already exists in the map
354 const char *base_filename = GetFilenameOnly(texname);
355 if (GetTexture(base_filename)) continue;
356 // skip shape textures for now. Will be added later after all base textures have been loaded
357 if (WildcardMatch("*" C4CFN_MaterialShapeFiles, texname)) continue;
358 // create surface
359 ctex = new C4Surface();
360 if (ctex->Read(hGroup, GetExtension(texname), C4SF_MipMap))
361 {
362 SReplaceChar(texname,'.',0);
363 if (AddTexture(texname,ctex)) texnum++;
364 else delete ctex;
365 }
366 else
367 {
368 delete ctex;
369 }
370 }
371
372 // Load texture shapes
373 hGroup.ResetSearch();
374 while (hGroup.AccessNextEntry("*" C4CFN_MaterialShapeFiles, &binlen, texname))
375 {
376 // get associated texture
377 StdStrBuf texname4shape(texname, true);
378 texname4shape.SetLength(texname4shape.getLength() - SLen(C4CFN_MaterialShapeFiles));
379 C4Texture *base_tex = GetTexture(texname4shape.getData());
380 if (!base_tex || !base_tex->Surface32)
381 {
382 LogF("ERROR: Texture shape %s not loaded because associated texture (%s) not found or invalid.", hGroup.GetFullName().getData(), texname4shape.getData());
383 continue;
384 }
385 std::unique_ptr<C4TextureShape> shape(new C4TextureShape());
386 int32_t scaler_zoom = 4;
387 if (!shape->Load(hGroup, texname, base_tex->Surface32->Wdt / scaler_zoom, base_tex->Surface32->Hgt / scaler_zoom))
388 {
389 LogF("Error loading texture shape %s.", hGroup.GetFullName().getData());
390 continue;
391 }
392 base_tex->SetMaterialShape(shape.release());
393 }
394
395 return texnum;
396 }
397
HasTextures(C4Group & hGroup)398 bool C4TextureMap::HasTextures(C4Group &hGroup)
399 {
400 return hGroup.EntryCount(C4CFN_PNGFiles) || hGroup.EntryCount(C4CFN_BitmapFiles);
401 }
402
MoveIndex(BYTE byOldIndex,BYTE byNewIndex)403 void C4TextureMap::MoveIndex(BYTE byOldIndex, BYTE byNewIndex)
404 {
405 if (byNewIndex == byOldIndex) return;
406 Entry[byNewIndex] = Entry[byOldIndex];
407 Entry[byOldIndex].Clear();
408 auto old_entry = std::find_if(Order.begin(), Order.end(),
409 [byOldIndex](const int32_t &entry) { return entry == byOldIndex; });
410 if (old_entry != Order.end()) *old_entry = byNewIndex;
411 fEntriesAdded = true;
412 }
413
GetIndex(const char * szMaterial,const char * szTexture,bool fAddIfNotExist,const char * szErrorIfFailed)414 int32_t C4TextureMap::GetIndex(const char *szMaterial, const char *szTexture, bool fAddIfNotExist, const char *szErrorIfFailed)
415 {
416 BYTE byIndex;
417 // Find existing
418 for (byIndex = 1; byIndex < C4M_MaxTexIndex; byIndex++)
419 if (!Entry[byIndex].isNull())
420 if (SEqualNoCase(Entry[byIndex].GetMaterialName(), szMaterial))
421 if (!szTexture || SEqualNoCase(Entry[byIndex].GetTextureName(), szTexture))
422 return byIndex;
423 // Add new entry
424 if (fAddIfNotExist)
425 for (byIndex=1; byIndex<C4M_MaxTexIndex; byIndex++)
426 if (Entry[byIndex].isNull())
427 {
428 if (AddEntry(byIndex, szMaterial, szTexture))
429 {
430 fEntriesAdded=true;
431 return byIndex;
432 }
433 if (szErrorIfFailed) DebugLogF("Error getting MatTex %s-%s for %s from TextureMap: Init failed.", szMaterial, szTexture, szErrorIfFailed);
434 return 0;
435 }
436 // Else, fail
437 if (szErrorIfFailed) DebugLogF("Error getting MatTex %s-%s for %s from TextureMap: %s.", szMaterial, szTexture, szErrorIfFailed, fAddIfNotExist ? "Map is full!" : "Entry not found.");
438 return 0;
439 }
440
GetIndexMatTex(const char * szMaterialTexture,const char * szDefaultTexture,bool fAddIfNotExist,const char * szErrorIfFailed)441 int32_t C4TextureMap::GetIndexMatTex(const char *szMaterialTexture, const char *szDefaultTexture, bool fAddIfNotExist, const char *szErrorIfFailed)
442 {
443 // split material/texture pair
444 StdStrBuf Material, Texture;
445 Material.CopyUntil(szMaterialTexture, '-');
446 Texture.Copy(SSearch(szMaterialTexture, "-"));
447 // texture not given or invalid?
448 int32_t iMatTex = 0;
449 if (Texture.getData())
450 if ((iMatTex = GetIndex(Material.getData(), Texture.getData(), fAddIfNotExist)))
451 return iMatTex;
452 if (szDefaultTexture)
453 if ((iMatTex = GetIndex(Material.getData(), szDefaultTexture, fAddIfNotExist)))
454 return iMatTex;
455 // search material
456 long iMaterial = ::MaterialMap.Get(szMaterialTexture);
457 if (!MatValid(iMaterial))
458 {
459 if (szErrorIfFailed) DebugLogF("Error getting MatTex for %s: Invalid material", szErrorIfFailed);
460 return 0;
461 }
462 // return default map entry
463 return ::MaterialMap.Map[iMaterial].DefaultMatTex;
464 }
465
GetTexture(const char * szTexture)466 C4Texture * C4TextureMap::GetTexture(const char *szTexture)
467 {
468 C4Texture *pTexture;
469 for (pTexture=FirstTexture; pTexture; pTexture=pTexture->Next)
470 if (SEqualNoCase(pTexture->Name.getData(),szTexture))
471 return pTexture;
472 return nullptr;
473 }
474
GetTextureIndex(const char * szName)475 int32_t C4TextureMap::GetTextureIndex(const char *szName)
476 {
477 C4Texture *pTexture;
478 int32_t i=0;
479 for (pTexture=FirstTexture; pTexture; pTexture=pTexture->Next, i++)
480 if (SEqualNoCase(pTexture->Name.getData(),szName))
481 return i;
482 return -1;
483 }
484
CheckTexture(const char * szTexture)485 bool C4TextureMap::CheckTexture(const char *szTexture)
486 {
487 C4Texture *pTexture;
488 for (pTexture=FirstTexture; pTexture; pTexture=pTexture->Next)
489 if (SEqualNoCase(pTexture->Name.getData(),szTexture))
490 return true;
491 return false;
492 }
493
GetTexture(int32_t iIndex)494 const char* C4TextureMap::GetTexture(int32_t iIndex)
495 {
496 C4Texture *pTexture;
497 int32_t cindex;
498 for (pTexture=FirstTexture,cindex=0; pTexture; pTexture=pTexture->Next,cindex++)
499 if (cindex==iIndex)
500 return pTexture->Name.getData();
501 return nullptr;
502 }
503
DefaultBkgMatTex(BYTE fg) const504 BYTE C4TextureMap::DefaultBkgMatTex(BYTE fg) const
505 {
506 // For the given foreground index, find the default background index
507 // If fg is semisolid, this is tunnel.
508 // Otherwise it is fg itself, so that tunnel and background bricks
509 // stay the way they are.
510 int32_t iTex = PixCol2Tex(fg);
511 if (!iTex) return fg; // sky
512
513 // Get material-texture mapping
514 const C4TexMapEntry *pTex = GetEntry(iTex);
515 // Texmap entry does not exist
516 if(!pTex || !pTex->GetMaterial()) return fg;
517
518 if(DensitySemiSolid(pTex->GetMaterial()->Density))
519 return Mat2PixColDefault(MTunnel);
520
521 return fg;
522
523 }
524
RemoveEntry(int32_t iIndex)525 void C4TextureMap::RemoveEntry(int32_t iIndex)
526 {
527 // remove entry from table and order vector
528 if (Inside<int32_t>(iIndex, 1, C4M_MaxTexIndex - 1))
529 {
530 Entry[iIndex].Clear();
531 auto last_entry = std::remove_if(Order.begin(), Order.end(),
532 [iIndex](const int32_t &entry) { return entry == iIndex; });
533 Order.erase(last_entry, Order.end());
534 }
535 }
536
StoreMapPalette(CStdPalette * Palette,C4MaterialMap & rMaterial)537 void C4TextureMap::StoreMapPalette(CStdPalette *Palette, C4MaterialMap &rMaterial)
538 {
539 // Sky color
540 Palette->Colors[0] = C4RGB(192, 196, 252);
541 // Material colors by texture map entries
542 bool fSet[C4M_MaxTexIndex];
543 ZeroMem(&fSet, sizeof (fSet));
544 int32_t i;
545 for (i = 1; i < C4M_MaxTexIndex; i++)
546 {
547 // Find material
548 DWORD dwPix;
549 auto texture = GetTexture(Entry[i].GetTextureName());
550 if (texture)
551 dwPix = texture->GetAverageColor();
552 else
553 dwPix = Entry[i].GetPattern().PatternClr(0, 0);
554 Palette->Colors[i] = dwPix;
555 fSet[i] = true;
556 }
557 // Crosscheck colors, change equal palette entries
558 for (i = 0; i < C4M_MaxTexIndex; i++) if (fSet[i])
559 for (;;)
560 {
561 // search equal entry
562 int32_t j = 0;
563 for (; j < i; j++)
564 if (fSet[j] && Palette->Colors[i] == Palette->Colors[j])
565 break;
566 // not found? ok then
567 if (j >= i) break;
568 // change randomly
569 Palette->Colors[i] = C4RGB(
570 UnsyncedRandom(2) ? GetRedValue(Palette->Colors[i]) + 3 : GetRedValue(Palette->Colors[i]) - 3,
571 UnsyncedRandom(2) ? GetGreenValue(Palette->Colors[i]) + 3 : GetGreenValue(Palette->Colors[i]) - 3,
572 UnsyncedRandom(2) ? GetBlueValue(Palette->Colors[i]) + 3 : GetBlueValue(Palette->Colors[i]) - 3);
573 }
574 }
575
576 C4TextureMap TextureMap;
577