From 40d513fcc2bf1a9fcc90137bdd5fd59883d740a0 Mon Sep 17 00:00:00 2001 From: T1ti <40864460+T1ti@users.noreply.github.com> Date: Sun, 8 Jun 2025 23:49:22 +0200 Subject: [PATCH] old local changes, mainly low-res wdl WMO support and some optimizations --- src/math/ray.cpp | 44 +++-- src/math/ray.hpp | 14 +- src/noggit/AsyncLoader.cpp | 12 +- src/noggit/MapChunk.h | 2 +- src/noggit/MapView.cpp | 4 + src/noggit/Model.cpp | 27 ++- src/noggit/Model.h | 10 +- src/noggit/ModelInstance.cpp | 4 +- src/noggit/ModelInstance.h | 5 +- src/noggit/SceneObject.hpp | 11 +- src/noggit/WMO.cpp | 17 +- src/noggit/WMO.h | 4 +- src/noggit/WMOInstance.cpp | 88 ++++++--- src/noggit/WMOInstance.h | 24 ++- src/noggit/World.cpp | 154 ++++++++++++---- src/noggit/World.h | 22 ++- src/noggit/map_horizon.cpp | 173 +++++++++++++++--- src/noggit/map_horizon.h | 15 +- src/noggit/rendering/WorldRender.cpp | 103 ++++++++++- src/noggit/tools/ObjectTool.cpp | 2 +- src/noggit/ui/ObjectEditor.cpp | 173 +++++++++++++++++- src/noggit/ui/ObjectEditor.h | 4 +- src/noggit/ui/minimap_widget.cpp | 12 +- .../tools/PreviewRenderer/PreviewRenderer.cpp | 11 +- .../ui/tools/ViewportGizmo/ViewportGizmo.cpp | 5 + src/noggit/world_model_instances_storage.cpp | 12 +- 26 files changed, 795 insertions(+), 157 deletions(-) diff --git a/src/math/ray.cpp b/src/math/ray.cpp index dbd6199d..8cd3611e 100755 --- a/src/math/ray.cpp +++ b/src/math/ray.cpp @@ -13,18 +13,40 @@ namespace math float tmin (std::numeric_limits::lowest()); float tmax (std::numeric_limits::max()); - auto calculate_tmin_tmax = [](float origin, float direction, float min, float max, float& tmin, float& tmax) { - if (direction != 0.0f) { - float t1 = (min - origin) / direction; - float t2 = (max - origin) / direction; - tmin = std::max(tmin, std::min(t1, t2)); - tmax = std::min(tmax, std::max(t1, t2)); - } - }; + if (_direction.x != 0.0f) + { + float tx1 = (min.x - _origin.x) * _inverted_direction.x; + float tx2 = (max.x - _origin.x) * _inverted_direction.x; + // float const tx1((min.x - _origin.x) / _direction.x); + // float const tx2((max.x - _origin.x) / _direction.x); - calculate_tmin_tmax(_origin.x, _direction.x, min.x, max.x, tmin, tmax); - calculate_tmin_tmax(_origin.y, _direction.y, min.y, max.y, tmin, tmax); - calculate_tmin_tmax(_origin.z, _direction.z, min.z, max.z, tmin, tmax); + tmin = std::max(tmin, std::min(tx1, tx2)); + tmax = std::min(tmax, std::max(tx1, tx2)); + } + + 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) { diff --git a/src/math/ray.hpp b/src/math/ray.hpp index 92919987..e2649f9b 100755 --- a/src/math/ray.hpp +++ b/src/math/ray.hpp @@ -16,6 +16,10 @@ namespace math { assert(false); } + // pre compute ivnerted direction + _inverted_direction.x = (_direction.x != 0.0f) ? 1.0f / _direction.x : std::numeric_limits::max(); + _inverted_direction.y = (_direction.y != 0.0f) ? 1.0f / _direction.y : std::numeric_limits::max(); + _inverted_direction.z = (_direction.z != 0.0f) ? 1.0f / _direction.z : std::numeric_limits::max(); } ray (glm::mat4x4 const& transform, ray const& other) @@ -35,8 +39,14 @@ namespace math return _origin + _direction * distance; } + glm::vec3 const origin() const + { + return _origin; + } + private: - glm::vec3 _origin; - glm::vec3 _direction; + glm::vec3 const _origin; + glm::vec3 const _direction; + glm::vec3 _inverted_direction; }; } diff --git a/src/noggit/AsyncLoader.cpp b/src/noggit/AsyncLoader.cpp index 80a5691e..044a9762 100755 --- a/src/noggit/AsyncLoader.cpp +++ b/src/noggit/AsyncLoader.cpp @@ -89,17 +89,18 @@ void AsyncLoader::process() _state_changed.notify_all(); } } - catch (BlizzardArchive::Exceptions::FileReadFailedError const&) + catch (BlizzardArchive::Exceptions::FileReadFailedError const& e) { std::lock_guard const lock(_guard); - + object->error_on_loading(); - + // LogError << e.what() << std::endl; + if (object->is_required_when_saving()) { _important_object_failed_loading = true; } - + _currently_loading.remove(object); } catch (...) @@ -107,7 +108,8 @@ void AsyncLoader::process() std::lock_guard const lock(_guard); 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()) { diff --git a/src/noggit/MapChunk.h b/src/noggit/MapChunk.h index 49e156b7..e490bfc7 100755 --- a/src/noggit/MapChunk.h +++ b/src/noggit/MapChunk.h @@ -78,7 +78,7 @@ public: // MapChunkHeader header; // uint32_t nLayers = 0; - float xbase, ybase, zbase; // global coords + float xbase, ybase, zbase; // global coords. height = ybase mcnk_flags header_flags; bool use_big_alphamap; diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index f14174fb..c6b72e29 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -3311,6 +3311,10 @@ selection_result MapView::intersect_result(bool terrain_only) , _draw_hidden_models.get() , _draw_wmo_exterior.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 ) ); diff --git a/src/noggit/Model.cpp b/src/noggit/Model.cpp index 2deaf2b9..c46513f1 100755 --- a/src/noggit/Model.cpp +++ b/src/noggit/Model.cpp @@ -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); } namespace { - glm::vec3 fixCoordSystem2(glm::vec3 v) + inline glm::vec3 fixCoordSystem2(glm::vec3 v) { 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); } @@ -895,7 +895,13 @@ void Bone::calcMatrix(glm::mat4x4 const& model_view } -std::vector>> Model::intersect (glm::mat4x4 const& model_view, math::ray const& ray, int animtime, bool calc_anims) +std::vector>> 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>> results; @@ -934,6 +940,12 @@ std::vector>> Model::intersect (glm:: bool const has_transformed_verts = !transformed_verts.empty(); 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) { std::optional distance; @@ -950,13 +962,12 @@ std::vector>> Model::intersect (glm:: transformed_verts[_indices[static_cast(i + 2)]].position); } - glm::vec3 debug_vert1 = _vertices[_indices[static_cast(i + 0)]].position; //// - glm::vec3 debug_vert2 = _vertices[_indices[static_cast(i + 1)]].position; //// - glm::vec3 debug_vert3 = _vertices[_indices[static_cast(i + 2)]].position; //// - if (distance) { results.emplace_back (*distance, std::make_tuple(i, i + 1, 1 + 2)); + + if (first_occurence) + return results; } } } diff --git a/src/noggit/Model.h b/src/noggit/Model.h index 9f8844eb..9f565bd4 100755 --- a/src/noggit/Model.h +++ b/src/noggit/Model.h @@ -52,7 +52,7 @@ enum M2GlobalFlags // TODO : MOP + }; -glm::vec3 fixCoordSystem(glm::vec3 v); +inline glm::vec3 fixCoordSystem(glm::vec3 v); class Bone { Animation::M2Value trans; @@ -159,7 +159,13 @@ public: Model(const std::string& name, Noggit::NoggitRenderContext context ); - std::vector>> intersect (glm::mat4x4 const& model_view, math::ray const&, int animtime, bool calc_anims); + std::vector>> 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); diff --git a/src/noggit/ModelInstance.cpp b/src/noggit/ModelInstance.cpp index 833c4fdb..d800cf4f 100755 --- a/src/noggit/ModelInstance.cpp +++ b/src/noggit/ModelInstance.cpp @@ -144,6 +144,8 @@ void ModelInstance::intersect (glm::mat4x4 const& model_view , selection_result* results , int animtime , bool animate + , bool first_occurence + , bool only_opaque_tris ) { if (!finishedLoading() || model->loading_failed()) @@ -162,7 +164,7 @@ void ModelInstance::intersect (glm::mat4x4 const& model_view 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, //! so should be inverted by model_matrix? diff --git a/src/noggit/ModelInstance.h b/src/noggit/ModelInstance.h index d9b2266b..674cf284 100755 --- a/src/noggit/ModelInstance.h +++ b/src/noggit/ModelInstance.h @@ -22,9 +22,6 @@ class WMOInstance; class ModelInstance : public SceneObject { public: - constexpr static float min_scale() { return 1.f / 1024.f; }; - constexpr static float max_scale() { return static_cast((1 << 16) - 1) / 1024.f; }; - scoped_model_reference model; glm::vec3 light_color = { 1.f, 1.f, 1.f }; @@ -89,6 +86,8 @@ public: , selection_result* , int animtime , bool animate + , bool first_occurence + , bool only_opaque_tris ); bool isInFrustum(math::frustum const& frustum); diff --git a/src/noggit/SceneObject.hpp b/src/noggit/SceneObject.hpp index d7a08908..4c3cdb0f 100755 --- a/src/noggit/SceneObject.hpp +++ b/src/noggit/SceneObject.hpp @@ -30,6 +30,9 @@ class MapTile; class SceneObject : public Selectable { public: + constexpr static float min_scale() { return 1.f / 1024.f; }; + constexpr static float max_scale() { return static_cast((1 << 16) - 1) / 1024.f; }; + SceneObject(SceneObjectTypes type, Noggit::NoggitRenderContext context); [[nodiscard]] @@ -79,25 +82,25 @@ public: glm::vec3 const getServerPos() { return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); } - bool _grouped = false; - public: glm::vec3 pos; glm::vec3 dir; - float scale = 1.f; + float scale = 1.f; // Note : max scale is uint16 max / 1024 = 63.999 unsigned int uid; int frame; // Note : First, need to check if the tile that contained it was rendered too bool _rendered_last_frame = false; + bool _grouped = false; + protected: SceneObjectTypes _type; glm::mat4x4 _transform_mat = glm::mat4x4(); glm::mat4x4 _transform_mat_inverted = glm::mat4x4(); - std::array extents; // axis aligned bounding box mni and max corners + std::array extents; // axis aligned bounding box min and max corners float bounding_radius; Noggit::NoggitRenderContext _context; diff --git a/src/noggit/WMO.cpp b/src/noggit/WMO.cpp index 46af4ba7..363fa6a0 100755 --- a/src/noggit/WMO.cpp +++ b/src/noggit/WMO.cpp @@ -355,7 +355,7 @@ void WMO::waitForChildrenLoaded() } } -std::vector WMO::intersect (math::ray const& ray, bool do_exterior) const +std::vector WMO::intersect (math::ray const& ray, bool do_exterior, bool do_interior, bool first_occurence) const { std::vector results; @@ -367,14 +367,17 @@ std::vector WMO::intersect (math::ray const& ray, bool do_exterior) const for (auto& group : groups) { 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()) { - // 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)); // results.clear(); // 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* results) const +void WMOGroup::intersect (math::ray const& ray, std::vector* results, bool first_occurence) const { if (!ray.intersect_bounds (VertexBoxMin, VertexBoxMax)) { @@ -1126,7 +1129,7 @@ void WMOGroup::intersect (math::ray const& ray, std::vector* results) con //! \todo Also allow clicking on doodads and liquids. 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 : option to only check collision @@ -1138,6 +1141,8 @@ void WMOGroup::intersect (math::ray const& ray, std::vector* results) con ) { results->emplace_back (*distance); + if (first_occurence) + return; } } } diff --git a/src/noggit/WMO.h b/src/noggit/WMO.h index eb72a003..9a67b1e8 100755 --- a/src/noggit/WMO.h +++ b/src/noggit/WMO.h @@ -182,7 +182,7 @@ public: void setupFog (bool draw_fog, std::function setup_fog); - void intersect (math::ray const&, std::vector* results) const; + void intersect (math::ray const&, std::vector* results, bool first_occurence) const; // todo: portal culling [[nodiscard]] @@ -310,7 +310,7 @@ public: explicit WMO(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context ); [[nodiscard]] - std::vector intersect (math::ray const&, bool do_exterior = true) const; + std::vector intersect (math::ray const&, bool do_exterior = true, bool do_interior = true, bool first_occurence = false) const; void finishLoading() override; diff --git a/src/noggit/WMOInstance.cpp b/src/noggit/WMOInstance.cpp index b9aa11c2..163f1428 100755 --- a/src/noggit/WMOInstance.cpp +++ b/src/noggit/WMOInstance.cpp @@ -37,8 +37,8 @@ WMOInstance::WMOInstance(BlizzardArchive::Listfile::FileKey const& file_key, ENT scale = 1.0f; } - extents[0] = glm::vec3(d->extents[0][0], d->extents[0][1], d->extents[0][2]); - extents[1] = glm::vec3(d->extents[1][0], d->extents[1][1], d->extents[1][2]); + extents[0] = d->extents[0]; + extents[1] = d->extents[1]; _need_recalc_extents = true; updateTransformMatrix(); @@ -80,6 +80,7 @@ void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader , bool draw_exterior , bool render_selection_aabb , bool render_group_bounds + , bool /*render_lowres*/ ) { 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->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 - ); + // render WDL model + [[unlikely]] + if (render_low_res && lowResWmo.has_value() + && lowResWmo.value()->get()->finishedLoading() && !lowResWmo.value()->get()->loading_failed()) + { + lowResWmo.value()->get()->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 + ); + } + 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) @@ -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()) return; @@ -164,7 +192,7 @@ void WMOInstance::intersect (math::ray const& ray, selection_result* results, bo 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); } @@ -303,6 +331,24 @@ void WMOInstance::recalcExtents() 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; } diff --git a/src/noggit/WMOInstance.h b/src/noggit/WMOInstance.h index ab32d9fe..1d308f6a 100755 --- a/src/noggit/WMOInstance.h +++ b/src/noggit/WMOInstance.h @@ -16,9 +16,16 @@ public: scoped_wmo_reference wmo; 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 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); [[nodiscard]] @@ -51,7 +58,12 @@ public: , _doodadset (other._doodadset) , _doodads_per_group(other._doodads_per_group) , _need_doodadset_update(other._need_doodadset_update) + , _update_group_extents(other._update_group_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); pos = other.pos; @@ -82,6 +94,11 @@ public: std::swap(_transform_mat_inverted, other._transform_mat_inverted); std::swap(_context, other._context); 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; } @@ -102,9 +119,10 @@ public: , bool draw_exterior , bool render_selection_aabb , 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 const& getExtents() override; // axis aligned std::array const& getLocalExtents() const; diff --git a/src/noggit/World.cpp b/src/noggit/World.cpp index 9b0dee2c..b59e81db 100644 --- a/src/noggit/World.cpp +++ b/src/noggit/World.cpp @@ -108,7 +108,7 @@ World::World(const std::string& name, int map_id, Noggit::NoggitRenderContext co , _model_instance_storage(this) , _tile_update_queue(this) , mapIndex(name, map_id, this, context, create_empty) - , horizon(name, &mapIndex) + , horizon(name, this) , mWmoFilename(mapIndex.globalWMOName) , mWmoEntry(mapIndex.wmoEntry) , animtime(0) @@ -120,6 +120,17 @@ World::World(const std::string& name, int map_id, Noggit::NoggitRenderContext co { LogDebug << "Loading world \"" << name << "\"." << std::endl; _loaded_tiles_buffer[0] = std::make_pair, 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() @@ -778,6 +789,8 @@ void World::scale_selected_models(float v, object_scaling_type type) if (!_selected_model_count) return; + v = std::clamp(v, SceneObject::min_scale(), SceneObject::max_scale()); + bool modern_features = Noggit::Application::NoggitApplication::instance()->getConfiguration()->modern_features; for (auto& entry : _current_selection) @@ -1131,14 +1144,18 @@ bool World::isInIndoorWmoGroup(std::array obj_bounds, glm::mat4x4 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 - , bool draw_wmo_exterior - , bool animate + , const bool pOnlyMap + , const bool do_objects + , const bool draw_terrain + , const bool draw_wmo + , const bool draw_models + , const bool draw_hidden_models + , const bool draw_wmo_exterior + , 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()"); @@ -1172,28 +1189,82 @@ selection_result World::intersect (glm::mat4x4 const& model_view 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, animate); - } - }); - } + if (!draw_models && !draw_wmo) + return std::move(results); - 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 modelInstances; + // std::unordered_set wmoInstances; + + for (auto& pair : _loaded_tiles_buffer) { - ZoneScopedN("World::intersect() : intersect WMOs"); - _model_instance_storage.for_each_wmo_instance([&] (WMOInstance& wmo_instance) + MapTile* tile = pair.second; + + if (!tile) + break; + + TileIndex index{ static_cast(pair.first.first) + , static_cast(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(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(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) { 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); + + // if (haslowres) + // { + // const auto obj = get_model(uid_after); + // assert(obj); + // if (obj) + // { + // WMOInstance* instance = static_cast(std::get(obj.value())); + // + // int breakpoint = 0; + // } + // } + // + // return uid_after; } std::optional 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) { ZoneScoped; @@ -3735,6 +3826,7 @@ void World::select_objects_in_area( glm::mat4 VPmatrix = projection * view; glm::mat4x4 const invertedProjViewMatrix = glm::inverse(VPmatrix); + auto const transposed_view = glm::transpose(view); 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 @@ -3864,7 +3956,7 @@ void World::select_objects_in_area( // processed_obj_count++; // 3: check if center point is occluded by terrain 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 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 // 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()))) { // if not occluded success! select it and skip other checks @@ -4061,7 +4153,7 @@ void World::select_objects_in_area( continue; bool corner_occluded = is_point_occluded_by_terrain(corner - , view + , transposed_view , VPmatrix , viewport_width , viewport_height @@ -4095,7 +4187,7 @@ void World::select_objects_in_area( bool World::is_point_occluded_by_terrain(const glm::vec3& point, - const glm::mat4x4& view, + const glm::mat4x4& transposed_view, const glm::mat4& VPmatrix, float viewport_width, 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 selection_result terrain_intersect_results (intersect - (glm::transpose(view) + (transposed_view , ray , true , false diff --git a/src/noggit/World.h b/src/noggit/World.h index 2804ba55..ae951970 100644 --- a/src/noggit/World.h +++ b/src/noggit/World.h @@ -101,14 +101,18 @@ public: selection_result intersect (glm::mat4x4 const& model_view , math::ray const& - , bool only_map - , bool do_objects - , bool draw_terrain - , bool draw_wmo - , bool draw_models - , bool draw_hidden_models - , bool draw_wmo_exterior - , bool animate + , const bool only_map + , const bool do_objects + , const bool draw_terrain + , const bool draw_wmo + , const bool draw_models + , const bool draw_hidden_models + , const bool draw_wmo_exterior + , 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); @@ -143,6 +147,7 @@ public: void remove_from_selection(std::uint32_t uid, bool skip_group = false, bool update_pivot = true); void reset_selection(); void delete_selected_models(); + // note : height is Y axis. glm::vec3 get_ground_height(glm::vec3 pos); void range_add_to_selection(glm::vec3 const& pos, float radius, bool remove); 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 setBaseTexture(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 swapTexture(glm::vec3 const& pos, scoped_blp_texture_reference tex); void swapTextureGlobal(scoped_blp_texture_reference tex); diff --git a/src/noggit/map_horizon.cpp b/src/noggit/map_horizon.cpp index 4a085687..58b025a2 100755 --- a/src/noggit/map_horizon.cpp +++ b/src/noggit/map_horizon.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -92,7 +93,7 @@ static inline uint32_t color_for_height (int16_t height) 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; filename << "World\\Maps\\" << basename << "\\" << basename << ".wdl"; @@ -126,10 +127,12 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind break; } - // todo: handle those too ? + case 'MWMO': { { + // TODO : use WMID instead for proper string parsing. + char const* lCurPos = reinterpret_cast(wdl_file.getPointer()); char const* lEnd = lCurPos + size; @@ -143,21 +146,21 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind break; } case 'MWID': - wdl_file.seekRelative(size); - break; // TODO + wdl_file.seekRelative(size); // jump to end of chunk + break; case 'MODF': { - wdl_file.seekRelative(size); + ENTRY_MODF const* modf_ptr = reinterpret_cast(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; - // { - // ENTRY_MODF const* modf_ptr = reinterpret_cast(wdl_file.getPointer()); - // for (unsigned int i = 0; i < size / sizeof(ENTRY_MODF); ++i) - // { - // lWMOInstances.push_back(modf_ptr[i]); - // } - // } - // break; } case 'MAOF': { @@ -212,9 +215,27 @@ map_horizon::map_horizon(const std::string& basename, const MapIndex * const ind } } 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(); - set_minimap(index); + set_minimap(&world->mapIndex); } 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; // } + // WMO objects export code is copy pasta from MapTile + + struct filenameOffsetThing + { + int nameID; + int filenamePosition; + }; + + filenameOffsetThing nullyThing = { 0, 0 }; + + std::map 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 // { + int lMWMO_Position = curPos; wdlFile.Extend(8); SetChunkHeader(wdlFile, curPos, 'MWMO', 0); + curPos += 8; + + // MWMO data + for (auto& object : lObjects) + { + object.second.filenamePosition = wdlFile.GetPointer(lMWMO_Position)->mSize; + wdlFile.Insert(curPos, static_cast(object.first.size() + 1), misc::normalize_adt_filename(object.first).c_str()); + curPos += static_cast(object.first.size() + 1); + wdlFile.GetPointer(lMWMO_Position)->mSize += static_cast(object.first.size() + 1); + LogDebug << "Added WDL object \"" << object.first << "\"." << std::endl; + } // } // MWID // { - wdlFile.Extend(8); - SetChunkHeader(wdlFile, curPos, 'MWID', 0); - curPos += 8; + int lMWID_Size = static_cast(4 * lObjects.size()); + wdlFile.Extend(8 + lMWID_Size); + SetChunkHeader(wdlFile, curPos, 'MWID', lMWID_Size); + + // MWID data + auto const lMWID_Data = wdlFile.GetPointer(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); - SetChunkHeader(wdlFile, curPos, 'MODF', 0); - curPos += 8; + int lMODF_Size = static_cast(0x40 * lWMOInstances.size()); + wdlFile.Extend(8 + lMODF_Size); + SetChunkHeader(wdlFile, curPos, 'MODF', lMODF_Size); + + // MODF data + auto const lMODF_Data = wdlFile.GetPointer(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 }; @@ -482,6 +573,46 @@ void map_horizon::save_wdl(World* world, bool regenerate) 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) { std::vector texture(1024 * 1024); diff --git a/src/noggit/map_horizon.h b/src/noggit/map_horizon.h index d5ca65f2..8ff2d5a0 100755 --- a/src/noggit/map_horizon.h +++ b/src/noggit/map_horizon.h @@ -78,7 +78,7 @@ public: 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); @@ -94,14 +94,21 @@ public: 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 mWMOFilenames; + std::vector lWMOInstances; + + std::vector wmos; + private: int16_t getWdlheight(MapTile* tile, float x, float y); std::string _filename; - std::vector mWMOFilenames; - // std::vector lWMOInstances; - std::unique_ptr _tiles[64][64]; }; diff --git a/src/noggit/rendering/WorldRender.cpp b/src/noggit/rendering/WorldRender.cpp index 46c82ebc..60e8083e 100755 --- a/src/noggit/rendering/WorldRender.cpp +++ b/src/noggit/rendering/WorldRender.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include #include @@ -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 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); if (global_wmo.has_value()) @@ -627,7 +630,91 @@ void WorldRender::draw (glm::mat4x4 const& model_view 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) { @@ -695,7 +782,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view , render_settings.draw_wmo_exterior , render_settings.render_select_wmo_aabb , 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 - glCullFace(GL_FRONT); + gl.cullFace(GL_FRONT); if (!render_settings.draw_only_inside_light_sphere) { 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) - glCullFace(GL_BACK); + gl.cullFace(GL_BACK); for (Sky const& sky : skies()->skies) { if (sky.global) @@ -1994,7 +2081,7 @@ bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings* , 0.f, 0.f, 0.f, 1.f )); - glFinish(); + gl.finish(); drawMinimap(mTile , look_at @@ -2064,13 +2151,13 @@ bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings* if (use_md5) { QCryptographicHash md5_hash(QCryptographicHash::Md5); - // auto data = reinterpret_cast(blp_image); - md5_hash.addData(reinterpret_cast(blp_image), file_size); + // md5_hash.addData(reinterpret_cast(blp_image), file_size); + QByteArray data(reinterpret_cast(blp_image), file_size); + md5_hash.addData(data); auto resulthex = md5_hash.result().toHex().toStdString() + ".blp"; tex_name = resulthex; } - QFile file(dir.filePath(tex_name.c_str())); file.open(QIODevice::WriteOnly); diff --git a/src/noggit/tools/ObjectTool.cpp b/src/noggit/tools/ObjectTool.cpp index c023aee1..050bf7e2 100644 --- a/src/noggit/tools/ObjectTool.cpp +++ b/src/noggit/tools/ObjectTool.cpp @@ -118,7 +118,7 @@ namespace Noggit }); QObject::connect(mapView(), &MapView::selectionUpdated, [=](auto) { - _objectEditor->update_selection_ui(mapView()->getWorld()); + _objectEditor->update_selection_ui(); }); using AssetBrowser = Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget; diff --git a/src/noggit/ui/ObjectEditor.cpp b/src/noggit/ui/ObjectEditor.cpp index 17b54442..eff82ca7 100644 --- a/src/noggit/ui/ObjectEditor.cpp +++ b/src/noggit/ui/ObjectEditor.cpp @@ -112,10 +112,38 @@ namespace Noggit _doodadSetSelector = 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); wmo_layout->addRow("Doodad Set:", _doodadSetSelector); wmo_layout->addRow("Name Set:", _nameSetSelector); + wmo_layout->addRow(horizon_label); + wmo_layout->addRow(horizon_layout); auto clipboard_box = new QGroupBox("Clipboard"); // clipboard_box->setWindowIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::clipboard)); @@ -178,8 +206,8 @@ namespace Noggit rotRangeEnd->setRange (-180.f, 180.f); tiltRangeStart->setRange (-180.f, 180.f); tiltRangeEnd->setRange (-180.f, 180.f); - scaleRangeStart->setRange (-180.f, 180.f); - scaleRangeEnd->setRange (-180.f, 180.f); + scaleRangeStart->setRange (SceneObject::min_scale(), SceneObject::max_scale()); + scaleRangeEnd->setRange (SceneObject::min_scale(), SceneObject::max_scale()); rotation_layout->addWidget(rotRangeStart, 0, 0); rotation_layout->addWidget(rotRangeEnd, 0 ,1); @@ -538,7 +566,11 @@ namespace Noggit , &QPushButton::clicked , [=]() { _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(); }); + 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(last_entry.value()); + + if (obj->which() == eWMO) + { + WMOInstance* wi = static_cast(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(last_entry.value()); + + if (obj->which() == eWMO) + { + _wmo_group->setDisabled(false); + _wmo_group->setHidden(false); + WMOInstance* wi = static_cast(obj); + wi->render_low_res = checked; + } + } + } + ); + auto mv_pos = mapView->pos(); auto mv_size = mapView->size(); @@ -930,11 +1074,20 @@ namespace Noggit return QSize(215, height()); } - void object_editor::update_selection_ui(World* world) + void object_editor::update_selection_ui() { _wmo_group->setDisabled(true); _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(); // for (auto& selection : selected) if (last_entry) @@ -985,6 +1138,18 @@ namespace Noggit } _nameSetSelector->insertItems(0, namesetnames); _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); } } } diff --git a/src/noggit/ui/ObjectEditor.h b/src/noggit/ui/ObjectEditor.h index 8e594001..535ca910 100755 --- a/src/noggit/ui/ObjectEditor.h +++ b/src/noggit/ui/ObjectEditor.h @@ -83,7 +83,7 @@ namespace Noggit helper_models* helper_models_widget; QSize sizeHint() const override; - void update_selection_ui(World* world); + void update_selection_ui(); signals: void objectPaletteBtnPressed(); @@ -102,6 +102,8 @@ namespace Noggit QGroupBox* _wmo_group; QComboBox* _doodadSetSelector; QComboBox* _nameSetSelector; + QLineEdit* _horizonModel; + QToolButton* _previewButton; QSettings* _settings; diff --git a/src/noggit/ui/minimap_widget.cpp b/src/noggit/ui/minimap_widget.cpp index 1628c1cc..d6a042b4 100755 --- a/src/noggit/ui/minimap_widget.cpp +++ b/src/noggit/ui/minimap_widget.cpp @@ -142,7 +142,7 @@ namespace Noggit ); - + if (world()) { painter.drawImage (drawing_rect, world()->horizon._qt_minimap); @@ -222,13 +222,17 @@ namespace Noggit //! \todo Get actual color from sky. //! \todo Get actual 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 , sky.pos.z * scale_factor ) - , 10.0 // radius - , 10.0 + , radius + , radius ); } } diff --git a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp index 5de78446..bb777fb8 100755 --- a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp +++ b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp @@ -192,7 +192,7 @@ void PreviewRenderer::draw() wmo_program, model_view(), projection(), frustum, culldistance, _camera.position, _draw_boxes.get(), _draw_models.get() , false, false, 0, false, display_mode::in_3D - , true, true, false, false + , true, true, false, false, false ); auto doodads = wmo_instance.get_doodads(true); @@ -450,10 +450,10 @@ QPixmap* PreviewRenderer::renderToPixmap() if (async_loader->is_loading()) { // wait for the loader to finish - // do - // { - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // } while (async_loader->is_loading()); + do + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (async_loader->is_loading()); // redraw gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -694,6 +694,7 @@ void PreviewRenderer::unloadOpenglData() return; } + assert(context() != nullptr); makeCurrent(); OpenGL::context::scoped_setter const _ (::gl, context()); diff --git a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp index d1fedc3d..dcb82082 100755 --- a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp +++ b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp @@ -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::ActionModalityControllers::eLMB); diff --git a/src/noggit/world_model_instances_storage.cpp b/src/noggit/world_model_instances_storage.cpp index d86ee618..ecf93ead 100755 --- a/src/noggit/world_model_instances_storage.cpp +++ b/src/noggit/world_model_instances_storage.cpp @@ -55,6 +55,7 @@ namespace Noggit } // 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; instance.uid = _world->mapIndex.newGUID(); @@ -74,7 +75,15 @@ namespace Noggit 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; @@ -104,6 +113,7 @@ namespace Noggit } // 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; instance.uid = _world->mapIndex.newGUID();