2964 lines
77 KiB
C++
Executable File
2964 lines
77 KiB
C++
Executable File
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
|
|
|
|
#include <noggit/World.h>
|
|
#include <noggit/World.inl>
|
|
|
|
#include <math/frustum.hpp>
|
|
#include <noggit/Brush.h> // brush
|
|
#include <noggit/DBC.h>
|
|
#include <noggit/Log.h>
|
|
#include <noggit/MapChunk.h>
|
|
#include <noggit/MapTile.h>
|
|
#include <noggit/Misc.h>
|
|
#include <noggit/ModelManager.h> // ModelManager
|
|
#include <noggit/TextureManager.h>
|
|
#include <noggit/WMOInstance.h> // WMOInstance
|
|
#include <noggit/texture_set.hpp>
|
|
#include <noggit/tool_enums.hpp>
|
|
#include <noggit/ui/ObjectEditor.h>
|
|
#include <noggit/ui/TexturingGUI.h>
|
|
#include <noggit/application/NoggitApplication.hpp>
|
|
#include <noggit/project/CurrentProject.hpp>
|
|
#include <noggit/ActionManager.hpp>
|
|
#include <external/tracy/Tracy.hpp>
|
|
#include <QByteArray>
|
|
#include <QImage>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <ctime>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <limits>
|
|
#include <array>
|
|
#include <cstdint>
|
|
|
|
#include <glm/gtc/quaternion.hpp>
|
|
#include <glm/gtx/quaternion.hpp>
|
|
|
|
|
|
bool World::IsEditableWorld(BlizzardDatabaseLib::Structures::BlizzardDatabaseRow& record)
|
|
{
|
|
ZoneScoped;
|
|
std::string lMapName = record.Columns["Directory"].Value;
|
|
|
|
std::stringstream ssfilename;
|
|
ssfilename << "World\\Maps\\" << lMapName << "\\" << lMapName << ".wdt";
|
|
|
|
if (!Noggit::Application::NoggitApplication::instance()->clientData()->exists(ssfilename.str()))
|
|
{
|
|
Log << "World " << record.RecordId << ": " << lMapName << " has no WDT file!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BlizzardArchive::ClientFile mf(ssfilename.str(), Noggit::Application::NoggitApplication::instance()->clientData());
|
|
|
|
//sometimes, wdts don't open, so ignore them...
|
|
if (mf.isEof())
|
|
return false;
|
|
|
|
const char * lPointer = reinterpret_cast<const char*>(mf.getPointer());
|
|
|
|
// Not using the libWDT here doubles performance. You might want to look at your lib again and improve it.
|
|
const int lFlags = *(reinterpret_cast<const int*>(lPointer + 8 + 4 + 8));
|
|
if (lFlags & 1)
|
|
return false;
|
|
|
|
const int * lData = reinterpret_cast<const int*>(lPointer + 8 + 4 + 8 + 0x20 + 8);
|
|
for (int i = 0; i < 8192; i += 2)
|
|
{
|
|
if (lData[i] & 1)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
World::World(const std::string& name, int map_id, Noggit::NoggitRenderContext context, bool create_empty)
|
|
: _renderer(Noggit::Rendering::WorldRender(this))
|
|
, _model_instance_storage(this)
|
|
, _tile_update_queue(this)
|
|
, mapIndex(name, map_id, this, context, create_empty)
|
|
, horizon(name, &mapIndex)
|
|
, mWmoFilename("")
|
|
, mWmoEntry(ENTRY_MODF())
|
|
, animtime(0)
|
|
, time(1450)
|
|
, basename(name)
|
|
, _current_selection()
|
|
, _settings(new QSettings())
|
|
, _context(context)
|
|
{
|
|
LogDebug << "Loading world \"" << name << "\"." << std::endl;
|
|
_loaded_tiles_buffer[0] = std::make_pair<std::pair<int, int>, MapTile*>(std::make_pair(0, 0), nullptr);
|
|
}
|
|
|
|
void World::update_selection_pivot()
|
|
{
|
|
ZoneScoped;
|
|
if (has_multiple_model_selected())
|
|
{
|
|
glm::vec3 pivot = glm::vec3(0);
|
|
int model_count = 0;
|
|
|
|
for (auto const& entry : _current_selection)
|
|
{
|
|
if (entry.index() == eEntry_Object)
|
|
{
|
|
pivot += std::get<selected_object_type>(entry)->pos;
|
|
model_count++;
|
|
}
|
|
}
|
|
|
|
_multi_select_pivot = pivot / static_cast<float>(model_count);
|
|
}
|
|
else
|
|
{
|
|
_multi_select_pivot = std::nullopt;
|
|
}
|
|
}
|
|
|
|
bool World::is_selected(selection_type selection) const
|
|
{
|
|
ZoneScoped;
|
|
if (selection.index() != eEntry_Object)
|
|
return false;
|
|
|
|
auto which = std::get<selected_object_type>(selection)->which();
|
|
|
|
if (which == eMODEL)
|
|
{
|
|
uint uid = static_cast<ModelInstance*>(std::get<selected_object_type>(selection))->uid;
|
|
auto const& it = std::find_if(_current_selection.begin()
|
|
, _current_selection.end()
|
|
, [uid] (selection_type type)
|
|
{
|
|
return var_type(type) == typeid(selected_object_type)
|
|
&& std::get<selected_object_type>(type)->which() == eMODEL
|
|
&& static_cast<ModelInstance*>(std::get<selected_object_type>(type))->uid == uid;
|
|
}
|
|
);
|
|
|
|
if (it != _current_selection.end())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (which == eWMO)
|
|
{
|
|
uint uid = static_cast<WMOInstance*>(std::get<selected_object_type>(selection))->uid;
|
|
auto const& it = std::find_if(_current_selection.begin()
|
|
, _current_selection.end()
|
|
, [uid] (selection_type type)
|
|
{
|
|
return var_type(type) == typeid(selected_object_type)
|
|
&& std::get<selected_object_type>(type)->which() == eWMO
|
|
&& static_cast<WMOInstance*>(std::get<selected_object_type>(type))->uid == uid;
|
|
}
|
|
);
|
|
if (it != _current_selection.end())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool World::is_selected(std::uint32_t uid) const
|
|
{
|
|
ZoneScoped;
|
|
for (selection_type const& entry : _current_selection)
|
|
{
|
|
if (entry.index() != eEntry_Object)
|
|
continue;
|
|
|
|
auto obj = std::get<selected_object_type>(entry);
|
|
|
|
if (obj->which() == eWMO)
|
|
{
|
|
if (static_cast<WMOInstance*>(obj)->uid == uid)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (obj->which() == eMODEL)
|
|
{
|
|
if (static_cast<ModelInstance*>(obj)->uid == uid)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::optional<selection_type> World::get_last_selected_model() const
|
|
{
|
|
ZoneScoped;
|
|
auto const it
|
|
( std::find_if ( _current_selection.rbegin()
|
|
, _current_selection.rend()
|
|
, [&] (selection_type const& entry)
|
|
{
|
|
return entry.index() != eEntry_MapChunk;
|
|
}
|
|
)
|
|
);
|
|
|
|
return it == _current_selection.rend()
|
|
? std::optional<selection_type>() : std::optional<selection_type> (*it);
|
|
}
|
|
|
|
glm::vec3 getBarycentricCoordinatesAt(
|
|
const glm::vec3& a,
|
|
const glm::vec3& b,
|
|
const glm::vec3& c,
|
|
const glm::vec3& point,
|
|
const glm::vec3& normal)
|
|
{
|
|
glm::vec3 bary;
|
|
// The area of a triangle is
|
|
|
|
glm::vec3 aMb = (b - a);
|
|
glm::vec3 cMa = (c - a);
|
|
glm::vec3 bMpoint = (b - point);
|
|
glm::vec3 cMpont = (c - point);
|
|
glm::vec3 aMpoint = (a - point);
|
|
|
|
glm::vec3 ABC = glm::cross(aMb ,cMa);
|
|
glm::vec3 PBC = glm::cross(bMpoint, cMpont);
|
|
glm::vec3 PCA = glm::cross(cMpont, aMpoint);
|
|
|
|
double areaABC = glm::dot(normal , ABC);
|
|
double areaPBC = glm::dot(normal , PBC);
|
|
double areaPCA = glm::dot(normal , PCA);
|
|
|
|
bary.x = areaPBC / areaABC; // alpha
|
|
bary.y = areaPCA / areaABC; // beta
|
|
bary.z = 1.0f - bary.x - bary.y; // gamma
|
|
|
|
return bary;
|
|
}
|
|
|
|
void World::rotate_selected_models_randomly(float minX, float maxX, float minY, float maxY, float minZ, float maxZ)
|
|
{
|
|
ZoneScoped;
|
|
bool has_multi_select = has_multiple_model_selected();
|
|
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
|
|
math::degrees::vec3& dir = obj->dir;
|
|
|
|
float rx = misc::randfloat(minX, maxX);
|
|
float ry = misc::randfloat(minY, maxY);
|
|
float rz = misc::randfloat(minZ, maxZ);
|
|
|
|
//Building rotations
|
|
auto heading = math::radians(math::degrees(dir.z))._ * 0.5;
|
|
auto attitude = math::radians(math::degrees(-dir.y))._ * 0.5;
|
|
auto bank = math::radians(math::degrees(dir.x))._ * 0.5;
|
|
// Assuming the angles are in radians.
|
|
double c1 = cos(heading);
|
|
double s1 = sin(heading);
|
|
double c2 = cos(attitude);
|
|
double s2 = sin(attitude);
|
|
double c3 = cos(bank);
|
|
double s3 = sin(bank);
|
|
double c1c2 = c1 * c2;
|
|
double s1s2 = s1 * s2;
|
|
auto w = static_cast<float>(c1c2 * c3 - s1s2 * s3);
|
|
auto x = static_cast<float>(c1c2 * s3 + s1s2 * c3);
|
|
auto y = static_cast<float>(s1 * c2 * c3 + c1 * s2 * s3);
|
|
auto z = static_cast<float>(c1 * s2 * c3 - s1 * c2 * s3);
|
|
|
|
glm::quat baseRotation = glm::quat(x,y,z,w);
|
|
|
|
//Building rotations
|
|
heading = math::radians(math::degrees(rx))._ * 0.5;
|
|
attitude = math::radians(math::degrees(ry))._ * 0.5;
|
|
bank = math::radians(math::degrees(rx))._ * 0.5;
|
|
// Assuming the angles are in radians.
|
|
c1 = cos(heading);
|
|
s1 = sin(heading);
|
|
c2 = cos(attitude);
|
|
s2 = sin(attitude);
|
|
c3 = cos(bank);
|
|
s3 = sin(bank);
|
|
c1c2 = c1 * c2;
|
|
s1s2 = s1 * s2;
|
|
w = static_cast<float>(c1c2 * c3 - s1s2 * s3);
|
|
x = static_cast<float>(c1c2 * s3 + s1s2 * c3);
|
|
y = static_cast<float>(s1 * c2 * c3 + c1 * s2 * s3);
|
|
z = static_cast<float>(c1 * s2 * c3 - s1 * c2 * s3);
|
|
|
|
glm::quat newRotation = glm::quat(x, y, z, w);
|
|
glm::quat finalRotation = baseRotation * newRotation;
|
|
glm::quat finalRotationNormalized = glm::normalize(finalRotation);
|
|
|
|
auto eulerAngles = glm::eulerAngles(finalRotationNormalized);
|
|
dir.x = math::degrees(math::radians(eulerAngles.z))._;
|
|
dir.y = math::degrees(math::radians(eulerAngles.x))._;
|
|
dir.z = math::degrees(math::radians(eulerAngles.y))._;
|
|
|
|
obj->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
}
|
|
|
|
|
|
void World::rotate_selected_models_to_ground_normal(bool smoothNormals)
|
|
{
|
|
ZoneScoped;
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
glm::vec3 rayPos = obj->pos;
|
|
math::degrees::vec3& dir = obj->dir;
|
|
|
|
|
|
selection_result results;
|
|
for_chunk_at(rayPos, [&](MapChunk* chunk)
|
|
{
|
|
{
|
|
math::ray intersect_ray(rayPos, glm::vec3(0.f, -1.f, 0.f));
|
|
chunk->intersect(intersect_ray, &results);
|
|
}
|
|
// object is below ground
|
|
if (results.empty())
|
|
{
|
|
math::ray intersect_ray(rayPos, glm::vec3(0.f, 1.f, 0.f));
|
|
chunk->intersect(intersect_ray, &results);
|
|
}
|
|
});
|
|
|
|
// We shouldn't end up with empty ever.
|
|
if (results.empty())
|
|
{
|
|
LogError << "rotate_selected_models_to_ground_normal ray intersection failed" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
|
|
// We hit the terrain, now we take the normal of this position and use it to get the rotation we want.
|
|
auto const& hitChunkInfo = std::get<selected_chunk_type>(results.front().second);
|
|
|
|
glm::quat q;
|
|
glm::vec3 varnormal;
|
|
|
|
// Surface Normal
|
|
auto &p0 = hitChunkInfo.chunk->mVertices[std::get<0>(hitChunkInfo.triangle)];
|
|
auto &p1 = hitChunkInfo.chunk->mVertices[std::get<1>(hitChunkInfo.triangle)];
|
|
auto &p2 = hitChunkInfo.chunk->mVertices[std::get<2>(hitChunkInfo.triangle)];
|
|
|
|
glm::vec3 v1 = p1 - p0;
|
|
glm::vec3 v2 = p2 - p0;
|
|
|
|
auto tmpVec = glm::cross(v2 ,v1);
|
|
varnormal.x = tmpVec.z;
|
|
varnormal.y = tmpVec.y;
|
|
varnormal.z = tmpVec.x;
|
|
|
|
// Smooth option, gradient the normal towards closest vertex
|
|
if (smoothNormals) // Vertex Normal
|
|
{
|
|
auto normalWeights = getBarycentricCoordinatesAt(p0, p1, p2, hitChunkInfo.position, varnormal);
|
|
|
|
auto& tile_buffer = hitChunkInfo.chunk->mt->getChunkHeightmapBuffer();
|
|
int chunk_start = (hitChunkInfo.chunk->px * 16 + hitChunkInfo.chunk->py) * mapbufsize * 4;
|
|
|
|
const auto& vNormal0 = *reinterpret_cast<glm::vec3*>(&tile_buffer[chunk_start + std::get<0>(hitChunkInfo.triangle) * 4]);
|
|
const auto& vNormal1 = *reinterpret_cast<glm::vec3*>(&tile_buffer[chunk_start + std::get<1>(hitChunkInfo.triangle) * 4]);
|
|
const auto& vNormal2 = *reinterpret_cast<glm::vec3*>(&tile_buffer[chunk_start + std::get<2>(hitChunkInfo.triangle) * 4]);
|
|
|
|
varnormal.x =
|
|
vNormal0.x * normalWeights.x +
|
|
vNormal1.x * normalWeights.y +
|
|
vNormal2.x * normalWeights.z;
|
|
|
|
varnormal.y =
|
|
vNormal0.y * normalWeights.x +
|
|
vNormal1.y * normalWeights.y +
|
|
vNormal2.y * normalWeights.z;
|
|
|
|
varnormal.z =
|
|
vNormal0.z * normalWeights.x +
|
|
vNormal1.z * normalWeights.y +
|
|
vNormal2.z * normalWeights.z;
|
|
}
|
|
|
|
|
|
glm::vec3 worldUp = glm::vec3(0, 1, 0);
|
|
glm::vec3 a =glm::cross(worldUp ,varnormal);
|
|
|
|
q.x = a.x;
|
|
q.y = a.y;
|
|
q.z = a.z;
|
|
|
|
auto worldLengthSqrd = glm::length(worldUp) * glm::length(worldUp);
|
|
auto normalLengthSqrd = glm::length(varnormal) * glm::length(varnormal);
|
|
auto worldDotNormal = glm::dot(worldUp, varnormal);
|
|
|
|
q.w = std::sqrt((worldLengthSqrd * normalLengthSqrd) + (worldDotNormal));
|
|
|
|
auto normalizedQ = glm::normalize(q);
|
|
|
|
math::degrees::vec3 new_dir;
|
|
// To euler, because wow
|
|
/*
|
|
// roll (x-axis rotation)
|
|
double sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
|
|
double cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
|
|
new_dir.z = std::atan2(sinr_cosp, cosr_cosp) * 180.0f / math::constants::pi;
|
|
|
|
// pitch (y-axis rotation)
|
|
double sinp = 2.0 * (q.w * q.y - q.z * q.x);
|
|
if (std::abs(sinp) >= 1)
|
|
new_dir.y = std::copysign(math::constants::pi / 2, sinp) * 180.0f / math::constants::pi; // use 90 degrees if out of range
|
|
else
|
|
new_dir.y = std::asin(sinp) * 180.0f / math::constants::pi;
|
|
|
|
// yaw (z-axis rotation)
|
|
double siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
|
|
double cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
|
|
new_dir.x = std::atan2(siny_cosp, cosy_cosp) * 180.0f / math::constants::pi;
|
|
}*/
|
|
|
|
auto eulerAngles = glm::eulerAngles(normalizedQ);
|
|
dir.x = math::degrees(math::radians(eulerAngles.z))._; //Roll
|
|
dir.y = math::degrees(math::radians(eulerAngles.x))._; //Pitch
|
|
dir.z = math::degrees(math::radians(eulerAngles.y))._; //Yaw
|
|
|
|
std::get<selected_object_type>(entry)->recalcExtents();
|
|
|
|
// yaw (z-axis rotation)
|
|
double siny_cosp = 2 * (q.w * q.z + q.x * q.y);
|
|
double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
}
|
|
|
|
void World::set_current_selection(selection_type entry)
|
|
{
|
|
ZoneScoped;
|
|
_current_selection.clear();
|
|
_current_selection.push_back(entry);
|
|
_multi_select_pivot = std::nullopt;
|
|
|
|
_selected_model_count = entry.index() == eEntry_MapChunk ? 0 : 1;
|
|
}
|
|
|
|
void World::add_to_selection(selection_type entry)
|
|
{
|
|
ZoneScoped;
|
|
if (entry.index() != eEntry_MapChunk)
|
|
{
|
|
_selected_model_count++;
|
|
}
|
|
|
|
_current_selection.push_back(entry);
|
|
update_selection_pivot();
|
|
}
|
|
|
|
void World::remove_from_selection(selection_type entry)
|
|
{
|
|
ZoneScoped;
|
|
std::vector<selection_type>::iterator position = std::find(_current_selection.begin(), _current_selection.end(), entry);
|
|
if (position != _current_selection.end())
|
|
{
|
|
if (entry.index() != eEntry_MapChunk)
|
|
{
|
|
_selected_model_count--;
|
|
}
|
|
|
|
_current_selection.erase(position);
|
|
update_selection_pivot();
|
|
}
|
|
}
|
|
|
|
void World::remove_from_selection(std::uint32_t uid)
|
|
{
|
|
ZoneScoped;
|
|
for (auto it = _current_selection.begin(); it != _current_selection.end(); ++it)
|
|
{
|
|
if (it->index() != eEntry_Object)
|
|
continue;
|
|
|
|
auto obj = std::get<selected_object_type>(*it);
|
|
|
|
if (obj->which() == eMODEL && static_cast<ModelInstance*>(obj)->uid == uid)
|
|
{
|
|
_current_selection.erase(it);
|
|
update_selection_pivot();
|
|
return;
|
|
}
|
|
else if (obj->which() == eWMO && static_cast<WMOInstance*>(obj)->uid == uid)
|
|
{
|
|
_current_selection.erase(it);
|
|
update_selection_pivot();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::reset_selection()
|
|
{
|
|
ZoneScoped;
|
|
_current_selection.clear();
|
|
_multi_select_pivot = std::nullopt;
|
|
_selected_model_count = 0;
|
|
}
|
|
|
|
void World::delete_selected_models()
|
|
{
|
|
ZoneScoped;
|
|
_model_instance_storage.delete_instances(_current_selection);
|
|
need_model_updates = true;
|
|
reset_selection();
|
|
}
|
|
|
|
void World::snap_selected_models_to_the_ground()
|
|
{
|
|
ZoneScoped;
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
glm::vec3& pos = obj->pos;
|
|
|
|
selection_result hits;
|
|
|
|
|
|
for_chunk_at(pos, [&] (MapChunk* chunk)
|
|
{
|
|
{
|
|
math::ray intersect_ray(pos, glm::vec3(0.f, -1.f, 0.f));
|
|
chunk->intersect(intersect_ray, &hits);
|
|
}
|
|
// object is below ground
|
|
if (hits.empty())
|
|
{
|
|
math::ray intersect_ray(pos, glm::vec3(0.f, 1.f, 0.f));
|
|
chunk->intersect(intersect_ray, &hits);
|
|
}
|
|
});
|
|
|
|
// this should never happen
|
|
if (hits.empty())
|
|
{
|
|
LogError << "Snap to ground ray intersection failed" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// the ground can only be intersected once
|
|
pos.y = std::get<selected_chunk_type>(hits[0].second).position.y;
|
|
|
|
std::get<selected_object_type>(entry)->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
|
|
update_selection_pivot();
|
|
}
|
|
|
|
void World::scale_selected_models(float v, m2_scaling_type type)
|
|
{
|
|
ZoneScoped;
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
if (entry.index() == eEntry_Object)
|
|
{
|
|
auto obj = std::get<selected_object_type>(entry);
|
|
|
|
if (obj->which() != eMODEL)
|
|
continue;
|
|
|
|
ModelInstance* mi = static_cast<ModelInstance*>(obj);
|
|
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(mi);
|
|
|
|
float scale = mi->scale;
|
|
|
|
switch (type)
|
|
{
|
|
case World::m2_scaling_type::set:
|
|
scale = v;
|
|
break;
|
|
case World::m2_scaling_type::add:
|
|
scale += v;
|
|
break;
|
|
case World::m2_scaling_type::mult:
|
|
scale *= v;
|
|
break;
|
|
}
|
|
|
|
// if the change is too small, do nothing
|
|
if (std::abs(scale - mi->scale) < ModelInstance::min_scale())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updateTilesModel(mi, model_update::remove);
|
|
mi->scale = std::min(ModelInstance::max_scale(), std::max(ModelInstance::min_scale(), scale));
|
|
mi->recalcExtents();
|
|
updateTilesModel(mi, model_update::add);
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::move_selected_models(float dx, float dy, float dz)
|
|
{
|
|
ZoneScoped;
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
glm::vec3& pos = obj->pos;
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
pos.x += dx;
|
|
pos.y += dy;
|
|
pos.z += dz;
|
|
|
|
std::get<selected_object_type>(entry)->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
|
|
update_selection_pivot();
|
|
}
|
|
|
|
void World::set_selected_models_pos(glm::vec3 const& pos, bool change_height)
|
|
{
|
|
ZoneScoped;
|
|
// move models relative to the pivot when several are selected
|
|
if (has_multiple_model_selected())
|
|
{
|
|
glm::vec3 diff = pos - _multi_select_pivot.value();
|
|
|
|
if (change_height)
|
|
{
|
|
move_selected_models(diff);
|
|
}
|
|
else
|
|
{
|
|
move_selected_models(diff.x, 0.f, diff.z);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
obj->pos = pos;
|
|
obj->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
|
|
update_selection_pivot();
|
|
}
|
|
|
|
void World::rotate_selected_models(math::degrees rx, math::degrees ry, math::degrees rz, bool use_pivot)
|
|
{
|
|
ZoneScoped;
|
|
math::degrees::vec3 dir_change(rx._, ry._, rz._);
|
|
bool has_multi_select = has_multiple_model_selected();
|
|
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
|
|
if (use_pivot && has_multi_select)
|
|
{
|
|
glm::vec3& pos = obj->pos;
|
|
math::degrees::vec3& dir = obj->dir;
|
|
glm::vec3 diff_pos = pos - _multi_select_pivot.value();
|
|
|
|
glm::quat rotationQuat = glm::quat(glm::vec3(glm::radians(rx._), glm::radians(ry._), glm::radians(rz._)));
|
|
glm::vec3 rot_result = glm::toMat4(rotationQuat) * glm::vec4(diff_pos,0);
|
|
|
|
pos += rot_result - diff_pos;
|
|
}
|
|
else
|
|
{
|
|
math::degrees::vec3& dir = obj->dir;
|
|
dir += dir_change;
|
|
}
|
|
|
|
obj->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
}
|
|
|
|
void World::set_selected_models_rotation(math::degrees rx, math::degrees ry, math::degrees rz)
|
|
{
|
|
ZoneScoped;
|
|
math::degrees::vec3 new_dir(rx._, ry._, rz._);
|
|
|
|
for (auto& entry : _current_selection)
|
|
{
|
|
auto type = entry.index();
|
|
if (type == eEntry_MapChunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto& obj = std::get<selected_object_type>(entry);
|
|
NOGGIT_CUR_ACTION->registerObjectTransformed(obj);
|
|
|
|
updateTilesEntry(entry, model_update::remove);
|
|
|
|
math::degrees::vec3& dir = obj->dir;
|
|
|
|
dir = new_dir;
|
|
|
|
obj->recalcExtents();
|
|
|
|
updateTilesEntry(entry, model_update::add);
|
|
}
|
|
}
|
|
|
|
|
|
MapChunk* World::getChunkAt(glm::vec3 const& pos)
|
|
{
|
|
MapTile* tile(mapIndex.getTile(pos));
|
|
if (tile && tile->finishedLoading())
|
|
{
|
|
return tile->getChunk((pos.x - tile->xbase) / CHUNKSIZE, (pos.z - tile->zbase) / CHUNKSIZE);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
selection_result World::intersect (glm::mat4x4 const& model_view
|
|
, math::ray const& ray
|
|
, bool pOnlyMap
|
|
, bool do_objects
|
|
, bool draw_terrain
|
|
, bool draw_wmo
|
|
, bool draw_models
|
|
, bool draw_hidden_models
|
|
)
|
|
{
|
|
ZoneScopedN("World::intersect()");
|
|
selection_result results;
|
|
|
|
if (draw_terrain)
|
|
{
|
|
ZoneScopedN("World::intersect() : intersect terrain");
|
|
|
|
for (auto& pair : _loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
break;
|
|
|
|
TileIndex index{ static_cast<std::size_t>(pair.first.first)
|
|
, static_cast<std::size_t>(pair.first.second) };
|
|
|
|
// handle tiles that got unloaded mid-frame to avoid illegal access
|
|
if (!mapIndex.tileLoaded(index) || mapIndex.tileAwaitingLoading(index))
|
|
continue;
|
|
|
|
if (!tile->finishedLoading())
|
|
continue;
|
|
|
|
if (tile->intersect(ray, &results))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pOnlyMap && do_objects)
|
|
{
|
|
if (draw_models)
|
|
{
|
|
ZoneScopedN("World::intersect() : intersect M2s");
|
|
_model_instance_storage.for_each_m2_instance([&] (ModelInstance& model_instance)
|
|
{
|
|
if (draw_hidden_models || !model_instance.model->is_hidden())
|
|
{
|
|
model_instance.intersect(model_view, ray, &results, animtime);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (draw_wmo)
|
|
{
|
|
ZoneScopedN("World::intersect() : intersect WMOs");
|
|
_model_instance_storage.for_each_wmo_instance([&] (WMOInstance& wmo_instance)
|
|
{
|
|
if (draw_hidden_models || !wmo_instance.wmo->is_hidden())
|
|
{
|
|
wmo_instance.intersect(ray, &results);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return std::move(results);
|
|
}
|
|
|
|
void World::update_models_emitters(float dt)
|
|
{
|
|
ZoneScoped;
|
|
while (dt > 0.1f)
|
|
{
|
|
ModelManager::updateEmitters(0.1f);
|
|
dt -= 0.1f;
|
|
}
|
|
ModelManager::updateEmitters(dt);
|
|
}
|
|
|
|
unsigned int World::getAreaID (glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
return for_maybe_chunk_at (pos, [&] (MapChunk* chunk) { return chunk->getAreaID(); }).value_or(-1);
|
|
}
|
|
|
|
void World::clearHeight(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
chunk->clearHeight();
|
|
});
|
|
for_all_chunks_on_tile(pos, [this] (MapChunk* chunk) {
|
|
recalc_norms (chunk);
|
|
});
|
|
}
|
|
|
|
void World::clearAllModelsOnADT(TileIndex const& tile)
|
|
{
|
|
ZoneScoped;
|
|
_model_instance_storage.delete_instances_from_tile(tile);
|
|
update_models_by_filename();
|
|
}
|
|
|
|
void World::CropWaterADT(const TileIndex& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at(pos, [](MapTile* tile)
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
for (int j = 0; j < 16; ++j)
|
|
NOGGIT_CUR_ACTION->registerChunkLiquidChange(tile->getChunk(i, j));
|
|
|
|
tile->CropWater();
|
|
});
|
|
}
|
|
|
|
void World::setAreaID(glm::vec3 const& pos, int id, bool adt, float radius)
|
|
{
|
|
ZoneScoped;
|
|
if (adt)
|
|
{
|
|
for_all_chunks_on_tile(pos, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkAreaIDChange(chunk);
|
|
chunk->setAreaID(id);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
|
|
if (radius >= 0)
|
|
{
|
|
for_all_chunks_in_range(pos, radius,
|
|
[&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkAreaIDChange(chunk);
|
|
chunk->setAreaID(id);
|
|
return true;
|
|
}
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
for_chunk_at(pos, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkAreaIDChange(chunk);
|
|
chunk->setAreaID(id);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
bool World::GetVertex(float x, float z, glm::vec3 *V) const
|
|
{
|
|
ZoneScoped;
|
|
TileIndex tile({x, 0, z});
|
|
|
|
if (!mapIndex.tileLoaded(tile))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MapTile* adt = mapIndex.getTile(tile);
|
|
|
|
return adt->GetVertex(x, z, V);
|
|
}
|
|
|
|
|
|
|
|
void World::changeShader(glm::vec3 const& pos, glm::vec4 const& color, float change, float radius, bool editMode)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkVertexColorChange(chunk);
|
|
return chunk->ChangeMCCV(pos, color, change, radius, editMode);
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::stampShader(glm::vec3 const& pos, glm::vec4 const& color, float change, float radius, bool editMode, QImage* img, bool paint, bool use_image_colors)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_rect
|
|
( pos, radius
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkVertexColorChange(chunk);
|
|
return chunk->stampMCCV(pos, color, change, radius, editMode, img, paint, use_image_colors);
|
|
}
|
|
);
|
|
}
|
|
|
|
glm::vec3 World::pickShaderColor(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
glm::vec3 color = glm::vec3(1.0f, 1.0f, 1.0f);
|
|
for_all_chunks_in_range
|
|
(pos, 0.1f
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
color = chunk->pickMCCV(pos);
|
|
return true;
|
|
}
|
|
);
|
|
|
|
return color;
|
|
}
|
|
|
|
auto World::stamp(glm::vec3 const& pos, float dt, QImage const* img, float radiusOuter
|
|
, float radiusInner, int brushType, bool sculpt) -> void
|
|
{
|
|
ZoneScoped;
|
|
auto action = NOGGIT_CUR_ACTION;
|
|
float delta = action->getDelta() + dt;
|
|
action->setDelta(delta);
|
|
|
|
for_all_chunks_in_rect(pos, radiusOuter,
|
|
[=](MapChunk* chunk) -> bool
|
|
{
|
|
auto action = NOGGIT_CUR_ACTION;
|
|
action->registerChunkTerrainChange(chunk);
|
|
action->setBlockCursor(!sculpt);
|
|
chunk->stamp(pos, dt, img, radiusOuter, radiusInner, brushType, sculpt); return true;
|
|
}
|
|
, [this](MapChunk* chunk) -> void
|
|
{
|
|
recalc_norms(chunk);
|
|
|
|
// check if coord axis > 0
|
|
// if true, get chunk by coord axis - 1
|
|
// else, check if tile coord axis > 0
|
|
// if true, get tile by tile coord axis - 1, get last chunk by axis
|
|
auto get_neighbor =
|
|
[this, chunk](int px, int py) -> MapChunk*
|
|
{
|
|
MapChunk* neighbor{};
|
|
|
|
int new_chunk_x = px + chunk->px;
|
|
int new_chunk_z = py + chunk->py;
|
|
|
|
if (new_chunk_x < 0 || new_chunk_z < 0 || new_chunk_x == 16 || new_chunk_z == 16)
|
|
{
|
|
TileIndex index(chunk->mt->index.x + px, chunk->mt->index.z + py);
|
|
if (index.x != std::numeric_limits<std::size_t>::max()
|
|
&& index.z != std::numeric_limits<std::size_t>::max()
|
|
&& index.x != 64
|
|
&& index.z != 64)
|
|
{
|
|
MapTile* neighbor_tile = mapIndex.getTile(index);
|
|
|
|
if (!neighbor_tile)
|
|
return nullptr;
|
|
|
|
neighbor = neighbor_tile->getChunk((new_chunk_x + 16) % 16,
|
|
(new_chunk_z + 16) % 16);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
neighbor = chunk->mt->getChunk(new_chunk_x, new_chunk_z);
|
|
}
|
|
|
|
return neighbor;
|
|
};
|
|
|
|
if (auto neighbor = get_neighbor(-1, 0); neighbor)
|
|
chunk->fixGapLeft(neighbor);
|
|
|
|
if (auto neighbor = get_neighbor(0, -1); neighbor)
|
|
chunk->fixGapAbove(neighbor);
|
|
|
|
if (auto neighbor = get_neighbor(1, 0); neighbor)
|
|
neighbor->fixGapLeft(chunk);
|
|
|
|
if (auto neighbor = get_neighbor(0, 1); neighbor)
|
|
neighbor->fixGapAbove(chunk);
|
|
|
|
});
|
|
}
|
|
|
|
|
|
void World::changeTerrain(glm::vec3 const& pos, float change, float radius, int BrushType, float inner_radius)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
return chunk->changeTerrain(pos, change, radius, BrushType, inner_radius);
|
|
}
|
|
, [this] (MapChunk* chunk)
|
|
{
|
|
recalc_norms (chunk);
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::flattenTerrain(glm::vec3 const& pos, float remain, float radius, int BrushType, flatten_mode const& mode, const glm::vec3& origin, math::degrees angle, math::degrees orientation)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
return chunk->flattenTerrain(pos, remain, radius, BrushType, mode, origin, angle, orientation);
|
|
}
|
|
, [this] (MapChunk* chunk)
|
|
{
|
|
recalc_norms (chunk);
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::blurTerrain(glm::vec3 const& pos, float remain, float radius, int BrushType, flatten_mode const& mode)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
return chunk->blurTerrain ( pos
|
|
, remain
|
|
, radius
|
|
, BrushType
|
|
, mode
|
|
, [this] (float x, float z) -> std::optional<float>
|
|
{
|
|
glm::vec3 vec;
|
|
auto res (GetVertex (x, z, &vec));
|
|
return res ? std::optional<float>(vec.y) : std::nullopt;
|
|
}
|
|
);
|
|
}
|
|
, [this] (MapChunk* chunk)
|
|
{
|
|
recalc_norms (chunk);
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::recalc_norms (MapChunk* chunk) const
|
|
{
|
|
ZoneScoped;
|
|
chunk->recalcNorms();
|
|
}
|
|
|
|
bool World::paintTexture(glm::vec3 const& pos, Brush* brush, float strength, float pressure, scoped_blp_texture_reference texture)
|
|
{
|
|
ZoneScoped;
|
|
return for_all_chunks_in_range
|
|
( pos, brush->getRadius()
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
return chunk->paintTexture(pos, brush, strength, pressure, texture);
|
|
}
|
|
);
|
|
}
|
|
|
|
bool World::stampTexture(glm::vec3 const& pos, Brush *brush, float strength, float pressure, scoped_blp_texture_reference texture, QImage* img, bool paint)
|
|
{
|
|
ZoneScoped;
|
|
return for_all_chunks_in_rect
|
|
( pos, brush->getRadius()
|
|
, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
return chunk->stampTexture(pos, brush, strength, pressure, texture, img, paint);
|
|
}
|
|
);
|
|
}
|
|
|
|
bool World::sprayTexture(glm::vec3 const& pos, Brush *brush, float strength, float pressure, float spraySize, float sprayPressure, scoped_blp_texture_reference texture)
|
|
{
|
|
ZoneScoped;
|
|
bool succ = false;
|
|
float inc = brush->getRadius() / 4.0f;
|
|
|
|
for (float pz = pos.z - spraySize; pz < pos.z + spraySize; pz += inc)
|
|
{
|
|
for (float px = pos.x - spraySize; px < pos.x + spraySize; px += inc)
|
|
{
|
|
if ((sqrt(pow(px - pos.x, 2) + pow(pz - pos.z, 2)) <= spraySize) && ((rand() % 1000) < sprayPressure))
|
|
{
|
|
succ |= paintTexture({px, pos.y, pz}, brush, strength, pressure, texture);
|
|
}
|
|
}
|
|
}
|
|
|
|
return succ;
|
|
}
|
|
|
|
bool World::replaceTexture(glm::vec3 const& pos, float radius, scoped_blp_texture_reference const& old_texture, scoped_blp_texture_reference new_texture, bool entire_chunk)
|
|
{
|
|
ZoneScoped;
|
|
return for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
return chunk->replaceTexture(pos, radius, old_texture, new_texture, entire_chunk);
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::eraseTextures(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_chunk_at(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->eraseTextures();
|
|
});
|
|
}
|
|
|
|
void World::overwriteTextureAtCurrentChunk(glm::vec3 const& pos, scoped_blp_texture_reference const& oldTexture, scoped_blp_texture_reference newTexture)
|
|
{
|
|
ZoneScoped;
|
|
for_chunk_at(pos, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->switchTexture(oldTexture, std::move (newTexture));
|
|
});
|
|
}
|
|
|
|
void World::setHole(glm::vec3 const& pos, float radius, bool big, bool hole)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range
|
|
( pos, radius
|
|
, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkHoleChange(chunk);
|
|
chunk->setHole(pos, radius, big, hole);
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::setHoleADT(glm::vec3 const& pos, bool hole)
|
|
{
|
|
ZoneScoped;
|
|
|
|
for_all_chunks_on_tile(pos, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkHoleChange(chunk);
|
|
chunk->setHole(pos, 1.0f, true, hole);
|
|
});
|
|
}
|
|
|
|
void World::loadAllTiles()
|
|
{
|
|
ZoneScoped;
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::convert_alphamap(bool to_big_alpha)
|
|
{
|
|
ZoneScoped;
|
|
|
|
if (to_big_alpha == mapIndex.hasBigAlpha())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
mTile->convert_alphamap(to_big_alpha);
|
|
mTile->saveTile(this);
|
|
mapIndex.markOnDisc (tile, true);
|
|
mapIndex.unsetChanged(tile);
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mapIndex.convert_alphamap(to_big_alpha);
|
|
mapIndex.save();
|
|
}
|
|
|
|
|
|
void World::deleteModelInstance(int uid)
|
|
{
|
|
ZoneScoped;
|
|
auto instance = _model_instance_storage.get_model_instance(uid);
|
|
|
|
if (instance)
|
|
{
|
|
_model_instance_storage.delete_instance(uid);
|
|
need_model_updates = true;
|
|
reset_selection();
|
|
}
|
|
}
|
|
|
|
void World::deleteWMOInstance(int uid)
|
|
{
|
|
ZoneScoped;
|
|
auto instance = _model_instance_storage.get_wmo_instance(uid);
|
|
|
|
if (instance)
|
|
{
|
|
_model_instance_storage.delete_instance(uid);
|
|
need_model_updates = true;
|
|
reset_selection();
|
|
}
|
|
}
|
|
|
|
void World::deleteInstance(int uid)
|
|
{
|
|
ZoneScoped;
|
|
auto instance = _model_instance_storage.get_instance(uid);
|
|
|
|
if (instance)
|
|
{
|
|
_model_instance_storage.delete_instance(uid);
|
|
need_model_updates = true;
|
|
reset_selection();
|
|
}
|
|
}
|
|
|
|
bool World::uid_duplicates_found() const
|
|
{
|
|
ZoneScoped;
|
|
return _model_instance_storage.uid_duplicates_found();
|
|
}
|
|
|
|
void World::delete_duplicate_model_and_wmo_instances()
|
|
{
|
|
ZoneScoped;
|
|
reset_selection();
|
|
|
|
_model_instance_storage.clear_duplicates();
|
|
need_model_updates = true;
|
|
}
|
|
|
|
void World::unload_every_model_and_wmo_instance()
|
|
{
|
|
ZoneScoped;
|
|
reset_selection();
|
|
|
|
_model_instance_storage.clear();
|
|
|
|
_models_by_filename.clear();
|
|
}
|
|
|
|
void World::addM2 ( BlizzardArchive::Listfile::FileKey const& file_key
|
|
, glm::vec3 newPos
|
|
, float scale
|
|
, glm::vec3 rotation
|
|
, Noggit::object_paste_params* paste_params
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
ModelInstance model_instance = ModelInstance(file_key, _context);
|
|
|
|
model_instance.uid = mapIndex.newGUID();
|
|
model_instance.pos = newPos;
|
|
model_instance.scale = scale;
|
|
model_instance.dir = rotation;
|
|
|
|
if (paste_params)
|
|
{
|
|
if (_settings->value("model/random_rotation", false).toBool())
|
|
{
|
|
float min = paste_params->minRotation;
|
|
float max = paste_params->maxRotation;
|
|
model_instance.dir.y += math::degrees(misc::randfloat(min, max))._;
|
|
}
|
|
|
|
if (_settings->value ("model/random_tilt", false).toBool ())
|
|
{
|
|
float min = paste_params->minTilt;
|
|
float max = paste_params->maxTilt;
|
|
model_instance.dir.x += math::degrees(misc::randfloat(min, max))._;
|
|
model_instance.dir.z += math::degrees(misc::randfloat(min, max))._;
|
|
}
|
|
|
|
if (_settings->value ("model/random_size", false).toBool ())
|
|
{
|
|
float min = paste_params->minScale;
|
|
float max = paste_params->maxScale;
|
|
model_instance.scale = misc::randfloat(min, max);
|
|
}
|
|
}
|
|
|
|
// to ensure the tiles are updated correctly
|
|
model_instance.model->wait_until_loaded();
|
|
model_instance.recalcExtents();
|
|
|
|
std::uint32_t uid = _model_instance_storage.add_model_instance(std::move(model_instance), true);
|
|
|
|
_models_by_filename[file_key.filepath()].push_back(_model_instance_storage.get_model_instance(uid).value());
|
|
}
|
|
|
|
ModelInstance* World::addM2AndGetInstance ( BlizzardArchive::Listfile::FileKey const& file_key
|
|
, glm::vec3 newPos
|
|
, float scale
|
|
, math::degrees::vec3 rotation
|
|
, Noggit::object_paste_params* paste_params
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
ModelInstance model_instance = ModelInstance(file_key, _context);
|
|
|
|
model_instance.uid = mapIndex.newGUID();
|
|
model_instance.pos = newPos;
|
|
model_instance.scale = scale;
|
|
model_instance.dir = rotation;
|
|
|
|
if (paste_params)
|
|
{
|
|
if (_settings->value("model/random_rotation", false).toBool())
|
|
{
|
|
float min = paste_params->minRotation;
|
|
float max = paste_params->maxRotation;
|
|
model_instance.dir.y += math::degrees(misc::randfloat(min, max))._;
|
|
}
|
|
|
|
if (_settings->value ("model/random_tilt", false).toBool ())
|
|
{
|
|
float min = paste_params->minTilt;
|
|
float max = paste_params->maxTilt;
|
|
model_instance.dir.x += math::degrees(misc::randfloat(min, max))._;
|
|
model_instance.dir.z += math::degrees(misc::randfloat(min, max))._;
|
|
}
|
|
|
|
if (_settings->value ("model/random_size", false).toBool ())
|
|
{
|
|
float min = paste_params->minScale;
|
|
float max = paste_params->maxScale;
|
|
model_instance.scale = misc::randfloat(min, max);
|
|
}
|
|
}
|
|
|
|
// to ensure the tiles are updated correctly
|
|
model_instance.model->wait_until_loaded();
|
|
model_instance.recalcExtents();
|
|
|
|
std::uint32_t uid = _model_instance_storage.add_model_instance(std::move(model_instance), true);
|
|
|
|
auto instance = _model_instance_storage.get_model_instance(uid).value();
|
|
_models_by_filename[file_key.filepath()].push_back(instance);
|
|
|
|
return instance;
|
|
}
|
|
|
|
void World::addWMO ( BlizzardArchive::Listfile::FileKey const& file_key
|
|
, glm::vec3 newPos
|
|
, math::degrees::vec3 rotation
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
WMOInstance wmo_instance(file_key, _context);
|
|
|
|
wmo_instance.uid = mapIndex.newGUID();
|
|
wmo_instance.pos = newPos;
|
|
wmo_instance.dir = rotation;
|
|
|
|
// to ensure the tiles are updated correctly
|
|
wmo_instance.wmo->wait_until_loaded();
|
|
wmo_instance.recalcExtents();
|
|
|
|
_model_instance_storage.add_wmo_instance(std::move(wmo_instance), true);
|
|
}
|
|
|
|
WMOInstance* World::addWMOAndGetInstance ( BlizzardArchive::Listfile::FileKey const& file_key
|
|
, glm::vec3 newPos
|
|
, math::degrees::vec3 rotation
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
WMOInstance wmo_instance(file_key, _context);
|
|
|
|
wmo_instance.uid = mapIndex.newGUID();
|
|
wmo_instance.pos = newPos;
|
|
wmo_instance.dir = rotation;
|
|
|
|
// to ensure the tiles are updated correctly
|
|
wmo_instance.wmo->wait_until_loaded();
|
|
wmo_instance.recalcExtents();
|
|
|
|
std::uint32_t uid = _model_instance_storage.add_wmo_instance(std::move(wmo_instance), true);
|
|
|
|
return _model_instance_storage.get_wmo_instance(uid).value();
|
|
}
|
|
|
|
|
|
std::uint32_t World::add_model_instance(ModelInstance model_instance, bool from_reloading)
|
|
{
|
|
ZoneScoped;
|
|
return _model_instance_storage.add_model_instance(std::move(model_instance), from_reloading);
|
|
}
|
|
|
|
std::uint32_t World::add_wmo_instance(WMOInstance wmo_instance, bool from_reloading)
|
|
{
|
|
ZoneScoped;
|
|
return _model_instance_storage.add_wmo_instance(std::move(wmo_instance), from_reloading);
|
|
}
|
|
|
|
std::optional<selection_type> World::get_model(std::uint32_t uid)
|
|
{
|
|
ZoneScoped;
|
|
return _model_instance_storage.get_instance(uid);
|
|
}
|
|
|
|
void World::remove_models_if_needed(std::vector<uint32_t> const& uids)
|
|
{
|
|
ZoneScoped;
|
|
// todo: manage instances properly
|
|
// don't unload anything during the uid fix all,
|
|
// otherwise models spanning several adts will be unloaded too soon
|
|
if (mapIndex.uid_fix_all_in_progress())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (uint32_t uid : uids)
|
|
{
|
|
// it handles the removal from the selection if necessary
|
|
_model_instance_storage.unload_instance_and_remove_from_selection_if_necessary(uid);
|
|
}
|
|
|
|
// deselect the terrain when an adt is unloaded
|
|
if (_current_selection.size() == 1 && _current_selection.at(0).index() == eEntry_MapChunk)
|
|
{
|
|
reset_selection();
|
|
}
|
|
|
|
update_models_by_filename();
|
|
}
|
|
|
|
void World::reload_tile(TileIndex const& tile)
|
|
{
|
|
ZoneScoped;
|
|
reset_selection();
|
|
mapIndex.reloadTile(tile);
|
|
}
|
|
|
|
void World::deleteObjects(std::vector<selection_type> const& types)
|
|
{
|
|
ZoneScoped;
|
|
_model_instance_storage.delete_instances(types);
|
|
need_model_updates = true;
|
|
}
|
|
|
|
void World::updateTilesEntry(selection_type const& entry, model_update type)
|
|
{
|
|
ZoneScoped;
|
|
if (entry.index() != eEntry_Object)
|
|
return;
|
|
|
|
auto obj = std::get<selected_object_type>(entry);
|
|
|
|
if (obj->which() == eWMO)
|
|
updateTilesWMO (static_cast<WMOInstance*>(obj), type);
|
|
else if (obj->which() == eMODEL)
|
|
updateTilesModel (static_cast<ModelInstance*>(obj), type);
|
|
|
|
}
|
|
|
|
|
|
void World::updateTilesEntry(SceneObject* entry, model_update type)
|
|
{
|
|
ZoneScoped;
|
|
if (entry->which() == eWMO)
|
|
updateTilesWMO (static_cast<WMOInstance*>(entry), type);
|
|
else if (entry->which() == eMODEL)
|
|
updateTilesModel (static_cast<ModelInstance*>(entry), type);
|
|
|
|
}
|
|
|
|
void World::updateTilesWMO(WMOInstance* wmo, model_update type)
|
|
{
|
|
ZoneScoped;
|
|
_tile_update_queue.queue_update(wmo, type);
|
|
}
|
|
|
|
void World::updateTilesModel(ModelInstance* m2, model_update type)
|
|
{
|
|
ZoneScoped;
|
|
_tile_update_queue.queue_update(m2, type);
|
|
}
|
|
|
|
void World::wait_for_all_tile_updates()
|
|
{
|
|
ZoneScoped;
|
|
_tile_update_queue.wait_for_all_update();
|
|
}
|
|
|
|
unsigned int World::getMapID()
|
|
{
|
|
ZoneScoped;
|
|
return mapIndex._map_id;
|
|
}
|
|
|
|
void World::clearTextures(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->eraseTextures();
|
|
});
|
|
}
|
|
|
|
|
|
void World::exportADTAlphamap(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
QImage img = tile->getAlphamapImage(i);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_layer" + std::to_string(i).c_str() + ".png", "PNG");
|
|
}
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::exportADTNormalmap(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QImage img = tile->getNormalmapImage();
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_normal.png", "PNG");
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::exportADTAlphamap(glm::vec3 const& pos, std::string const& filename)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QString tex(filename.c_str());
|
|
QImage img = tile->getAlphamapImage(filename);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_" + tex.replace("/", "-") + ".png", "PNG");
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::exportADTHeightmap(glm::vec3 const& pos, float min_height, float max_height)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QImage img = tile->getHeightmapImage(min_height, max_height);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_height.png", "PNG");
|
|
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::exportADTVertexColorMap(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QImage img = tile->getVertexColorsImage();
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_vcol.png", "PNG");
|
|
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::importADTAlphamap(glm::vec3 const& pos, QImage const& image, unsigned layer)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
});
|
|
|
|
if (image.width() != 1024 || image.height() != 1024)
|
|
{
|
|
QImage scaled = image.scaled(1024, 1024, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setAlphaImage(scaled, layer);
|
|
}
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setAlphaImage(image, layer);
|
|
}
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
void World::importADTAlphamap(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
});
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_layer" + std::to_string(i).c_str() + ".png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
continue;
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 1024 || img.height() != 1024)
|
|
img = img.scaled(1024, 1024, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
tile->setAlphaImage(img, i);
|
|
}
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::importADTHeightmap(glm::vec3 const& pos, QImage const& image, float multiplier, unsigned mode)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
});
|
|
|
|
if (image.width() != 257 || image.height() != 257)
|
|
{
|
|
QImage scaled = image.scaled(257, 257, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setHeightmapImage(scaled, multiplier, mode);
|
|
}
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setHeightmapImage(image, multiplier, mode);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
void World::importADTHeightmap(glm::vec3 const& pos, float multiplier, unsigned mode)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_height" + ".png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
return;
|
|
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
});
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 257 || img.height() != 257)
|
|
img = img.scaled(257, 257, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
tile->setHeightmapImage(img, multiplier, mode);
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::importADTVertexColorMap(glm::vec3 const& pos, int mode)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(tile->index.x).c_str() + "_" + std::to_string(tile->index.z).c_str()
|
|
+ "_vcol" + ".png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
return;
|
|
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkVertexColorChange(chunk);
|
|
});
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 257 || img.height() != 257)
|
|
img = img.scaled(257, 257, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
tile->setVertexColorImage(img, mode);
|
|
|
|
}
|
|
);
|
|
}
|
|
|
|
void World::ensureAllTilesetsADT(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
static QStringList textures {"tileset/generic/black.blp",
|
|
"tileset/generic/red.blp",
|
|
"tileset/generic/green.blp",
|
|
"tileset/generic/blue.blp",};
|
|
|
|
for_all_chunks_on_tile(pos, [=](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (chunk->texture_set->num() <= i)
|
|
{
|
|
scoped_blp_texture_reference tex {textures[i].toStdString(), Noggit::NoggitRenderContext::MAP_VIEW};
|
|
chunk->texture_set->addTexture(tex);
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
void World::importADTVertexColorMap(glm::vec3 const& pos, QImage const& image, int mode)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkVertexColorChange(chunk);
|
|
});
|
|
|
|
if (image.width() != 257 || image.height() != 257)
|
|
{
|
|
QImage scaled = image.scaled(257, 257, Qt::AspectRatioMode::IgnoreAspectRatio);
|
|
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setVertexColorImage(scaled, mode);
|
|
}
|
|
);
|
|
|
|
}
|
|
else
|
|
{
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
tile->setVertexColorImage(image, mode);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
void World::setBaseTexture(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->eraseTextures();
|
|
if (!!Noggit::Ui::selected_texture::get())
|
|
{
|
|
chunk->addTexture(*Noggit::Ui::selected_texture::get());
|
|
}
|
|
});
|
|
}
|
|
|
|
void World::clear_shadows(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkShadowChange(chunk);
|
|
chunk->clear_shadows();
|
|
});
|
|
}
|
|
|
|
void World::swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex)
|
|
{
|
|
ZoneScoped;
|
|
if (!!Noggit::Ui::selected_texture::get())
|
|
{
|
|
for_all_chunks_on_tile(pos, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->switchTexture(tex, *Noggit::Ui::selected_texture::get());
|
|
});
|
|
}
|
|
}
|
|
|
|
void World::removeTexDuplicateOnADT(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_on_tile(pos, [](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->texture_set->removeDuplicate();
|
|
} );
|
|
}
|
|
|
|
void World::change_texture_flag(glm::vec3 const& pos, scoped_blp_texture_reference const& tex, std::size_t flag, bool add)
|
|
{
|
|
ZoneScoped;
|
|
for_chunk_at(pos, [&] (MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkTextureChange(chunk);
|
|
chunk->change_texture_flag(tex, flag, add);
|
|
});
|
|
}
|
|
|
|
void World::paintLiquid( glm::vec3 const& pos
|
|
, float radius
|
|
, int liquid_id
|
|
, bool add
|
|
, math::radians const& angle
|
|
, math::radians const& orientation
|
|
, bool lock
|
|
, glm::vec3 const& origin
|
|
, bool override_height
|
|
, bool override_liquid_id
|
|
, float opacity_factor
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
for_all_chunks_in_range(pos, radius, [&](MapChunk* chunk)
|
|
{
|
|
NOGGIT_CUR_ACTION->registerChunkLiquidChange(chunk);
|
|
chunk->liquid_chunk()->paintLiquid(pos, radius, liquid_id, add, angle, orientation, lock, origin, override_height, override_liquid_id, chunk, opacity_factor);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void World::setWaterType(const TileIndex& pos, int type, int layer)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at ( pos
|
|
, [&] (MapTile* tile)
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
for (int j = 0; j < 16; ++j)
|
|
NOGGIT_CUR_ACTION->registerChunkLiquidChange(tile->getChunk(i, j));
|
|
|
|
tile->Water.setType (type, layer);
|
|
}
|
|
);
|
|
}
|
|
|
|
int World::getWaterType(const TileIndex& tile, int layer)
|
|
{
|
|
ZoneScoped;
|
|
if (mapIndex.tileLoaded(tile))
|
|
{
|
|
return mapIndex.getTile(tile)->Water.getType (layer);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void World::autoGenWaterTrans(const TileIndex& pos, float factor)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at(pos, [&](MapTile* tile)
|
|
{
|
|
for (int i = 0; i < 16; ++i)
|
|
for (int j = 0; j < 16; ++j)
|
|
NOGGIT_CUR_ACTION->registerChunkLiquidChange(tile->getChunk(i, j));
|
|
|
|
tile->Water.autoGen(factor);
|
|
});
|
|
}
|
|
|
|
|
|
void World::fixAllGaps()
|
|
{
|
|
ZoneScoped;
|
|
std::vector<MapChunk*> chunks;
|
|
|
|
for (MapTile* tile : mapIndex.loaded_tiles())
|
|
{
|
|
MapTile* left = mapIndex.getTileLeft(tile);
|
|
MapTile* above = mapIndex.getTileAbove(tile);
|
|
bool tileChanged = false;
|
|
|
|
// fix the gaps with the adt at the left of the current one
|
|
if (left)
|
|
{
|
|
for (size_t ty = 0; ty < 16; ty++)
|
|
{
|
|
MapChunk* chunk = tile->getChunk(0, ty);
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
if (chunk->fixGapLeft(left->getChunk(15, ty)))
|
|
{
|
|
chunks.emplace_back(chunk);
|
|
tileChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// fix the gaps with the adt above the current one
|
|
if (above)
|
|
{
|
|
for (size_t tx = 0; tx < 16; tx++)
|
|
{
|
|
MapChunk* chunk = tile->getChunk(tx, 0);
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
if (chunk->fixGapAbove(above->getChunk(tx, 15)))
|
|
{
|
|
chunks.emplace_back(chunk);
|
|
tileChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// fix gaps within the adt
|
|
for (size_t ty = 0; ty < 16; ty++)
|
|
{
|
|
for (size_t tx = 0; tx < 16; tx++)
|
|
{
|
|
MapChunk* chunk = tile->getChunk(tx, ty);
|
|
NOGGIT_CUR_ACTION->registerChunkTerrainChange(chunk);
|
|
bool changed = false;
|
|
|
|
// if the chunk isn't the first of the row
|
|
if (tx && chunk->fixGapLeft(tile->getChunk(tx - 1, ty)))
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
// if the chunk isn't the first of the column
|
|
if (ty && chunk->fixGapAbove(tile->getChunk(tx, ty - 1)))
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
chunks.emplace_back(chunk);
|
|
tileChanged = true;
|
|
}
|
|
}
|
|
}
|
|
if (tileChanged)
|
|
{
|
|
mapIndex.setChanged(tile);
|
|
}
|
|
}
|
|
|
|
for (MapChunk* chunk : chunks)
|
|
{
|
|
recalc_norms (chunk);
|
|
}
|
|
}
|
|
|
|
bool World::isUnderMap(glm::vec3 const& pos)
|
|
{
|
|
ZoneScoped;
|
|
TileIndex const tile (pos);
|
|
|
|
if (mapIndex.tileLoaded(tile))
|
|
{
|
|
size_t chnkX = (pos.x / CHUNKSIZE) - tile.x * 16;
|
|
size_t chnkZ = (pos.z / CHUNKSIZE) - tile.z * 16;
|
|
|
|
// check using the cursor height
|
|
return (mapIndex.getTile(tile)->getChunk(chnkX, chnkZ)->getMinHeight()) > pos.y + 2.0f;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void World::selectVertices(glm::vec3 const& pos, float radius)
|
|
{
|
|
ZoneScoped;
|
|
NOGGIT_CUR_ACTION->registerVertexSelectionChange();
|
|
|
|
_vertex_center_updated = false;
|
|
_vertex_border_updated = false;
|
|
|
|
for_all_chunks_in_range(pos, radius, [&](MapChunk* chunk){
|
|
_vertex_chunks.emplace(chunk);
|
|
_vertex_tiles.emplace(chunk->mt);
|
|
chunk->selectVertex(pos, radius, _vertices_selected);
|
|
return true;
|
|
});
|
|
|
|
}
|
|
|
|
bool World::deselectVertices(glm::vec3 const& pos, float radius)
|
|
{
|
|
ZoneScoped;
|
|
NOGGIT_CUR_ACTION->registerVertexSelectionChange();
|
|
|
|
_vertex_center_updated = false;
|
|
_vertex_border_updated = false;
|
|
std::unordered_set<glm::vec3*> inRange;
|
|
|
|
for (glm::vec3* v : _vertices_selected)
|
|
{
|
|
if (misc::dist(*v, pos) <= radius)
|
|
{
|
|
inRange.emplace(v);
|
|
}
|
|
}
|
|
|
|
for (glm::vec3* v : inRange)
|
|
{
|
|
_vertices_selected.erase(v);
|
|
}
|
|
|
|
return _vertices_selected.empty();
|
|
}
|
|
|
|
void World::moveVertices(float h)
|
|
{
|
|
ZoneScoped;
|
|
Noggit::Action* cur_action = NOGGIT_CUR_ACTION;
|
|
|
|
assert(cur_action && "moveVertices called without an action running.");
|
|
|
|
for (auto& chunk : _vertex_chunks)
|
|
cur_action->registerChunkTerrainChange(chunk);
|
|
|
|
_vertex_center_updated = false;
|
|
for (glm::vec3* v : _vertices_selected)
|
|
{
|
|
v->y += h;
|
|
}
|
|
|
|
updateVertexCenter();
|
|
updateSelectedVertices();
|
|
}
|
|
|
|
void World::updateSelectedVertices()
|
|
{
|
|
ZoneScoped;
|
|
for (MapTile* tile : _vertex_tiles)
|
|
{
|
|
mapIndex.setChanged(tile);
|
|
}
|
|
|
|
// fix only the border chunks to be more efficient
|
|
for (MapChunk* chunk : vertexBorderChunks())
|
|
{
|
|
chunk->fixVertices(_vertices_selected);
|
|
}
|
|
|
|
for (MapChunk* chunk : _vertex_chunks)
|
|
{
|
|
chunk->registerChunkUpdate(ChunkUpdateFlags::VERTEX);
|
|
recalc_norms (chunk);
|
|
}
|
|
}
|
|
|
|
void World::orientVertices ( glm::vec3 const& ref_pos
|
|
, math::degrees vertex_angle
|
|
, math::degrees vertex_orientation
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
Noggit::Action* cur_action = NOGGIT_CUR_ACTION;
|
|
|
|
assert(cur_action && "orientVertices called without an action running.");
|
|
|
|
for (auto& chunk : _vertex_chunks)
|
|
cur_action->registerChunkTerrainChange(chunk);
|
|
|
|
for (glm::vec3* v : _vertices_selected)
|
|
{
|
|
v->y = misc::angledHeight(ref_pos, *v, vertex_angle, vertex_orientation);
|
|
}
|
|
updateSelectedVertices();
|
|
}
|
|
|
|
void World::flattenVertices (float height)
|
|
{
|
|
ZoneScoped;
|
|
for (glm::vec3* v : _vertices_selected)
|
|
{
|
|
v->y = height;
|
|
}
|
|
updateSelectedVertices();
|
|
}
|
|
|
|
void World::clearVertexSelection()
|
|
{
|
|
ZoneScoped;
|
|
NOGGIT_CUR_ACTION->registerVertexSelectionChange();
|
|
_vertex_border_updated = false;
|
|
_vertex_center_updated = false;
|
|
_vertices_selected.clear();
|
|
_vertex_chunks.clear();
|
|
_vertex_tiles.clear();
|
|
}
|
|
|
|
void World::updateVertexCenter()
|
|
{
|
|
ZoneScoped;
|
|
_vertex_center_updated = true;
|
|
_vertex_center = { 0,0,0 };
|
|
float f = 1.0f / _vertices_selected.size();
|
|
for (glm::vec3* v : _vertices_selected)
|
|
{
|
|
_vertex_center += (*v) * f;
|
|
}
|
|
}
|
|
|
|
glm::vec3 const& World::vertexCenter()
|
|
{
|
|
ZoneScoped;
|
|
if (!_vertex_center_updated)
|
|
{
|
|
updateVertexCenter();
|
|
}
|
|
|
|
return _vertex_center;
|
|
}
|
|
|
|
std::unordered_set<MapChunk*>& World::vertexBorderChunks()
|
|
{
|
|
ZoneScoped;
|
|
if (!_vertex_border_updated)
|
|
{
|
|
_vertex_border_updated = true;
|
|
_vertex_border_chunks.clear();
|
|
|
|
for (MapChunk* chunk : _vertex_chunks)
|
|
{
|
|
if (chunk->isBorderChunk(_vertices_selected))
|
|
{
|
|
_vertex_border_chunks.emplace(chunk);
|
|
}
|
|
}
|
|
}
|
|
return _vertex_border_chunks;
|
|
}
|
|
|
|
void World::update_models_by_filename()
|
|
{
|
|
ZoneScoped;
|
|
_models_by_filename.clear();
|
|
|
|
_model_instance_storage.for_each_m2_instance([&] (ModelInstance& model_instance)
|
|
{
|
|
_models_by_filename[model_instance.model->file_key().filepath()].push_back(&model_instance);
|
|
// to make sure the transform matrix are up to date
|
|
model_instance.recalcExtents();
|
|
});
|
|
|
|
need_model_updates = false;
|
|
}
|
|
|
|
void World::range_add_to_selection(glm::vec3 const& pos, float radius, bool remove)
|
|
{
|
|
ZoneScoped;
|
|
for_tile_at(pos, [this, pos, radius, remove](MapTile* tile)
|
|
{
|
|
std::vector<uint32_t>* uids = tile->get_uids();
|
|
|
|
if (remove)
|
|
{
|
|
for (uint32_t uid : *uids)
|
|
{
|
|
auto instance = _model_instance_storage.get_instance(uid);
|
|
|
|
if (instance && instance.value().index() == eEntry_Object)
|
|
{
|
|
auto obj = std::get<selected_object_type>(instance.value());
|
|
|
|
if (glm::distance(obj->pos, pos) <= radius && is_selected(obj))
|
|
{
|
|
remove_from_selection(obj);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint32_t uid : *uids)
|
|
{
|
|
auto instance = _model_instance_storage.get_instance(uid);
|
|
|
|
if (instance && instance.value().index() == eEntry_Object)
|
|
{
|
|
auto obj = std::get<selected_object_type>(instance.value());
|
|
|
|
if (glm::distance(obj->pos, pos) <= radius && !is_selected(obj))
|
|
{
|
|
add_to_selection(obj);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
float World::getMaxTileHeight(const TileIndex& tile)
|
|
{
|
|
ZoneScoped;
|
|
MapTile* m_tile = mapIndex.getTile(tile);
|
|
|
|
m_tile->forceRecalcExtents();
|
|
float max_height = m_tile->getMaxHeight();
|
|
|
|
std::vector<uint32_t>* uids = m_tile->get_uids();
|
|
|
|
for (uint32_t uid : *uids)
|
|
{
|
|
auto instance = _model_instance_storage.get_instance(uid);
|
|
|
|
if (instance.value().index() == eEntry_Object)
|
|
{
|
|
auto obj = std::get<selected_object_type>(instance.value());
|
|
obj->ensureExtents();
|
|
max_height = std::max(max_height, std::max(obj->extents[0].y, obj->extents[1].y));
|
|
}
|
|
}
|
|
|
|
|
|
return max_height;
|
|
}
|
|
|
|
SceneObject* World::getObjectInstance(std::uint32_t uid)
|
|
{
|
|
ZoneScoped;
|
|
auto instance = _model_instance_storage.get_instance(uid);
|
|
|
|
if (!instance)
|
|
return nullptr;
|
|
|
|
if (instance.value().index() == eEntry_Object)
|
|
{
|
|
return std::get<selected_object_type>(instance.value());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void World::setBasename(const std::string &name)
|
|
{
|
|
ZoneScoped;
|
|
basename = name;
|
|
mapIndex.set_basename(name);
|
|
}
|
|
|
|
|
|
Noggit::VertexSelectionCache World::getVertexSelectionCache()
|
|
{
|
|
ZoneScoped;
|
|
return std::move(Noggit::VertexSelectionCache{_vertex_tiles, _vertex_chunks, _vertex_border_chunks,
|
|
_vertices_selected, _vertex_center});
|
|
}
|
|
|
|
void World::setVertexSelectionCache(Noggit::VertexSelectionCache& cache)
|
|
{
|
|
ZoneScoped;
|
|
_vertex_tiles = cache.vertex_tiles;
|
|
_vertex_chunks = cache.vertex_chunks;
|
|
_vertex_border_chunks = cache.vertex_border_chunks;
|
|
_vertices_selected = cache.vertices_selected;
|
|
_vertex_center = cache.vertex_center;
|
|
|
|
_vertex_center_updated = false;
|
|
_vertex_border_updated = false;
|
|
}
|
|
|
|
void World::exportAllADTsAlphamap()
|
|
{
|
|
ZoneScoped;
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
QImage img = mTile->getAlphamapImage(i);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_layer" + std::to_string(i).c_str() + ".png", "PNG");
|
|
}
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::exportAllADTsAlphamap(const std::string& filename)
|
|
{
|
|
ZoneScoped;
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
auto chunk = mTile->getChunk(i, j);
|
|
|
|
for (int k = 1; k < chunk->texture_set->num(); ++k)
|
|
{
|
|
if (chunk->texture_set->filename(k) == filename)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
continue;
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QString tex(filename.c_str());
|
|
QImage img = mTile->getAlphamapImage(filename);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_" + tex.replace("/", "-") + ".png", "PNG");
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::exportAllADTsHeightmap()
|
|
{
|
|
ZoneScoped;
|
|
float min_height = std::numeric_limits<float>::max();
|
|
float max_height = std::numeric_limits<float>::lowest();
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
float max = mTile->getMaxHeight();
|
|
float min = mTile->getMinHeight();
|
|
|
|
if (max_height < max)
|
|
max_height = max;
|
|
|
|
if (min_height > min)
|
|
min_height = min;
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QImage img = mTile->getHeightmapImage(min_height, max_height);
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_height.png", "PNG");
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::exportAllADTsVertexColorMap()
|
|
{
|
|
ZoneScoped;
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
QDir dir(path + "/world/maps/" + basename.c_str());
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
QImage img = mTile->getVertexColorsImage();
|
|
img.save(path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_vcol.png", "PNG");
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::importAllADTsAlphamaps()
|
|
{
|
|
ZoneScoped;
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_layer" + std::to_string(i).c_str() + ".png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
continue;
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 1024 || img.height() != 1024)
|
|
{
|
|
QImage scaled = img.scaled(1024, 1024, Qt::IgnoreAspectRatio);
|
|
mTile->setAlphaImage(scaled, i);
|
|
}
|
|
else
|
|
{
|
|
mTile->setAlphaImage(img, i);
|
|
}
|
|
|
|
}
|
|
|
|
mTile->saveTile(this);
|
|
mapIndex.markOnDisc (tile, true);
|
|
mapIndex.unsetChanged(tile);
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::importAllADTsHeightmaps(float multiplier, unsigned int mode)
|
|
{
|
|
ZoneScoped;
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_height.png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
continue;
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 257 || img.height() != 257)
|
|
{
|
|
QImage scaled = img.scaled(257, 257, Qt::IgnoreAspectRatio);
|
|
mTile->setHeightmapImage(scaled, multiplier, mode);
|
|
}
|
|
else
|
|
{
|
|
mTile->setHeightmapImage(img, multiplier, mode);
|
|
}
|
|
|
|
mTile->saveTile(this);
|
|
mapIndex.markOnDisc (tile, true);
|
|
mapIndex.unsetChanged(tile);
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::importAllADTVertexColorMaps(unsigned int mode)
|
|
{
|
|
ZoneScoped;
|
|
QString path = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(path.endsWith('\\') || path.endsWith('/')))
|
|
{
|
|
path += "/";
|
|
}
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
QString filename = path + "/world/maps/" + basename.c_str() + "/" + basename.c_str()
|
|
+ "_" + std::to_string(mTile->index.x).c_str() + "_" + std::to_string(mTile->index.z).c_str()
|
|
+ "_vcol.png";
|
|
|
|
if(!QFileInfo::exists(filename))
|
|
continue;
|
|
|
|
QImage img;
|
|
img.load(filename, "PNG");
|
|
|
|
if (img.width() != 257 || img.height() != 257)
|
|
{
|
|
QImage scaled = img.scaled(257, 257, Qt::IgnoreAspectRatio);
|
|
mTile->setVertexColorImage(scaled, mode);
|
|
}
|
|
else
|
|
{
|
|
mTile->setVertexColorImage(img, mode);
|
|
}
|
|
|
|
mTile->saveTile(this);
|
|
mapIndex.markOnDisc (tile, true);
|
|
mapIndex.unsetChanged(tile);
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::ensureAllTilesetsAllADTs()
|
|
{
|
|
ZoneScoped;
|
|
static QStringList textures {"tileset/generic/black.blp",
|
|
"tileset/generic/red.blp",
|
|
"tileset/generic/green.blp",
|
|
"tileset/generic/blue.blp",};
|
|
|
|
for (size_t z = 0; z < 64; z++)
|
|
{
|
|
for (size_t x = 0; x < 64; x++)
|
|
{
|
|
TileIndex tile(x, z);
|
|
|
|
bool unload = !mapIndex.tileLoaded(tile) && !mapIndex.tileAwaitingLoading(tile);
|
|
MapTile* mTile = mapIndex.loadTile(tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
{
|
|
for (int j = 0; j < 16; ++j)
|
|
{
|
|
auto chunk = mTile->getChunk(i, j);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (chunk->texture_set->num() <= i)
|
|
{
|
|
scoped_blp_texture_reference tex {textures[i].toStdString(), Noggit::NoggitRenderContext::MAP_VIEW};
|
|
chunk->texture_set->addTexture(tex);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
mTile->saveTile(this);
|
|
mapIndex.markOnDisc (tile, true);
|
|
mapIndex.unsetChanged(tile);
|
|
|
|
if (unload)
|
|
{
|
|
mapIndex.unloadTile(tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void World::notifyTileRendererOnSelectedTextureChange()
|
|
{
|
|
ZoneScoped;
|
|
|
|
for (MapTile* tile : mapIndex.loaded_tiles())
|
|
{
|
|
tile->renderer()->notifyTileRendererOnSelectedTextureChange();
|
|
}
|
|
}
|
|
|