1918 lines
54 KiB
C++
1918 lines
54 KiB
C++
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
|
|
|
|
#include <noggit/Log.h>
|
|
#include <noggit/MapChunk.h>
|
|
#include <noggit/MapTile.h>
|
|
#include <noggit/Misc.h>
|
|
#include <noggit/ModelInstance.h> // ModelInstance
|
|
#include <noggit/ModelManager.h> // ModelManager
|
|
#include <noggit/TileWater.hpp>
|
|
#include <noggit/WMOInstance.h> // WMOInstance
|
|
#include <noggit/World.h>
|
|
#include <noggit/Alphamap.hpp>
|
|
#include <noggit/texture_set.hpp>
|
|
#include <noggit/ui/TexturingGUI.h>
|
|
#include <noggit/application/NoggitApplication.hpp>
|
|
#include <noggit/project/CurrentProject.hpp>
|
|
#include <ClientFile.hpp>
|
|
#include <opengl/scoped.hpp>
|
|
#include <opengl/shader.hpp>
|
|
#include <external/tracy/Tracy.hpp>
|
|
#include <util/CurrentFunction.hpp>
|
|
#include <util/sExtendableArray.hpp>
|
|
|
|
#include <noggit/World.inl>
|
|
#include <QtCore/QSettings>
|
|
|
|
#include <cassert>
|
|
#include <list>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <limits>
|
|
|
|
|
|
MapTile::MapTile( int pX
|
|
, int pZ
|
|
, std::string const& pFilename
|
|
, bool pBigAlpha
|
|
, bool pLoadModels
|
|
, bool use_mclq_green_lava
|
|
, bool reloading_tile
|
|
, World* world
|
|
, Noggit::NoggitRenderContext context
|
|
, tile_mode mode
|
|
, bool pLoadTextures
|
|
)
|
|
: AsyncObject(pFilename)
|
|
, _renderer(this)
|
|
, _fl_bounds_render(this)
|
|
, index(TileIndex(pX, pZ))
|
|
, xbase(pX * TILESIZE)
|
|
, zbase(pZ * TILESIZE)
|
|
, changed(false)
|
|
, Water (this, xbase, zbase, use_mclq_green_lava)
|
|
, _mode(mode)
|
|
, _tile_is_being_reloaded(reloading_tile)
|
|
, mBigAlpha(pBigAlpha)
|
|
, _load_models(pLoadModels)
|
|
, _load_textures(pLoadTextures)
|
|
, _world(world)
|
|
, _context(context)
|
|
, _chunk_update_flags(ChunkUpdateFlags::VERTEX | ChunkUpdateFlags::ALPHAMAP
|
|
| ChunkUpdateFlags::SHADOW | ChunkUpdateFlags::MCCV
|
|
| ChunkUpdateFlags::NORMALS| ChunkUpdateFlags::HOLES
|
|
| ChunkUpdateFlags::AREA_ID| ChunkUpdateFlags::FLAGS
|
|
| ChunkUpdateFlags::GROUND_EFFECT | ChunkUpdateFlags::DETAILDOODADS_EXCLUSION)
|
|
, _extents{glm::vec3{pX * TILESIZE, std::numeric_limits<float>::max(), pZ * TILESIZE},
|
|
glm::vec3{pX * TILESIZE + TILESIZE, std::numeric_limits<float>::lowest(), pZ * TILESIZE + TILESIZE}}
|
|
, _combined_extents{glm::vec3{pX * TILESIZE, std::numeric_limits<float>::max(), pZ * TILESIZE},
|
|
glm::vec3{pX * TILESIZE + TILESIZE, std::numeric_limits<float>::lowest(), pZ * TILESIZE + TILESIZE}}
|
|
, _object_instance_extents{glm::vec3{std::numeric_limits<float>::max()}, glm::vec3{std::numeric_limits<float>::lowest()}}
|
|
, _center{pX * TILESIZE + TILESIZE / 2.f, 0.f, pZ * TILESIZE + TILESIZE / 2.f}
|
|
{
|
|
}
|
|
|
|
MapTile::~MapTile()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> const lock(_mutex);
|
|
|
|
for (auto& pair : object_instances)
|
|
{
|
|
for (auto& instance : pair.second)
|
|
{
|
|
instance->derefTile(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
_world->remove_models_if_needed(uids);
|
|
}
|
|
|
|
void MapTile::waitForChildrenLoaded()
|
|
{
|
|
for (auto& instance : object_instances)
|
|
{
|
|
instance.first->wait_until_loaded();
|
|
instance.first->waitForChildrenLoaded();
|
|
}
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
for (int k = 0; k < mChunks[i][j].get()->texture_set->num(); ++k)
|
|
{
|
|
(*mChunks[i][j].get()->texture_set->getTextures())[k].get()->wait_until_loaded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapTile::finishLoading()
|
|
{
|
|
|
|
if (finished)
|
|
return;
|
|
|
|
BlizzardArchive::ClientFile theFile(_file_key, Noggit::Application::NoggitApplication::instance()->clientData());
|
|
|
|
Log << "Opening tile " << index.x << ", " << index.z << " (\"" << _file_key.stringRepr() << "\") from " << (theFile.isExternal() ? "disk" : "MPQ") << "." << std::endl;
|
|
|
|
// - Parsing the file itself. --------------------------
|
|
|
|
// We store this data to load it at the end.
|
|
uint32_t lMCNKOffsets[256];
|
|
std::vector<ENTRY_MDDF> lModelInstances;
|
|
std::vector<ENTRY_MODF> lWMOInstances;
|
|
|
|
std::vector<std::string> mModelFilenames;
|
|
std::vector<std::string> mWMOFilenames;
|
|
|
|
// std::map<std::string, mtxf_entry> _mtxf_entries;
|
|
|
|
uint32_t fourcc;
|
|
uint32_t size;
|
|
|
|
MHDR Header;
|
|
|
|
// - MVER ----------------------------------------------
|
|
|
|
uint32_t version;
|
|
|
|
theFile.read(&fourcc, 4);
|
|
theFile.seekRelative(4);
|
|
theFile.read(&version, 4);
|
|
|
|
assert(fourcc == 'MVER' && version == 18);
|
|
|
|
// - MHDR ----------------------------------------------
|
|
|
|
theFile.read(&fourcc, 4);
|
|
theFile.seekRelative(4);
|
|
|
|
assert(fourcc == 'MHDR');
|
|
|
|
theFile.read(&Header, sizeof(MHDR));
|
|
|
|
mFlags = Header.flags;
|
|
|
|
// - MCIN ----------------------------------------------
|
|
|
|
theFile.seek(Header.mcin + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.seekRelative(4);
|
|
|
|
assert(fourcc == 'MCIN');
|
|
|
|
for (int i = 0; i < 256; ++i)
|
|
{
|
|
theFile.read(&lMCNKOffsets[i], 4);
|
|
theFile.seekRelative(0xC);
|
|
}
|
|
|
|
// - MTEX ----------------------------------------------
|
|
|
|
if (_load_textures)
|
|
{
|
|
theFile.seek(Header.mtex + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MTEX');
|
|
|
|
{
|
|
char const* lCurPos = reinterpret_cast<char const*>(theFile.getPointer());
|
|
char const* lEnd = lCurPos + size;
|
|
|
|
while (lCurPos < lEnd)
|
|
{
|
|
mTextureFilenames.push_back(BlizzardArchive::ClientData::normalizeFilenameInternal(std::string(lCurPos)));
|
|
lCurPos += strlen(lCurPos) + 1;
|
|
}
|
|
}
|
|
}
|
|
if (_load_models)
|
|
{
|
|
// - MMDX ----------------------------------------------
|
|
|
|
theFile.seek(Header.mmdx + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MMDX');
|
|
|
|
{
|
|
char const* lCurPos = reinterpret_cast<char const*>(theFile.getPointer());
|
|
char const* lEnd = lCurPos + size;
|
|
|
|
while (lCurPos < lEnd)
|
|
{
|
|
mModelFilenames.push_back(BlizzardArchive::ClientData::normalizeFilenameInternal(std::string(lCurPos)));
|
|
lCurPos += strlen(lCurPos) + 1;
|
|
}
|
|
}
|
|
|
|
// - MWMO ----------------------------------------------
|
|
|
|
theFile.seek(Header.mwmo + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MWMO');
|
|
|
|
{
|
|
char const* lCurPos = reinterpret_cast<char const*>(theFile.getPointer());
|
|
char const* lEnd = lCurPos + size;
|
|
|
|
while (lCurPos < lEnd)
|
|
{
|
|
mWMOFilenames.push_back(BlizzardArchive::ClientData::normalizeFilenameInternal(std::string(lCurPos)));
|
|
lCurPos += strlen(lCurPos) + 1;
|
|
}
|
|
}
|
|
|
|
// - MDDF ----------------------------------------------
|
|
|
|
theFile.seek(Header.mddf + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MDDF');
|
|
|
|
ENTRY_MDDF const* mddf_ptr = reinterpret_cast<ENTRY_MDDF const*>(theFile.getPointer());
|
|
for (unsigned int i = 0; i < size / sizeof(ENTRY_MDDF); ++i)
|
|
{
|
|
lModelInstances.push_back(mddf_ptr[i]);
|
|
}
|
|
|
|
// - MODF ----------------------------------------------
|
|
|
|
theFile.seek(Header.modf + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MODF');
|
|
|
|
ENTRY_MODF const* modf_ptr = reinterpret_cast<ENTRY_MODF const*>(theFile.getPointer());
|
|
for (unsigned int i = 0; i < size / sizeof(ENTRY_MODF); ++i)
|
|
{
|
|
lWMOInstances.push_back(modf_ptr[i]);
|
|
if(lWMOInstances[i].scale == 0.0f)
|
|
lWMOInstances[i].scale = 1024.0f;
|
|
}
|
|
}
|
|
|
|
// - MISC ----------------------------------------------
|
|
|
|
//! \todo Parse all chunks in the new style!
|
|
|
|
// - MH2O ----------------------------------------------
|
|
if (Header.mh2o != 0) {
|
|
theFile.seek(Header.mh2o + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
int ofsW = Header.mh2o + 0x14 + 0x8;
|
|
assert(fourcc == 'MH2O');
|
|
|
|
Water.readFromFile(theFile, ofsW);
|
|
|
|
// Water.update_underground_vertices_depth();
|
|
}
|
|
|
|
// - MFBO ----------------------------------------------
|
|
|
|
if (mFlags & 1)
|
|
{
|
|
theFile.seek(Header.mfbo + 0x14);
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MFBO');
|
|
|
|
int16_t mMaximum[9], mMinimum[9];
|
|
theFile.read(mMaximum, sizeof(mMaximum));
|
|
theFile.read(mMinimum, sizeof(mMinimum));
|
|
|
|
const float xPositions[] = { this->xbase, this->xbase + 266.0f, this->xbase + 533.0f };
|
|
const float yPositions[] = { this->zbase, this->zbase + 266.0f, this->zbase + 533.0f };
|
|
|
|
for (int y = 0; y < 3; y++)
|
|
{
|
|
for (int x = 0; x < 3; x++)
|
|
{
|
|
int pos = x + y * 3;
|
|
// fix bug with old noggit version inverting values
|
|
auto&& z{ std::minmax (mMinimum[pos], mMaximum[pos]) };
|
|
|
|
mMinimumValues[pos] = { xPositions[x], static_cast<float>(z.first), yPositions[y] };
|
|
mMaximumValues[pos] = { xPositions[x], static_cast<float>(z.second), yPositions[y] };
|
|
}
|
|
}
|
|
}
|
|
|
|
// - MTXF ----------------------------------------------
|
|
if (Header.mtxf != 0)
|
|
{
|
|
theFile.seek(Header.mtxf + 0x14);
|
|
|
|
theFile.read(&fourcc, 4);
|
|
theFile.read(&size, 4);
|
|
|
|
assert(fourcc == 'MTXF');
|
|
|
|
int count = size / 0x4;
|
|
|
|
std::vector<mtxf_entry> mtxf_data(count);
|
|
|
|
theFile.read(mtxf_data.data(), size);
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
// _mtxf_entries[mTextureFilenames[i]] = mtxf_data[i];
|
|
// only save those with flags set
|
|
if (mtxf_data[i].use_cubemap)
|
|
_mtxf_entries[mTextureFilenames[i]] = mtxf_data[i];
|
|
}
|
|
}
|
|
|
|
// - Done. ---------------------------------------------
|
|
|
|
// - Load textures -------------------------------------
|
|
|
|
//! \note We no longer pre load textures but the chunks themselves do.
|
|
|
|
if (_load_models)
|
|
{
|
|
// - Load WMOs -----------------------------------------
|
|
|
|
for (auto const& object : lWMOInstances)
|
|
{
|
|
add_model(_world->add_wmo_instance(WMOInstance(mWMOFilenames[object.nameID],
|
|
&object, _context), _tile_is_being_reloaded, false));
|
|
}
|
|
|
|
// - Load M2s ------------------------------------------
|
|
|
|
for (auto const& model : lModelInstances)
|
|
{
|
|
add_model(_world->add_model_instance(ModelInstance(mModelFilenames[model.nameID],
|
|
&model, _context), _tile_is_being_reloaded, false));
|
|
}
|
|
|
|
_world->need_model_updates = true;
|
|
}
|
|
|
|
// - Load chunks ---------------------------------------
|
|
|
|
for (int nextChunk = 0; nextChunk < 256; ++nextChunk)
|
|
{
|
|
theFile.seek(lMCNKOffsets[nextChunk]);
|
|
|
|
unsigned x = nextChunk / 16;
|
|
unsigned z = nextChunk % 16;
|
|
|
|
mChunks[x][z] = std::make_unique<MapChunk> (this, &theFile, mBigAlpha, _mode, _context, false, 0, _load_textures);
|
|
|
|
auto& chunk = mChunks[x][z];
|
|
_renderer.initChunkData(chunk.get());
|
|
}
|
|
// can be cleared after texture sets are loaded in chunks.
|
|
mTextureFilenames.clear();
|
|
_mtxf_entries.clear();
|
|
|
|
theFile.close();
|
|
|
|
// - Really done. --------------------------------------
|
|
|
|
LogDebug << "Done loading tile " << index.x << "," << index.z << "." << std::endl;
|
|
finished = true;
|
|
_tile_is_being_reloaded = false;
|
|
_state_changed.notify_all();
|
|
}
|
|
|
|
bool MapTile::isTile(int pX, int pZ)
|
|
{
|
|
return pX == index.x && pZ == index.z;
|
|
}
|
|
|
|
float MapTile::getMaxHeight()
|
|
{
|
|
return getExtents()[1].y;
|
|
}
|
|
|
|
float MapTile::getMinHeight()
|
|
{
|
|
return getExtents()[0].y;
|
|
}
|
|
|
|
void MapTile::forceRecalcExtents()
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
MapChunk* chunk = mChunks[i][j].get();
|
|
chunk->recalcExtents();
|
|
_extents[0].y = std::min(_extents[0].y, chunk->getMinHeight());
|
|
_extents[1].y = std::max(_extents[1].y, chunk->getMaxHeight());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapTile::convert_alphamap(bool to_big_alpha)
|
|
{
|
|
mBigAlpha = true;
|
|
for (size_t i = 0; i < 16; i++)
|
|
{
|
|
for (size_t j = 0; j < 16; j++)
|
|
{
|
|
mChunks[i][j]->use_big_alphamap = to_big_alpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool MapTile::intersect (math::ray const& ray, selection_result* results)
|
|
{
|
|
if (!finished)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
recalcExtents();
|
|
|
|
if (!ray.intersect_bounds(_extents[0], _extents[1]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (size_t j (0); j < 16; ++j)
|
|
{
|
|
for (size_t i (0); i < 16; ++i)
|
|
{
|
|
mChunks[j][i]->intersect (ray, results);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MapChunk* MapTile::getChunk(unsigned int x, unsigned int z)
|
|
{
|
|
if (x < 16 && z < 16)
|
|
{
|
|
return mChunks[z][x].get();
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::vector<MapChunk*> MapTile::chunks_in_range (glm::vec3 const& pos, float radius) const
|
|
{
|
|
std::vector<MapChunk*> chunks;
|
|
|
|
for (size_t ty (0); ty < 16; ++ty)
|
|
{
|
|
for (size_t tx (0); tx < 16; ++tx)
|
|
{
|
|
if (misc::getShortestDist (pos.x, pos.z, mChunks[ty][tx]->xbase, mChunks[ty][tx]->zbase, CHUNKSIZE) <= radius)
|
|
{
|
|
chunks.emplace_back (mChunks[ty][tx].get());
|
|
}
|
|
}
|
|
}
|
|
|
|
return chunks;
|
|
}
|
|
|
|
std::vector<MapChunk*> MapTile::chunks_in_rect (glm::vec3 const& pos, float radius) const
|
|
{
|
|
std::vector<MapChunk*> chunks;
|
|
|
|
for (size_t ty (0); ty < 16; ++ty)
|
|
{
|
|
for (size_t tx (0); tx < 16; ++tx)
|
|
{
|
|
MapChunk* chunk = mChunks[ty][tx].get();
|
|
glm::vec2 l_rect{pos.x - radius, pos.z - radius};
|
|
glm::vec2 r_rect{pos.x + radius, pos.z + radius};
|
|
|
|
glm::vec2 l_chunk{chunk->xbase, chunk->zbase};
|
|
glm::vec2 r_chunk{chunk->xbase + CHUNKSIZE, chunk->zbase + CHUNKSIZE};
|
|
|
|
if ((l_rect.x < r_chunk.x) && (r_rect.x >= l_chunk.x) && (l_rect.y < r_chunk.y) && (r_rect.y >= l_chunk.y))
|
|
{
|
|
chunks.emplace_back (chunk);
|
|
}
|
|
}
|
|
}
|
|
|
|
return chunks;
|
|
}
|
|
|
|
bool MapTile::GetVertex(float x, float z, glm::vec3 *V)
|
|
{
|
|
int xcol = (int)((x - xbase) / CHUNKSIZE);
|
|
int ycol = (int)((z - zbase) / CHUNKSIZE);
|
|
|
|
return xcol >= 0 && xcol <= 15 && ycol >= 0 && ycol <= 15 && mChunks[ycol][xcol]->GetVertex(x, z, V);
|
|
}
|
|
|
|
void MapTile::getVertexInternal(float x, float z, glm::vec3* v)
|
|
{
|
|
int xcol = (int)((x - xbase) / CHUNKSIZE);
|
|
int ycol = (int)((z - zbase) / CHUNKSIZE);
|
|
|
|
mChunks[ycol][xcol]->getVertexInternal(x, z, v);
|
|
}
|
|
|
|
/// --- Only saving related below this line. --------------------------
|
|
|
|
void MapTile::saveTile(World* world)
|
|
{
|
|
// if we want to save a duplicate with mclq in a separate folder
|
|
/*
|
|
save(world, false);
|
|
|
|
if (NoggitSettings.value("use_mclq_liquids_export", false).toBool())
|
|
{
|
|
save(world, true);
|
|
}
|
|
*/
|
|
|
|
QSettings settings;
|
|
bool use_mclq = settings.value("use_mclq_liquids_export", false).toBool();
|
|
|
|
save(world, use_mclq);
|
|
}
|
|
|
|
void MapTile::save(World* world, bool save_using_mclq_liquids)
|
|
{
|
|
Log << "Saving ADT \"" << _file_key.stringRepr() << "\"." << std::endl;
|
|
|
|
int lID; // This is a global counting variable. Do not store something in here you need later.
|
|
std::vector<WMOInstance*> lObjectInstances;
|
|
std::vector<ModelInstance*> lModelInstances;
|
|
|
|
// Check which doodads and WMOs are on this ADT.
|
|
glm::vec3 lTileExtents[2];
|
|
lTileExtents[0] = glm::vec3(xbase, 0.0f, zbase);
|
|
lTileExtents[1] = glm::vec3(xbase + TILESIZE, 0.0f, zbase + TILESIZE);
|
|
|
|
// get every models on the tile
|
|
for (std::uint32_t const uid : uids)
|
|
{
|
|
auto model = world->get_model(uid);
|
|
|
|
if (!model)
|
|
{
|
|
// todo: save elsewhere if this happens ? it shouldn't but still
|
|
LogError << "Could not find model with uid=" << uid << " when saving " << _file_key.stringRepr() << std::endl;
|
|
}
|
|
else
|
|
{
|
|
if (model.value().index() == eEntry_Object)
|
|
{
|
|
auto which = std::get<selected_object_type>(model.value())->which();
|
|
if (which == eWMO)
|
|
{
|
|
lObjectInstances.emplace_back(static_cast<WMOInstance*>(std::get<selected_object_type>(model.value())));
|
|
}
|
|
else if (which == eMODEL)
|
|
{
|
|
lModelInstances.emplace_back(static_cast<ModelInstance*>(std::get<selected_object_type>(model.value())));
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
struct filenameOffsetThing
|
|
{
|
|
int nameID;
|
|
int filenamePosition;
|
|
};
|
|
|
|
filenameOffsetThing nullyThing = { 0, 0 };
|
|
|
|
std::map<std::string, filenameOffsetThing> lModels;
|
|
|
|
for (auto const& model : lModelInstances)
|
|
{
|
|
model->ensureExtents();
|
|
if (lModels.find(model->model->file_key().filepath()) == lModels.end())
|
|
{
|
|
lModels.emplace (model->model->file_key().filepath(), nullyThing);
|
|
}
|
|
}
|
|
|
|
lID = 0;
|
|
for (auto& model : lModels)
|
|
{
|
|
model.second.nameID = lID++;
|
|
}
|
|
|
|
std::map<std::string, filenameOffsetThing> lObjects;
|
|
|
|
for (auto const& object : lObjectInstances)
|
|
{
|
|
if (lObjects.find(object->wmo->file_key().filepath()) == lObjects.end())
|
|
{
|
|
lObjects.emplace(object->wmo->file_key().filepath(), nullyThing);
|
|
}
|
|
}
|
|
|
|
lID = 0;
|
|
for (auto& object : lObjects)
|
|
{
|
|
object.second.nameID = lID++;
|
|
}
|
|
|
|
// Check which textures are on this ADT.
|
|
std::map<std::string, int> lTextures;
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
if (!mChunks[i][j]->texture_set)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (size_t tex = 0; tex < mChunks[i][j]->texture_set->num(); tex++)
|
|
{
|
|
if (lTextures.find(mChunks[i][j]->texture_set->filename(tex)) == lTextures.end())
|
|
{
|
|
lTextures.emplace(mChunks[i][j]->texture_set->filename(tex), -1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lID = 0;
|
|
for (auto& texture : lTextures)
|
|
texture.second = lID++;
|
|
|
|
// Now write the file.
|
|
util::sExtendableArray lADTFile;
|
|
|
|
int lCurrentPosition = 0;
|
|
|
|
// MVER
|
|
lADTFile.Extend(8 + 0x4);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MVER', 4);
|
|
|
|
// MVER data
|
|
*(lADTFile.GetPointer<int>(8)) = 18;
|
|
lCurrentPosition += 8 + 0x4;
|
|
|
|
// MHDR
|
|
int lMHDR_Position = lCurrentPosition;
|
|
lADTFile.Extend(8 + 0x40);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MHDR', 0x40);
|
|
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->flags = mFlags;
|
|
|
|
lCurrentPosition += 8 + 0x40;
|
|
|
|
|
|
// MCIN
|
|
int lMCIN_Position = lCurrentPosition;
|
|
|
|
lADTFile.Extend(8 + 256 * 0x10);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MCIN', 256 * 0x10);
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mcin = lCurrentPosition - 0x14;
|
|
|
|
lCurrentPosition += 8 + 256 * 0x10;
|
|
|
|
// MTEX
|
|
int lMTEX_Position = lCurrentPosition;
|
|
lADTFile.Extend(8 + 0); // We don't yet know how big this will be.
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MTEX');
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mtex = lCurrentPosition - 0x14;
|
|
|
|
lCurrentPosition += 8 + 0;
|
|
|
|
// MTEX data
|
|
for (auto const& texture : lTextures)
|
|
{
|
|
lADTFile.Insert(lCurrentPosition, static_cast<unsigned long>(texture.first.size() + 1), texture.first.c_str());
|
|
|
|
lCurrentPosition += static_cast<int>(texture.first.size() + 1);
|
|
lADTFile.GetPointer<sChunkHeader>(lMTEX_Position)->mSize += static_cast<int>(texture.first.size() + 1);
|
|
LogDebug << "Added texture \"" << texture.first << "\"." << std::endl;
|
|
}
|
|
|
|
// MMDX
|
|
int lMMDX_Position = lCurrentPosition;
|
|
lADTFile.Extend(8 + 0); // We don't yet know how big this will be.
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MMDX');
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mmdx = lCurrentPosition - 0x14;
|
|
|
|
lCurrentPosition += 8 + 0;
|
|
|
|
// MMDX data
|
|
for (auto it = lModels.begin(); it != lModels.end(); ++it)
|
|
{
|
|
it->second.filenamePosition = lADTFile.GetPointer<sChunkHeader>(lMMDX_Position)->mSize;
|
|
lADTFile.Insert(lCurrentPosition, static_cast<unsigned long>(it->first.size() + 1), misc::normalize_adt_filename(it->first).c_str());
|
|
lCurrentPosition += static_cast<int>(it->first.size() + 1);
|
|
lADTFile.GetPointer<sChunkHeader>(lMMDX_Position)->mSize += static_cast<int>(it->first.size() + 1);
|
|
LogDebug << "Added model \"" << it->first << "\"." << std::endl;
|
|
}
|
|
|
|
// MMID
|
|
// M2 model names
|
|
int lMMID_Size = static_cast<int>(4 * lModels.size());
|
|
lADTFile.Extend(8 + lMMID_Size);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MMID', lMMID_Size);
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mmid = lCurrentPosition - 0x14;
|
|
|
|
// MMID data
|
|
// WMO model names
|
|
auto const lMMID_Data = lADTFile.GetPointer<int>(lCurrentPosition + 8);
|
|
|
|
lID = 0;
|
|
for (auto const& model : lModels)
|
|
{
|
|
lMMID_Data[lID] = model.second.filenamePosition;
|
|
lID++;
|
|
}
|
|
lCurrentPosition += 8 + lMMID_Size;
|
|
|
|
// MWMO
|
|
int lMWMO_Position = lCurrentPosition;
|
|
lADTFile.Extend(8 + 0); // We don't yet know how big this will be.
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MWMO');
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mwmo = lCurrentPosition - 0x14;
|
|
|
|
lCurrentPosition += 8 + 0;
|
|
|
|
// MWMO data
|
|
for (auto& object : lObjects)
|
|
{
|
|
object.second.filenamePosition = lADTFile.GetPointer<sChunkHeader>(lMWMO_Position)->mSize;
|
|
lADTFile.Insert(lCurrentPosition, static_cast<unsigned long>(object.first.size() + 1), misc::normalize_adt_filename(object.first).c_str());
|
|
lCurrentPosition += static_cast<int>(object.first.size() + 1);
|
|
lADTFile.GetPointer<sChunkHeader>(lMWMO_Position)->mSize += static_cast<int>(object.first.size() + 1);
|
|
LogDebug << "Added object \"" << object.first << "\"." << std::endl;
|
|
}
|
|
|
|
// MWID
|
|
int lMWID_Size = static_cast<int>(4 * lObjects.size());
|
|
lADTFile.Extend(8 + lMWID_Size);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MWID', lMWID_Size);
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mwid = lCurrentPosition - 0x14;
|
|
|
|
// MWID data
|
|
auto const lMWID_Data = lADTFile.GetPointer<int>(lCurrentPosition + 8);
|
|
|
|
lID = 0;
|
|
for (auto const& object : lObjects)
|
|
lMWID_Data[lID++] = object.second.filenamePosition;
|
|
|
|
lCurrentPosition += 8 + lMWID_Size;
|
|
|
|
// MDDF
|
|
int lMDDF_Size = static_cast<int>(0x24 * lModelInstances.size());
|
|
lADTFile.Extend(8 + lMDDF_Size);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MDDF', lMDDF_Size);
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mddf = lCurrentPosition - 0x14;
|
|
|
|
// MDDF data
|
|
auto const lMDDF_Data = lADTFile.GetPointer<ENTRY_MDDF>(lCurrentPosition + 8);
|
|
|
|
if(world->mapIndex.sort_models_by_size_class())
|
|
{
|
|
std::sort(lModelInstances.begin(), lModelInstances.end(), [](ModelInstance* m1, ModelInstance* m2)
|
|
{
|
|
return m1->size_cat > m2->size_cat;
|
|
});
|
|
}
|
|
|
|
lID = 0;
|
|
for (auto const& model : lModelInstances)
|
|
{
|
|
auto filename_to_offset_and_name = lModels.find(model->model->file_key().filepath());
|
|
if (filename_to_offset_and_name == lModels.end())
|
|
{
|
|
LogError << "There is a problem with saving the doodads. We have a doodad that somehow changed the name during the saving function. However this got produced, you can get a reward from schlumpf by pasting him this line." << std::endl;
|
|
return;
|
|
}
|
|
|
|
lMDDF_Data[lID].nameID = filename_to_offset_and_name->second.nameID;
|
|
lMDDF_Data[lID].uniqueID = model->uid;
|
|
lMDDF_Data[lID].pos[0] = model->pos.x;
|
|
lMDDF_Data[lID].pos[1] = model->pos.y;
|
|
lMDDF_Data[lID].pos[2] = model->pos.z;
|
|
lMDDF_Data[lID].rot[0] = model->dir.x;
|
|
lMDDF_Data[lID].rot[1] = model->dir.y;
|
|
lMDDF_Data[lID].rot[2] = model->dir.z;
|
|
lMDDF_Data[lID].scale = (uint16_t)(model->scale * 1024);
|
|
lMDDF_Data[lID].flags = 0;
|
|
lID++;
|
|
}
|
|
|
|
lCurrentPosition += 8 + lMDDF_Size;
|
|
|
|
LogDebug << "Added " << lID << " doodads to MDDF" << std::endl;
|
|
|
|
// MODF
|
|
int lMODF_Size = static_cast<int>(0x40 * lObjectInstances.size());
|
|
lADTFile.Extend(8 + lMODF_Size);
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MODF', lMODF_Size);
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->modf = lCurrentPosition - 0x14;
|
|
|
|
// MODF data
|
|
auto const lMODF_Data = lADTFile.GetPointer<ENTRY_MODF>(lCurrentPosition + 8);
|
|
|
|
lID = 0;
|
|
for (auto const& object : lObjectInstances)
|
|
{
|
|
auto filename_to_offset_and_name = lObjects.find(object->wmo->file_key().filepath());
|
|
if (filename_to_offset_and_name == lObjects.end())
|
|
{
|
|
LogError << "There is a problem with saving the objects. We have an object that somehow changed the name during the saving function. However this got produced, you can get a reward from schlumpf by pasting him this line." << std::endl;
|
|
return;
|
|
}
|
|
|
|
lMODF_Data[lID].nameID = filename_to_offset_and_name->second.nameID;
|
|
lMODF_Data[lID].uniqueID = object->uid;
|
|
lMODF_Data[lID].pos[0] = object->pos.x;
|
|
lMODF_Data[lID].pos[1] = object->pos.y;
|
|
lMODF_Data[lID].pos[2] = object->pos.z;
|
|
|
|
lMODF_Data[lID].rot[0] = object->dir.x;
|
|
lMODF_Data[lID].rot[1] = object->dir.y;
|
|
lMODF_Data[lID].rot[2] = object->dir.z;
|
|
|
|
lMODF_Data[lID].extents[0][0] = object->getExtents()[0].x;
|
|
lMODF_Data[lID].extents[0][1] = object->getExtents()[0].y;
|
|
lMODF_Data[lID].extents[0][2] = object->getExtents()[0].z;
|
|
|
|
lMODF_Data[lID].extents[1][0] = object->getExtents()[1].x;
|
|
lMODF_Data[lID].extents[1][1] = object->getExtents()[1].y;
|
|
lMODF_Data[lID].extents[1][2] = object->getExtents()[1].z;
|
|
|
|
lMODF_Data[lID].flags = object->mFlags;
|
|
lMODF_Data[lID].doodadSet = object->doodadset();
|
|
lMODF_Data[lID].nameSet = object->mNameset;
|
|
lMODF_Data[lID].scale = (uint16_t)(object->scale * 1024);
|
|
lID++;
|
|
}
|
|
|
|
LogDebug << "Added " << lID << " wmos to MODF" << std::endl;
|
|
|
|
lCurrentPosition += 8 + lMODF_Size;
|
|
|
|
//MH2O
|
|
if (!save_using_mclq_liquids)
|
|
{
|
|
Water.saveToFile(lADTFile, lMHDR_Position, lCurrentPosition);
|
|
}
|
|
|
|
// MCNK
|
|
for (int y = 0; y < 16; ++y)
|
|
{
|
|
for (int x = 0; x < 16; ++x)
|
|
{
|
|
mChunks[y][x]->save(lADTFile, lCurrentPosition, lMCIN_Position, lTextures, lObjectInstances, lModelInstances, save_using_mclq_liquids);
|
|
}
|
|
}
|
|
|
|
// MFBO
|
|
if (mFlags & 1)
|
|
{
|
|
size_t chunkSize = sizeof(int16_t) * 9 * 2;
|
|
lADTFile.Extend(static_cast<long>(8 + chunkSize));
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MFBO', static_cast<int>(chunkSize));
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mfbo = lCurrentPosition - 0x14;
|
|
|
|
auto const lMFBO_Data = lADTFile.GetPointer<int16_t>(lCurrentPosition + 8);
|
|
|
|
lID = 0;
|
|
|
|
for (int i = 0; i < 9; ++i)
|
|
lMFBO_Data[lID++] = (int16_t)mMaximumValues[i].y;
|
|
|
|
for (int i = 0; i < 9; ++i)
|
|
lMFBO_Data[lID++] = (int16_t)mMinimumValues[i].y;
|
|
|
|
lCurrentPosition += static_cast<int>(8 + chunkSize);
|
|
}
|
|
|
|
//! \todo Do not do bullshit here in MTFX.
|
|
#if 0
|
|
if (!mTextureEffects.empty()) {
|
|
//! \todo check if nTexEffects == nTextures, correct order etc.
|
|
lADTFile.Extend(8 + 4 * mTextureEffects.size());
|
|
SetChunkHeader(lADTFile, lCurrentPosition, 'MTFX', 4 * mTextureEffects.size());
|
|
lADTFile.GetPointer<MHDR>(lMHDR_Position + 8)->mtxf = lCurrentPosition - 0x14;
|
|
|
|
auto const lMTFX_Data = lADTFile.GetPointer<uint32_t>(lCurrentPosition + 8);
|
|
|
|
lID = 0;
|
|
//they should be in the correct order...
|
|
for (auto const& effect : mTextureEffects)
|
|
{
|
|
lMTFX_Data[lID] = effect;
|
|
++lID;
|
|
}
|
|
lCurrentPosition += 8 + sizeof(uint32_t) * mTextureEffects.size();
|
|
}
|
|
#endif
|
|
|
|
{
|
|
BlizzardArchive::ClientFile f(_file_key.filepath(), Noggit::Application::NoggitApplication::instance()->clientData()
|
|
, BlizzardArchive::ClientFile::NEW_FILE);
|
|
// \todo This sounds wrong. There shouldn't *be* unused nulls to
|
|
// begin with.
|
|
f.setBuffer(lADTFile.data_up_to(lCurrentPosition)); // cleaning unused nulls at the end of file
|
|
f.save();
|
|
|
|
// adspartan's way, save MCLQ files separately
|
|
/*
|
|
if (save_using_mclq_liquids)
|
|
{
|
|
f.save_file_to_folder(NoggitSettings.value("project/mclq_liquids_path").toString().toStdString());
|
|
}
|
|
else
|
|
{
|
|
f.save();
|
|
}*/
|
|
}
|
|
|
|
lObjectInstances.clear();
|
|
lModelInstances.clear();
|
|
lModels.clear();
|
|
}
|
|
|
|
|
|
void MapTile::CropWater()
|
|
{
|
|
for (int z = 0; z < 16; ++z)
|
|
{
|
|
for (int x = 0; x < 16; ++x)
|
|
{
|
|
Water.CropMiniChunk(x, z, mChunks[z][x].get());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapTile::remove_model(uint32_t uid)
|
|
{
|
|
std::lock_guard<std::mutex> const lock (_mutex);
|
|
|
|
auto it = std::find(uids.begin(), uids.end(), uid);
|
|
|
|
if (it != uids.end())
|
|
{
|
|
uids.erase(it);
|
|
|
|
const auto obj = _world->get_model(uid);
|
|
if (!obj.has_value())
|
|
return;
|
|
|
|
auto instance = std::get<selected_object_type>(obj.value());
|
|
|
|
auto& instances = object_instances[instance->instance_model()];
|
|
auto it2 = std::find(instances.begin(), instances.end(), instance);
|
|
|
|
if (it2 != instances.end())
|
|
{
|
|
instances.erase(it2);
|
|
}
|
|
|
|
if (instances.empty())
|
|
{
|
|
object_instances.erase(instance->instance_model());
|
|
}
|
|
|
|
instance->derefTile(this);
|
|
_requires_object_extents_recalc = true;
|
|
}
|
|
}
|
|
|
|
void MapTile::remove_model(SceneObject* instance)
|
|
{
|
|
std::lock_guard<std::mutex> const lock (_mutex);
|
|
|
|
auto it = std::find(uids.begin(), uids.end(), instance->uid);
|
|
|
|
if (it != uids.end())
|
|
{
|
|
uids.erase(it);
|
|
|
|
auto& instances = object_instances[instance->instance_model()];
|
|
auto it2 = std::find(instances.begin(), instances.end(), instance);
|
|
|
|
if (it2 != instances.end())
|
|
{
|
|
instance->derefTile(this);
|
|
instances.erase(it2);
|
|
}
|
|
|
|
if (instances.empty())
|
|
{
|
|
object_instances.erase(instance->instance_model());
|
|
}
|
|
|
|
_requires_object_extents_recalc = true;
|
|
}
|
|
}
|
|
|
|
void MapTile::add_model(uint32_t uid)
|
|
{
|
|
std::lock_guard<std::mutex> const lock(_mutex);
|
|
|
|
if (std::find(uids.begin(), uids.end(), uid) == uids.end())
|
|
{
|
|
uids.push_back(uid);
|
|
|
|
const auto obj = _world->get_model(uid);
|
|
if (!obj)
|
|
return;
|
|
|
|
auto instance = std::get<selected_object_type>(obj.value());
|
|
object_instances[instance->instance_model()].push_back(instance);
|
|
|
|
if (instance->finishedLoading())
|
|
{
|
|
instance->ensureExtents();
|
|
|
|
_object_instance_extents[0].x = std::min(_object_instance_extents[0].x, instance->getExtents()[0].x);
|
|
_object_instance_extents[0].y = std::min(_object_instance_extents[0].y, instance->getExtents()[0].y);
|
|
_object_instance_extents[0].z = std::min(_object_instance_extents[0].z, instance->getExtents()[0].z);
|
|
|
|
_object_instance_extents[1].x = std::max(_object_instance_extents[1].x, instance->getExtents()[1].x);
|
|
_object_instance_extents[1].y = std::max(_object_instance_extents[1].y, instance->getExtents()[1].y);
|
|
_object_instance_extents[1].z = std::max(_object_instance_extents[1].z, instance->getExtents()[1].z);
|
|
|
|
tagCombinedExtents(true);
|
|
}
|
|
else
|
|
{
|
|
_requires_object_extents_recalc = true;
|
|
}
|
|
|
|
instance->refTile(this);
|
|
}
|
|
}
|
|
|
|
void MapTile::add_model(SceneObject* instance)
|
|
{
|
|
std::lock_guard<std::mutex> const lock(_mutex);
|
|
|
|
if (std::find(uids.begin(), uids.end(), instance->uid) == uids.end())
|
|
{
|
|
uids.push_back(instance->uid);
|
|
|
|
object_instances[instance->instance_model()].push_back(instance);
|
|
|
|
if (instance->finishedLoading())
|
|
{
|
|
instance->ensureExtents();
|
|
|
|
_object_instance_extents[0].x = std::min(_object_instance_extents[0].x, instance->getExtents()[0].x);
|
|
_object_instance_extents[0].y = std::min(_object_instance_extents[0].y, instance->getExtents()[0].y);
|
|
_object_instance_extents[0].z = std::min(_object_instance_extents[0].z, instance->getExtents()[0].z);
|
|
|
|
_object_instance_extents[1].x = std::max(_object_instance_extents[1].x, instance->getExtents()[1].x);
|
|
_object_instance_extents[1].y = std::max(_object_instance_extents[1].y, instance->getExtents()[1].y);
|
|
_object_instance_extents[1].z = std::max(_object_instance_extents[1].z, instance->getExtents()[1].z);
|
|
|
|
tagCombinedExtents(true);
|
|
}
|
|
else
|
|
{
|
|
_requires_object_extents_recalc = true;
|
|
}
|
|
|
|
instance->refTile(this);
|
|
}
|
|
}
|
|
|
|
void MapTile::initEmptyChunks()
|
|
{
|
|
for (int nextChunk = 0; nextChunk < 256; ++nextChunk)
|
|
{
|
|
mChunks[nextChunk / 16][nextChunk % 16] = std::make_unique<MapChunk> (this, nullptr, mBigAlpha, _mode, _context, true, nextChunk);
|
|
}
|
|
}
|
|
|
|
QImage MapTile::getHeightmapImage(float min_height, float max_height)
|
|
{
|
|
// grayscale 16 doesn't work, it rounds values or is actually 8bit
|
|
QImage image(257, 257, QImage::Format_RGBA64);
|
|
int depth = image.depth();
|
|
|
|
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
glm::vec3* heightmap = chunk->getHeightmap();
|
|
|
|
for (unsigned y = 0; y < SUM; ++y)
|
|
{
|
|
for (unsigned x = 0; x < SUM; ++x)
|
|
{
|
|
unsigned const plain {y * SUM + x};
|
|
bool const is_virtual {static_cast<bool>(plain % 2)};
|
|
bool const erp = plain % DSUM / SUM;
|
|
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
|
|
float value = is_virtual ? (heightmap[idx].y + heightmap[idx + (erp ? SUM : 1)].y) / 2.f : heightmap[idx].y;
|
|
value = std::min(1.0f, std::max(0.0f, ((value - min_height) / (max_height - min_height))));
|
|
image.setPixelColor((k * 16) + x, (l * 16) + y, QColor::fromRgbF(value, value, value, 1.0)); // grayscale uses alpha channel ?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::move(image);
|
|
}
|
|
|
|
QImage MapTile::getNormalmapImage()
|
|
{
|
|
QImage image(257, 257, QImage::Format_RGBA64);
|
|
|
|
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
const glm::vec3* normals = chunk->getNormals();
|
|
|
|
for (unsigned y = 0; y < SUM; ++y)
|
|
{
|
|
for (unsigned x = 0; x < SUM; ++x)
|
|
{
|
|
unsigned const plain {y * SUM + x};
|
|
bool const is_virtual {static_cast<bool>(plain % 2)};
|
|
bool const erp = plain % DSUM / SUM;
|
|
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
|
|
|
|
auto normal = glm::normalize(normals[idx]);
|
|
auto normal_inner = glm::normalize(normals[idx + (erp ? SUM : 1)]);
|
|
|
|
float value_r = is_virtual ? (normal.x + normal_inner.x) / 2.f : normal.x;
|
|
float value_g = is_virtual ? (normal.y + normal_inner.y) / 2.f : normal.y;
|
|
float value_b = is_virtual ? (normal.z + normal_inner.z) / 2.f : normal.z;
|
|
|
|
image.setPixelColor((k * 16) + x, (l * 16) + y, QColor::fromRgbF(value_r, value_g, value_b, 1.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::move(image);
|
|
}
|
|
|
|
QImage MapTile::getAlphamapImage(unsigned layer)
|
|
{
|
|
QImage image(1024, 1024, QImage::Format_RGBA8888);
|
|
image.fill(Qt::black);
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
MapChunk* chunk = getChunk(i, j);
|
|
|
|
if (layer >= chunk->texture_set->num())
|
|
continue;
|
|
|
|
chunk->texture_set->apply_alpha_changes();
|
|
auto alphamaps = chunk->texture_set->getAlphamaps();
|
|
|
|
auto alpha_layer = *alphamaps->at(layer - 1);
|
|
|
|
for (int k = 0; k < 64; ++k)
|
|
{
|
|
for (int l = 0; l < 64; ++l)
|
|
{
|
|
int value = alpha_layer.getAlpha(64 * l + k);
|
|
image.setPixelColor((i * 64) + k, (j * 64) + l, QColor(value, value, value, 255));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::move(image);
|
|
}
|
|
|
|
QImage MapTile::getAlphamapImage(std::string const& filename)
|
|
{
|
|
QImage image(1024, 1024, QImage::Format_RGBA8888);
|
|
image.fill(Qt::black);
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
MapChunk *chunk = getChunk(i, j);
|
|
|
|
unsigned layer = 0;
|
|
bool chunk_has_texture = false;
|
|
|
|
for (int k = 0; k < chunk->texture_set->num(); ++k)
|
|
{
|
|
if (chunk->texture_set->filename(k) == filename)
|
|
{
|
|
layer = k;
|
|
chunk_has_texture = true;
|
|
}
|
|
}
|
|
|
|
if (!chunk_has_texture)
|
|
{
|
|
for (int k = 0; k < 64; ++k)
|
|
{
|
|
for (int l = 0; l < 64; ++l)
|
|
{
|
|
// if texture is not in the chunk, set chunk to black
|
|
image.setPixelColor((i * 64) + k, (j * 64) + l, QColor(0, 0, 0, 255));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
chunk->texture_set->apply_alpha_changes();
|
|
auto alphamaps = chunk->texture_set->getAlphamaps();
|
|
|
|
for (int k = 0; k < 64; ++k)
|
|
{
|
|
for (int l = 0; l < 64; ++l)
|
|
{
|
|
if (layer == 0)
|
|
{
|
|
// WoW calculates layer 0 as 255 - sum(Layer[1]...Layer[3])
|
|
int layers_sum = 0;
|
|
if (alphamaps->at(0))
|
|
layers_sum += alphamaps->at(0)->getAlpha(64 * l + k);
|
|
if (alphamaps->at(1))
|
|
layers_sum += alphamaps->at(1)->getAlpha(64 * l + k);
|
|
if (alphamaps->at(2))
|
|
layers_sum += alphamaps->at(2)->getAlpha(64 * l + k);
|
|
|
|
int value = std::clamp((255 - layers_sum), 0, 255);
|
|
image.setPixelColor((i * 64) + k, (j * 64) + l, QColor(value, value, value, 255));
|
|
}
|
|
else // layer 1-3
|
|
{
|
|
auto& alpha_layer = *alphamaps->at(layer - 1);
|
|
|
|
int value = alpha_layer.getAlpha(64 * l + k);
|
|
image.setPixelColor((i * 64) + k, (j * 64) + l, QColor(value, value, value, 255));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::move(image);
|
|
}
|
|
|
|
void MapTile::setHeightmapImage(QImage const& baseimage, float min_height, float max_height, int mode, bool tiledEdges) // image
|
|
{
|
|
auto image = baseimage.convertToFormat(QImage::Format_RGBA64);
|
|
|
|
float const height_range = (max_height - min_height);
|
|
|
|
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
chunk->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
|
|
glm::vec3* heightmap = chunk->getHeightmap();
|
|
|
|
for (unsigned y = 0; y < SUM; ++y)
|
|
for (unsigned x = 0; x < SUM; ++x)
|
|
{
|
|
unsigned const plain {y * SUM + x};
|
|
bool const is_virtual {static_cast<bool>(plain % 2)};
|
|
|
|
if (is_virtual)
|
|
continue;
|
|
|
|
bool const erp = plain % DSUM / SUM;
|
|
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
|
|
|
|
if (tiledEdges && ((y == 16 && l == 15) || (x == 16 && k == 15)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (image.depth())
|
|
{
|
|
case 8:
|
|
case 16:
|
|
case 32:
|
|
{
|
|
float const ratio = qGray(image.pixel((k * 16) + x, (l * 16) + y)) / 255.0f; // 0.0 - 1.0
|
|
float const new_height = (height_range * ratio) + min_height;
|
|
|
|
float test_newheight = (ratio + min_height) * (height_range);
|
|
|
|
switch (mode)
|
|
{
|
|
case 0: // Set
|
|
heightmap[idx].y = new_height;
|
|
break;
|
|
|
|
case 1: // Add
|
|
heightmap[idx].y += new_height;
|
|
break;
|
|
|
|
case 2: // Subtract
|
|
heightmap[idx].y -= new_height;
|
|
break;
|
|
|
|
case 3: // Multiply
|
|
heightmap[idx].y *= new_height;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 64:
|
|
{
|
|
double const ratio = image.pixelColor((k * 16) + x, (l * 16) + y).redF(); // 0.0 - 1.0
|
|
float new_height = height_range * ratio + min_height;
|
|
|
|
switch (mode)
|
|
{
|
|
case 0: // Set
|
|
heightmap[idx].y = new_height;
|
|
break;
|
|
|
|
case 1: // Add
|
|
heightmap[idx].y += new_height;
|
|
break;
|
|
|
|
case 2: // Subtract
|
|
heightmap[idx].y -= new_height;
|
|
break;
|
|
|
|
case 3: // Multiply
|
|
heightmap[idx].y *= new_height;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
|
|
// else we recalculate after tiled edges updates
|
|
if (!tiledEdges)
|
|
chunk->recalcNorms();
|
|
}
|
|
}
|
|
|
|
if (tiledEdges) // resize + fit
|
|
{
|
|
if (index.z > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex{ index.x, index.z-1}
|
|
, [&](MapTile* tile)
|
|
{
|
|
for (int chunk_x = 0; chunk_x < 16; ++chunk_x)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(chunk_x, 15);
|
|
MapChunk* sourceChunk = this->getChunk(chunk_x, 0);
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
for (int vert_x = 0; vert_x < 9; ++vert_x)
|
|
{
|
|
int target_vert = 136 + vert_x;
|
|
int source_vert = vert_x;
|
|
targetChunk->getHeightmap()[target_vert].y = sourceChunk->getHeightmap()[source_vert].y;
|
|
}
|
|
targetChunk->recalcNorms();
|
|
}
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (index.x > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex{ index.x-1, index.z}
|
|
, [&](MapTile* tile)
|
|
{
|
|
for (int chunk_y = 0; chunk_y < 16; ++chunk_y)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(15, chunk_y);
|
|
MapChunk* sourceChunk = this->getChunk(0, chunk_y);
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
for (int vert_y = 0; vert_y < 9; ++vert_y)
|
|
{
|
|
int target_vert = vert_y * 17 + 8;
|
|
int source_vert = vert_y * 17;
|
|
targetChunk->getHeightmap()[target_vert].y = sourceChunk->getHeightmap()[source_vert].y;
|
|
}
|
|
targetChunk->recalcNorms();
|
|
}
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (index.x > 0 && index.z > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex { index.x-1, index.z-1 }
|
|
, [&] (MapTile* tile)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(15, 15);
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
tile->getChunk(15,15)->getHeightmap()[144].y = this->getChunk(0,0)->getHeightmap()[0].y;
|
|
targetChunk->recalcNorms();
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
}
|
|
);
|
|
}
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
// chunk->recalcNorms();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapTile::setAlphaImage(QImage const& baseimage, unsigned layer, bool cleanup)
|
|
{
|
|
auto image = baseimage.convertToFormat(QImage::Format_RGBA8888);
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
if (layer >= chunk->texture_set->num())
|
|
continue;
|
|
|
|
chunk->registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
|
|
|
|
chunk->texture_set->create_temporary_alphamaps_if_needed();
|
|
auto& temp_alphamaps = *chunk->texture_set->getTempAlphamaps();
|
|
|
|
for (int i = 0; i < 64; ++i)
|
|
{
|
|
for (int j = 0; j < 64; ++j)
|
|
{
|
|
temp_alphamaps[layer][64 * j + i] = static_cast<float>(qGray(image.pixel((k * 64) + i, (l * 64) + j)));
|
|
}
|
|
}
|
|
|
|
if (cleanup)
|
|
chunk->texture_set->eraseUnusedTextures();
|
|
|
|
chunk->texture_set->markDirty();
|
|
chunk->texture_set->apply_alpha_changes();
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
QImage MapTile::getVertexColorsImage()
|
|
{
|
|
QImage image(257, 257, QImage::Format_RGBA8888);
|
|
image.fill(QColor(127, 127, 127, 255));
|
|
|
|
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
if (!chunk->header_flags.flags.has_mccv)
|
|
continue;
|
|
|
|
glm::vec3* colors = chunk->getVertexColors();
|
|
|
|
for (unsigned y = 0; y < SUM; ++y)
|
|
{
|
|
for (unsigned x = 0; x < SUM; ++x)
|
|
{
|
|
unsigned const plain {y * SUM + x};
|
|
bool const is_virtual {static_cast<bool>(plain % 2)};
|
|
bool const erp = plain % DSUM / SUM;
|
|
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
|
|
float r = is_virtual ? (colors[idx].x + colors[idx + (erp ? SUM : 1)].x) / 4.f : colors[idx].x / 2.f;
|
|
float g = is_virtual ? (colors[idx].y + colors[idx + (erp ? SUM : 1)].y) / 4.f : colors[idx].y / 2.f;
|
|
float b = is_virtual ? (colors[idx].z + colors[idx + (erp ? SUM : 1)].z) / 4.f : colors[idx].z / 2.f;
|
|
image.setPixelColor((k * 16) + x, (l * 16) + y, QColor::fromRgbF(r, g, b, 1.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::move(image);
|
|
}
|
|
|
|
void MapTile::setVertexColorImage(QImage const& baseimage, int mode, bool tiledEdges)
|
|
{
|
|
QImage image = baseimage.convertToFormat(QImage::Format_RGBA8888);
|
|
|
|
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
|
|
|
|
for (int k = 0; k < 16; ++k)
|
|
{
|
|
for (int l = 0; l < 16; ++l)
|
|
{
|
|
MapChunk* chunk = getChunk(k, l);
|
|
|
|
if (!chunk->hasColors())
|
|
{
|
|
chunk->initMCCV();
|
|
}
|
|
|
|
chunk->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
|
|
glm::vec3* colors = chunk->getVertexColors();
|
|
|
|
for (unsigned y = 0; y < SUM; ++y)
|
|
for (unsigned x = 0; x < SUM; ++x)
|
|
{
|
|
unsigned const plain {y * SUM + x};
|
|
bool const is_virtual {static_cast<bool>(plain % 2)};
|
|
|
|
if (is_virtual)
|
|
continue;
|
|
|
|
bool const erp = plain % DSUM / SUM;
|
|
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
|
|
|
|
if (tiledEdges && ((y == 16 && l == 15) || (x == 16 && k == 15)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case 0: // Set
|
|
{
|
|
auto color = image.pixelColor((k * 16) + x, (l * 16) + y);
|
|
colors[idx].x = color.redF() * 2.f;
|
|
colors[idx].y = color.greenF() * 2.f;
|
|
colors[idx].z = color.blueF() * 2.f;
|
|
break;
|
|
}
|
|
case 1: // Add
|
|
{
|
|
auto color = image.pixelColor((k * 16) + x, (l * 16) + y);
|
|
colors[idx].x = std::min(2.0f, std::max(0.0f, colors[idx].x + color.redF() * 2.f));
|
|
colors[idx].y = std::min(2.0f, std::max(0.0f, colors[idx].y + color.greenF() * 2.f));
|
|
colors[idx].z = std::min(2.0f, std::max(0.0f, colors[idx].z + color.blueF() * 2.f));
|
|
break;
|
|
}
|
|
|
|
case 2: // Subtract
|
|
{
|
|
auto color = image.pixelColor((k * 16) + x, (l * 16) + y);
|
|
colors[idx].x = std::min(2.0f, std::max(0.0f, colors[idx].x - color.redF() * 2.f));
|
|
colors[idx].y = std::min(2.0f, std::max(0.0f, colors[idx].y - color.greenF() * 2.f));
|
|
colors[idx].z = std::min(2.0f, std::max(0.0f, colors[idx].z - color.blueF() * 2.f));
|
|
break;
|
|
}
|
|
|
|
case 3: // Multiply
|
|
{
|
|
auto color = image.pixelColor((k * 16) + x, (l * 16) + y);
|
|
colors[idx].x = std::min(2.0f, std::max(0.0f, colors[idx].x * color.redF() * 2.f));
|
|
colors[idx].y = std::min(2.0f, std::max(0.0f, colors[idx].y * color.greenF() * 2.f));
|
|
colors[idx].z = std::min(2.0f, std::max(0.0f, colors[idx].z * color.blueF() * 2.f));
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
chunk->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
}
|
|
}
|
|
|
|
if (tiledEdges)
|
|
{
|
|
if (index.z > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex{ index.x, index.z-1}
|
|
, [&](MapTile* tile)
|
|
{
|
|
for (int chunk_x = 0; chunk_x < 16; ++chunk_x)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(chunk_x, 15);
|
|
MapChunk* sourceChunk = this->getChunk(chunk_x, 0);
|
|
|
|
if (!targetChunk->hasColors())
|
|
{
|
|
targetChunk->initMCCV();
|
|
}
|
|
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
for (int vert_x = 0; vert_x < 9; ++vert_x)
|
|
{
|
|
int target_vert = 136 + vert_x;
|
|
int source_vert = vert_x;
|
|
|
|
targetChunk->getVertexColors()[target_vert] = sourceChunk->getVertexColors()[source_vert];
|
|
}
|
|
}
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (index.x > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex{ index.x-1, index.z}
|
|
, [&](MapTile* tile)
|
|
{
|
|
for (int chunk_y = 0; chunk_y < 16; ++chunk_y)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(15, chunk_y);
|
|
MapChunk* sourceChunk = this->getChunk(0, chunk_y);
|
|
|
|
if (!targetChunk->hasColors())
|
|
{
|
|
targetChunk->initMCCV();
|
|
}
|
|
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
for (int vert_y = 0; vert_y < 9; ++vert_y)
|
|
{
|
|
int target_vert = vert_y * 17 + 8;
|
|
int source_vert = vert_y * 17;
|
|
targetChunk->getVertexColors()[target_vert] = sourceChunk->getVertexColors()[source_vert];
|
|
}
|
|
}
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (index.x > 0 && index.z > 0)
|
|
{
|
|
getWorld()->for_tile_at_force(TileIndex { index.x-1, index.z-1 }
|
|
, [&] (MapTile* tile)
|
|
{
|
|
MapChunk* targetChunk = tile->getChunk(15, 15);
|
|
|
|
if (!targetChunk->hasColors())
|
|
{
|
|
targetChunk->initMCCV();
|
|
}
|
|
|
|
targetChunk->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
tile->getChunk(15,15)->getVertexColors()[144] = this->getChunk(0,0)->getVertexColors()[0];
|
|
tile->registerChunkUpdate(ChunkUpdateFlags::MCCV);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapTile::recalcExtents()
|
|
{
|
|
if (!_extents_dirty)
|
|
return;
|
|
|
|
_extents[0].y = std::numeric_limits<float>::max();
|
|
_extents[1].y = std::numeric_limits<float>::lowest();
|
|
|
|
if (!finishedLoading())
|
|
{
|
|
_extents_dirty = true;
|
|
return;
|
|
}
|
|
|
|
if (loading_failed())
|
|
{
|
|
_extents_dirty = false;
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < 256; ++i)
|
|
{
|
|
unsigned x = i / 16;
|
|
unsigned z = i % 16;
|
|
|
|
auto& chunk = mChunks[x][z];
|
|
|
|
_extents[0].y = std::min(_extents[0].y, chunk->getMinHeight());
|
|
_extents[1].y = std::max(_extents[1].y, chunk->getMaxHeight());
|
|
}
|
|
|
|
_center.y = (_extents[0].y + _extents[1].y) / 2;
|
|
|
|
_extents_dirty = false;
|
|
tagCombinedExtents(true);
|
|
}
|
|
|
|
void MapTile::recalcObjectInstanceExtents()
|
|
{
|
|
if (!_requires_object_extents_recalc)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (object_instances.empty())
|
|
{
|
|
_object_instance_extents[0] = {0.f, 0.f, 0.f};
|
|
_object_instance_extents[1] = {0.f, 0.f, 0.f};
|
|
|
|
_requires_object_extents_recalc = false;
|
|
tagCombinedExtents(true);
|
|
return;
|
|
}
|
|
|
|
_object_instance_extents[0] = {std::numeric_limits<float>::max(),
|
|
std::numeric_limits<float>::max(),
|
|
std::numeric_limits<float>::max()};
|
|
|
|
_object_instance_extents[1] = {std::numeric_limits<float>::lowest(),
|
|
std::numeric_limits<float>::lowest(),
|
|
std::numeric_limits<float>::lowest()};
|
|
|
|
_requires_object_extents_recalc = false;
|
|
|
|
for (auto& pair : object_instances)
|
|
{
|
|
for (auto& instance : pair.second)
|
|
{
|
|
if (!instance->finishedLoading())
|
|
{
|
|
_requires_object_extents_recalc = true;
|
|
continue;
|
|
}
|
|
|
|
instance->ensureExtents();
|
|
|
|
glm::vec3 min = instance->getExtents()[0];
|
|
glm::vec3 max = instance->getExtents()[1];
|
|
|
|
_object_instance_extents[0].x = std::min(_object_instance_extents[0].x, min.x);
|
|
_object_instance_extents[0].y = std::min(_object_instance_extents[0].y, min.y);
|
|
_object_instance_extents[0].z = std::min(_object_instance_extents[0].z, min.z);
|
|
|
|
_object_instance_extents[1].x = std::max(_object_instance_extents[1].x, max.x);
|
|
_object_instance_extents[1].y = std::max(_object_instance_extents[1].y, max.y);
|
|
_object_instance_extents[1].z = std::max(_object_instance_extents[1].z, max.z);
|
|
}
|
|
}
|
|
|
|
tagCombinedExtents(true);
|
|
|
|
}
|
|
|
|
void MapTile::calcCamDist(glm::vec3 const& camera)
|
|
{
|
|
_cam_dist = glm::distance(camera, _center);
|
|
}
|
|
|
|
const texture_heightmapping_data& MapTile::GetTextureHeightMappingData(const std::string& name) const
|
|
{
|
|
return Noggit::Project::CurrentProject::get()->ExtraMapData.GetTextureHeightDataForADT(_world->mapIndex._map_id, index,name);
|
|
}
|
|
|
|
void MapTile::forceAlphaUpdate()
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
auto chunk = mChunks[i][j].get();
|
|
auto texSet = chunk->getTextureSet();
|
|
texSet->markDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MapTile::childrenFinishedLoading()
|
|
{
|
|
if (!objectsFinishedLoading())
|
|
return false;
|
|
|
|
if (!texturesFinishedLoading())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// TODO : Is there a way for objects to notify their parent when they finish loading ?
|
|
// TODO : we can store a cache of unloaded models/textures to check fast instead of re iterating everything.
|
|
bool MapTile::texturesFinishedLoading()
|
|
{
|
|
if (_textures_finished_loading)
|
|
return true;
|
|
|
|
// having a list of textures in the adt would speed this up
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
auto& chunk = *mChunks[j][i];
|
|
auto& chunk_textures = *(chunk.texture_set->getTextures());
|
|
for (int k = 0; k < chunk.texture_set->num(); ++k)
|
|
{
|
|
if (!chunk_textures[k]->finishedLoading())
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _textures_finished_loading = true;
|
|
}
|
|
|
|
bool MapTile::objectsFinishedLoading()
|
|
{
|
|
if (_objects_finished_loading)
|
|
return true;
|
|
|
|
for (auto& instance : object_instances)
|
|
{
|
|
if (!instance.first->finishedLoading())
|
|
return false;
|
|
}
|
|
|
|
return _objects_finished_loading = true;
|
|
}
|
|
|
|
void MapTile::recalcCombinedExtents()
|
|
{
|
|
if (!_combined_extents_dirty)
|
|
return;
|
|
|
|
if (!finishedLoading())
|
|
{
|
|
_combined_extents_dirty = true;
|
|
return;
|
|
}
|
|
|
|
if (loading_failed())
|
|
{
|
|
_combined_extents_dirty = false;
|
|
return;
|
|
}
|
|
|
|
// ensure all extents are updated
|
|
{
|
|
recalcExtents();
|
|
|
|
if (Water.needsUpdate())
|
|
{
|
|
Water.recalcExtents();
|
|
}
|
|
|
|
recalcObjectInstanceExtents();
|
|
}
|
|
|
|
_combined_extents = _extents;
|
|
|
|
auto& water_extents = Water.getExtents();
|
|
_combined_extents[0].y = std::min(_combined_extents[0].y, water_extents[0].y);
|
|
_combined_extents[1].y = std::max(_combined_extents[1].y, water_extents[1].y);
|
|
|
|
if (!object_instances.empty())
|
|
{
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
_combined_extents[0][i] = std::min(_combined_extents[0][i], _object_instance_extents[0][i]);
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
_combined_extents[1][i] = std::max(_combined_extents[1][i], _object_instance_extents[1][i]);
|
|
}
|
|
}
|
|
|
|
_combined_extents_dirty = false;
|
|
} |