Files
noggit-red/src/noggit/texture_set.cpp

1679 lines
42 KiB
C++

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/Brush.h>
#include <noggit/Log.h>
#include <noggit/MapTile.h>
#include <noggit/Misc.h>
#include <noggit/TextureManager.h> // TextureManager, Texture
#include <noggit/World.h>
#include <noggit/texture_set.hpp>
#include <ClientFile.hpp>
#include <algorithm> // std::min
#include <array>
#include <sstream>
TextureSet::TextureSet (MapChunk* chunk, BlizzardArchive::ClientFile* f, size_t base
, bool use_big_alphamaps, bool do_not_fix_alpha_map, bool do_not_convert_alphamaps
, Noggit::NoggitRenderContext context, MapChunkHeader const& header)
: nTextures(header.nLayers)
, _do_not_convert_alphamaps(do_not_convert_alphamaps)
, _context(context)
, _chunk(chunk)
{
std::copy(header.doodadMapping, header.doodadMapping + 8, _doodadMapping.begin());
std::copy(header.doodadStencil, header.doodadStencil + 8, _doodadStencil.begin());
if (nTextures)
{
f->seek(base + header.ofsLayer + 8);
ENTRY_MCLY tmp_entry_mcly[4];
for (size_t i = 0; i<nTextures; ++i)
{
f->read (&tmp_entry_mcly[i], sizeof(ENTRY_MCLY)); // f->read (&_layers_info[i], sizeof(ENTRY_MCLY));
std::string const& texturefilename = chunk->mt->mTextureFilenames[tmp_entry_mcly[i].textureID];
textures.emplace_back (texturefilename, _context);
if (chunk->mt->_mtxf_entries.contains(texturefilename))
{
if (chunk->mt->_mtxf_entries[texturefilename].use_cubemap)
textures.back().use_cubemap = true;
}
_layers_info[i].effectID = tmp_entry_mcly[i].effectID;
_layers_info[i].flags = tmp_entry_mcly[i].flags;
}
size_t alpha_base = base + header.ofsAlpha + 8;
for (unsigned int layer = 0; layer < nTextures; ++layer)
{
if (_layers_info[layer].flags & FLAG_USE_ALPHA)
{
f->seek (alpha_base + tmp_entry_mcly[layer].ofsAlpha);
alphamaps[layer - 1] = std::make_unique<Alphamap>(f, _layers_info[layer].flags, use_big_alphamaps, do_not_fix_alpha_map);
}
}
// always use big alpha for editing / rendering
if (!use_big_alphamaps && !_do_not_convert_alphamaps)
{
convertToBigAlpha();
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
}
}
int TextureSet::addTexture (scoped_blp_texture_reference texture)
{
int texLevel = -1;
if (nTextures < 4U)
{
texLevel = static_cast<int>(nTextures);
nTextures++;
textures.emplace_back (std::move (texture));
_layers_info[texLevel] = layer_info();
if (texLevel)
{
alphamaps[texLevel - 1] = std::make_unique<Alphamap>();
}
if (tmp_edit_values && nTextures == 1)
{
tmp_edit_values->map[0].fill(255.f);
}
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return texLevel;
}
bool TextureSet::replace_texture (scoped_blp_texture_reference const& texture_to_replace, scoped_blp_texture_reference replacement_texture)
{
int texture_to_replace_level = -1, replacement_texture_level = -1;
for (int i = 0; i < nTextures; ++i)
{
if (textures[i] == texture_to_replace)
{
texture_to_replace_level = i;
}
else if (textures[i] == replacement_texture)
{
replacement_texture_level = i;
}
}
if (texture_to_replace_level != -1)
{
textures[texture_to_replace_level] = std::move (replacement_texture);
QSettings settings;
bool do_merge_layers = settings.value("texture_merge_layers", true).toBool();
// prevent texture duplication
if (replacement_texture_level != -1 && replacement_texture_level != texture_to_replace_level)
{
if (!do_merge_layers)
{
auto sstream = std::stringstream();
sstream << "error_" << replacement_texture_level << ".blp";
std::string fallback_tex_name = sstream.str();
auto fallback = scoped_blp_texture_reference(fallback_tex_name, _context);
textures[replacement_texture_level] = std::move(fallback);
}
else
{
// temp alphamap changes are applied in here
merge_layers(texture_to_replace_level, replacement_texture_level);
}
}
}
if (texture_to_replace_level != -1 || replacement_texture_level != -1)
return true;
else
return false;
}
void TextureSet::swap_layers(int layer_1, int layer_2)
{
int lower_texture_id = std::min(layer_1, layer_2);
int upper_texture_id = std::max(layer_1, layer_2);
if (lower_texture_id == upper_texture_id)
{
return;
}
if (lower_texture_id > upper_texture_id)
{
std::swap(lower_texture_id, upper_texture_id);
}
if (lower_texture_id >= 0 && upper_texture_id >= 0 && lower_texture_id < nTextures && upper_texture_id < nTextures)
{
apply_alpha_changes();
std::swap(textures[lower_texture_id], textures[upper_texture_id]);
std::swap(_layers_info[lower_texture_id], _layers_info[upper_texture_id]);
int a1 = lower_texture_id - 1, a2 = upper_texture_id - 1;
if (lower_texture_id)
{
std::swap(alphamaps[a1], alphamaps[a2]);
}
else
{
uint8_t alpha[4096];
for (int i = 0; i < 4096; ++i)
{
alpha[i] = 255 - sum_alpha(i);
}
alphamaps[a2]->setAlpha(alpha);
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
}
}
void TextureSet::eraseTextures()
{
if (nTextures == 0)
{
return;
}
textures.clear();
for (int i = 0; i < 4; ++i)
{
if (i > 0)
{
alphamaps[i - 1].reset();
}
_layers_info[i] = layer_info();
}
nTextures = 0;
std::fill(_doodadMapping.begin(), _doodadMapping.end(), 0);
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
tmp_edit_values.reset();
}
void TextureSet::eraseTexture(size_t id)
{
if (id >= nTextures)
{
return;
}
// shift textures above
for (size_t i = id; i < nTextures - 1; i++)
{
if (i)
{
alphamaps[i - 1].reset();
std::swap (alphamaps[i - 1], alphamaps[i]);
}
if (tmp_edit_values)
{
tmp_edit_values->map[i].swap(tmp_edit_values->map[i+1]);
}
_layers_info[i] = _layers_info[i + 1];
}
if (nTextures > 1)
{
alphamaps[nTextures - 2].reset();
}
nTextures--;
textures.erase(textures.begin() + id);
// erase the old info as a precaution but it's overriden when adding a new texture
_layers_info[nTextures] = layer_info();
// set the default values for the temporary alphamap too
if (tmp_edit_values)
{
tmp_edit_values->map[nTextures].fill(0.f);
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
}
bool TextureSet::canPaintTexture(scoped_blp_texture_reference const& texture)
{
if (nTextures)
{
for (size_t k = 0; k < nTextures; ++k)
{
if (textures[k] == texture)
{
return true;
}
}
return nTextures < 4;
}
return true;
}
const std::string& TextureSet::filename(size_t id)
{
return textures[id]->file_key().filepath();
}
bool TextureSet::eraseUnusedTextures()
{
if (nTextures < 2)
{
return false;
}
QSettings settings;
bool cleanup_unused_textures = settings.value("cleanup_unused_textures", true).toBool();
if (!cleanup_unused_textures)
{
return false;
}
std::set<int> visible_tex;
if (tmp_edit_values)
{
auto& amaps = *tmp_edit_values;
for (int layer = 0; layer < nTextures && visible_tex.size() < nTextures; ++layer)
{
for (int i = 0; i < 4096; ++i)
{
if (amaps[layer][i] > 0.f)
{
visible_tex.emplace(layer);
break; // texture visible, go to the next layer
}
}
}
}
else
{
for (int i = 0; i < 4096 && visible_tex.size() < nTextures; ++i)
{
uint8_t sum = 0;
for (int n = 0; n < nTextures - 1; ++n)
{
uint8_t a = alphamaps[n]->getAlpha(i);
sum += a;
if (a > 0)
{
visible_tex.emplace(n + 1);
}
}
// base layer visible
if (sum < 255)
{
visible_tex.emplace(0);
}
}
}
if (visible_tex.size() < nTextures)
{
for (int i = static_cast<int>(nTextures) - 1; i >= 0; --i)
{
if (visible_tex.find(i) == visible_tex.end())
{
eraseTexture(i);
}
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return true;
}
return false;
}
int TextureSet::texture_id(scoped_blp_texture_reference const& texture)
{
for (int i = 0; i < nTextures; ++i)
{
if (textures[i] == texture)
{
return i;
}
}
return -1;
}
int TextureSet::get_texture_index_or_add (scoped_blp_texture_reference texture, float target)
{
for (int i = 0; i < nTextures; ++i)
{
if (textures[i] == texture)
{
return i;
}
}
// don't add a texture for nothing
if (target == 0)
{
return -1;
}
if (nTextures == 4 && !eraseUnusedTextures())
{
return -1;
}
return addTexture (std::move (texture));
}
std::array<std::array<std::uint8_t, 8>, 8> const TextureSet::getDoodadMappingReadable()
{
std::array<std::array<std::uint8_t, 8>, 8> doodad_mapping{};
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
doodad_mapping[y][x] = getDoodadActiveLayerIdAt(x, y);
}
}
return doodad_mapping;
}
uint8_t const TextureSet::getDoodadActiveLayerIdAt(unsigned int x, unsigned int y)
{
if (x >= 8 || y >= 8)
return 0; // not valid
unsigned int firstbit_pos = x * 2;
// bool first_bit = _doodadMapping[y] & (1 << firstbit_pos);
// bool second_bit = _doodadMapping[y] & (1 << (firstbit_pos + 1) );
// uint8_t layer_id = first_bit | (second_bit << 1); // first_bit + second_bit * 2;
uint8_t layer_id = (_doodadMapping[y] >> firstbit_pos) & 0x03;
return layer_id;
}
bool const TextureSet::getDoodadDisabledAt(int x, int y)
{
if (x >= 8 || y >= 8)
return true; // not valid. default to enabled
// X and Y are swapped
bool is_enabled = _doodadStencil[y] & (1 << (x));
return is_enabled;
}
void TextureSet::setDetailDoodadsExclusion(float xbase, float zbase, glm::vec3 const& pos, float radius, bool big, bool add)
{
// big = fill chunk
if (big)
{
std::fill(_doodadStencil.begin(), _doodadStencil.end(), add ? 0xFF : 0x0);
}
else
{
for (int x = 0; x < 8; ++x)
{
for (int y = 0; y < 8; ++y)
{
if (misc::getShortestDist(pos.x, pos.z, xbase + (UNITSIZE * x),
zbase + (UNITSIZE * y), UNITSIZE) <= radius)
{
int v = (1 << (x));
if (add)
_doodadStencil[y] |= v;
else
_doodadStencil[y] &= ~v;
}
}
}
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::DETAILDOODADS_EXCLUSION);
}
bool TextureSet::stampTexture(float xbase, float zbase, float x, float z, Brush* brush, float strength, float pressure, scoped_blp_texture_reference texture, QImage* image, bool paint)
{
bool changed = false;
float zPos, xPos, dist, radius;
int tex_layer = get_texture_index_or_add (std::move (texture), strength);
if (tex_layer == -1 || nTextures == 1)
{
return nTextures == 1;
}
radius = brush->getRadius();
create_temporary_alphamaps_if_needed();
auto& amaps = *tmp_edit_values;
zPos = zbase;
for (int j = 0; j < 64; j++)
{
xPos = xbase;
for (int i = 0; i < 64; ++i)
{
if(std::abs(x - (xPos + TEXDETAILSIZE / 2.0)) > radius || std::abs(z - (zPos + TEXDETAILSIZE / 2.0f)) > radius)
{
xPos += TEXDETAILSIZE;
continue;
}
dist = misc::dist(x, z, xPos + TEXDETAILSIZE / 2.0f, zPos + TEXDETAILSIZE / 2.0f);
glm::vec3 const diff{glm::vec3{xPos + TEXDETAILSIZE / 2.0f, 0.f, zPos + TEXDETAILSIZE / 2.0f} - glm::vec3{x, 0.f, z}};
int pixel_x = std::round(((diff.x + radius) / (2.f * radius)) * image->width());
int pixel_y = std::round(((diff.z + radius) / (2.f * radius)) * image->height());
float image_factor;
if (pixel_x >= 0 && pixel_x < image->width() && pixel_y >= 0 && pixel_y < image->height())
{
auto color = image->pixelColor(pixel_x, pixel_y);
image_factor = (color.redF() + color.greenF() + color.blueF()) / 3.0f;
}
else
{
image_factor = 0;
}
std::size_t offset = i + 64 * j;
// use double for more precision
std::array<double,4> alpha_values;
double total = 0.;
for (int n = 0; n < 4; ++n)
{
total += alpha_values[n] = amaps[n][i + 64 * j];
}
double current_alpha = alpha_values[tex_layer];
double sum_other_alphas = (total - current_alpha);
double alpha_change = image_factor * (strength - current_alpha) * pressure;
// alpha too low, set it to 0 directly
if (alpha_change < 0. && current_alpha + alpha_change < 1.)
{
alpha_change = -current_alpha;
}
if (misc::float_equals(current_alpha, strength))
{
xPos += TEXDETAILSIZE;
continue;
}
if (sum_other_alphas < 1.)
{
// alpha is currently at 254/255 -> set it at 255 and clear the rest of the values
if (alpha_change > 0.f)
{
for (int layer = 0; layer < nTextures; ++layer)
{
alpha_values[layer] = layer == tex_layer ? 255. : 0.f;
}
}
// all the other textures amount for less an 1/255 -> add the alpha_change (negative) to current texture and remove it from the first non current texture, clear the rest
else
{
bool change_applied = false;
for (int layer = 0; layer < nTextures; ++layer)
{
if (layer == tex_layer)
{
alpha_values[layer] += alpha_change;
}
else
{
if (!change_applied)
{
alpha_values[layer] -= alpha_change;
}
else
{
alpha_values[tex_layer] += alpha_values[layer];
alpha_values[layer] = 0.;
}
change_applied = true;
}
}
}
}
else
{
for (int layer = 0; layer < nTextures; ++layer)
{
if (layer == tex_layer)
{
alpha_values[layer] += alpha_change;
}
else
{
alpha_values[layer] -= alpha_change * alpha_values[layer] / sum_other_alphas;
// clear values too low to be visible
if (alpha_values[layer] < 1.)
{
alpha_values[tex_layer] += alpha_values[layer];
alpha_values[layer] = 0.f;
}
}
}
}
double total_final = std::accumulate(alpha_values.begin(), alpha_values.end(), 0.);
// failsafe in case the sum of all alpha values deviate
if (std::abs(total_final - 255.) > 0.001)
{
for (double& d : alpha_values)
{
d = d * 255. / total_final;
}
}
for (int n = 0; n < 4; ++n)
{
amaps[n][i + 64 * j] = static_cast<float>(alpha_values[n]);
}
changed = true;
xPos += TEXDETAILSIZE;
}
zPos += TEXDETAILSIZE;
}
if (!changed)
{
return false;
}
// cleanup
eraseUnusedTextures();
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return true;
/*
bool changed = false;
float zPos, xPos, dist, radius;
int tex_layer = get_texture_index_or_add (std::move (texture), strength);
if (tex_layer == -1 || nTextures == 1)
{
return nTextures == 1;
}
radius = brush->getRadius();
if (misc::getShortestDist(x, z, xbase, zbase, CHUNKSIZE) > radius)
{
return changed;
}
create_temporary_alphamaps_if_needed();
auto& amaps = tmp_edit_values.get();
zPos = zbase;
for (int j = 0; j < 64; j++)
{
xPos = xbase;
for (int i = 0; i < 64; ++i)
{
if(std::abs(x - (xPos + TEXDETAILSIZE / 2.0)) > radius || std::abs(z - (zPos + TEXDETAILSIZE / 2.0f)) > radius)
continue;
dist = misc::dist(x, z, xPos + TEXDETAILSIZE / 2.0f, zPos + TEXDETAILSIZE / 2.0f);
glm::vec3 const diff{glm::vec3{xPos + TEXDETAILSIZE / 2.0f, 0.f, zPos + TEXDETAILSIZE / 2.0f} - glm::vec3{x, 0.f, z}};
int pixel_x = std::floor(diff.x + radius);
int pixel_y = std::floor(diff.z + radius);
auto color = image->pixelColor(pixel_x, pixel_y);
float image_factor = (color.redF() + color.greenF() + color.blueF()) / 3.0f;
std::size_t offset = i + 64 * j;
float current_alpha = amaps[tex_layer][i + 64 * j];
float sum_other_alphas = 1.f - current_alpha;
float alpha_change = image_factor * ((strength - current_alpha) * pressure * brush->getValue(dist));
if (misc::float_equals(current_alpha, strength))
continue;
float totOthers = 0.0f;
for (int layer = 0; layer < nTextures; ++layer)
{
if (layer == tex_layer)
{
amaps[layer][offset] += alpha_change;
}
else
{
amaps[layer][offset] -= alpha_change * (amaps[layer][offset] / sum_other_alphas);
}
if (amaps[layer][offset] > 1.0f)
amaps[layer][offset] = 1.0f;
else if (amaps[layer][offset] < 0.0f || isnan(amaps[layer][offset]))
amaps[layer][offset] = 0.0f;
if(layer != 0)
totOthers += amaps[layer][offset];
}
if (totOthers > 1)
{
float mul = (1 / totOthers);
amaps[1][offset] = amaps[1][offset] * mul;
totOthers = amaps[1][offset];
if (nTextures > 2)
{
amaps[2][offset] = amaps[2][offset] * mul;
totOthers += amaps[2][offset];
}
if (nTextures > 3)
{
amaps[3][offset] = amaps[3][offset] * mul;
totOthers += amaps[3][offset];
}
}
amaps[0][offset] = std::max(0.0f, std::min(1.0f - totOthers, 1.0f));
changed = true;
xPos += TEXDETAILSIZE;
}
zPos += TEXDETAILSIZE;
}
if (!changed)
{
return false;
}
// cleanup
eraseUnusedTextures();
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return true;
*/
}
bool TextureSet::paintTexture(float xbase, float zbase, float x, float z, Brush* brush, float strength, float pressure, scoped_blp_texture_reference texture)
{
bool changed = false;
float zPos, xPos, dist, radius;
// todo: investigate the root cause
// shift brush origin to avoid disconnects at the chunks' borders
if (x < xbase)
{
float chunk_dist = std::abs(x - (std::fmod(x, CHUNKSIZE)) - xbase) / CHUNKSIZE;
x += TEXDETAILSIZE * chunk_dist;
}
if (x > xbase + CHUNKSIZE)
{
float chunk_dist = std::abs(x - (std::fmod(x, CHUNKSIZE)) - xbase) / CHUNKSIZE;
x -= TEXDETAILSIZE * chunk_dist;
}
if (z < zbase)
{
float chunk_dist = std::abs(z - (std::fmod(z, CHUNKSIZE)) - zbase) / CHUNKSIZE;
z += TEXDETAILSIZE * chunk_dist;
}
if (z > zbase + CHUNKSIZE)
{
float chunk_dist = std::abs(z - (std::fmod(z, CHUNKSIZE)) - zbase) / CHUNKSIZE;
z -= TEXDETAILSIZE * chunk_dist;
}
int tex_layer = get_texture_index_or_add (std::move (texture), strength);
if (tex_layer == -1 || nTextures == 1)
{
return nTextures == 1;
}
radius = brush->getRadius();
if (misc::getShortestDist(x, z, xbase, zbase, CHUNKSIZE) > radius)
{
return changed;
}
create_temporary_alphamaps_if_needed();
auto& amaps = *tmp_edit_values;
zPos = zbase;
for (int j = 0; j < 64; j++)
{
xPos = xbase;
for (int i = 0; i < 64; ++i)
{
dist = misc::getShortestDist(x, z, xPos, zPos, TEXDETAILSIZE);
if (dist <= radius)
{
std::size_t offset = i + 64 * j;
// use double for more precision
std::array<double,4> alpha_values;
double total = 0.;
for (int n = 0; n < 4; ++n)
{
total += alpha_values[n] = amaps[n][i + 64 * j];
}
double current_alpha = alpha_values[tex_layer];
double sum_other_alphas = (total - current_alpha);
double alpha_change = (strength - current_alpha) * pressure * brush->getValue(dist);
// alpha too low, set it to 0 directly
if (alpha_change < 0. && current_alpha + alpha_change < 1.)
{
alpha_change = -current_alpha;
}
if (!misc::float_equals(current_alpha, strength))
{
if (sum_other_alphas < 1.)
{
// alpha is currently at 254/255 -> set it at 255 and clear the rest of the values
if (alpha_change > 0.f)
{
for (int layer = 0; layer < nTextures; ++layer)
{
alpha_values[layer] = layer == tex_layer ? 255. : 0.f;
}
}
// all the other textures amount for less an 1/255 -> add the alpha_change (negative) to current texture and remove it from the first non current texture, clear the rest
else
{
bool change_applied = false;
for (int layer = 0; layer < nTextures; ++layer)
{
if (layer == tex_layer)
{
alpha_values[layer] += alpha_change;
}
else
{
if (!change_applied)
{
alpha_values[layer] -= alpha_change;
}
else
{
alpha_values[tex_layer] += alpha_values[layer];
alpha_values[layer] = 0.;
}
change_applied = true;
}
}
}
}
else
{
for (int layer = 0; layer < nTextures; ++layer)
{
if (layer == tex_layer)
{
alpha_values[layer] += alpha_change;
}
else
{
alpha_values[layer] -= alpha_change * alpha_values[layer] / sum_other_alphas;
// clear values too low to be visible
if (alpha_values[layer] < 1.)
{
alpha_values[tex_layer] += alpha_values[layer];
alpha_values[layer] = 0.f;
}
}
}
}
double total_final = std::accumulate(alpha_values.begin(), alpha_values.end(), 0.);
// failsafe in case the sum of all alpha values deviate
if (std::abs(total_final - 255.) > 0.001)
{
for (double& d : alpha_values)
{
d = d * 255. / total_final;
}
}
for (int n = 0; n < 4; ++n)
{
amaps[n][i + 64 * j] = static_cast<float>(alpha_values[n]);
}
changed = true;
}
}
xPos += TEXDETAILSIZE;
}
zPos += TEXDETAILSIZE;
}
if (!changed)
{
return false;
}
// cleanup
eraseUnusedTextures();
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return true;
}
bool TextureSet::replace_texture( float xbase
, float zbase
, float x
, float z
, float radius
, scoped_blp_texture_reference const& texture_to_replace
, scoped_blp_texture_reference replacement_texture
, bool entire_chunk
)
{
float dist = misc::getShortestDist(x, z, xbase, zbase, CHUNKSIZE);
if (!entire_chunk && dist > radius)
{
return false;
}
if (entire_chunk)
{
replace_texture(texture_to_replace, std::move (replacement_texture));
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
return true;
}
// if the chunk is fully inside the brush, just swap the 2 textures
if (misc::square_is_in_circle(x, z, radius, xbase, zbase, CHUNKSIZE))
{
replace_texture(texture_to_replace, std::move (replacement_texture));
return true;
}
bool changed = false;
int old_tex_level = -1, new_tex_level = -1;
float x_pos, z_pos = zbase;
for (int i=0; i<nTextures; ++i)
{
if (textures[i] == texture_to_replace)
{
old_tex_level = i;
}
if (textures[i] == replacement_texture)
{
new_tex_level = i;
}
}
if (old_tex_level == -1 || (new_tex_level == -1 && nTextures == 4 && !eraseUnusedTextures()))
{
return false;
}
if (new_tex_level == -1)
{
new_tex_level = addTexture(std::move (replacement_texture));
}
if (old_tex_level == new_tex_level)
{
return false;
}
create_temporary_alphamaps_if_needed();
auto& amap = *tmp_edit_values;
for (int j = 0; j < 64; j++)
{
x_pos = xbase;
for (int i = 0; i < 64; ++i)
{
dist = misc::dist(x, z, x_pos + TEXDETAILSIZE / 2.0f, z_pos + TEXDETAILSIZE / 2.0f);
if (dist <= radius)
{
int offset = j * 64 + i;
amap[new_tex_level][offset] += amap[old_tex_level][offset];
amap[old_tex_level][offset] = 0.f;
changed = true;
}
x_pos += TEXDETAILSIZE;
}
z_pos += TEXDETAILSIZE;
}
if (changed)
{
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
}
return changed;
}
unsigned int TextureSet::flag(size_t id)
{
return _layers_info[id].flags;
}
unsigned int TextureSet::effect(size_t id)
{
return _layers_info[id].effectID;
}
bool TextureSet::is_animated(std::size_t id) const
{
return (id < nTextures ? (_layers_info[id].flags & FLAG_ANIMATE) : false);
}
void TextureSet::change_texture_flag(scoped_blp_texture_reference const& tex, std::size_t flag, bool add)
{
for (size_t i = 0; i < nTextures; ++i)
{
if (textures[i] == tex)
{
auto flag_view = reinterpret_cast<MCLYFlags*>(&_layers_info[i].flags);
auto flag_view_new = reinterpret_cast<MCLYFlags*>(&flag);
flag_view->animation_speed = flag_view_new->animation_speed;
flag_view->animation_rotation = flag_view_new->animation_rotation;
flag_view->animation_enabled = flag_view_new->animation_enabled;
if (flag & FLAG_GLOW)
{
_layers_info[i].flags |= FLAG_GLOW;
}
else
{
_layers_info[i].flags &= ~FLAG_GLOW;
}
break;
}
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::FLAGS);
}
std::vector<std::vector<uint8_t>> TextureSet::save_alpha(bool big_alphamap)
{
std::vector<std::vector<uint8_t>> amaps;
apply_alpha_changes();
if (nTextures > 1)
{
if (big_alphamap)
{
for (int i = 0; i < nTextures - 1; ++i)
{
const uint8_t* alphamap = alphamaps[i]->getAlpha();
amaps.emplace_back(alphamap, alphamap + 4096);
}
}
else
{
uint8_t tab[4096 * 3];
if (_do_not_convert_alphamaps)
{
for (size_t k = 0; k < nTextures - 1; k++)
{
memcpy(tab + (k*64*64), alphamaps[k]->getAlpha(), 64 * 64);
}
}
else
{
alphas_to_old_alpha(tab);
}
auto const combine_nibble
(
[&] (int layer, int pos)
{
int index = layer * 4096 + pos * 2;
return ((tab[index] & 0xF0) >> 4) | (tab[index + 1] & 0xF0);
}
);
for (int layer = 0; layer < nTextures - 1; ++layer)
{
amaps.emplace_back(2048);
auto& layer_data = amaps.back();
for (int i = 0; i < 2048; ++i)
{
layer_data[i] = combine_nibble(layer, i);
}
}
}
}
return amaps;
}
scoped_blp_texture_reference TextureSet::texture(size_t id)
{
return textures[id];
}
// dest = tab [4096 * (nTextures - 1)]
// call only if nTextures > 1
void TextureSet::alphas_to_big_alpha(uint8_t* dest)
{
auto alpha
(
[&] (int layer, int pos = 0)
{
return dest + layer * 4096 + pos;
}
);
for (int k = 0; k < nTextures - 1; k++)
{
memcpy(alpha(k), alphamaps[k]->getAlpha(), 4096);
}
for (int i = 0; i < 64 * 64; ++i)
{
int a = 255;
for (int k = static_cast<int>(nTextures - 2); k >= 0; --k)
{
// uint8_t val = misc::rounded_255_int_div(*alpha(k, i) * a);
uint8_t* value_ptr = alpha(k, i);
uint8_t val = alpha_convertion_lookup[*value_ptr * a];
a -= val;
// *alpha(k, i) = val;
*value_ptr = val;
}
}
}
void TextureSet::convertToBigAlpha()
{
// nothing to do
if (nTextures < 2)
{
return;
}
std::array<std::uint8_t, 4096 * 3> tab;
apply_alpha_changes();
alphas_to_big_alpha(tab.data());
for (size_t k = 0; k < nTextures - 1; k++)
{
alphamaps[k]->setAlpha(tab.data() + 4096 * k);
}
}
// dest = tab [4096 * (nTextures - 1)]
// call only if nTextures > 1
void TextureSet::alphas_to_old_alpha(uint8_t* dest)
{
auto alpha
(
[&] (int layer, int pos = 0)
{
return dest + layer * 4096 + pos;
}
);
for (int k = 0; k < nTextures - 1; k++)
{
memcpy(alpha(k), alphamaps[k]->getAlpha(), 64 * 64);
}
for (int i = 0; i < 64 * 64; ++i)
{
// a = remaining visibility
int a = 255;
for (int k = static_cast<int>(nTextures - 2); k >= 0; --k)
{
if (a <= 0)
{
*alpha(k, i) = 0;
}
else
{
int current = *alpha(k, i);
// convert big alpha value to old alpha
*alpha(k, i) = misc::rounded_int_div(current * 255, a);
// remove big alpha value from the remaining visibility
a -= current;
}
}
}
}
void TextureSet::convertToOldAlpha()
{
// nothing to do
if (nTextures < 2)
{
return;
}
uint8_t tab[3 * 4096];
apply_alpha_changes();
alphas_to_old_alpha(tab);
for (size_t k = 0; k < nTextures - 1; k++)
{
alphamaps[k]->setAlpha(tab + k * 4096);
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
}
void TextureSet::merge_layers(size_t id1, size_t id2)
{
if (id1 >= nTextures || id2 >= nTextures || id1 == id2)
{
throw std::invalid_argument("merge_layers: invalid layer id(s)");
}
if (id2 < id1)
{
std::swap(id2, id1);
}
create_temporary_alphamaps_if_needed();
auto& amap = *tmp_edit_values;
for (int i = 0; i < 64 * 64; ++i)
{
amap[id1][i] += amap[id2][i];
// no need to set the id alphamap to 0, it'll be done in "eraseTexture(id2)"
}
eraseTexture(id2);
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
}
bool TextureSet::removeDuplicate()
{
bool changed = apply_alpha_changes();
for (size_t i = 0; i < nTextures; i++)
{
for (size_t j = i + 1; j < nTextures; j++)
{
if (textures[i] == textures[j])
{
merge_layers(i, j);
changed = true;
j--; // otherwise it skips the next texture
}
}
}
return changed;
}
void TextureSet::uploadAlphamapData()
{
// This method assumes tile's alphamap storage is currently bound to the current texture unit
if (!(_chunk->getUpdateFlags() & ChunkUpdateFlags::ALPHAMAP) || !nTextures)
return;
static std::array<float, 3 * 64 * 64> amap{};
std::fill(amap.begin(), amap.end(), 0.0f);
if (tmp_edit_values)
{
auto& tmp_amaps = *tmp_edit_values;
for (int i = 0; i < 64 * 64; ++i)
{
for (int alpha_id = 0; alpha_id < 3; ++alpha_id)
{
// amap[i * 3 + alpha_id] = (alpha_id < nTextures - 1) ? tmp_amaps[alpha_id + 1][i] / 255.f : 0.f;
if (alpha_id < nTextures - 1)
amap[i * 3 + alpha_id] = tmp_amaps[alpha_id + 1][i] / 255.f;
}
}
}
else
{
uint8_t const* alpha_ptr[3]{ nullptr, nullptr, nullptr };
for (int i = 0; i < nTextures - 1; ++i)
{
alpha_ptr[i] = alphamaps[i]->getAlpha();
}
for (int i = 0; i < 64 * 64; ++i)
{
for (int alpha_id = 0; alpha_id < 3; ++alpha_id)
{
if (alpha_id < nTextures - 1)
{
amap[i * 3 + alpha_id] = static_cast<float>(*(alpha_ptr[alpha_id]++)) / 255.0f;
}
// else
// {
// amap[i * 3 + alpha_id] = 0.f;
// }
}
}
}
gl.texSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, _chunk->px * 16 + _chunk->py,
64, 64, 1, GL_RGB, GL_FLOAT, amap.data());
}
namespace
{
misc::max_capacity_stack_vector<std::size_t, 4> current_layer_values
(std::uint8_t nTextures, const std::array<std::unique_ptr<Alphamap>, 3>& alphamaps, std::size_t pz, std::size_t px)
{
misc::max_capacity_stack_vector<std::size_t, 4> values (nTextures, 0xFF);
for (std::uint8_t i = 1; i < nTextures; ++i)
{
values[i] = alphamaps[i - 1]->getAlpha(64 * pz + px);
values[0] -= values[i];
}
return values;
}
}
void TextureSet::update_lod_texture_map()
{
std::array<std::uint16_t, 8> lod{};
for (int z = 0; z < 8; ++z)
{
for (int x = 0; x < 8; ++x)
{
misc::max_capacity_stack_vector<std::size_t, 4> dominant_square_count (nTextures);
for (int pz = z * 8; pz < (z + 1) * 8; ++pz)
{
for (int px = x * 8; px < (x + 1) * 8; ++px)
{
++dominant_square_count[max_element_index (current_layer_values (static_cast<std::uint8_t>(nTextures), alphamaps, pz, px))];
}
}
//lod.push_back (max_element_index (dominant_square_count));
}
}
_doodadMapping = lod;
_need_lod_texture_map_update = false;
}
std::array<float, 4> TextureSet::get_textures_weight_for_unit(unsigned int unit_x, unsigned int unit_y)
{
float total_layer_0 = 0.f;
float total_layer_1 = 0.f;
float total_layer_2 = 0.f;
float total_layer_3 = 0.f;
if (nTextures == 0)
return std::array<float, 4> { 0.f, 0.f, 0.f, 0.f };
else if (nTextures == 1)
return std::array<float, 4> { 100.f, 0.f, 0.f, 0.f };
// 8x8 bits per unit
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
float base_alpha = 255.f;
for (int alpha_layer = 0; alpha_layer < (nTextures - 1); ++alpha_layer)
{
float f = static_cast<float>(alphamaps[alpha_layer]->getAlpha( (unit_y * 8 + y)* 64 + (unit_x * 8 + x) )); // getAlpha(64 * y + x))
if (alpha_layer == 0)
total_layer_1 += f;
else if (alpha_layer == 1)
total_layer_2 += f;
else if (alpha_layer == 2)
total_layer_3 += f;
base_alpha -= f;
}
total_layer_0 += base_alpha;
}
}
float sum = total_layer_0 + total_layer_1 + total_layer_2 + total_layer_3;
std::array<float, 4> weights = { total_layer_0 / sum * 100.f,
total_layer_1 / sum * 100.f,
total_layer_2 / sum * 100.f,
total_layer_3 / sum * 100.f };
return weights;
}
void TextureSet::updateDoodadMapping()
{
// NOTE : tempalphamap needs to be applied first with apply_alpha_changes()
std::array<std::uint16_t, 8> new_doodad_mapping{};
// std::array<std::array<std::uint8_t, 8>, 8> new_doodad_mapping{};
// for (auto& row : new_doodad_mapping) {
// row.fill(nTextures - 1);
// }
if (nTextures <= 1)
{
// 0 or 1 layer, we just use the 0 default
_doodadMapping = new_doodad_mapping;
return;
}
// test comparison variables
int matching_count = 0;
int not_matching_count = 0;
int very_innacurate_count = 0;
int higher_count = 0;
int lower_count = 0;
std::array<std::array<std::uint8_t, 8>, 8> blizzard_mapping_readable;
bool debug_test = false;
if (debug_test)
blizzard_mapping_readable = getDoodadMappingReadable();
// 8x8 bits per unit
for (int unit_x = 0; unit_x < 8; unit_x++)
{
for (int unit_y = 0; unit_y < 8; unit_y++)
{
int layer_totals[4]{ 0,0,0,0 };
// 8x8 bits per unit
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
int base_alpha = 255;
for (int alpha_layer = 0; alpha_layer < (nTextures - 1); ++alpha_layer)
{
int alpha = static_cast<int>(alphamaps[alpha_layer]->getAlpha((unit_y * 8 + y) * 64 + (unit_x * 8 + x)));
layer_totals[alpha_layer+1] += alpha;
base_alpha -= alpha;
}
layer_totals[0] += base_alpha;
}
}
// int sum = layer_totals[0] + layer_totals[1] + layer_totals[2] + layer_totals[3];
// std::array<float, 4> percent_weights = { total_layer_0 / sum * 100.f,
// total_layer_1 / sum * 100.f,
// total_layer_2 / sum * 100.f,
// total_layer_3 / sum * 100.f };
int max = layer_totals[0];
int max_layer_index = 0;
for (int i = 1; i < nTextures; i++)
{
// in old azeroth maps superior layers seems to have higher priority
// error margin is ~4% in old azeroth without adjusted weight, 2% with
// error margin is < 0.5% in northrend without adjusting
float adjusted_weight = layer_totals[i] * (1 + 0.01*i); // superior layer seems to have priority, adjust by 1% per layer
// if (layer_totals[i] >= max)
// if (std::floor(weights[i]) >= max)
// if (std::round(weights[i]) >= max)
if (adjusted_weight >= max) // this with 5% works the best in old continents
{
max = layer_totals[i];
max_layer_index = i;
}
}
unsigned int firstbit_pos = unit_x * 2;
new_doodad_mapping[unit_y] |= ((max_layer_index & 3) << firstbit_pos);
// new_chunk_mapping[y][x] = max_layer_index;
// debug compare with original data
if (debug_test)
{
uint8_t blizzard_layer_id = blizzard_mapping_readable[unit_y][unit_x];
uint8_t blizzard_layer_id2 = getDoodadActiveLayerIdAt(unit_x, unit_y); // make sure both work the same
if (blizzard_layer_id != blizzard_layer_id2)
throw;
// bool test_doodads_enabled = local_chunk->getTextureSet()->getDoodadDisabledAt(x, y);
if (max_layer_index < blizzard_layer_id)
lower_count++;
if (max_layer_index > blizzard_layer_id)
higher_count++;
if (max_layer_index != blizzard_layer_id)
{
int blizzard_effect_id = getEffectForLayer(blizzard_layer_id);
int found_effect_id = getEffectForLayer(max_layer_index);
not_matching_count++;
/*
float percent_innacuracy = ((layer_totals[max_layer_index] - layer_totals[blizzard_layer_id]) / ((static_cast<float>(layer_totals[max_layer_index]) + layer_totals[blizzard_layer_id]) / 2)) * 100.f;
if (percent_innacuracy > 15)
very_innacurate_count++;*/
}
else
matching_count++;
}
}
}
_doodadMapping = new_doodad_mapping;
}
uint8_t TextureSet::sum_alpha(size_t offset) const
{
uint8_t sum = 0;
for (auto const& amap : alphamaps)
{
if (amap)
{
sum += amap->getAlpha(offset);
}
}
return sum;
}
namespace
{
inline std::uint8_t float_alpha_to_uint8(float a)
{
return static_cast<std::uint8_t>(std::max(0.f, std::min(255.f, std::round(a))));
}
}
bool TextureSet::apply_alpha_changes()
{
if (!tmp_edit_values || nTextures < 2)
{
tmp_edit_values.reset();
return false;
}
auto& new_amaps = *tmp_edit_values;
std::array<std::uint16_t, 64 * 64> totals;
totals.fill(0);
for (int alpha_layer = 0; alpha_layer < nTextures - 1; ++alpha_layer)
{
std::array<std::uint8_t, 64 * 64> values;
for (int i = 0; i < 64 * 64; ++i)
{
values[i] = float_alpha_to_uint8(new_amaps[alpha_layer + 1][i]);
totals[i] += values[i];
// remove the possible overflow with rounding
// max 2 if all 4 values round up so it won't change the layer's alpha much
if (totals[i] > 255)
{
values[i] -= static_cast<std::uint8_t>(totals[i] - 255);
}
}
alphamaps[alpha_layer]->setAlpha(values.data());
}
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
tmp_edit_values.reset();
return true;
}
void TextureSet::create_temporary_alphamaps_if_needed()
{
if (tmp_edit_values || nTextures < 2)
{
return;
}
tmp_edit_values = std::make_unique<tmp_edit_alpha_values>();
tmp_edit_alpha_values& values = *tmp_edit_values;
for (int i = 0; i < 64 * 64; ++i)
{
float base_alpha = 255.f;
for (int alpha_layer = 0; alpha_layer < nTextures - 1; ++alpha_layer)
{
float f = static_cast<float>(alphamaps[alpha_layer]->getAlpha(i));
values[alpha_layer + 1][i] = f;
base_alpha -= f;
}
values[0][i] = base_alpha;
}
}
void TextureSet::markDirty()
{
_chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
_need_lod_texture_map_update = true;
}
void TextureSet::setEffect(size_t id, int value)
{
_layers_info[id].effectID = value;
_chunk->registerChunkUpdate(ChunkUpdateFlags::FLAGS);
}
std::array<std::uint16_t, 8> TextureSet::lod_texture_map()
{
// make sure all changes have been applied
apply_alpha_changes();
if (_need_lod_texture_map_update)
{
update_lod_texture_map();
}
return _doodadMapping;
}
std::array<std::uint8_t, 256 * 256> TextureSet::alpha_convertion_lookup = TextureSet::make_alpha_lookup_array();