old local changes, mainly low-res wdl WMO support and some optimizations
This commit is contained in:
@@ -13,18 +13,40 @@ namespace math
|
|||||||
float tmin (std::numeric_limits<float>::lowest());
|
float tmin (std::numeric_limits<float>::lowest());
|
||||||
float tmax (std::numeric_limits<float>::max());
|
float tmax (std::numeric_limits<float>::max());
|
||||||
|
|
||||||
auto calculate_tmin_tmax = [](float origin, float direction, float min, float max, float& tmin, float& tmax) {
|
if (_direction.x != 0.0f)
|
||||||
if (direction != 0.0f) {
|
{
|
||||||
float t1 = (min - origin) / direction;
|
float tx1 = (min.x - _origin.x) * _inverted_direction.x;
|
||||||
float t2 = (max - origin) / direction;
|
float tx2 = (max.x - _origin.x) * _inverted_direction.x;
|
||||||
tmin = std::max(tmin, std::min(t1, t2));
|
// float const tx1((min.x - _origin.x) / _direction.x);
|
||||||
tmax = std::min(tmax, std::max(t1, t2));
|
// float const tx2((max.x - _origin.x) / _direction.x);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
calculate_tmin_tmax(_origin.x, _direction.x, min.x, max.x, tmin, tmax);
|
tmin = std::max(tmin, std::min(tx1, tx2));
|
||||||
calculate_tmin_tmax(_origin.y, _direction.y, min.y, max.y, tmin, tmax);
|
tmax = std::min(tmax, std::max(tx1, tx2));
|
||||||
calculate_tmin_tmax(_origin.z, _direction.z, min.z, max.z, tmin, tmax);
|
}
|
||||||
|
|
||||||
|
if (_direction.y != 0.0f)
|
||||||
|
{
|
||||||
|
// float const ty1((min.y - _origin.y) / _direction.y);
|
||||||
|
// float const ty2((max.y - _origin.y) / _direction.y);
|
||||||
|
|
||||||
|
float ty1 = (min.y - _origin.y) * _inverted_direction.y;
|
||||||
|
float ty2 = (max.y - _origin.y) * _inverted_direction.y;
|
||||||
|
|
||||||
|
tmin = std::max(tmin, std::min(ty1, ty2));
|
||||||
|
tmax = std::min(tmax, std::max(ty1, ty2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_direction.z != 0.0f)
|
||||||
|
{
|
||||||
|
// float const tz1((min.z - _origin.z) / _direction.z);
|
||||||
|
// float const tz2((max.z - _origin.z) / _direction.z);
|
||||||
|
|
||||||
|
float tz1 = (min.z - _origin.z) * _inverted_direction.z;
|
||||||
|
float tz2 = (max.z - _origin.z) * _inverted_direction.z;
|
||||||
|
|
||||||
|
tmin = std::max(tmin, std::min(tz1, tz2));
|
||||||
|
tmax = std::min(tmax, std::max(tz1, tz2));
|
||||||
|
}
|
||||||
|
|
||||||
if (tmax >= tmin)
|
if (tmax >= tmin)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace math
|
|||||||
{
|
{
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
// pre compute ivnerted direction
|
||||||
|
_inverted_direction.x = (_direction.x != 0.0f) ? 1.0f / _direction.x : std::numeric_limits<float>::max();
|
||||||
|
_inverted_direction.y = (_direction.y != 0.0f) ? 1.0f / _direction.y : std::numeric_limits<float>::max();
|
||||||
|
_inverted_direction.z = (_direction.z != 0.0f) ? 1.0f / _direction.z : std::numeric_limits<float>::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
ray (glm::mat4x4 const& transform, ray const& other)
|
ray (glm::mat4x4 const& transform, ray const& other)
|
||||||
@@ -35,8 +39,14 @@ namespace math
|
|||||||
return _origin + _direction * distance;
|
return _origin + _direction * distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 const origin() const
|
||||||
|
{
|
||||||
|
return _origin;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 _origin;
|
glm::vec3 const _origin;
|
||||||
glm::vec3 _direction;
|
glm::vec3 const _direction;
|
||||||
|
glm::vec3 _inverted_direction;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,11 +89,12 @@ void AsyncLoader::process()
|
|||||||
_state_changed.notify_all();
|
_state_changed.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (BlizzardArchive::Exceptions::FileReadFailedError const&)
|
catch (BlizzardArchive::Exceptions::FileReadFailedError const& e)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> const lock(_guard);
|
std::lock_guard<std::mutex> const lock(_guard);
|
||||||
|
|
||||||
object->error_on_loading();
|
object->error_on_loading();
|
||||||
|
// LogError << e.what() << std::endl;
|
||||||
|
|
||||||
if (object->is_required_when_saving())
|
if (object->is_required_when_saving())
|
||||||
{
|
{
|
||||||
@@ -107,7 +108,8 @@ void AsyncLoader::process()
|
|||||||
std::lock_guard<std::mutex> const lock(_guard);
|
std::lock_guard<std::mutex> const lock(_guard);
|
||||||
|
|
||||||
object->error_on_loading();
|
object->error_on_loading();
|
||||||
LogError << "Caught unknown error." << std::endl;
|
std::string const reason{ util::exception_to_string(std::current_exception()) };
|
||||||
|
LogError << "Caught unknown error: " << reason << std::endl;
|
||||||
|
|
||||||
if (object->is_required_when_saving())
|
if (object->is_required_when_saving())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public:
|
|||||||
// MapChunkHeader header;
|
// MapChunkHeader header;
|
||||||
// uint32_t nLayers = 0;
|
// uint32_t nLayers = 0;
|
||||||
|
|
||||||
float xbase, ybase, zbase; // global coords
|
float xbase, ybase, zbase; // global coords. height = ybase
|
||||||
|
|
||||||
mcnk_flags header_flags;
|
mcnk_flags header_flags;
|
||||||
bool use_big_alphamap;
|
bool use_big_alphamap;
|
||||||
|
|||||||
@@ -3311,6 +3311,10 @@ selection_result MapView::intersect_result(bool terrain_only)
|
|||||||
, _draw_hidden_models.get()
|
, _draw_hidden_models.get()
|
||||||
, _draw_wmo_exterior.get()
|
, _draw_wmo_exterior.get()
|
||||||
, _draw_model_animations.get()
|
, _draw_model_animations.get()
|
||||||
|
, false
|
||||||
|
, false
|
||||||
|
, 0.0f
|
||||||
|
, true // !_draw_wmo_exterior.get() // invert so that we only cast interiors if exterior is hidden
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -226,19 +226,19 @@ bool Model::isAnimated(const BlizzardArchive::ClientFile& f, ModelHeader& header
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
glm::vec3 fixCoordSystem(glm::vec3 v)
|
inline glm::vec3 fixCoordSystem(glm::vec3 v)
|
||||||
{
|
{
|
||||||
return glm::vec3(v.x, v.z, -v.y);
|
return glm::vec3(v.x, v.z, -v.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
glm::vec3 fixCoordSystem2(glm::vec3 v)
|
inline glm::vec3 fixCoordSystem2(glm::vec3 v)
|
||||||
{
|
{
|
||||||
return glm::vec3(v.x, v.z, v.y);
|
return glm::vec3(v.x, v.z, v.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::quat fixCoordSystemQuat(glm::quat v)
|
inline glm::quat fixCoordSystemQuat(glm::quat v)
|
||||||
{
|
{
|
||||||
return glm::quat(-v.x, -v.z, v.y, v.w);
|
return glm::quat(-v.x, -v.z, v.y, v.w);
|
||||||
}
|
}
|
||||||
@@ -895,7 +895,13 @@ void Bone::calcMatrix(glm::mat4x4 const& model_view
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::pair<float, std::tuple<int, int, int>>> Model::intersect (glm::mat4x4 const& model_view, math::ray const& ray, int animtime, bool calc_anims)
|
std::vector<std::pair<float, std::tuple<int, int, int>>> Model::intersect (
|
||||||
|
glm::mat4x4 const& model_view
|
||||||
|
, math::ray const& ray
|
||||||
|
, int animtime
|
||||||
|
, bool calc_anims
|
||||||
|
, bool first_occurence
|
||||||
|
, bool only_opaque_tris)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<float, std::tuple<int, int, int>>> results;
|
std::vector<std::pair<float, std::tuple<int, int, int>>> results;
|
||||||
|
|
||||||
@@ -934,6 +940,12 @@ std::vector<std::pair<float, std::tuple<int, int, int>>> Model::intersect (glm::
|
|||||||
bool const has_transformed_verts = !transformed_verts.empty();
|
bool const has_transformed_verts = !transformed_verts.empty();
|
||||||
for (auto const& pass : _renderer.renderPasses())
|
for (auto const& pass : _renderer.renderPasses())
|
||||||
{
|
{
|
||||||
|
// todo
|
||||||
|
if (only_opaque_tris && pass.blend_mode != 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (int i (pass.index_start); i < pass.index_start + pass.index_count; i += 3)
|
for (int i (pass.index_start); i < pass.index_start + pass.index_count; i += 3)
|
||||||
{
|
{
|
||||||
std::optional<float> distance;
|
std::optional<float> distance;
|
||||||
@@ -950,13 +962,12 @@ std::vector<std::pair<float, std::tuple<int, int, int>>> Model::intersect (glm::
|
|||||||
transformed_verts[_indices[static_cast<std::size_t>(i + 2)]].position);
|
transformed_verts[_indices[static_cast<std::size_t>(i + 2)]].position);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 debug_vert1 = _vertices[_indices[static_cast<std::size_t>(i + 0)]].position; ////
|
|
||||||
glm::vec3 debug_vert2 = _vertices[_indices[static_cast<std::size_t>(i + 1)]].position; ////
|
|
||||||
glm::vec3 debug_vert3 = _vertices[_indices[static_cast<std::size_t>(i + 2)]].position; ////
|
|
||||||
|
|
||||||
if (distance)
|
if (distance)
|
||||||
{
|
{
|
||||||
results.emplace_back (*distance, std::make_tuple(i, i + 1, 1 + 2));
|
results.emplace_back (*distance, std::make_tuple(i, i + 1, 1 + 2));
|
||||||
|
|
||||||
|
if (first_occurence)
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ enum M2GlobalFlags
|
|||||||
// TODO : MOP +
|
// TODO : MOP +
|
||||||
};
|
};
|
||||||
|
|
||||||
glm::vec3 fixCoordSystem(glm::vec3 v);
|
inline glm::vec3 fixCoordSystem(glm::vec3 v);
|
||||||
|
|
||||||
class Bone {
|
class Bone {
|
||||||
Animation::M2Value<glm::vec3> trans;
|
Animation::M2Value<glm::vec3> trans;
|
||||||
@@ -159,7 +159,13 @@ public:
|
|||||||
|
|
||||||
Model(const std::string& name, Noggit::NoggitRenderContext context );
|
Model(const std::string& name, Noggit::NoggitRenderContext context );
|
||||||
|
|
||||||
std::vector<std::pair<float, std::tuple<int, int, int>>> intersect (glm::mat4x4 const& model_view, math::ray const&, int animtime, bool calc_anims);
|
std::vector<std::pair<float, std::tuple<int, int, int>>> intersect (
|
||||||
|
glm::mat4x4 const& model_view
|
||||||
|
, math::ray const&
|
||||||
|
, int animtime
|
||||||
|
, bool calc_anims
|
||||||
|
, bool first_occurence
|
||||||
|
, bool only_opaque_tris);
|
||||||
|
|
||||||
void updateEmitters(float dt);
|
void updateEmitters(float dt);
|
||||||
|
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ void ModelInstance::intersect (glm::mat4x4 const& model_view
|
|||||||
, selection_result* results
|
, selection_result* results
|
||||||
, int animtime
|
, int animtime
|
||||||
, bool animate
|
, bool animate
|
||||||
|
, bool first_occurence
|
||||||
|
, bool only_opaque_tris
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!finishedLoading() || model->loading_failed())
|
if (!finishedLoading() || model->loading_failed())
|
||||||
@@ -162,7 +164,7 @@ void ModelInstance::intersect (glm::mat4x4 const& model_view
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto&& result : model->intersect (model_view, subray, animtime, animate))
|
for (auto&& result : model->intersect (model_view, subray, animtime, animate, first_occurence, only_opaque_tris))
|
||||||
{
|
{
|
||||||
//! \todo why is only sc important? these are relative to subray,
|
//! \todo why is only sc important? these are relative to subray,
|
||||||
//! so should be inverted by model_matrix?
|
//! so should be inverted by model_matrix?
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ class WMOInstance;
|
|||||||
class ModelInstance : public SceneObject
|
class ModelInstance : public SceneObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
constexpr static float min_scale() { return 1.f / 1024.f; };
|
|
||||||
constexpr static float max_scale() { return static_cast<float>((1 << 16) - 1) / 1024.f; };
|
|
||||||
|
|
||||||
scoped_model_reference model;
|
scoped_model_reference model;
|
||||||
|
|
||||||
glm::vec3 light_color = { 1.f, 1.f, 1.f };
|
glm::vec3 light_color = { 1.f, 1.f, 1.f };
|
||||||
@@ -89,6 +86,8 @@ public:
|
|||||||
, selection_result*
|
, selection_result*
|
||||||
, int animtime
|
, int animtime
|
||||||
, bool animate
|
, bool animate
|
||||||
|
, bool first_occurence
|
||||||
|
, bool only_opaque_tris
|
||||||
);
|
);
|
||||||
|
|
||||||
bool isInFrustum(math::frustum const& frustum);
|
bool isInFrustum(math::frustum const& frustum);
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ class MapTile;
|
|||||||
class SceneObject : public Selectable
|
class SceneObject : public Selectable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
constexpr static float min_scale() { return 1.f / 1024.f; };
|
||||||
|
constexpr static float max_scale() { return static_cast<float>((1 << 16) - 1) / 1024.f; };
|
||||||
|
|
||||||
SceneObject(SceneObjectTypes type, Noggit::NoggitRenderContext context);
|
SceneObject(SceneObjectTypes type, Noggit::NoggitRenderContext context);
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -79,25 +82,25 @@ public:
|
|||||||
|
|
||||||
glm::vec3 const getServerPos() { return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); }
|
glm::vec3 const getServerPos() { return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); }
|
||||||
|
|
||||||
bool _grouped = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
glm::vec3 pos;
|
glm::vec3 pos;
|
||||||
|
|
||||||
glm::vec3 dir;
|
glm::vec3 dir;
|
||||||
float scale = 1.f;
|
float scale = 1.f; // Note : max scale is uint16 max / 1024 = 63.999
|
||||||
unsigned int uid;
|
unsigned int uid;
|
||||||
int frame;
|
int frame;
|
||||||
|
|
||||||
// Note : First, need to check if the tile that contained it was rendered too
|
// Note : First, need to check if the tile that contained it was rendered too
|
||||||
bool _rendered_last_frame = false;
|
bool _rendered_last_frame = false;
|
||||||
|
|
||||||
|
bool _grouped = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SceneObjectTypes _type;
|
SceneObjectTypes _type;
|
||||||
|
|
||||||
glm::mat4x4 _transform_mat = glm::mat4x4();
|
glm::mat4x4 _transform_mat = glm::mat4x4();
|
||||||
glm::mat4x4 _transform_mat_inverted = glm::mat4x4();
|
glm::mat4x4 _transform_mat_inverted = glm::mat4x4();
|
||||||
std::array<glm::vec3, 2> extents; // axis aligned bounding box mni and max corners
|
std::array<glm::vec3, 2> extents; // axis aligned bounding box min and max corners
|
||||||
float bounding_radius;
|
float bounding_radius;
|
||||||
|
|
||||||
Noggit::NoggitRenderContext _context;
|
Noggit::NoggitRenderContext _context;
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ void WMO::waitForChildrenLoaded()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<float> WMO::intersect (math::ray const& ray, bool do_exterior) const
|
std::vector<float> WMO::intersect (math::ray const& ray, bool do_exterior, bool do_interior, bool first_occurence) const
|
||||||
{
|
{
|
||||||
std::vector<float> results;
|
std::vector<float> results;
|
||||||
|
|
||||||
@@ -367,14 +367,17 @@ std::vector<float> WMO::intersect (math::ray const& ray, bool do_exterior) const
|
|||||||
for (auto& group : groups)
|
for (auto& group : groups)
|
||||||
{
|
{
|
||||||
if (!do_exterior && !group.is_indoor())
|
if (!do_exterior && !group.is_indoor())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
group.intersect (ray, &results);
|
else if (!do_interior && group.is_indoor())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
group.intersect (ray, &results, first_occurence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!do_exterior && results.size())
|
if (!do_exterior && results.size())
|
||||||
{
|
{
|
||||||
// dirty way to find the furthest face and ignore invisible faces, cleaner way would be to do a direction check on faces
|
// dirty way to find the furthest face and ignore back culled(invisible) faces, cleaner way would be to do a direction check on faces
|
||||||
// float max = *std::max_element(std::begin(results), std::end(results));
|
// float max = *std::max_element(std::begin(results), std::end(results));
|
||||||
// results.clear();
|
// results.clear();
|
||||||
// results.push_back(max);
|
// results.push_back(max);
|
||||||
@@ -1116,7 +1119,7 @@ bool WMOGroup::is_visible( glm::mat4x4 const& transform
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WMOGroup::intersect (math::ray const& ray, std::vector<float>* results) const
|
void WMOGroup::intersect (math::ray const& ray, std::vector<float>* results, bool first_occurence) const
|
||||||
{
|
{
|
||||||
if (!ray.intersect_bounds (VertexBoxMin, VertexBoxMax))
|
if (!ray.intersect_bounds (VertexBoxMin, VertexBoxMax))
|
||||||
{
|
{
|
||||||
@@ -1126,7 +1129,7 @@ void WMOGroup::intersect (math::ray const& ray, std::vector<float>* results) con
|
|||||||
//! \todo Also allow clicking on doodads and liquids.
|
//! \todo Also allow clicking on doodads and liquids.
|
||||||
for (auto&& batch : _batches)
|
for (auto&& batch : _batches)
|
||||||
{
|
{
|
||||||
for (size_t i (batch.index_start); i < batch.index_start + batch.index_count; i += 3)
|
for (int i (batch.index_start); i < batch.index_start + batch.index_count; i += 3)
|
||||||
{
|
{
|
||||||
// TODO : only intersect visible triangles
|
// TODO : only intersect visible triangles
|
||||||
// TODO : option to only check collision
|
// TODO : option to only check collision
|
||||||
@@ -1138,6 +1141,8 @@ void WMOGroup::intersect (math::ray const& ray, std::vector<float>* results) con
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
results->emplace_back (*distance);
|
results->emplace_back (*distance);
|
||||||
|
if (first_occurence)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public:
|
|||||||
|
|
||||||
void setupFog (bool draw_fog, std::function<void (bool)> setup_fog);
|
void setupFog (bool draw_fog, std::function<void (bool)> setup_fog);
|
||||||
|
|
||||||
void intersect (math::ray const&, std::vector<float>* results) const;
|
void intersect (math::ray const&, std::vector<float>* results, bool first_occurence) const;
|
||||||
|
|
||||||
// todo: portal culling
|
// todo: portal culling
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -310,7 +310,7 @@ public:
|
|||||||
explicit WMO(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context );
|
explicit WMO(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context );
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::vector<float> intersect (math::ray const&, bool do_exterior = true) const;
|
std::vector<float> intersect (math::ray const&, bool do_exterior = true, bool do_interior = true, bool first_occurence = false) const;
|
||||||
|
|
||||||
void finishLoading() override;
|
void finishLoading() override;
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ WMOInstance::WMOInstance(BlizzardArchive::Listfile::FileKey const& file_key, ENT
|
|||||||
scale = 1.0f;
|
scale = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
extents[0] = glm::vec3(d->extents[0][0], d->extents[0][1], d->extents[0][2]);
|
extents[0] = d->extents[0];
|
||||||
extents[1] = glm::vec3(d->extents[1][0], d->extents[1][1], d->extents[1][2]);
|
extents[1] = d->extents[1];
|
||||||
|
|
||||||
_need_recalc_extents = true;
|
_need_recalc_extents = true;
|
||||||
updateTransformMatrix();
|
updateTransformMatrix();
|
||||||
@@ -80,6 +80,7 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
|
|||||||
, bool draw_exterior
|
, bool draw_exterior
|
||||||
, bool render_selection_aabb
|
, bool render_selection_aabb
|
||||||
, bool render_group_bounds
|
, bool render_group_bounds
|
||||||
|
, bool /*render_lowres*/
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!wmo->finishedLoading() || wmo->loading_failed())
|
if (!wmo->finishedLoading() || wmo->loading_failed())
|
||||||
@@ -113,23 +114,50 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
|
|||||||
|
|
||||||
wmo_shader.uniform("transform", _transform_mat);
|
wmo_shader.uniform("transform", _transform_mat);
|
||||||
|
|
||||||
wmo->renderer()->draw( wmo_shader
|
// render WDL model
|
||||||
, model_view
|
[[unlikely]]
|
||||||
, projection
|
if (render_low_res && lowResWmo.has_value()
|
||||||
, _transform_mat
|
&& lowResWmo.value()->get()->finishedLoading() && !lowResWmo.value()->get()->loading_failed())
|
||||||
, is_selected || _grouped
|
{
|
||||||
, frustum
|
lowResWmo.value()->get()->renderer()->draw(wmo_shader
|
||||||
, cull_distance
|
, model_view
|
||||||
, camera
|
, projection
|
||||||
, draw_doodads
|
, _transform_mat
|
||||||
, draw_fog
|
, is_selected || _grouped
|
||||||
, animtime
|
, frustum
|
||||||
, world_has_skies
|
, cull_distance
|
||||||
, display
|
, camera
|
||||||
, !draw_exterior
|
, draw_doodads
|
||||||
, render_group_bounds
|
, draw_fog
|
||||||
, _grouped
|
, animtime
|
||||||
);
|
, world_has_skies
|
||||||
|
, display
|
||||||
|
, !draw_exterior
|
||||||
|
, render_group_bounds
|
||||||
|
, _grouped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else // regular model
|
||||||
|
{
|
||||||
|
wmo->renderer()->draw( wmo_shader
|
||||||
|
, model_view
|
||||||
|
, projection
|
||||||
|
, _transform_mat
|
||||||
|
, is_selected || _grouped
|
||||||
|
, frustum
|
||||||
|
, cull_distance
|
||||||
|
, camera
|
||||||
|
, draw_doodads
|
||||||
|
, draw_fog
|
||||||
|
, animtime
|
||||||
|
, world_has_skies
|
||||||
|
, display
|
||||||
|
, !draw_exterior
|
||||||
|
, render_group_bounds
|
||||||
|
, _grouped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// axis aligned bounding box (extents)
|
// axis aligned bounding box (extents)
|
||||||
@@ -150,7 +178,7 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WMOInstance::intersect (math::ray const& ray, selection_result* results, bool do_exterior)
|
void WMOInstance::intersect (math::ray const& ray, selection_result* results, bool do_exterior, bool do_interior, bool first_occurence)
|
||||||
{
|
{
|
||||||
if (!finishedLoading() || wmo->loading_failed())
|
if (!finishedLoading() || wmo->loading_failed())
|
||||||
return;
|
return;
|
||||||
@@ -164,7 +192,7 @@ void WMOInstance::intersect (math::ray const& ray, selection_result* results, bo
|
|||||||
|
|
||||||
math::ray subray(_transform_mat_inverted, ray);
|
math::ray subray(_transform_mat_inverted, ray);
|
||||||
|
|
||||||
for (auto&& result : wmo->intersect(subray, do_exterior))
|
for (auto&& result : wmo->intersect(subray, do_exterior, do_interior, first_occurence))
|
||||||
{
|
{
|
||||||
results->emplace_back (result, this);
|
results->emplace_back (result, this);
|
||||||
}
|
}
|
||||||
@@ -303,6 +331,24 @@ void WMOInstance::recalcExtents()
|
|||||||
|
|
||||||
bounding_radius = glm::distance(wmo->extents[1], wmo->extents[0]) * scale / 2.0f;
|
bounding_radius = glm::distance(wmo->extents[1], wmo->extents[0]) * scale / 2.0f;
|
||||||
|
|
||||||
|
// Update wdl if needed
|
||||||
|
[[unlikely]]
|
||||||
|
if (lowResWmo.has_value())
|
||||||
|
{
|
||||||
|
lowResInstance->pos[0] = pos.x;
|
||||||
|
lowResInstance->pos[1] = pos.y;
|
||||||
|
lowResInstance->pos[2] = pos.z;
|
||||||
|
|
||||||
|
lowResInstance->rot[0] = dir.x;
|
||||||
|
lowResInstance->rot[1] = dir.y;
|
||||||
|
lowResInstance->rot[2] = dir.z;
|
||||||
|
|
||||||
|
lowResInstance->scale = scale * 1024.0f;
|
||||||
|
|
||||||
|
lowResInstance->extents[0] = extents[0];
|
||||||
|
lowResInstance->extents[1] = extents[1];
|
||||||
|
}
|
||||||
|
|
||||||
_need_recalc_extents = false;
|
_need_recalc_extents = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,14 @@ public:
|
|||||||
uint16_t mFlags;
|
uint16_t mFlags;
|
||||||
uint16_t mNameset;
|
uint16_t mNameset;
|
||||||
|
|
||||||
uint16_t doodadset() const { return _doodadset; }
|
// TODO figure out a better structure for this
|
||||||
|
// bool hasLowResModel = false;
|
||||||
|
std::optional<scoped_wmo_reference*> lowResWmo;
|
||||||
|
ENTRY_MODF* lowResInstance = nullptr; // assume this is never nullptr when lowResWmo has a value
|
||||||
|
bool render_low_res = false;
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t doodadset() const { return _doodadset; };
|
||||||
void change_doodadset(uint16_t doodad_set);
|
void change_doodadset(uint16_t doodad_set);
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -51,7 +58,12 @@ public:
|
|||||||
, _doodadset (other._doodadset)
|
, _doodadset (other._doodadset)
|
||||||
, _doodads_per_group(other._doodads_per_group)
|
, _doodads_per_group(other._doodads_per_group)
|
||||||
, _need_doodadset_update(other._need_doodadset_update)
|
, _need_doodadset_update(other._need_doodadset_update)
|
||||||
|
, _update_group_extents(other._update_group_extents)
|
||||||
, _need_recalc_extents(other._need_recalc_extents)
|
, _need_recalc_extents(other._need_recalc_extents)
|
||||||
|
// , hasLowResModel(other.hasLowResModel)
|
||||||
|
, render_low_res(other.render_low_res)
|
||||||
|
, lowResInstance(other.lowResInstance)
|
||||||
|
, lowResWmo(other.lowResWmo)
|
||||||
{
|
{
|
||||||
std::swap (extents, other.extents);
|
std::swap (extents, other.extents);
|
||||||
pos = other.pos;
|
pos = other.pos;
|
||||||
@@ -82,6 +94,11 @@ public:
|
|||||||
std::swap(_transform_mat_inverted, other._transform_mat_inverted);
|
std::swap(_transform_mat_inverted, other._transform_mat_inverted);
|
||||||
std::swap(_context, other._context);
|
std::swap(_context, other._context);
|
||||||
std::swap(_need_recalc_extents, other._need_recalc_extents);
|
std::swap(_need_recalc_extents, other._need_recalc_extents);
|
||||||
|
std::swap(_update_group_extents, other._update_group_extents);
|
||||||
|
// std::swap(hasLowResModel, other.hasLowResModel);
|
||||||
|
std::swap(render_low_res, other.render_low_res);
|
||||||
|
std::swap(lowResInstance, other.lowResInstance);
|
||||||
|
std::swap(lowResWmo, other.lowResWmo);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +119,10 @@ public:
|
|||||||
, bool draw_exterior
|
, bool draw_exterior
|
||||||
, bool render_selection_aabb
|
, bool render_selection_aabb
|
||||||
, bool render_group_bounds
|
, bool render_group_bounds
|
||||||
|
, bool render_low_res
|
||||||
);
|
);
|
||||||
|
|
||||||
void intersect (math::ray const&, selection_result*, bool do_exterior = true);
|
void intersect (math::ray const&, selection_result*, bool do_exterior = true, bool do_interior = true, bool first_occurence = false);
|
||||||
|
|
||||||
std::array<glm::vec3, 2> const& getExtents() override; // axis aligned
|
std::array<glm::vec3, 2> const& getExtents() override; // axis aligned
|
||||||
std::array<glm::vec3, 2> const& getLocalExtents() const;
|
std::array<glm::vec3, 2> const& getLocalExtents() const;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ World::World(const std::string& name, int map_id, Noggit::NoggitRenderContext co
|
|||||||
, _model_instance_storage(this)
|
, _model_instance_storage(this)
|
||||||
, _tile_update_queue(this)
|
, _tile_update_queue(this)
|
||||||
, mapIndex(name, map_id, this, context, create_empty)
|
, mapIndex(name, map_id, this, context, create_empty)
|
||||||
, horizon(name, &mapIndex)
|
, horizon(name, this)
|
||||||
, mWmoFilename(mapIndex.globalWMOName)
|
, mWmoFilename(mapIndex.globalWMOName)
|
||||||
, mWmoEntry(mapIndex.wmoEntry)
|
, mWmoEntry(mapIndex.wmoEntry)
|
||||||
, animtime(0)
|
, animtime(0)
|
||||||
@@ -120,6 +120,17 @@ World::World(const std::string& name, int map_id, Noggit::NoggitRenderContext co
|
|||||||
{
|
{
|
||||||
LogDebug << "Loading world \"" << name << "\"." << std::endl;
|
LogDebug << "Loading world \"" << name << "\"." << std::endl;
|
||||||
_loaded_tiles_buffer[0] = std::make_pair<std::pair<int, int>, MapTile*>(std::make_pair(0, 0), nullptr);
|
_loaded_tiles_buffer[0] = std::make_pair<std::pair<int, int>, MapTile*>(std::make_pair(0, 0), nullptr);
|
||||||
|
|
||||||
|
// initialize wdl models here
|
||||||
|
if (horizon.wmos.size() < horizon.lWMOInstances.size())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < horizon.mWMOFilenames.size(); ++i)
|
||||||
|
{
|
||||||
|
// auto instance = horizon.lWMOInstances[i];
|
||||||
|
auto& filepath = horizon.mWMOFilenames[i];
|
||||||
|
horizon.wmos.push_back(scoped_wmo_reference(filepath, _context));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::LoadSavedSelectionGroups()
|
void World::LoadSavedSelectionGroups()
|
||||||
@@ -778,6 +789,8 @@ void World::scale_selected_models(float v, object_scaling_type type)
|
|||||||
if (!_selected_model_count)
|
if (!_selected_model_count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
v = std::clamp(v, SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
|
|
||||||
bool modern_features = Noggit::Application::NoggitApplication::instance()->getConfiguration()->modern_features;
|
bool modern_features = Noggit::Application::NoggitApplication::instance()->getConfiguration()->modern_features;
|
||||||
|
|
||||||
for (auto& entry : _current_selection)
|
for (auto& entry : _current_selection)
|
||||||
@@ -1131,14 +1144,18 @@ bool World::isInIndoorWmoGroup(std::array<glm::vec3, 2> obj_bounds, glm::mat4x4
|
|||||||
|
|
||||||
selection_result World::intersect (glm::mat4x4 const& model_view
|
selection_result World::intersect (glm::mat4x4 const& model_view
|
||||||
, math::ray const& ray
|
, math::ray const& ray
|
||||||
, bool pOnlyMap
|
, const bool pOnlyMap
|
||||||
, bool do_objects
|
, const bool do_objects
|
||||||
, bool draw_terrain
|
, const bool draw_terrain
|
||||||
, bool draw_wmo
|
, const bool draw_wmo
|
||||||
, bool draw_models
|
, const bool draw_models
|
||||||
, bool draw_hidden_models
|
, const bool draw_hidden_models
|
||||||
, bool draw_wmo_exterior
|
, const bool draw_wmo_exterior
|
||||||
, bool animate
|
, const bool animate
|
||||||
|
, const bool first_object_occurence
|
||||||
|
, const bool opaque_only_tris
|
||||||
|
, const float obj_distance_max
|
||||||
|
, const bool do_wmo_interiors
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ZoneScopedN("World::intersect()");
|
ZoneScopedN("World::intersect()");
|
||||||
@@ -1172,28 +1189,82 @@ selection_result World::intersect (glm::mat4x4 const& model_view
|
|||||||
|
|
||||||
if (!pOnlyMap && do_objects)
|
if (!pOnlyMap && do_objects)
|
||||||
{
|
{
|
||||||
if (draw_models)
|
if (!draw_models && !draw_wmo)
|
||||||
{
|
return std::move(results);
|
||||||
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, animate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draw_wmo)
|
////////////// Optimized version, can iterate the same objects multiple times if they are on borders though
|
||||||
|
|
||||||
|
// store in a set container to avoid duplicates, this is pretty slow, doing a few extra rays is much faster if duplicates aren't a problem
|
||||||
|
// std::unordered_set<ModelInstance*> modelInstances;
|
||||||
|
// std::unordered_set<WMOInstance*> wmoInstances;
|
||||||
|
|
||||||
|
for (auto& pair : _loaded_tiles_buffer)
|
||||||
{
|
{
|
||||||
ZoneScopedN("World::intersect() : intersect WMOs");
|
MapTile* tile = pair.second;
|
||||||
_model_instance_storage.for_each_wmo_instance([&] (WMOInstance& wmo_instance)
|
|
||||||
|
if (!tile)
|
||||||
|
break;
|
||||||
|
|
||||||
|
TileIndex index{ static_cast<std::size_t>(pair.first.first)
|
||||||
|
, static_cast<std::size_t>(pair.first.second) };
|
||||||
|
|
||||||
|
// add some distance check ?
|
||||||
|
// if (tile-> > )
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
if (!mapIndex.tileLoaded(index) || mapIndex.tileAwaitingLoading(index))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!tile->finishedLoading())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tile->recalcCombinedExtents();
|
||||||
|
|
||||||
|
if (!ray.intersect_bounds(tile->getCombinedExtents()[0], tile->getCombinedExtents()[1]))
|
||||||
{
|
{
|
||||||
if (draw_hidden_models || !wmo_instance.wmo->is_hidden())
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& pair : tile->getObjectInstances())
|
||||||
|
{
|
||||||
|
if (pair.second[0]->which() == eMODEL && draw_models)
|
||||||
{
|
{
|
||||||
wmo_instance.intersect(ray, &results, draw_wmo_exterior);
|
|
||||||
|
for (auto& instance : pair.second)
|
||||||
|
{
|
||||||
|
auto model_instance = static_cast<ModelInstance*>(instance);
|
||||||
|
|
||||||
|
if (obj_distance_max != 0.0f)
|
||||||
|
{
|
||||||
|
const float distance = glm::distance(ray.origin(), instance->pos);
|
||||||
|
if ((distance - instance->getBoundingRadius()) > obj_distance_max)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_hidden_models || !model_instance->model->is_hidden())
|
||||||
|
// modelInstances.insert(model_instance);
|
||||||
|
model_instance->intersect(model_view, ray, &results, animtime, animate, first_object_occurence, opaque_only_tris);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
else if (pair.second[0]->which() == eWMO && draw_wmo)
|
||||||
|
{
|
||||||
|
for (auto& instance : pair.second)
|
||||||
|
{
|
||||||
|
auto wmo_instance = static_cast<WMOInstance*>(instance);
|
||||||
|
|
||||||
|
if (obj_distance_max != 0.0f)
|
||||||
|
{
|
||||||
|
const float distance = glm::distance(ray.origin(), instance->pos);
|
||||||
|
if ((distance - instance->getBoundingRadius()) > obj_distance_max)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_hidden_models || !wmo_instance->wmo->is_hidden())
|
||||||
|
// wmoInstances.insert(wmo_instance);
|
||||||
|
wmo_instance->intersect(ray, &results, draw_wmo_exterior, do_wmo_interiors, first_object_occurence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2147,7 +2218,25 @@ std::uint32_t World::add_model_instance(ModelInstance model_instance, bool from_
|
|||||||
std::uint32_t World::add_wmo_instance(WMOInstance wmo_instance, bool from_reloading, bool action)
|
std::uint32_t World::add_wmo_instance(WMOInstance wmo_instance, bool from_reloading, bool action)
|
||||||
{
|
{
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
// Check if WMO has a low resolution model
|
||||||
|
// also sets up all attributes currently
|
||||||
|
bool haslowres = horizon.wmoHasLowRes(&wmo_instance);
|
||||||
|
|
||||||
return _model_instance_storage.add_wmo_instance(std::move(wmo_instance), from_reloading, action);
|
return _model_instance_storage.add_wmo_instance(std::move(wmo_instance), from_reloading, action);
|
||||||
|
|
||||||
|
// if (haslowres)
|
||||||
|
// {
|
||||||
|
// const auto obj = get_model(uid_after);
|
||||||
|
// assert(obj);
|
||||||
|
// if (obj)
|
||||||
|
// {
|
||||||
|
// WMOInstance* instance = static_cast<WMOInstance*>(std::get<selected_object_type>(obj.value()));
|
||||||
|
//
|
||||||
|
// int breakpoint = 0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return uid_after;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<selection_type> World::get_model(std::uint32_t uid)
|
std::optional<selection_type> World::get_model(std::uint32_t uid)
|
||||||
@@ -2695,6 +2784,8 @@ void World::clear_shadows(glm::vec3 const& pos)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr float HALFSHADOWSIZE = (TEXDETAILSIZE / 2.0f);
|
||||||
|
|
||||||
void World::swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex)
|
void World::swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex)
|
||||||
{
|
{
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
@@ -3735,6 +3826,7 @@ void World::select_objects_in_area(
|
|||||||
|
|
||||||
glm::mat4 VPmatrix = projection * view;
|
glm::mat4 VPmatrix = projection * view;
|
||||||
glm::mat4x4 const invertedProjViewMatrix = glm::inverse(VPmatrix);
|
glm::mat4x4 const invertedProjViewMatrix = glm::inverse(VPmatrix);
|
||||||
|
auto const transposed_view = glm::transpose(view);
|
||||||
|
|
||||||
constexpr int max_position_raycast_processing = 10000;
|
constexpr int max_position_raycast_processing = 10000;
|
||||||
constexpr int max_bounds_raycast_processing = 5000; // when selecting large amount of objects, avoid doing complex ray calculations to not freeze
|
constexpr int max_bounds_raycast_processing = 5000; // when selecting large amount of objects, avoid doing complex ray calculations to not freeze
|
||||||
@@ -3864,7 +3956,7 @@ void World::select_objects_in_area(
|
|||||||
// processed_obj_count++;
|
// processed_obj_count++;
|
||||||
// 3: check if center point is occluded by terrain
|
// 3: check if center point is occluded by terrain
|
||||||
if (processed_obj_count < max_position_raycast_processing &&
|
if (processed_obj_count < max_position_raycast_processing &&
|
||||||
!is_point_occluded_by_terrain(aabb_center, view, VPmatrix, viewport_width, viewport_height, camera_position))
|
!is_point_occluded_by_terrain(aabb_center, transposed_view, VPmatrix, viewport_width, viewport_height, camera_position))
|
||||||
{
|
{
|
||||||
// if not occluded success! select it and skip other checks
|
// if not occluded success! select it and skip other checks
|
||||||
add_to_selection(instance, false, false);
|
add_to_selection(instance, false, false);
|
||||||
@@ -3997,7 +4089,7 @@ void World::select_objects_in_area(
|
|||||||
|
|
||||||
// 4.5 2nd raycast. Check if center of the intersection box is visible
|
// 4.5 2nd raycast. Check if center of the intersection box is visible
|
||||||
// TODO : for WMOs this is way to generous due to their more complex shape, it would be better to iterate the bounding box of each group
|
// TODO : for WMOs this is way to generous due to their more complex shape, it would be better to iterate the bounding box of each group
|
||||||
if (!is_point_occluded_by_terrain(intersectionCenter_pos, view, VPmatrix, viewport_width, viewport_height
|
if (!is_point_occluded_by_terrain(intersectionCenter_pos, transposed_view, VPmatrix, viewport_width, viewport_height
|
||||||
, camera_position, (distance - instance->getBoundingRadius())))
|
, camera_position, (distance - instance->getBoundingRadius())))
|
||||||
{
|
{
|
||||||
// if not occluded success! select it and skip other checks
|
// if not occluded success! select it and skip other checks
|
||||||
@@ -4061,7 +4153,7 @@ void World::select_objects_in_area(
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool corner_occluded = is_point_occluded_by_terrain(corner
|
bool corner_occluded = is_point_occluded_by_terrain(corner
|
||||||
, view
|
, transposed_view
|
||||||
, VPmatrix
|
, VPmatrix
|
||||||
, viewport_width
|
, viewport_width
|
||||||
, viewport_height
|
, viewport_height
|
||||||
@@ -4095,7 +4187,7 @@ void World::select_objects_in_area(
|
|||||||
|
|
||||||
|
|
||||||
bool World::is_point_occluded_by_terrain(const glm::vec3& point,
|
bool World::is_point_occluded_by_terrain(const glm::vec3& point,
|
||||||
const glm::mat4x4& view,
|
const glm::mat4x4& transposed_view,
|
||||||
const glm::mat4& VPmatrix,
|
const glm::mat4& VPmatrix,
|
||||||
float viewport_width,
|
float viewport_width,
|
||||||
float viewport_height,
|
float viewport_height,
|
||||||
@@ -4117,7 +4209,7 @@ bool World::is_point_occluded_by_terrain(const glm::vec3& point,
|
|||||||
// intersect only terrain with a ray to object's position
|
// intersect only terrain with a ray to object's position
|
||||||
selection_result terrain_intersect_results
|
selection_result terrain_intersect_results
|
||||||
(intersect
|
(intersect
|
||||||
(glm::transpose(view)
|
(transposed_view
|
||||||
, ray
|
, ray
|
||||||
, true
|
, true
|
||||||
, false
|
, false
|
||||||
|
|||||||
@@ -101,14 +101,18 @@ public:
|
|||||||
|
|
||||||
selection_result intersect (glm::mat4x4 const& model_view
|
selection_result intersect (glm::mat4x4 const& model_view
|
||||||
, math::ray const&
|
, math::ray const&
|
||||||
, bool only_map
|
, const bool only_map
|
||||||
, bool do_objects
|
, const bool do_objects
|
||||||
, bool draw_terrain
|
, const bool draw_terrain
|
||||||
, bool draw_wmo
|
, const bool draw_wmo
|
||||||
, bool draw_models
|
, const bool draw_models
|
||||||
, bool draw_hidden_models
|
, const bool draw_hidden_models
|
||||||
, bool draw_wmo_exterior
|
, const bool draw_wmo_exterior
|
||||||
, bool animate
|
, const bool animate
|
||||||
|
, const bool first_object_occurence = false
|
||||||
|
, const bool opaque_only_tris = false
|
||||||
|
, const float obj_distance_max = 0.0f
|
||||||
|
, const bool do_wmo_interiors = true
|
||||||
);
|
);
|
||||||
|
|
||||||
MapChunk* getChunkAt(glm::vec3 const& pos);
|
MapChunk* getChunkAt(glm::vec3 const& pos);
|
||||||
@@ -143,6 +147,7 @@ public:
|
|||||||
void remove_from_selection(std::uint32_t uid, bool skip_group = false, bool update_pivot = true);
|
void remove_from_selection(std::uint32_t uid, bool skip_group = false, bool update_pivot = true);
|
||||||
void reset_selection();
|
void reset_selection();
|
||||||
void delete_selected_models();
|
void delete_selected_models();
|
||||||
|
// note : height is Y axis.
|
||||||
glm::vec3 get_ground_height(glm::vec3 pos);
|
glm::vec3 get_ground_height(glm::vec3 pos);
|
||||||
void range_add_to_selection(glm::vec3 const& pos, float radius, bool remove);
|
void range_add_to_selection(glm::vec3 const& pos, float radius, bool remove);
|
||||||
Noggit::world_model_instances_storage& getModelInstanceStorage() { return _model_instance_storage; };
|
Noggit::world_model_instances_storage& getModelInstanceStorage() { return _model_instance_storage; };
|
||||||
@@ -244,6 +249,7 @@ public:
|
|||||||
void paintGroundEffectExclusion(glm::vec3 const& pos, float radius, bool exclusion);
|
void paintGroundEffectExclusion(glm::vec3 const& pos, float radius, bool exclusion);
|
||||||
void setBaseTexture(glm::vec3 const& pos);
|
void setBaseTexture(glm::vec3 const& pos);
|
||||||
void clear_shadows(glm::vec3 const& pos);
|
void clear_shadows(glm::vec3 const& pos);
|
||||||
|
void bake_shadows(glm::vec3 const& pos, int mode, const glm::mat4x4& view);
|
||||||
void clearTextures(glm::vec3 const& pos);
|
void clearTextures(glm::vec3 const& pos);
|
||||||
void swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex);
|
void swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex);
|
||||||
void swapTextureGlobal(scoped_blp_texture_reference tex);
|
void swapTextureGlobal(scoped_blp_texture_reference tex);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <noggit/World.h>
|
#include <noggit/World.h>
|
||||||
#include <opengl/context.hpp>
|
#include <opengl/context.hpp>
|
||||||
#include <opengl/context.inl>
|
#include <opengl/context.inl>
|
||||||
|
#include <noggit/Misc.h>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
@@ -92,7 +93,7 @@ static inline uint32_t color_for_height (int16_t height)
|
|||||||
namespace Noggit
|
namespace Noggit
|
||||||
{
|
{
|
||||||
|
|
||||||
map_horizon::map_horizon(const std::string& basename, const MapIndex * const index)
|
map_horizon::map_horizon(const std::string& basename, World * const world)
|
||||||
{
|
{
|
||||||
std::stringstream filename;
|
std::stringstream filename;
|
||||||
filename << "World\\Maps\\" << basename << "\\" << basename << ".wdl";
|
filename << "World\\Maps\\" << basename << "\\" << basename << ".wdl";
|
||||||
@@ -126,10 +127,12 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// todo: handle those too ?
|
|
||||||
case 'MWMO':
|
case 'MWMO':
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
// TODO : use WMID instead for proper string parsing.
|
||||||
|
|
||||||
char const* lCurPos = reinterpret_cast<char const*>(wdl_file.getPointer());
|
char const* lCurPos = reinterpret_cast<char const*>(wdl_file.getPointer());
|
||||||
char const* lEnd = lCurPos + size;
|
char const* lEnd = lCurPos + size;
|
||||||
|
|
||||||
@@ -143,21 +146,21 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'MWID':
|
case 'MWID':
|
||||||
wdl_file.seekRelative(size);
|
|
||||||
break;
|
|
||||||
// TODO
|
// TODO
|
||||||
|
wdl_file.seekRelative(size); // jump to end of chunk
|
||||||
|
break;
|
||||||
case 'MODF':
|
case 'MODF':
|
||||||
{
|
{
|
||||||
wdl_file.seekRelative(size);
|
ENTRY_MODF const* modf_ptr = reinterpret_cast<ENTRY_MODF const*>(wdl_file.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
wdl_file.seekRelative(size); // jump to end of chunk
|
||||||
break;
|
break;
|
||||||
// {
|
|
||||||
// ENTRY_MODF const* modf_ptr = reinterpret_cast<ENTRY_MODF const*>(wdl_file.getPointer());
|
|
||||||
// for (unsigned int i = 0; i < size / sizeof(ENTRY_MODF); ++i)
|
|
||||||
// {
|
|
||||||
// lWMOInstances.push_back(modf_ptr[i]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
case 'MAOF':
|
case 'MAOF':
|
||||||
{
|
{
|
||||||
@@ -212,9 +215,27 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind
|
|||||||
}
|
}
|
||||||
} while (!done && !wdl_file.isEof());
|
} while (!done && !wdl_file.isEof());
|
||||||
|
|
||||||
|
constexpr bool _load_models = true;
|
||||||
|
if (_load_models)
|
||||||
|
{
|
||||||
|
// - Load WMOs -----------------------------------------
|
||||||
|
|
||||||
|
// Don't load them to storage, they share UIDs wth regular models
|
||||||
|
|
||||||
|
// for rendering in unloaded tiles
|
||||||
|
for (auto const& object : lWMOInstances)
|
||||||
|
{
|
||||||
|
// world->add_wmo_instance(WMOInstance(mWMOFilenames[object.nameID],
|
||||||
|
// &object, world->getRenderContext()), false, false);
|
||||||
|
|
||||||
|
// auto& filepath = mWMOFilenames[object.nameID];
|
||||||
|
// wmos.push_back(scoped_wmo_reference(filepath, world->getRenderContext()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wdl_file.close();
|
wdl_file.close();
|
||||||
|
|
||||||
set_minimap(index);
|
set_minimap(&world->mapIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void map_horizon::update_minimap_tile(int y, int x, bool has_data = false )
|
void map_horizon::update_minimap_tile(int y, int x, bool has_data = false )
|
||||||
@@ -378,25 +399,95 @@ void map_horizon::save_wdl(World* world, bool regenerate)
|
|||||||
curPos += 8 + 0x4;
|
curPos += 8 + 0x4;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// WMO objects export code is copy pasta from MapTile
|
||||||
|
|
||||||
|
struct filenameOffsetThing
|
||||||
|
{
|
||||||
|
int nameID;
|
||||||
|
int filenamePosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
filenameOffsetThing nullyThing = { 0, 0 };
|
||||||
|
|
||||||
|
std::map<std::string, filenameOffsetThing> lObjects;
|
||||||
|
|
||||||
|
// avoid duplicates, not really necessary here as we directly used MWMO string list
|
||||||
|
for (auto const& filename : mWMOFilenames)
|
||||||
|
{
|
||||||
|
if (lObjects.find(filename) == lObjects.end())
|
||||||
|
{
|
||||||
|
lObjects.emplace(filename, nullyThing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lID = 0;
|
||||||
|
for (auto& object : lObjects)
|
||||||
|
{
|
||||||
|
object.second.nameID = lID++;
|
||||||
|
}
|
||||||
|
|
||||||
// MWMO
|
// MWMO
|
||||||
// {
|
// {
|
||||||
|
int lMWMO_Position = curPos;
|
||||||
wdlFile.Extend(8);
|
wdlFile.Extend(8);
|
||||||
SetChunkHeader(wdlFile, curPos, 'MWMO', 0);
|
SetChunkHeader(wdlFile, curPos, 'MWMO', 0);
|
||||||
|
|
||||||
curPos += 8;
|
curPos += 8;
|
||||||
|
|
||||||
|
// MWMO data
|
||||||
|
for (auto& object : lObjects)
|
||||||
|
{
|
||||||
|
object.second.filenamePosition = wdlFile.GetPointer<sChunkHeader>(lMWMO_Position)->mSize;
|
||||||
|
wdlFile.Insert(curPos, static_cast<unsigned long>(object.first.size() + 1), misc::normalize_adt_filename(object.first).c_str());
|
||||||
|
curPos += static_cast<int>(object.first.size() + 1);
|
||||||
|
wdlFile.GetPointer<sChunkHeader>(lMWMO_Position)->mSize += static_cast<int>(object.first.size() + 1);
|
||||||
|
LogDebug << "Added WDL object \"" << object.first << "\"." << std::endl;
|
||||||
|
}
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// MWID
|
// MWID
|
||||||
// {
|
// {
|
||||||
wdlFile.Extend(8);
|
int lMWID_Size = static_cast<int>(4 * lObjects.size());
|
||||||
SetChunkHeader(wdlFile, curPos, 'MWID', 0);
|
wdlFile.Extend(8 + lMWID_Size);
|
||||||
curPos += 8;
|
SetChunkHeader(wdlFile, curPos, 'MWID', lMWID_Size);
|
||||||
|
|
||||||
|
// MWID data
|
||||||
|
auto const lMWID_Data = wdlFile.GetPointer<int>(curPos + 8);
|
||||||
|
|
||||||
|
lID = 0;
|
||||||
|
for (auto const& object : lObjects)
|
||||||
|
lMWID_Data[lID++] = object.second.filenamePosition;
|
||||||
|
|
||||||
|
curPos += 8 + lMWID_Size;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO : MODF
|
// MODF
|
||||||
// {
|
// {
|
||||||
wdlFile.Extend(8);
|
int lMODF_Size = static_cast<int>(0x40 * lWMOInstances.size());
|
||||||
SetChunkHeader(wdlFile, curPos, 'MODF', 0);
|
wdlFile.Extend(8 + lMODF_Size);
|
||||||
curPos += 8;
|
SetChunkHeader(wdlFile, curPos, 'MODF', lMODF_Size);
|
||||||
|
|
||||||
|
// MODF data
|
||||||
|
auto const lMODF_Data = wdlFile.GetPointer<ENTRY_MODF>(curPos + 8);
|
||||||
|
|
||||||
|
lID = 0;
|
||||||
|
for (auto const& object : lWMOInstances)
|
||||||
|
{
|
||||||
|
auto filename_to_offset_and_name = lObjects.find(mWMOFilenames[object.nameID]);
|
||||||
|
if (filename_to_offset_and_name == lObjects.end())
|
||||||
|
{
|
||||||
|
LogError << "There is a problem with saving the WDL objects. We have an object that somehow changed the name during the saving function." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lMODF_Data[lID] = object;
|
||||||
|
// only need to update name id
|
||||||
|
lMODF_Data[lID].nameID = filename_to_offset_and_name->second.nameID;
|
||||||
|
lID++;
|
||||||
|
}
|
||||||
|
LogDebug << "Added " << lID << " wmos to WDL MODF" << std::endl;
|
||||||
|
|
||||||
|
curPos += 8 + lMODF_Size;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//uint32_t mare_offsets[64][64] = { 0 };
|
//uint32_t mare_offsets[64][64] = { 0 };
|
||||||
@@ -482,6 +573,46 @@ void map_horizon::save_wdl(World* world, bool regenerate)
|
|||||||
set_minimap(&world->mapIndex);
|
set_minimap(&world->mapIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool map_horizon::wmoHasLowRes(WMOInstance* instance)
|
||||||
|
{
|
||||||
|
assert(instance->lowResWmo.has_value() == false);
|
||||||
|
if (instance->lowResWmo.has_value())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (auto& lowres_wmo : lWMOInstances)
|
||||||
|
{
|
||||||
|
if (lowres_wmo.uniqueID == instance->uid)
|
||||||
|
{
|
||||||
|
auto low_res_model = mWMOFilenames[lowres_wmo.nameID];
|
||||||
|
|
||||||
|
// TODO check positions
|
||||||
|
// need to convert coords?
|
||||||
|
auto dir = math::degrees::vec3{ math::degrees(
|
||||||
|
lowres_wmo.rot[0])._, math::degrees(lowres_wmo.rot[1])._, math::degrees(lowres_wmo.rot[2])._ };
|
||||||
|
|
||||||
|
if (misc::vec3d_equals(glm::vec3(lowres_wmo.pos[0], lowres_wmo.pos[1], lowres_wmo.pos[2]), instance->pos)
|
||||||
|
&& misc::deg_vec3d_equals(dir, instance->dir)
|
||||||
|
&& misc::float_equals( (lowres_wmo.scale / 1024.0f), instance->scale))
|
||||||
|
{
|
||||||
|
// instance->lowResWmo = scoped_wmo_reference(low_res_model, instance->wmo->_context);
|
||||||
|
instance->lowResInstance = &lowres_wmo;
|
||||||
|
instance->lowResWmo = &wmos[instance->lowResInstance->nameID];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
map_horizon::minimap::minimap(const map_horizon& horizon)
|
map_horizon::minimap::minimap(const map_horizon& horizon)
|
||||||
{
|
{
|
||||||
std::vector<uint32_t> texture(1024 * 1024);
|
std::vector<uint32_t> texture(1024 * 1024);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public:
|
|||||||
minimap(const map_horizon& horizon);
|
minimap(const map_horizon& horizon);
|
||||||
};
|
};
|
||||||
|
|
||||||
map_horizon(const std::string& basename, const MapIndex * const index);
|
map_horizon(const std::string& basename, World * const world);
|
||||||
|
|
||||||
void update_minimap_tile(int y, int x, bool has_data);
|
void update_minimap_tile(int y, int x, bool has_data);
|
||||||
|
|
||||||
@@ -94,14 +94,21 @@ public:
|
|||||||
|
|
||||||
void save_wdl(World* world, bool regenerate = false);
|
void save_wdl(World* world, bool regenerate = false);
|
||||||
|
|
||||||
|
bool wmoHasLowRes(WMOInstance* instance);
|
||||||
|
// void updateWmoLowRes(WMOInstance* instance);
|
||||||
|
|
||||||
|
// note : access those two with ENTRY_MODF.nameID
|
||||||
|
// TODO make a proper structure instead of raw access to wow chunks
|
||||||
|
std::vector<std::string> mWMOFilenames;
|
||||||
|
std::vector<ENTRY_MODF> lWMOInstances;
|
||||||
|
|
||||||
|
std::vector<scoped_wmo_reference> wmos;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int16_t getWdlheight(MapTile* tile, float x, float y);
|
int16_t getWdlheight(MapTile* tile, float x, float y);
|
||||||
|
|
||||||
std::string _filename;
|
std::string _filename;
|
||||||
|
|
||||||
std::vector<std::string> mWMOFilenames;
|
|
||||||
// std::vector<ENTRY_MODF> lWMOInstances;
|
|
||||||
|
|
||||||
std::unique_ptr<map_horizon_tile> _tiles[64][64];
|
std::unique_ptr<map_horizon_tile> _tiles[64][64];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <noggit/project/CurrentProject.hpp>
|
#include <noggit/project/CurrentProject.hpp>
|
||||||
#include <noggit/application/NoggitApplication.hpp>
|
#include <noggit/application/NoggitApplication.hpp>
|
||||||
|
|
||||||
|
#include <glm/gtx/euler_angles.hpp>
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
@@ -618,7 +620,8 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
|||||||
// make this check per WMO or global WMO with tiles may not work
|
// make this check per WMO or global WMO with tiles may not work
|
||||||
bool disable_cull = false;
|
bool disable_cull = false;
|
||||||
|
|
||||||
if (_world->mapIndex.hasAGlobalWMO() && !wmos_to_draw.size())
|
// WDT wmo
|
||||||
|
if (_world->mapIndex.hasAGlobalWMO() && wmos_to_draw.empty())
|
||||||
{
|
{
|
||||||
auto global_wmo = _world->_model_instance_storage.get_wmo_instance(_world->mWmoEntry.uniqueID);
|
auto global_wmo = _world->_model_instance_storage.get_wmo_instance(_world->mWmoEntry.uniqueID);
|
||||||
if (global_wmo.has_value())
|
if (global_wmo.has_value())
|
||||||
@@ -627,7 +630,91 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
|||||||
disable_cull = true;
|
disable_cull = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// draw wdl models in horizon/fog
|
||||||
|
if (render_settings.draw_fog)
|
||||||
|
{
|
||||||
|
// initialize the models here
|
||||||
|
// if (_world->horizon.wmos.size() < _world->horizon.lWMOInstances.size())
|
||||||
|
// {
|
||||||
|
// for (int i = 0; i < _world->horizon.lWMOInstances.size(); ++i)
|
||||||
|
// {
|
||||||
|
// auto instance = _world->horizon.lWMOInstances[i];
|
||||||
|
// auto& filepath = _world->horizon.mWMOFilenames[instance.nameID];
|
||||||
|
// _world->horizon.wmos.push_back(scoped_wmo_reference(filepath, _world->_context));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (int i = 0; i < _world->horizon.lWMOInstances.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& instance = _world->horizon.lWMOInstances[i];
|
||||||
|
|
||||||
|
auto model = _world->horizon.wmos[instance.nameID];
|
||||||
|
|
||||||
|
if (!model->finishedLoading() || model->loading_failed())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pos = glm::vec3(instance.pos[0], instance.pos[1], instance.pos[2]);
|
||||||
|
|
||||||
|
float dist = glm::distance(camera_pos, pos);
|
||||||
|
|
||||||
|
if (render_settings.draw_fog)
|
||||||
|
{
|
||||||
|
// Fog : only render if between cull distance and render distance ?
|
||||||
|
if (dist < _cull_distance || dist > _view_distance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else if (dist > _view_distance)
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
// calc transform everytime for now
|
||||||
|
glm::mat4x4 matrix = glm::mat4x4(1.0f);
|
||||||
|
|
||||||
|
matrix = glm::translate(matrix, pos);
|
||||||
|
|
||||||
|
matrix *= glm::eulerAngleYZX(
|
||||||
|
glm::radians(instance.rot[1] - math::degrees(90.0f)._),
|
||||||
|
glm::radians(-instance.rot[0]),
|
||||||
|
glm::radians(instance.rot[2])
|
||||||
|
);
|
||||||
|
|
||||||
|
float scale = instance.scale / 1024.0f;
|
||||||
|
if (scale != 1.0f)
|
||||||
|
matrix = glm::scale(matrix, glm::vec3(scale, scale, scale));
|
||||||
|
|
||||||
|
wmo_program.uniform("transform", matrix);
|
||||||
|
|
||||||
|
model->renderer()->draw(wmo_program
|
||||||
|
, model_view
|
||||||
|
, projection
|
||||||
|
, matrix
|
||||||
|
, false
|
||||||
|
, frustum
|
||||||
|
, _view_distance
|
||||||
|
, camera_pos
|
||||||
|
, false
|
||||||
|
, render_settings.draw_fog
|
||||||
|
, _world->animtime
|
||||||
|
, _skies->hasSkies()
|
||||||
|
, render_settings.display_mode
|
||||||
|
, !render_settings.draw_wmo_exterior
|
||||||
|
, false
|
||||||
|
, false
|
||||||
|
);
|
||||||
|
|
||||||
|
// auto instance = _world->_model_instance_storage.get_wmo_instance(_world->horizon.lWMOInstances[i].uniqueID);
|
||||||
|
// if (instance.has_value())
|
||||||
|
// {
|
||||||
|
// wmos_to_draw.push_back(instance.value());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO setting
|
||||||
|
bool constexpr draw_wdl_models = false;
|
||||||
|
|
||||||
for (auto& instance: wmos_to_draw)
|
for (auto& instance: wmos_to_draw)
|
||||||
{
|
{
|
||||||
@@ -695,7 +782,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
|||||||
, render_settings.draw_wmo_exterior
|
, render_settings.draw_wmo_exterior
|
||||||
, render_settings.render_select_wmo_aabb
|
, render_settings.render_select_wmo_aabb
|
||||||
, render_settings.render_select_wmo_groups_bounds
|
, render_settings.render_select_wmo_groups_bounds
|
||||||
|
, draw_wdl_models // draw wdl model in render dist
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1232,7 +1319,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw Sky/Light spheres
|
// Draw Sky/Light spheres
|
||||||
glCullFace(GL_FRONT);
|
gl.cullFace(GL_FRONT);
|
||||||
if (!render_settings.draw_only_inside_light_sphere)
|
if (!render_settings.draw_only_inside_light_sphere)
|
||||||
{
|
{
|
||||||
for (Sky const& sky : skies()->skies)
|
for (Sky const& sky : skies()->skies)
|
||||||
@@ -1271,7 +1358,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now draw the current light (light that we're inside of)
|
// now draw the current light (light that we're inside of)
|
||||||
glCullFace(GL_BACK);
|
gl.cullFace(GL_BACK);
|
||||||
for (Sky const& sky : skies()->skies)
|
for (Sky const& sky : skies()->skies)
|
||||||
{
|
{
|
||||||
if (sky.global)
|
if (sky.global)
|
||||||
@@ -1994,7 +2081,7 @@ bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings*
|
|||||||
, 0.f, 0.f, 0.f, 1.f
|
, 0.f, 0.f, 0.f, 1.f
|
||||||
));
|
));
|
||||||
|
|
||||||
glFinish();
|
gl.finish();
|
||||||
|
|
||||||
drawMinimap(mTile
|
drawMinimap(mTile
|
||||||
, look_at
|
, look_at
|
||||||
@@ -2064,13 +2151,13 @@ bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings*
|
|||||||
if (use_md5)
|
if (use_md5)
|
||||||
{
|
{
|
||||||
QCryptographicHash md5_hash(QCryptographicHash::Md5);
|
QCryptographicHash md5_hash(QCryptographicHash::Md5);
|
||||||
// auto data = reinterpret_cast<char*>(blp_image);
|
// md5_hash.addData(reinterpret_cast<char*>(blp_image), file_size);
|
||||||
md5_hash.addData(reinterpret_cast<char*>(blp_image), file_size);
|
QByteArray data(reinterpret_cast<const char*>(blp_image), file_size);
|
||||||
|
md5_hash.addData(data);
|
||||||
auto resulthex = md5_hash.result().toHex().toStdString() + ".blp";
|
auto resulthex = md5_hash.result().toHex().toStdString() + ".blp";
|
||||||
tex_name = resulthex;
|
tex_name = resulthex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QFile file(dir.filePath(tex_name.c_str()));
|
QFile file(dir.filePath(tex_name.c_str()));
|
||||||
file.open(QIODevice::WriteOnly);
|
file.open(QIODevice::WriteOnly);
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ namespace Noggit
|
|||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(mapView(), &MapView::selectionUpdated, [=](auto) {
|
QObject::connect(mapView(), &MapView::selectionUpdated, [=](auto) {
|
||||||
_objectEditor->update_selection_ui(mapView()->getWorld());
|
_objectEditor->update_selection_ui();
|
||||||
});
|
});
|
||||||
|
|
||||||
using AssetBrowser = Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget;
|
using AssetBrowser = Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget;
|
||||||
|
|||||||
@@ -112,10 +112,38 @@ namespace Noggit
|
|||||||
|
|
||||||
_doodadSetSelector = new QComboBox(this);
|
_doodadSetSelector = new QComboBox(this);
|
||||||
_nameSetSelector = new QComboBox(this);
|
_nameSetSelector = new QComboBox(this);
|
||||||
|
|
||||||
|
QLabel* horizon_label = new QLabel("Low Res Model:", this);
|
||||||
|
|
||||||
|
QHBoxLayout* horizon_layout = new QHBoxLayout;
|
||||||
|
|
||||||
|
_horizonModel = new QLineEdit(this);
|
||||||
|
_horizonModel->setToolTip("Sets a model that will be rendered in the horizon fog, allowing to render further than normal render distance."
|
||||||
|
"Used to show landmarks from far away.");
|
||||||
|
horizon_layout->addWidget(_horizonModel, 1);
|
||||||
|
|
||||||
|
QToolButton* validateButton = new QToolButton;
|
||||||
|
validateButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogApplyButton));
|
||||||
|
// validateButton->setIconSize(QSize(24, 24));
|
||||||
|
validateButton->setAutoRaise(true); // Gives it a flat toolbar-style appearance
|
||||||
|
validateButton->setToolTip("Apply Low Res Model Filepath change.");
|
||||||
|
horizon_layout->addWidget(validateButton, 0);
|
||||||
|
|
||||||
|
_previewButton = new QToolButton;
|
||||||
|
_previewButton->setIcon(Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::Icons::VISIBILITY_HIDDEN_MODELS));
|
||||||
|
_previewButton->setToolTip("Preview Low Res Model");
|
||||||
|
_previewButton->setCheckable(true);
|
||||||
|
_previewButton->setChecked(false);
|
||||||
|
// previewButton->setIconSize(QSize(24, 24)); // Set icon size
|
||||||
|
// previewButton->setAutoRaise(true);
|
||||||
|
horizon_layout->addWidget(_previewButton, 0);
|
||||||
|
|
||||||
layout->addWidget(_wmo_group);
|
layout->addWidget(_wmo_group);
|
||||||
|
|
||||||
wmo_layout->addRow("Doodad Set:", _doodadSetSelector);
|
wmo_layout->addRow("Doodad Set:", _doodadSetSelector);
|
||||||
wmo_layout->addRow("Name Set:", _nameSetSelector);
|
wmo_layout->addRow("Name Set:", _nameSetSelector);
|
||||||
|
wmo_layout->addRow(horizon_label);
|
||||||
|
wmo_layout->addRow(horizon_layout);
|
||||||
|
|
||||||
auto clipboard_box = new QGroupBox("Clipboard");
|
auto clipboard_box = new QGroupBox("Clipboard");
|
||||||
// clipboard_box->setWindowIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::clipboard));
|
// clipboard_box->setWindowIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::clipboard));
|
||||||
@@ -178,8 +206,8 @@ namespace Noggit
|
|||||||
rotRangeEnd->setRange (-180.f, 180.f);
|
rotRangeEnd->setRange (-180.f, 180.f);
|
||||||
tiltRangeStart->setRange (-180.f, 180.f);
|
tiltRangeStart->setRange (-180.f, 180.f);
|
||||||
tiltRangeEnd->setRange (-180.f, 180.f);
|
tiltRangeEnd->setRange (-180.f, 180.f);
|
||||||
scaleRangeStart->setRange (-180.f, 180.f);
|
scaleRangeStart->setRange (SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
scaleRangeEnd->setRange (-180.f, 180.f);
|
scaleRangeEnd->setRange (SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
|
|
||||||
rotation_layout->addWidget(rotRangeStart, 0, 0);
|
rotation_layout->addWidget(rotRangeStart, 0, 0);
|
||||||
rotation_layout->addWidget(rotRangeEnd, 0 ,1);
|
rotation_layout->addWidget(rotRangeEnd, 0 ,1);
|
||||||
@@ -538,7 +566,11 @@ namespace Noggit
|
|||||||
, &QPushButton::clicked
|
, &QPushButton::clicked
|
||||||
, [=]() {
|
, [=]() {
|
||||||
_map_view->getAssetBrowserWidget()->set_browse_mode(Tools::AssetBrowser::asset_browse_mode::world);
|
_map_view->getAssetBrowserWidget()->set_browse_mode(Tools::AssetBrowser::asset_browse_mode::world);
|
||||||
mapView->getAssetBrowser()->setVisible(mapView->getAssetBrowser()->isHidden());
|
// mapView->getAssetBrowser()->setVisible(mapView->getAssetBrowser()->isHidden());
|
||||||
|
mapView->getAssetBrowser()->setFloating(true); // if it's not docked
|
||||||
|
mapView->getAssetBrowser()->resize(600, 400);
|
||||||
|
mapView->getAssetBrowser()->move(100, 100); // make sure it's on screen
|
||||||
|
mapView->getAssetBrowser()->show();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -566,6 +598,118 @@ namespace Noggit
|
|||||||
NOGGIT_ACTION_MGR->endAction();
|
NOGGIT_ACTION_MGR->endAction();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QObject::connect(validateButton, &QToolButton::clicked, [&]
|
||||||
|
{
|
||||||
|
if (_horizonModel->text().isEmpty())
|
||||||
|
_previewButton->setEnabled(false);
|
||||||
|
|
||||||
|
auto last_entry = _map_view->getWorld()->get_last_selected_model();
|
||||||
|
// for (auto& selection : selected)
|
||||||
|
if (last_entry)
|
||||||
|
{
|
||||||
|
if (last_entry.value().index() != eEntry_Object)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto obj = std::get<selected_object_type>(last_entry.value());
|
||||||
|
|
||||||
|
if (obj->which() == eWMO)
|
||||||
|
{
|
||||||
|
WMOInstance* wi = static_cast<WMOInstance*>(obj);
|
||||||
|
|
||||||
|
// verify if model exists if not empty
|
||||||
|
if (!_horizonModel->text().isEmpty())
|
||||||
|
{
|
||||||
|
if (!Noggit::Application::NoggitApplication::instance()->clientData()->exists(_horizonModel->text().toStdString()))
|
||||||
|
{
|
||||||
|
QMessageBox::warning
|
||||||
|
(nullptr
|
||||||
|
, "Warning"
|
||||||
|
, QString::fromStdString(_horizonModel->text().toStdString() + " not found.")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto world = _map_view->getWorld();
|
||||||
|
|
||||||
|
auto lowresmodel = wi->lowResWmo;
|
||||||
|
if (lowresmodel.has_value()) // had data
|
||||||
|
{
|
||||||
|
// same model
|
||||||
|
if (lowresmodel.value()->get()->file_key().filepath() == _horizonModel->text().toStdString())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_horizonModel->text().isEmpty()) // empty text but had a model
|
||||||
|
{
|
||||||
|
// TODO, deleting elements.
|
||||||
|
|
||||||
|
wi->lowResWmo = std::nullopt;
|
||||||
|
// delete wi->lowResInstance;
|
||||||
|
wi->lowResInstance = nullptr;
|
||||||
|
wi->render_low_res = false;
|
||||||
|
}
|
||||||
|
else // text not empty
|
||||||
|
{
|
||||||
|
// add a model instead of replacing, because a filename can be referenced by multiple.
|
||||||
|
// TODO store instances filenames directly for this reason instead of using raw MWMO/MODF
|
||||||
|
world->horizon.mWMOFilenames.push_back(_horizonModel->text().toStdString());
|
||||||
|
world->horizon.wmos.emplace_back(scoped_wmo_reference(_horizonModel->text().toStdString(), _map_view->getRenderContext()));
|
||||||
|
|
||||||
|
wi->lowResInstance->nameID = world->horizon.mWMOFilenames.size() - 1;
|
||||||
|
wi->lowResWmo = &world->horizon.wmos.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!_horizonModel->text().isEmpty()) // if had no wdl data
|
||||||
|
{
|
||||||
|
world->horizon.mWMOFilenames.push_back(_horizonModel->text().toStdString());
|
||||||
|
world->horizon.wmos.emplace_back(scoped_wmo_reference(_horizonModel->text().toStdString(), _map_view->getRenderContext()));
|
||||||
|
world->horizon.lWMOInstances.emplace_back(ENTRY_MODF());
|
||||||
|
|
||||||
|
auto& modf_instance = world->horizon.lWMOInstances.back();
|
||||||
|
modf_instance.nameID = world->horizon.mWMOFilenames.size() - 1;
|
||||||
|
modf_instance.uniqueID = wi->uid;
|
||||||
|
modf_instance.scale = 1024;
|
||||||
|
modf_instance.nameSet = 0;
|
||||||
|
modf_instance.flags = 0;
|
||||||
|
modf_instance.doodadSet = 0;
|
||||||
|
|
||||||
|
wi->lowResInstance = &modf_instance;
|
||||||
|
wi->lowResWmo = &world->horizon.wmos.back();
|
||||||
|
|
||||||
|
// recalc extents to update pos, rot, extents in modf_instance
|
||||||
|
wi->recalcExtents();
|
||||||
|
}
|
||||||
|
|
||||||
|
world->horizon.save_wdl(world, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(_previewButton, &QToolButton::toggled, this, [&](bool checked)
|
||||||
|
{
|
||||||
|
auto last_entry = _map_view->getWorld()->get_last_selected_model();
|
||||||
|
// for (auto& selection : selected)
|
||||||
|
if (last_entry)
|
||||||
|
{
|
||||||
|
if (last_entry.value().index() != eEntry_Object)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto obj = std::get<selected_object_type>(last_entry.value());
|
||||||
|
|
||||||
|
if (obj->which() == eWMO)
|
||||||
|
{
|
||||||
|
_wmo_group->setDisabled(false);
|
||||||
|
_wmo_group->setHidden(false);
|
||||||
|
WMOInstance* wi = static_cast<WMOInstance*>(obj);
|
||||||
|
wi->render_low_res = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
auto mv_pos = mapView->pos();
|
auto mv_pos = mapView->pos();
|
||||||
auto mv_size = mapView->size();
|
auto mv_size = mapView->size();
|
||||||
|
|
||||||
@@ -930,11 +1074,20 @@ namespace Noggit
|
|||||||
return QSize(215, height());
|
return QSize(215, height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void object_editor::update_selection_ui(World* world)
|
void object_editor::update_selection_ui()
|
||||||
{
|
{
|
||||||
_wmo_group->setDisabled(true);
|
_wmo_group->setDisabled(true);
|
||||||
_wmo_group->hide();
|
_wmo_group->hide();
|
||||||
|
|
||||||
|
QSignalBlocker const previewblocker(_previewButton);
|
||||||
|
_previewButton->setChecked(false);
|
||||||
|
_previewButton->setDisabled(true);
|
||||||
|
|
||||||
|
auto world = _map_view->getWorld();
|
||||||
|
|
||||||
|
if (world->has_multiple_model_selected())
|
||||||
|
return;
|
||||||
|
|
||||||
auto last_entry = world->get_last_selected_model();
|
auto last_entry = world->get_last_selected_model();
|
||||||
// for (auto& selection : selected)
|
// for (auto& selection : selected)
|
||||||
if (last_entry)
|
if (last_entry)
|
||||||
@@ -985,6 +1138,18 @@ namespace Noggit
|
|||||||
}
|
}
|
||||||
_nameSetSelector->insertItems(0, namesetnames);
|
_nameSetSelector->insertItems(0, namesetnames);
|
||||||
_nameSetSelector->setCurrentIndex(wi->mNameset);
|
_nameSetSelector->setCurrentIndex(wi->mNameset);
|
||||||
|
|
||||||
|
auto lowresmodel = wi->lowResWmo;
|
||||||
|
if (lowresmodel.has_value())
|
||||||
|
{
|
||||||
|
_horizonModel->setText(lowresmodel.value()->get()->file_key().filepath().c_str());
|
||||||
|
_previewButton->setDisabled(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_horizonModel->setText("");
|
||||||
|
|
||||||
|
if (wi->render_low_res)
|
||||||
|
_previewButton->setChecked(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace Noggit
|
|||||||
helper_models* helper_models_widget;
|
helper_models* helper_models_widget;
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
void update_selection_ui(World* world);
|
void update_selection_ui();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void objectPaletteBtnPressed();
|
void objectPaletteBtnPressed();
|
||||||
@@ -102,6 +102,8 @@ namespace Noggit
|
|||||||
QGroupBox* _wmo_group;
|
QGroupBox* _wmo_group;
|
||||||
QComboBox* _doodadSetSelector;
|
QComboBox* _doodadSetSelector;
|
||||||
QComboBox* _nameSetSelector;
|
QComboBox* _nameSetSelector;
|
||||||
|
QLineEdit* _horizonModel;
|
||||||
|
QToolButton* _previewButton;
|
||||||
|
|
||||||
QSettings* _settings;
|
QSettings* _settings;
|
||||||
|
|
||||||
|
|||||||
@@ -222,13 +222,17 @@ namespace Noggit
|
|||||||
//! \todo Get actual color from sky.
|
//! \todo Get actual color from sky.
|
||||||
//! \todo Get actual radius.
|
//! \todo Get actual radius.
|
||||||
//! \todo Inner and outer radius?
|
//! \todo Inner and outer radius?
|
||||||
painter.setPen (Qt::blue);
|
// painter.setPen (Qt::blue);
|
||||||
|
auto sky_noon_color = sky.colorFor(LIGHT_GLOBAL_DIFFUSE, 1441);
|
||||||
|
auto color = QColor::fromRgbF(sky_noon_color.r, sky_noon_color.g, sky_noon_color.b);
|
||||||
|
painter.setPen(color);
|
||||||
|
auto radius = sky.r2 * scale_factor;
|
||||||
|
|
||||||
painter.drawEllipse ( QPointF ( sky.pos.x * scale_factor
|
painter.drawEllipse ( QPointF ( sky.pos.x * scale_factor
|
||||||
, sky.pos.z * scale_factor
|
, sky.pos.z * scale_factor
|
||||||
)
|
)
|
||||||
, 10.0 // radius
|
, radius
|
||||||
, 10.0
|
, radius
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ void PreviewRenderer::draw()
|
|||||||
wmo_program, model_view(), projection(), frustum, culldistance,
|
wmo_program, model_view(), projection(), frustum, culldistance,
|
||||||
_camera.position, _draw_boxes.get(), _draw_models.get()
|
_camera.position, _draw_boxes.get(), _draw_models.get()
|
||||||
, false, false, 0, false, display_mode::in_3D
|
, false, false, 0, false, display_mode::in_3D
|
||||||
, true, true, false, false
|
, true, true, false, false, false
|
||||||
);
|
);
|
||||||
|
|
||||||
auto doodads = wmo_instance.get_doodads(true);
|
auto doodads = wmo_instance.get_doodads(true);
|
||||||
@@ -450,10 +450,10 @@ QPixmap* PreviewRenderer::renderToPixmap()
|
|||||||
if (async_loader->is_loading())
|
if (async_loader->is_loading())
|
||||||
{
|
{
|
||||||
// wait for the loader to finish
|
// wait for the loader to finish
|
||||||
// do
|
do
|
||||||
// {
|
{
|
||||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
// } while (async_loader->is_loading());
|
} while (async_loader->is_loading());
|
||||||
|
|
||||||
// redraw
|
// redraw
|
||||||
gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
@@ -694,6 +694,7 @@ void PreviewRenderer::unloadOpenglData()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(context() != nullptr);
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
OpenGL::context::scoped_setter const _ (::gl, context());
|
OpenGL::context::scoped_setter const _ (::gl, context());
|
||||||
|
|
||||||
|
|||||||
@@ -144,6 +144,11 @@ void ViewportGizmo::handleTransformGizmo(MapView* map_view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clamp scale to allowed values
|
||||||
|
new_scale.x = std::clamp(new_scale.x, SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
|
new_scale.y = std::clamp(new_scale.y, SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
|
new_scale.z = std::clamp(new_scale.z, SceneObject::min_scale(), SceneObject::max_scale());
|
||||||
|
|
||||||
NOGGIT_ACTION_MGR->beginAction(map_view, Noggit::ActionFlags::eOBJECTS_TRANSFORMED,
|
NOGGIT_ACTION_MGR->beginAction(map_view, Noggit::ActionFlags::eOBJECTS_TRANSFORMED,
|
||||||
Noggit::ActionModalityControllers::eLMB);
|
Noggit::ActionModalityControllers::eLMB);
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace Noggit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the uid is already used for another model/wmo, use a new one
|
// the uid is already used for another model/wmo, use a new one
|
||||||
|
LogDebug << "UID " << uid << " is already in use (" << instance.model->file_key().stringRepr() << ")" << std::endl;
|
||||||
_uid_duplicates_found = true;
|
_uid_duplicates_found = true;
|
||||||
instance.uid = _world->mapIndex.newGUID();
|
instance.uid = _world->mapIndex.newGUID();
|
||||||
|
|
||||||
@@ -74,7 +75,15 @@ namespace Noggit
|
|||||||
|
|
||||||
if (from_reloading || uid_after != uid)
|
if (from_reloading || uid_after != uid)
|
||||||
{
|
{
|
||||||
_world->updateTilesWMO(&_wmos.at(uid_after), model_update::add);
|
WMOInstance* wmo_instance = &_wmos.at(uid_after);
|
||||||
|
_world->updateTilesWMO(wmo_instance, model_update::add);
|
||||||
|
|
||||||
|
// update WDL uid if it changed
|
||||||
|
[[unlikely]]
|
||||||
|
if (wmo_instance->lowResWmo.has_value())
|
||||||
|
{
|
||||||
|
wmo_instance->lowResInstance->uniqueID = uid_after;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return uid_after;
|
return uid_after;
|
||||||
@@ -104,6 +113,7 @@ namespace Noggit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the uid is already used for another model/wmo, use a new one
|
// the uid is already used for another model/wmo, use a new one
|
||||||
|
LogDebug << "UID " << uid << " is already in use (" << instance.wmo->file_key().stringRepr() << ")" << std::endl;
|
||||||
_uid_duplicates_found = true;
|
_uid_duplicates_found = true;
|
||||||
instance.uid = _world->mapIndex.newGUID();
|
instance.uid = _world->mapIndex.newGUID();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user