1679 lines
42 KiB
C++
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(); |