// This file is part of Noggit3, licensed under GNU General Public License (version 3). #include #include #include #include #include // TextureManager, Texture #include #include #include #include // std::min #include #include 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; iread (&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(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(nTextures); nTextures++; textures.emplace_back (std::move (texture)); _layers_info[texLevel] = layer_info(); if (texLevel) { alphamaps[texLevel - 1] = std::make_unique(); } 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 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(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, 8> const TextureSet::getDoodadMappingReadable() { std::array, 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 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(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 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(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; iregisterChunkUpdate(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(&_layers_info[i].flags); auto flag_view_new = reinterpret_cast(&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> TextureSet::save_alpha(bool big_alphamap) { std::vector> 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(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 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(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 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(*(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 current_layer_values (std::uint8_t nTextures, const std::array, 3>& alphamaps, std::size_t pz, std::size_t px) { misc::max_capacity_stack_vector 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 lod{}; for (int z = 0; z < 8; ++z) { for (int x = 0; x < 8; ++x) { misc::max_capacity_stack_vector 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(nTextures), alphamaps, pz, px))]; } } //lod.push_back (max_element_index (dominant_square_count)); } } _doodadMapping = lod; _need_lod_texture_map_update = false; } std::array 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 { 0.f, 0.f, 0.f, 0.f }; else if (nTextures == 1) return std::array { 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(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 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 new_doodad_mapping{}; // std::array, 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, 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(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 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(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::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 totals; totals.fill(0); for (int alpha_layer = 0; alpha_layer < nTextures - 1; ++alpha_layer) { std::array 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(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& 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(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 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 TextureSet::alpha_convertion_lookup = TextureSet::make_alpha_lookup_array();