1760 lines
56 KiB
C++
Executable File
1760 lines
56 KiB
C++
Executable File
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
|
|
|
|
#include "WorldRender.hpp"
|
|
#include <external/tracy/Tracy.hpp>
|
|
#include <math/frustum.hpp>
|
|
#include <noggit/World.h>
|
|
#include <external/PNG2BLP/Png2Blp.h>
|
|
#include <noggit/DBC.h>
|
|
#include <noggit/project/CurrentProject.hpp>
|
|
|
|
#include <QDir>
|
|
#include <QBuffer>
|
|
|
|
using namespace Noggit::Rendering;
|
|
|
|
WorldRender::WorldRender(World* world)
|
|
: BaseRender()
|
|
, _world(world)
|
|
, _liquid_texture_manager(world->_context)
|
|
, _view_distance(world->_settings->value("view_distance", 1000.f).toFloat())
|
|
, _cull_distance(0.f)
|
|
{
|
|
}
|
|
|
|
void WorldRender::draw (glm::mat4x4 const& model_view
|
|
, glm::mat4x4 const& projection
|
|
, glm::vec3 const& cursor_pos
|
|
, float cursorRotation
|
|
, glm::vec4 const& cursor_color
|
|
, CursorType cursor_type
|
|
, float brush_radius
|
|
, bool show_unpaintable_chunks
|
|
, bool draw_only_inside_light_sphere
|
|
, bool draw_wireframe_light_sphere
|
|
, float alpha_light_sphere
|
|
, float inner_radius_ratio
|
|
, glm::vec3 const& ref_pos
|
|
, float angle
|
|
, float orientation
|
|
, bool use_ref_pos
|
|
, bool angled_mode
|
|
, bool draw_paintability_overlay
|
|
, editing_mode terrainMode
|
|
, glm::vec3 const& camera_pos
|
|
, bool camera_moved
|
|
, bool draw_mfbo
|
|
, bool draw_terrain
|
|
, bool draw_wmo
|
|
, bool draw_water
|
|
, bool draw_wmo_doodads
|
|
, bool draw_models
|
|
, bool draw_model_animations
|
|
, bool draw_models_with_box
|
|
, bool draw_hidden_models
|
|
, MinimapRenderSettings* minimap_render_settings
|
|
, bool draw_fog
|
|
, eTerrainType ground_editing_brush
|
|
, int water_layer
|
|
, display_mode display
|
|
, bool draw_occlusion_boxes
|
|
, bool minimap_render
|
|
, bool draw_wmo_exterior
|
|
)
|
|
{
|
|
|
|
ZoneScoped;
|
|
|
|
glm::mat4x4 const mvp(projection * model_view);
|
|
math::frustum const frustum (mvp);
|
|
|
|
if (camera_moved)
|
|
updateMVPUniformBlock(model_view, projection);
|
|
|
|
gl.disable(GL_DEPTH_TEST);
|
|
|
|
if (!minimap_render)
|
|
updateLightingUniformBlock(draw_fog, camera_pos);
|
|
else
|
|
updateLightingUniformBlockMinimap(minimap_render_settings);
|
|
|
|
// setup render settings for minimap
|
|
if (minimap_render)
|
|
{
|
|
_terrain_params_ubo_data.draw_shadows = minimap_render_settings->draw_shadows;
|
|
_terrain_params_ubo_data.draw_lines = minimap_render_settings->draw_adt_grid;
|
|
_terrain_params_ubo_data.draw_terrain_height_contour = minimap_render_settings->draw_elevation;
|
|
_terrain_params_ubo_data.draw_hole_lines = false;
|
|
_terrain_params_ubo_data.draw_impass_overlay = false;
|
|
_terrain_params_ubo_data.draw_areaid_overlay = false;
|
|
_terrain_params_ubo_data.draw_paintability_overlay = false;
|
|
_terrain_params_ubo_data.draw_selection_overlay = false;
|
|
_terrain_params_ubo_data.draw_wireframe = false;
|
|
_need_terrain_params_ubo_update = true;
|
|
}
|
|
|
|
if (_need_terrain_params_ubo_update)
|
|
updateTerrainParamsUniformBlock();
|
|
|
|
// Frustum culling
|
|
_world->_n_loaded_tiles = 0;
|
|
unsigned tile_counter = 0;
|
|
for (MapTile* tile : _world->mapIndex.loaded_tiles())
|
|
{
|
|
tile->recalcObjectInstanceExtents();
|
|
tile->recalcCombinedExtents();
|
|
|
|
if (minimap_render)
|
|
{
|
|
auto& tile_extents = tile->getCombinedExtents();
|
|
tile->calcCamDist(camera_pos);
|
|
tile->renderer()->setFrustumCulled(false);
|
|
tile->renderer()->setObjectsFrustumCullTest(2);
|
|
tile->renderer()->setOccluded(false);
|
|
_world->_loaded_tiles_buffer[tile_counter] = std::make_pair(std::make_pair(static_cast<int>(tile->index.x), static_cast<int>(tile->index.z)), tile);
|
|
|
|
tile_counter++;
|
|
_world->_n_loaded_tiles++;
|
|
continue;
|
|
}
|
|
|
|
auto& tile_extents = tile->getCombinedExtents();
|
|
if (frustum.intersects(tile_extents[1], tile_extents[0]) || tile->getChunkUpdateFlags())
|
|
{
|
|
tile->calcCamDist(camera_pos);
|
|
_world->_loaded_tiles_buffer[tile_counter] = std::make_pair(std::make_pair(static_cast<int>(tile->index.x), static_cast<int>(tile->index.z)), tile);
|
|
|
|
tile->renderer()->setObjectsFrustumCullTest(1);
|
|
if (frustum.contains(tile_extents[0]) && frustum.contains(tile_extents[1]))
|
|
{
|
|
tile->renderer()->setObjectsFrustumCullTest( tile->renderer()->objectsFrustumCullTest() + 1);
|
|
}
|
|
|
|
if (tile->renderer()->isFrustumCulled())
|
|
{
|
|
tile->renderer()->setOverrideOcclusionCulling(true);
|
|
tile->renderer()->discardTileOcclusionQuery();
|
|
tile->renderer()->setOccluded(false);
|
|
}
|
|
|
|
tile->renderer()->setFrustumCulled(false);
|
|
|
|
tile_counter++;
|
|
}
|
|
else
|
|
{
|
|
tile->renderer()->setFrustumCulled(true);
|
|
tile->renderer()->setObjectsFrustumCullTest(0);
|
|
}
|
|
|
|
_world->_n_loaded_tiles++;
|
|
}
|
|
|
|
auto buf_end = _world->_loaded_tiles_buffer.begin() + tile_counter;
|
|
_world->_loaded_tiles_buffer[tile_counter] = std::make_pair<std::pair<int, int>, MapTile*>(std::make_pair<int, int>(0, 0), nullptr);
|
|
|
|
|
|
// It is always import to sort tiles __front to back__.
|
|
// Otherwise selection would not work. Overdraw overhead is gonna occur as well.
|
|
// TODO: perhaps parallel sort?
|
|
std::sort(_world->_loaded_tiles_buffer.begin(), buf_end,
|
|
[](std::pair<std::pair<int, int>, MapTile*>& a, std::pair<std::pair<int, int>, MapTile*>& b) -> bool
|
|
{
|
|
if (!a.second)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!b.second)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return a.second->camDist() < b.second->camDist();
|
|
});
|
|
|
|
// only draw the sky in 3D
|
|
if(!minimap_render && display == display_mode::in_3D)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw skies");
|
|
OpenGL::Scoped::use_program m2_shader {*_m2_program.get()};
|
|
|
|
bool hadSky = false;
|
|
|
|
if (draw_wmo || _world->mapIndex.hasAGlobalWMO())
|
|
{
|
|
_world->_model_instance_storage.for_each_wmo_instance
|
|
(
|
|
[&] (WMOInstance& wmo)
|
|
{
|
|
if (wmo.wmo->finishedLoading() && wmo.wmo->skybox)
|
|
{
|
|
if (wmo.group_extents.empty())
|
|
{
|
|
wmo.recalcExtents();
|
|
}
|
|
|
|
hadSky = wmo.wmo->renderer()->drawSkybox(model_view
|
|
, camera_pos
|
|
, m2_shader
|
|
, frustum
|
|
, _cull_distance
|
|
, _world->animtime
|
|
, draw_model_animations
|
|
, wmo.extents[0]
|
|
, wmo.extents[1]
|
|
, wmo.group_extents
|
|
);
|
|
}
|
|
|
|
}
|
|
, [&] () { return hadSky; }
|
|
);
|
|
}
|
|
|
|
if (!hadSky)
|
|
{
|
|
_skies->draw( model_view
|
|
, projection
|
|
, camera_pos
|
|
, m2_shader
|
|
, frustum
|
|
, _cull_distance
|
|
, _world->animtime
|
|
, _outdoor_light_stats
|
|
);
|
|
}
|
|
}
|
|
|
|
_cull_distance= draw_fog ? _skies->fog_distance_end() : _view_distance;
|
|
|
|
// Draw verylowres heightmap
|
|
if (!_world->mapIndex.hasAGlobalWMO() && draw_fog && draw_terrain)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw horizon");
|
|
_horizon_render->draw (model_view, projection, &_world->mapIndex, _skies->color_set[FOG_COLOR], _cull_distance, frustum, camera_pos, display);
|
|
}
|
|
|
|
gl.enable(GL_DEPTH_TEST);
|
|
gl.depthFunc(GL_LEQUAL); // less z-fighting artifacts this way, I think
|
|
//gl.disable(GL_BLEND);
|
|
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
//gl.disable(GL_CULL_FACE);
|
|
|
|
_world->_n_rendered_tiles = 0;
|
|
|
|
if (draw_terrain)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw terrain");
|
|
|
|
gl.disable(GL_BLEND);
|
|
|
|
{
|
|
OpenGL::Scoped::use_program mcnk_shader{ *_mcnk_program.get() };
|
|
|
|
mcnk_shader.uniform("camera", glm::vec3(camera_pos.x, camera_pos.y, camera_pos.z));
|
|
mcnk_shader.uniform("animtime", static_cast<int>(_world->animtime));
|
|
|
|
if (cursor_type != CursorType::NONE)
|
|
{
|
|
mcnk_shader.uniform("draw_cursor_circle", static_cast<int>(cursor_type));
|
|
mcnk_shader.uniform("cursor_position", glm::vec3(cursor_pos.x, cursor_pos.y, cursor_pos.z));
|
|
mcnk_shader.uniform("cursorRotation", cursorRotation);
|
|
mcnk_shader.uniform("outer_cursor_radius", brush_radius);
|
|
mcnk_shader.uniform("inner_cursor_ratio", inner_radius_ratio);
|
|
mcnk_shader.uniform("cursor_color", cursor_color);
|
|
}
|
|
else
|
|
{
|
|
mcnk_shader.uniform("draw_cursor_circle", 0);
|
|
}
|
|
|
|
gl.bindVertexArray(_mapchunk_vao);
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, _mapchunk_index);
|
|
|
|
for (auto& pair : _world->_loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (minimap_render)
|
|
tile->renderer()->setOccluded(false);
|
|
|
|
if (tile->renderer()->isOccluded() && !tile->getChunkUpdateFlags() && !tile->renderer()->isOverridingOcclusionCulling())
|
|
continue;
|
|
|
|
tile->renderer()->draw(
|
|
mcnk_shader
|
|
, camera_pos
|
|
, show_unpaintable_chunks
|
|
, draw_paintability_overlay
|
|
, terrainMode == editing_mode::minimap
|
|
&& minimap_render_settings->selected_tiles.at(64 * tile->index.x + tile->index.z)
|
|
);
|
|
|
|
_world->_n_rendered_tiles++;
|
|
|
|
}
|
|
|
|
gl.bindVertexArray(0);
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
}
|
|
}
|
|
|
|
if (terrainMode == editing_mode::object && _world->has_multiple_model_selected())
|
|
{
|
|
ZoneScopedN("World::draw() : Draw pivot point");
|
|
OpenGL::Scoped::bool_setter<GL_DEPTH_TEST, GL_FALSE> const disable_depth_test;
|
|
|
|
float dist = glm::distance(camera_pos, _world->_multi_select_pivot.value());
|
|
_sphere_render.draw(mvp, _world->_multi_select_pivot.value(), cursor_color, std::min(2.f, std::max(0.15f, dist * 0.02f)));
|
|
}
|
|
|
|
if (use_ref_pos)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw ref pos");
|
|
_sphere_render.draw(mvp, ref_pos, cursor_color, 0.3f);
|
|
}
|
|
|
|
if (terrainMode == editing_mode::ground && ground_editing_brush == eTerrainType_Vertex)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw vertex points");
|
|
float size = glm::distance(_world->vertexCenter(), camera_pos);
|
|
gl.pointSize(std::max(0.001f, 10.0f - (1.25f * size / CHUNKSIZE)));
|
|
|
|
for (glm::vec3 const* pos : _world->_vertices_selected)
|
|
{
|
|
_sphere_render.draw(mvp, *pos, glm::vec4(1.f, 0.f, 0.f, 1.f), 0.5f);
|
|
}
|
|
|
|
_sphere_render.draw(mvp, _world->vertexCenter(), cursor_color, 2.f);
|
|
}
|
|
|
|
std::unordered_map<Model*, std::size_t> model_with_particles;
|
|
|
|
tsl::robin_map<Model*, std::vector<glm::mat4x4>> models_to_draw;
|
|
std::vector<WMOInstance*> wmos_to_draw;
|
|
|
|
// frame counter loop. pretty hacky but works
|
|
// this is used to make sure no object is processed more than once within a frame
|
|
static int frame = 0;
|
|
|
|
if (frame == std::numeric_limits<int>::max())
|
|
{
|
|
frame = 0;
|
|
}
|
|
else
|
|
{
|
|
frame++;
|
|
}
|
|
|
|
for (auto& pair : _world->_loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (minimap_render)
|
|
tile->renderer()->setOccluded(false);
|
|
|
|
if (tile->renderer()->isOccluded() && !tile->getChunkUpdateFlags() && !tile->renderer()->isOverridingOcclusionCulling())
|
|
continue;
|
|
|
|
// early dist check
|
|
// TODO: optional
|
|
if (tile->camDist() > _cull_distance)
|
|
continue;
|
|
|
|
|
|
// TODO: subject to potential generalization
|
|
for (auto& pair : tile->getObjectInstances())
|
|
{
|
|
if (pair.second[0]->which() == eMODEL)
|
|
{
|
|
auto& instances = models_to_draw[reinterpret_cast<Model*>(pair.first)];
|
|
|
|
// memory allocation heuristic. all objects will pass if tile is entirely in frustum.
|
|
// otherwise we only allocate for a half
|
|
|
|
if (tile->renderer()->objectsFrustumCullTest() > 1)
|
|
{
|
|
instances.reserve(instances.size() + pair.second.size());
|
|
}
|
|
else
|
|
{
|
|
instances.reserve(instances.size() + pair.second.size() / 2);
|
|
}
|
|
|
|
|
|
for (auto& instance : pair.second)
|
|
{
|
|
// do not render twice the cross-referenced objects twice
|
|
if (instance->frame == frame)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
instance->frame = frame;
|
|
|
|
auto m2_instance = static_cast<ModelInstance*>(instance);
|
|
|
|
if ((tile->renderer()->objectsFrustumCullTest() > 1 || m2_instance->isInFrustum(frustum)) && m2_instance->isInRenderDist(_cull_distance, camera_pos, display))
|
|
{
|
|
instances.push_back(m2_instance->transformMatrix());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// memory allocation heuristic. all objects will pass if tile is entirely in frustum.
|
|
// otherwise we only allocate for a half
|
|
|
|
if (tile->renderer()->objectsFrustumCullTest() > 1)
|
|
{
|
|
wmos_to_draw.reserve(wmos_to_draw.size() + pair.second.size());
|
|
}
|
|
else
|
|
{
|
|
wmos_to_draw.reserve(wmos_to_draw.size() + pair.second.size() / 2);
|
|
}
|
|
|
|
for (auto& instance : pair.second)
|
|
{
|
|
// do not render twice the cross-referenced objects twice
|
|
if (instance->frame == frame)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
instance->frame = frame;
|
|
|
|
auto wmo_instance = static_cast<WMOInstance*>(instance);
|
|
|
|
if (tile->renderer()->objectsFrustumCullTest() > 1 || frustum.intersects(wmo_instance->extents[1], wmo_instance->extents[0]))
|
|
{
|
|
wmos_to_draw.push_back(wmo_instance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WMOs / map objects
|
|
if (draw_wmo || _world->mapIndex.hasAGlobalWMO())
|
|
{
|
|
ZoneScopedN("World::draw() : Draw WMOs");
|
|
{
|
|
OpenGL::Scoped::use_program wmo_program{*_wmo_program.get()};
|
|
|
|
wmo_program.uniform("camera", glm::vec3(camera_pos.x, camera_pos.y, camera_pos.z));
|
|
|
|
// 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())
|
|
{
|
|
auto global_wmo = _world->_model_instance_storage.get_wmo_instance(_world->mWmoEntry.uniqueID);
|
|
if (global_wmo.has_value())
|
|
{
|
|
wmos_to_draw.push_back(global_wmo.value());
|
|
disable_cull = true;
|
|
}
|
|
}
|
|
|
|
|
|
for (auto& instance: wmos_to_draw)
|
|
{
|
|
bool is_hidden = instance->wmo->is_hidden();
|
|
|
|
bool is_exclusion_filtered = false;
|
|
|
|
// minimap render exclusion filters
|
|
// per-model
|
|
if (minimap_render && minimap_render_settings->use_filters)
|
|
{
|
|
if (instance->instance_model()->file_key().hasFilepath())
|
|
{
|
|
for(int i = 0; i < minimap_render_settings->wmo_model_filter_exclude->count(); ++i)
|
|
{
|
|
auto item = reinterpret_cast<Ui::MinimapWMOModelFilterEntry*>(
|
|
minimap_render_settings->wmo_model_filter_exclude->itemWidget(
|
|
minimap_render_settings->wmo_model_filter_exclude->item(i)));
|
|
|
|
if (item->getFileName().toStdString() == instance->instance_model()->file_key().filepath())
|
|
{
|
|
is_exclusion_filtered = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// per-instance
|
|
for(int i = 0; i < minimap_render_settings->wmo_instance_filter_exclude->count(); ++i)
|
|
{
|
|
auto item = reinterpret_cast<Ui::MinimapInstanceFilterEntry*>(
|
|
minimap_render_settings->wmo_instance_filter_exclude->itemWidget(
|
|
minimap_render_settings->wmo_instance_filter_exclude->item(i)));
|
|
|
|
if (item->getUid() == instance->uid)
|
|
{
|
|
is_exclusion_filtered = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// skip model rendering if excluded by filter
|
|
if (is_exclusion_filtered)
|
|
continue;
|
|
}
|
|
|
|
|
|
if (draw_hidden_models || !is_hidden)
|
|
{
|
|
instance->draw(wmo_program
|
|
, model_view
|
|
, projection
|
|
, frustum
|
|
, _cull_distance
|
|
, camera_pos
|
|
, is_hidden
|
|
, draw_wmo_doodads
|
|
, draw_fog
|
|
, _world->current_selection()
|
|
, _world->animtime
|
|
, _skies->hasSkies()
|
|
, display
|
|
, disable_cull
|
|
, draw_wmo_exterior
|
|
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// occlusion culling
|
|
// terrain tiles act as occluders for each other, water and M2/WMOs.
|
|
// occlusion culling is not performed on per model instance basis
|
|
// rendering a little extra is cheaper than querying.
|
|
// occlusion latency has 1-2 frames delay.
|
|
|
|
constexpr bool occlusion_cull = true;
|
|
if (occlusion_cull)
|
|
{
|
|
OpenGL::Scoped::use_program occluder_shader{ *_occluder_program.get() };
|
|
gl.colorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
gl.depthMask(GL_FALSE);
|
|
gl.bindVertexArray(_occluder_vao);
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, _occluder_index);
|
|
gl.disable(GL_CULL_FACE); // TODO: figure out why indices are bad and we need this
|
|
|
|
for (auto& pair : _world->_loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
tile->renderer()->setOccluded(!tile->renderer()->getTileOcclusionQueryResult(camera_pos));
|
|
tile->renderer()->doTileOcclusionQuery(occluder_shader);
|
|
}
|
|
|
|
gl.enable(GL_CULL_FACE);
|
|
gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
gl.depthMask(GL_TRUE);
|
|
gl.bindVertexArray(0);
|
|
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
|
|
// draw occlusion AABBs
|
|
if (draw_occlusion_boxes)
|
|
{
|
|
|
|
for (auto& pair : _world->_loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
glm::mat4x4 identity_mtx = glm::mat4x4{1};
|
|
auto& extents = tile->getCombinedExtents();
|
|
Noggit::Rendering::Primitives::WireBox::getInstance(_world->_context).draw ( model_view
|
|
, projection
|
|
, identity_mtx
|
|
, { 1.0f, 1.0f, 0.0f, 1.0f }
|
|
, extents[0]
|
|
, extents[1]
|
|
);
|
|
}
|
|
}
|
|
|
|
bool draw_doodads_wmo = draw_wmo && draw_wmo_doodads;
|
|
// M2s / models
|
|
if (draw_models || draw_doodads_wmo || (minimap_render && minimap_render_settings->use_filters))
|
|
{
|
|
ZoneScopedN("World::draw() : Draw M2s");
|
|
|
|
if (draw_model_animations)
|
|
{
|
|
ModelManager::resetAnim();
|
|
}
|
|
/*
|
|
if (_world->need_model_updates)
|
|
{
|
|
_world->update_models_by_filename();
|
|
}*/
|
|
|
|
std::unordered_map<Model*, std::size_t> model_boxes_to_draw;
|
|
|
|
{
|
|
if (draw_models || (minimap_render && minimap_render_settings->use_filters))
|
|
{
|
|
OpenGL::Scoped::use_program m2_shader {*_m2_instanced_program.get()};
|
|
|
|
OpenGL::M2RenderState model_render_state;
|
|
model_render_state.tex_arrays = {0, 0};
|
|
model_render_state.tex_indices = {0, 0};
|
|
model_render_state.tex_unit_lookups = {0, 0};
|
|
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
gl.disable(GL_BLEND);
|
|
gl.depthMask(GL_TRUE);
|
|
gl.enable(GL_CULL_FACE);
|
|
m2_shader.uniform("blend_mode", 0);
|
|
m2_shader.uniform("unfogged", static_cast<int>(model_render_state.unfogged));
|
|
m2_shader.uniform("unlit", static_cast<int>(model_render_state.unlit));
|
|
m2_shader.uniform("tex_unit_lookup_1", 0);
|
|
m2_shader.uniform("tex_unit_lookup_2", 0);
|
|
m2_shader.uniform("pixel_shader", 0);
|
|
|
|
for (auto& pair : models_to_draw)
|
|
{
|
|
bool is_inclusion_filtered = false;
|
|
|
|
// minimap render inclusion filters
|
|
// per-model
|
|
if (minimap_render && minimap_render_settings->use_filters)
|
|
{
|
|
if (pair.first->file_key().hasFilepath())
|
|
{
|
|
for(int i = 0; i < minimap_render_settings->m2_model_filter_include->count(); ++i)
|
|
{
|
|
auto item = reinterpret_cast<Ui::MinimapM2ModelFilterEntry*>(
|
|
minimap_render_settings->m2_model_filter_include->itemWidget(
|
|
minimap_render_settings->m2_model_filter_include->item(i)));
|
|
|
|
if (item->getFileName().toStdString() == pair.first->file_key().filepath())
|
|
{
|
|
is_inclusion_filtered = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip model rendering if excluded by filter
|
|
if (!is_inclusion_filtered)
|
|
continue;
|
|
}
|
|
|
|
if (draw_hidden_models || !pair.first->is_hidden())
|
|
{
|
|
pair.first->renderer()->draw( model_view
|
|
, pair.second
|
|
, m2_shader
|
|
, model_render_state
|
|
, frustum
|
|
, _cull_distance
|
|
, camera_pos
|
|
, _world->animtime
|
|
, draw_models_with_box
|
|
, model_boxes_to_draw
|
|
, display
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (draw_doodads_wmo)
|
|
{
|
|
_model_instance_storage.for_each_wmo_instance([&] (WMOInstance& wmo)
|
|
{
|
|
auto doodads = wmo.get_doodads(draw_hidden_models);
|
|
|
|
if (!doodads)
|
|
return;
|
|
|
|
static std::vector<ModelInstance*> instance_temp = {nullptr};
|
|
for (auto& pair : *doodads)
|
|
{
|
|
for (auto& doodad : pair.second)
|
|
{
|
|
instance_temp[0] = &doodad;
|
|
doodad.model->draw( model_view
|
|
, instance_temp
|
|
, m2_shader
|
|
, model_render_state
|
|
, frustum
|
|
, culldistance
|
|
, camera_pos
|
|
, animtime
|
|
, draw_models_with_box
|
|
, model_boxes_to_draw
|
|
, display
|
|
);
|
|
}
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
*/
|
|
}
|
|
|
|
}
|
|
|
|
gl.disable(GL_BLEND);
|
|
gl.enable(GL_CULL_FACE);
|
|
gl.depthMask(GL_TRUE);
|
|
|
|
|
|
models_to_draw.clear();
|
|
wmos_to_draw.clear();
|
|
|
|
if(draw_models_with_box || (draw_hidden_models && !model_boxes_to_draw.empty()))
|
|
{
|
|
OpenGL::Scoped::use_program m2_box_shader{ *_m2_box_program.get() };
|
|
|
|
OpenGL::Scoped::bool_setter<GL_LINE_SMOOTH, GL_TRUE> const line_smooth;
|
|
gl.hint (GL_LINE_SMOOTH_HINT, GL_NICEST);
|
|
|
|
for (auto& it : model_boxes_to_draw)
|
|
{
|
|
glm::vec4 color = it.first->is_hidden()
|
|
? glm::vec4(0.f, 0.f, 1.f, 1.f)
|
|
: ( it.first->use_fake_geometry()
|
|
? glm::vec4(1.f, 0.f, 0.f, 1.f)
|
|
: glm::vec4(0.75f, 0.75f, 0.75f, 1.f)
|
|
)
|
|
;
|
|
|
|
m2_box_shader.uniform("color", color);
|
|
it.first->renderer()->drawBox(m2_box_shader, it.second);
|
|
}
|
|
}
|
|
|
|
for (auto& selection : _world->current_selection())
|
|
{
|
|
if (selection.index() == eEntry_Object)
|
|
{
|
|
auto obj = std::get<selected_object_type>(selection);
|
|
|
|
if (obj->which() != eMODEL)
|
|
continue;
|
|
|
|
auto model = static_cast<ModelInstance*>(obj);
|
|
|
|
|
|
|
|
if (model->isInFrustum(frustum) && model->isInRenderDist(_cull_distance, camera_pos, display))
|
|
{
|
|
bool is_selected = false;
|
|
/*
|
|
auto id = model->uid;
|
|
bool const is_selected = _world->current_selection().size() > 0 &&
|
|
std::find_if(_world->current_selection().begin(), _world->current_selection().end(),
|
|
[id](selection_type type)
|
|
{
|
|
return var_type(type) == typeid(selected_object_type)
|
|
&& std::get<selected_object_type>(type)->which() == SceneObjectTypes::eMODEL
|
|
&& static_cast<ModelInstance*>(std::get<selected_object_type>(type))->uid == id;
|
|
}) != _world->current_selection().end();*/
|
|
|
|
model->draw_box(model_view, projection, is_selected); // make optional!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// render selection group boxes
|
|
for (auto& selection_group : _world->_selection_groups)
|
|
{
|
|
if (!selection_group.isSelected())
|
|
continue;
|
|
|
|
glm::mat4x4 identity_mtx = glm::mat4x4{ 1 };
|
|
auto& extents = selection_group.getExtents();
|
|
Noggit::Rendering::Primitives::WireBox::getInstance(_world->_context).draw(model_view
|
|
, projection
|
|
, identity_mtx
|
|
, { 0.0f, 0.0f, 1.0f, 1.0f } // blue
|
|
, extents[0]
|
|
, extents[1]
|
|
);
|
|
}
|
|
|
|
// set anim time only once per frame
|
|
{
|
|
OpenGL::Scoped::use_program water_shader {*_liquid_program.get()};
|
|
water_shader.uniform("camera", glm::vec3(camera_pos.x, camera_pos.y, camera_pos.z));
|
|
water_shader.uniform("animtime", _world->animtime);
|
|
|
|
|
|
if (draw_wmo || _world->mapIndex.hasAGlobalWMO())
|
|
{
|
|
water_shader.uniform("use_transform", 1);
|
|
}
|
|
}
|
|
/*
|
|
// model particles
|
|
if (draw_model_animations && !model_with_particles.empty())
|
|
{
|
|
OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> const cull;
|
|
OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;
|
|
|
|
OpenGL::Scoped::use_program particles_shader {*_m2_particles_program.get()};
|
|
|
|
particles_shader.uniform("model_view_projection", mvp);
|
|
OpenGL::texture::set_active_texture(0);
|
|
|
|
for (auto& it : model_with_particles)
|
|
{
|
|
it.first->draw_particles(model_view, particles_shader, it.second);
|
|
}
|
|
}
|
|
|
|
|
|
if (draw_model_animations && !model_with_particles.empty())
|
|
{
|
|
OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> const cull;
|
|
OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;
|
|
|
|
OpenGL::Scoped::use_program ribbon_shader {*_m2_ribbons_program.get()};
|
|
|
|
ribbon_shader.uniform("model_view_projection", mvp);
|
|
|
|
gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
|
|
for (auto& it : model_with_particles)
|
|
{
|
|
it.first->draw_ribbons(ribbon_shader, it.second);
|
|
}
|
|
}
|
|
|
|
*/
|
|
|
|
gl.enable(GL_BLEND);
|
|
gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
if (draw_water)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw water");
|
|
|
|
// draw the water on both sides
|
|
OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> const cull;
|
|
|
|
OpenGL::Scoped::use_program water_shader{ *_liquid_program.get()};
|
|
|
|
gl.bindVertexArray(_liquid_chunk_vao);
|
|
|
|
water_shader.uniform ("use_transform", 0);
|
|
|
|
for (auto& pair : _world->_loaded_tiles_buffer)
|
|
{
|
|
MapTile* tile = pair.second;
|
|
|
|
if (!tile)
|
|
break;
|
|
|
|
if (tile->renderer()->isOccluded() && !tile->Water.needsUpdate() && !tile->renderer()->isOverridingOcclusionCulling())
|
|
continue;
|
|
|
|
tile->Water.renderer()->draw(
|
|
frustum
|
|
, camera_pos
|
|
, camera_moved
|
|
, water_shader
|
|
, _world->animtime
|
|
, water_layer
|
|
, display
|
|
, &_liquid_texture_manager
|
|
);
|
|
}
|
|
|
|
gl.bindVertexArray(0);
|
|
}
|
|
|
|
if (angled_mode || use_ref_pos)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw angles");
|
|
OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> cull;
|
|
OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;
|
|
|
|
math::degrees orient = math::degrees(orientation);
|
|
math::degrees incl = math::degrees(angle);
|
|
glm::vec4 color = cursor_color;
|
|
// always half transparent regardless or the cursor transparency
|
|
color.w = 0.5f;
|
|
|
|
float radius = 1.2f * brush_radius;
|
|
|
|
if (angled_mode && terrainMode == editing_mode::flatten_blur)
|
|
{
|
|
if (angle > 49.0f) // 0.855 radian
|
|
{
|
|
color.x = 1.f;
|
|
color.y = 0.f;
|
|
color.z = 0.f;
|
|
}
|
|
}
|
|
|
|
if (angled_mode && !use_ref_pos)
|
|
{
|
|
glm::vec3 pos = cursor_pos;
|
|
pos.y += 0.1f; // to avoid z-fighting with the ground
|
|
_square_render.draw(mvp, pos, radius, incl, orient, color);
|
|
}
|
|
else if (use_ref_pos)
|
|
{
|
|
if (angled_mode)
|
|
{
|
|
glm::vec3 pos = cursor_pos;
|
|
pos.y = misc::angledHeight(ref_pos, pos, incl, orient);
|
|
pos.y += 0.1f;
|
|
_square_render.draw(mvp, pos, radius, incl, orient, color);
|
|
|
|
// display the plane when the cursor is far from ref_point
|
|
if (misc::dist(pos.x, pos.z, ref_pos.x, ref_pos.z) > 10.f + radius)
|
|
{
|
|
glm::vec3 ref = ref_pos;
|
|
ref.y += 0.1f;
|
|
_square_render.draw(mvp, ref, 10.f, incl, orient, color);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glm::vec3 pos = cursor_pos;
|
|
pos.y = ref_pos.y + 0.1f;
|
|
_square_render.draw(mvp, pos, radius, math::degrees(0.f), math::degrees(0.f), color);
|
|
}
|
|
}
|
|
}
|
|
|
|
gl.enable(GL_BLEND);
|
|
|
|
// draw last because of the transparency
|
|
if (draw_mfbo)
|
|
{
|
|
ZoneScopedN("World::draw() : Draw flight bounds");
|
|
// don't write on the depth buffer
|
|
OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;
|
|
|
|
OpenGL::Scoped::use_program mfbo_shader {*_mfbo_program.get()};
|
|
|
|
for (MapTile* tile : _world->mapIndex.loaded_tiles())
|
|
{
|
|
tile->flightBoundsRenderer()->draw(mfbo_shader);
|
|
}
|
|
}
|
|
|
|
if (terrainMode == editing_mode::light)
|
|
{
|
|
Sky* CurrentSky = skies()->findClosestSkyByDistance(camera_pos);
|
|
if (!CurrentSky)
|
|
return;
|
|
|
|
int CurrentSkyID = CurrentSky->Id;
|
|
|
|
const int MAX_TIME_VALUE_C = 2880;
|
|
const int CurrenTime = static_cast<int>(_world->time) % MAX_TIME_VALUE_C;
|
|
|
|
glCullFace(GL_FRONT);
|
|
for (Sky& sky : skies()->skies)
|
|
{
|
|
if (CurrentSkyID > 1 && draw_only_inside_light_sphere)
|
|
break;
|
|
|
|
if (CurrentSkyID == sky.Id)
|
|
continue;
|
|
|
|
if (glm::distance(sky.pos, camera_pos) <= _cull_distance) // TODO: frustum cull here
|
|
{
|
|
glm::vec4 diffuse = { sky.colorFor(LIGHT_GLOBAL_DIFFUSE, CurrenTime), 1.f };
|
|
glm::vec4 ambient = { sky.colorFor(LIGHT_GLOBAL_AMBIENT, CurrenTime), 1.f };
|
|
|
|
_sphere_render.draw(mvp, sky.pos, ambient, sky.r1, 32, 18, alpha_light_sphere, false, draw_wireframe_light_sphere);
|
|
_sphere_render.draw(mvp, sky.pos, diffuse, sky.r2, 32, 18, alpha_light_sphere, false, draw_wireframe_light_sphere);
|
|
}
|
|
}
|
|
|
|
glCullFace(GL_BACK);
|
|
if (CurrentSky && draw_only_inside_light_sphere)
|
|
{
|
|
glm::vec4 diffuse = { CurrentSky->colorFor(LIGHT_GLOBAL_DIFFUSE, CurrenTime), 1.f };
|
|
glm::vec4 ambient = { CurrentSky->colorFor(LIGHT_GLOBAL_AMBIENT, CurrenTime), 1.f };
|
|
|
|
_sphere_render.draw(mvp, CurrentSky->pos, ambient, CurrentSky->r1, 32, 18, alpha_light_sphere, false, draw_wireframe_light_sphere);
|
|
_sphere_render.draw(mvp, CurrentSky->pos, diffuse, CurrentSky->r2, 32, 18, alpha_light_sphere, false, draw_wireframe_light_sphere);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WorldRender::upload()
|
|
{
|
|
ZoneScoped;
|
|
_world->mapIndex.setAdt(false);
|
|
|
|
if (_world->mapIndex.hasAGlobalWMO())
|
|
{
|
|
WMOInstance inst(_world->mWmoFilename, &_world->mWmoEntry, _world->_context);
|
|
|
|
_world->_model_instance_storage.add_wmo_instance(std::move(inst), false);
|
|
}
|
|
else
|
|
{
|
|
_horizon_render = std::make_unique<Noggit::map_horizon::render>(_world->horizon);
|
|
}
|
|
|
|
_skies = std::make_unique<Skies>(_world->mapIndex._map_id, _world->_context);
|
|
|
|
_outdoor_lighting = std::make_unique<OutdoorLighting>();
|
|
|
|
_m2_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("m2_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_fs") }
|
|
}
|
|
);
|
|
|
|
_m2_instanced_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("m2_vs", {"instanced"}) }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_fs") }
|
|
}
|
|
);
|
|
|
|
_m2_box_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("m2_box_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_box_fs") }
|
|
}
|
|
);
|
|
|
|
_m2_ribbons_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("ribbon_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("ribbon_fs") }
|
|
}
|
|
);
|
|
|
|
_m2_particles_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("particle_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("particle_fs") }
|
|
}
|
|
);
|
|
|
|
_mcnk_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("terrain_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("terrain_fs") }
|
|
}
|
|
);
|
|
|
|
_mfbo_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("mfbo_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("mfbo_fs") }
|
|
}
|
|
);
|
|
|
|
_wmo_program.reset
|
|
( new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("wmo_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("wmo_fs") }
|
|
}
|
|
);
|
|
|
|
_liquid_program.reset(
|
|
new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("liquid_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("liquid_fs") }
|
|
}
|
|
);
|
|
|
|
_occluder_program.reset(
|
|
new OpenGL::program
|
|
{ { GL_VERTEX_SHADER, OpenGL::shader::src_from_qrc("occluder_vs") }
|
|
, { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("occluder_fs") }
|
|
}
|
|
);
|
|
|
|
_liquid_texture_manager.upload();
|
|
|
|
_buffers.upload();
|
|
_vertex_arrays.upload();
|
|
|
|
setupOccluderBuffers();
|
|
|
|
{
|
|
OpenGL::Scoped::use_program m2_shader {*_m2_program.get()};
|
|
m2_shader.uniform("bone_matrices", 0);
|
|
m2_shader.uniform("tex1", 1);
|
|
m2_shader.uniform("tex2", 2);
|
|
|
|
m2_shader.bind_uniform_block("matrices", 0);
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _mvp_ubo);
|
|
gl.bufferData(GL_UNIFORM_BUFFER, sizeof(OpenGL::MVPUniformBlock), NULL, GL_DYNAMIC_DRAW);
|
|
gl.bindBufferRange(GL_UNIFORM_BUFFER, OpenGL::ubo_targets::MVP, _mvp_ubo, 0, sizeof(OpenGL::MVPUniformBlock));
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
m2_shader.bind_uniform_block("lighting", 1);
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _lighting_ubo);
|
|
gl.bufferData(GL_UNIFORM_BUFFER, sizeof(OpenGL::LightingUniformBlock), NULL, GL_DYNAMIC_DRAW);
|
|
gl.bindBufferRange(GL_UNIFORM_BUFFER, OpenGL::ubo_targets::LIGHTING, _lighting_ubo, 0, sizeof(OpenGL::LightingUniformBlock));
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
}
|
|
|
|
{
|
|
std::vector<int> samplers {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
|
|
OpenGL::Scoped::use_program wmo_program {*_wmo_program.get()};
|
|
wmo_program.uniform("render_batches_tex", 0);
|
|
wmo_program.uniform("texture_samplers", samplers);
|
|
wmo_program.bind_uniform_block("matrices", 0);
|
|
wmo_program.bind_uniform_block("lighting", 1);
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program mcnk_shader {*_mcnk_program.get()};
|
|
|
|
setupChunkBuffers();
|
|
setupChunkVAO(mcnk_shader);
|
|
|
|
mcnk_shader.bind_uniform_block("matrices", 0);
|
|
mcnk_shader.bind_uniform_block("lighting", 1);
|
|
mcnk_shader.bind_uniform_block("overlay_params", 2);
|
|
mcnk_shader.bind_uniform_block("chunk_instances", 3);
|
|
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _terrain_params_ubo);
|
|
gl.bufferData(GL_UNIFORM_BUFFER, sizeof(OpenGL::TerrainParamsUniformBlock), NULL, GL_STATIC_DRAW);
|
|
gl.bindBufferRange(GL_UNIFORM_BUFFER, OpenGL::ubo_targets::TERRAIN_OVERLAYS, _terrain_params_ubo, 0, sizeof(OpenGL::TerrainParamsUniformBlock));
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
mcnk_shader.uniform("heightmap", 0);
|
|
mcnk_shader.uniform("mccv", 1);
|
|
mcnk_shader.uniform("shadowmap", 2);
|
|
mcnk_shader.uniform("alphamap", 3);
|
|
mcnk_shader.uniform("stamp_brush", 4);
|
|
mcnk_shader.uniform("base_instance", 0);
|
|
|
|
std::vector<int> samplers {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
mcnk_shader.uniform("textures", samplers);
|
|
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program m2_shader_instanced {*_m2_instanced_program.get()};
|
|
m2_shader_instanced.bind_uniform_block("matrices", 0);
|
|
m2_shader_instanced.bind_uniform_block("lighting", 1);
|
|
m2_shader_instanced.uniform("bone_matrices", 0);
|
|
m2_shader_instanced.uniform("tex1", 1);
|
|
m2_shader_instanced.uniform("tex2", 2);
|
|
}
|
|
|
|
/*
|
|
{
|
|
OpenGL::Scoped::use_program particles_shader {*_m2_particles_program.get()};
|
|
particles_shader.uniform("tex", 0);
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program ribbon_shader {*_m2_ribbons_program.get()};
|
|
ribbon_shader.uniform("tex", 0);
|
|
}
|
|
|
|
*/
|
|
|
|
{
|
|
OpenGL::Scoped::use_program liquid_render {*_liquid_program.get()};
|
|
|
|
setupLiquidChunkBuffers();
|
|
setupLiquidChunkVAO(liquid_render);
|
|
|
|
static std::vector<int> samplers {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
|
|
liquid_render.bind_uniform_block("matrices", 0);
|
|
liquid_render.bind_uniform_block("lighting", 1);
|
|
liquid_render.bind_uniform_block("liquid_layers_params", 4);
|
|
liquid_render.uniform("vertex_data", 0);
|
|
liquid_render.uniform("texture_samplers", samplers);
|
|
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program mfbo_shader {*_mfbo_program.get()};
|
|
mfbo_shader.bind_uniform_block("matrices", 0);
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program m2_box_shader {*_m2_box_program.get()};
|
|
m2_box_shader.bind_uniform_block("matrices", 0);
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::use_program occluder_shader {*_occluder_program.get()};
|
|
occluder_shader.bind_uniform_block("matrices", 0);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void WorldRender::unload()
|
|
{
|
|
ZoneScoped;
|
|
_mcnk_program.reset();
|
|
_mfbo_program.reset();
|
|
_m2_program.reset();
|
|
_m2_instanced_program.reset();
|
|
_m2_particles_program.reset();
|
|
_m2_ribbons_program.reset();
|
|
_m2_box_program.reset();
|
|
_wmo_program.reset();
|
|
_liquid_program.reset();
|
|
|
|
_cursor_render.unload();
|
|
_sphere_render.unload();
|
|
_square_render.unload();
|
|
_line_render.unload();
|
|
_horizon_render.reset();
|
|
|
|
_liquid_texture_manager.unload();
|
|
|
|
_skies->unload();
|
|
|
|
_buffers.unload();
|
|
_vertex_arrays.unload();
|
|
|
|
Noggit::Rendering::Primitives::WireBox::getInstance(_world->_context).unload();
|
|
}
|
|
|
|
|
|
void WorldRender::updateMVPUniformBlock(const glm::mat4x4& model_view, const glm::mat4x4& projection)
|
|
{
|
|
ZoneScoped;
|
|
|
|
_mvp_ubo_data.model_view = model_view;
|
|
_mvp_ubo_data.projection = projection;
|
|
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _mvp_ubo);
|
|
gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::MVPUniformBlock), &_mvp_ubo_data);
|
|
|
|
}
|
|
|
|
void WorldRender::updateLightingUniformBlock(bool draw_fog, glm::vec3 const& camera_pos)
|
|
{
|
|
ZoneScoped;
|
|
|
|
int daytime = static_cast<int>(_world->time) % 2880;
|
|
|
|
_skies->update_sky_colors(camera_pos, daytime);
|
|
_outdoor_light_stats = _outdoor_lighting->getLightStats(static_cast<int>(_world->time));
|
|
|
|
glm::vec3 diffuse = _skies->color_set[LIGHT_GLOBAL_DIFFUSE];
|
|
glm::vec3 ambient = _skies->color_set[LIGHT_GLOBAL_AMBIENT];
|
|
glm::vec3 fog_color = _skies->color_set[FOG_COLOR];
|
|
glm::vec3 ocean_color_light = _skies->color_set[OCEAN_COLOR_LIGHT];
|
|
glm::vec3 ocean_color_dark = _skies->color_set[OCEAN_COLOR_DARK];
|
|
glm::vec3 river_color_light = _skies->color_set[RIVER_COLOR_LIGHT];
|
|
glm::vec3 river_color_dark = _skies->color_set[RIVER_COLOR_DARK];
|
|
|
|
|
|
_lighting_ubo_data.DiffuseColor_FogStart = {diffuse.x,diffuse.y,diffuse.z, _skies->fog_distance_start()};
|
|
_lighting_ubo_data.AmbientColor_FogEnd = {ambient.x,ambient.y,ambient.z, _skies->fog_distance_end()};
|
|
_lighting_ubo_data.FogColor_FogOn = {fog_color.x,fog_color.y,fog_color.z, static_cast<float>(draw_fog)};
|
|
_lighting_ubo_data.LightDir_FogRate = {_outdoor_light_stats.dayDir.x, _outdoor_light_stats.dayDir.y, _outdoor_light_stats.dayDir.z, _skies->fogRate()};
|
|
_lighting_ubo_data.OceanColorLight = { ocean_color_light.x,ocean_color_light.y,ocean_color_light.z, _skies->ocean_shallow_alpha()};
|
|
_lighting_ubo_data.OceanColorDark = { ocean_color_dark.x,ocean_color_dark.y,ocean_color_dark.z, _skies->ocean_deep_alpha()};
|
|
_lighting_ubo_data.RiverColorLight = { river_color_light.x,river_color_light.y,river_color_light.z, _skies->river_shallow_alpha()};
|
|
_lighting_ubo_data.RiverColorDark = { river_color_dark.x,river_color_dark.y,river_color_dark.z, _skies->river_deep_alpha()};
|
|
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _lighting_ubo);
|
|
gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::LightingUniformBlock), &_lighting_ubo_data);
|
|
}
|
|
|
|
void WorldRender::updateLightingUniformBlockMinimap(MinimapRenderSettings* settings)
|
|
{
|
|
ZoneScoped;
|
|
|
|
glm::vec3 diffuse = settings->diffuse_color;
|
|
glm::vec3 ambient = settings->ambient_color;
|
|
|
|
_lighting_ubo_data.DiffuseColor_FogStart = { diffuse, 0 };
|
|
_lighting_ubo_data.AmbientColor_FogEnd = { ambient, 0 };
|
|
_lighting_ubo_data.FogColor_FogOn = { 0, 0, 0, 0 };
|
|
_lighting_ubo_data.LightDir_FogRate = { _outdoor_light_stats.dayDir.x, _outdoor_light_stats.dayDir.y, _outdoor_light_stats.dayDir.z, _skies->fogRate() };
|
|
_lighting_ubo_data.OceanColorLight = settings->ocean_color_light;
|
|
_lighting_ubo_data.OceanColorDark = settings->ocean_color_dark;
|
|
_lighting_ubo_data.RiverColorLight = settings->river_color_light;
|
|
_lighting_ubo_data.RiverColorDark = settings->river_color_dark;
|
|
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _lighting_ubo);
|
|
gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::LightingUniformBlock), &_lighting_ubo_data);
|
|
}
|
|
|
|
void WorldRender::updateTerrainParamsUniformBlock()
|
|
{
|
|
ZoneScoped;
|
|
gl.bindBuffer(GL_UNIFORM_BUFFER, _terrain_params_ubo);
|
|
gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::TerrainParamsUniformBlock), &_terrain_params_ubo_data);
|
|
_need_terrain_params_ubo_update = false;
|
|
}
|
|
|
|
void WorldRender::setupChunkVAO(OpenGL::Scoped::use_program& mcnk_shader)
|
|
{
|
|
ZoneScoped;
|
|
OpenGL::Scoped::vao_binder const _ (_mapchunk_vao);
|
|
|
|
{
|
|
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const binder(_mapchunk_texcoord);
|
|
mcnk_shader.attrib("texcoord", 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
|
|
{
|
|
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const binder(_mapchunk_vertex);
|
|
mcnk_shader.attrib("position", 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
}
|
|
|
|
void WorldRender::setupChunkBuffers()
|
|
{
|
|
ZoneScoped;
|
|
|
|
// vertices
|
|
|
|
glm::vec2 vertices[mapbufsize];
|
|
glm::vec2 *ttv = vertices;
|
|
|
|
for (int j = 0; j < 17; ++j)
|
|
{
|
|
bool is_lod = j % 2;
|
|
for (int i = 0; i < (is_lod ? 8 : 9); ++i)
|
|
{
|
|
float xpos, zpos;
|
|
xpos = i * UNITSIZE;
|
|
zpos = j * 0.5f * UNITSIZE;
|
|
|
|
if (is_lod)
|
|
{
|
|
xpos += UNITSIZE*0.5f;
|
|
}
|
|
|
|
auto v = glm::vec2(xpos, zpos);
|
|
*ttv++ = v;
|
|
}
|
|
}
|
|
|
|
gl.bufferData<GL_ARRAY_BUFFER>(_mapchunk_vertex, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
|
|
static constexpr std::array<std::uint16_t, 768 + 192> indices {
|
|
|
|
9, 0, 17, 9, 17, 18, 9, 18, 1, 9, 1, 0, 26, 17, 34, 26,
|
|
34, 35, 26, 35, 18, 26, 18, 17, 43, 34, 51, 43, 51, 52, 43, 52,
|
|
35, 43, 35, 34, 60, 51, 68, 60, 68, 69, 60, 69, 52, 60, 52, 51,
|
|
77, 68, 85, 77, 85, 86, 77, 86, 69, 77, 69, 68, 94, 85, 102, 94,
|
|
102, 103, 94, 103, 86, 94, 86, 85, 111, 102, 119, 111, 119, 120, 111, 120,
|
|
103, 111, 103, 102, 128, 119, 136, 128, 136, 137, 128, 137, 120, 128, 120, 119,
|
|
10, 1, 18, 10, 18, 19, 10, 19, 2, 10, 2, 1, 27, 18, 35, 27,
|
|
35, 36, 27, 36, 19, 27, 19, 18, 44, 35, 52, 44, 52, 53, 44, 53,
|
|
36, 44, 36, 35, 61, 52, 69, 61, 69, 70, 61, 70, 53, 61, 53, 52,
|
|
78, 69, 86, 78, 86, 87, 78, 87, 70, 78, 70, 69, 95, 86, 103, 95,
|
|
103, 104, 95, 104, 87, 95, 87, 86, 112, 103, 120, 112, 120, 121, 112, 121,
|
|
104, 112, 104, 103, 129, 120, 137, 129, 137, 138, 129, 138, 121, 129, 121, 120,
|
|
11, 2, 19, 11, 19, 20, 11, 20, 3, 11, 3, 2, 28, 19, 36, 28,
|
|
36, 37, 28, 37, 20, 28, 20, 19, 45, 36, 53, 45, 53, 54, 45, 54,
|
|
37, 45, 37, 36, 62, 53, 70, 62, 70, 71, 62, 71, 54, 62, 54, 53,
|
|
79, 70, 87, 79, 87, 88, 79, 88, 71, 79, 71, 70, 96, 87, 104, 96,
|
|
104, 105, 96, 105, 88, 96, 88, 87, 113, 104, 121, 113, 121, 122, 113, 122,
|
|
105, 113, 105, 104, 130, 121, 138, 130, 138, 139, 130, 139, 122, 130, 122, 121,
|
|
12, 3, 20, 12, 20, 21, 12, 21, 4, 12, 4, 3, 29, 20, 37, 29,
|
|
37, 38, 29, 38, 21, 29, 21, 20, 46, 37, 54, 46, 54, 55, 46, 55,
|
|
38, 46, 38, 37, 63, 54, 71, 63, 71, 72, 63, 72, 55, 63, 55, 54,
|
|
80, 71, 88, 80, 88, 89, 80, 89, 72, 80, 72, 71, 97, 88, 105, 97,
|
|
105, 106, 97, 106, 89, 97, 89, 88, 114, 105, 122, 114, 122, 123, 114, 123,
|
|
106, 114, 106, 105, 131, 122, 139, 131, 139, 140, 131, 140, 123, 131, 123, 122,
|
|
13, 4, 21, 13, 21, 22, 13, 22, 5, 13, 5, 4, 30, 21, 38, 30,
|
|
38, 39, 30, 39, 22, 30, 22, 21, 47, 38, 55, 47, 55, 56, 47, 56,
|
|
39, 47, 39, 38, 64, 55, 72, 64, 72, 73, 64, 73, 56, 64, 56, 55,
|
|
81, 72, 89, 81, 89, 90, 81, 90, 73, 81, 73, 72, 98, 89, 106, 98,
|
|
106, 107, 98, 107, 90, 98, 90, 89, 115, 106, 123, 115, 123, 124, 115, 124,
|
|
107, 115, 107, 106, 132, 123, 140, 132, 140, 141, 132, 141, 124, 132, 124, 123,
|
|
14, 5, 22, 14, 22, 23, 14, 23, 6, 14, 6, 5, 31, 22, 39, 31,
|
|
39, 40, 31, 40, 23, 31, 23, 22, 48, 39, 56, 48, 56, 57, 48, 57,
|
|
40, 48, 40, 39, 65, 56, 73, 65, 73, 74, 65, 74, 57, 65, 57, 56,
|
|
82, 73, 90, 82, 90, 91, 82, 91, 74, 82, 74, 73, 99, 90, 107, 99,
|
|
107, 108, 99, 108, 91, 99, 91, 90, 116, 107, 124, 116, 124, 125, 116, 125,
|
|
108, 116, 108, 107, 133, 124, 141, 133, 141, 142, 133, 142, 125, 133, 125, 124,
|
|
15, 6, 23, 15, 23, 24, 15, 24, 7, 15, 7, 6, 32, 23, 40, 32,
|
|
40, 41, 32, 41, 24, 32, 24, 23, 49, 40, 57, 49, 57, 58, 49, 58,
|
|
41, 49, 41, 40, 66, 57, 74, 66, 74, 75, 66, 75, 58, 66, 58, 57,
|
|
83, 74, 91, 83, 91, 92, 83, 92, 75, 83, 75, 74, 100, 91, 108, 100,
|
|
108, 109, 100, 109, 92, 100, 92, 91, 117, 108, 125, 117, 125, 126, 117, 126,
|
|
109, 117, 109, 108, 134, 125, 142, 134, 142, 143, 134, 143, 126, 134, 126, 125,
|
|
16, 7, 24, 16, 24, 25, 16, 25, 8, 16, 8, 7, 33, 24, 41, 33,
|
|
41, 42, 33, 42, 25, 33, 25, 24, 50, 41, 58, 50, 58, 59, 50, 59,
|
|
42, 50, 42, 41, 67, 58, 75, 67, 75, 76, 67, 76, 59, 67, 59, 58,
|
|
84, 75, 92, 84, 92, 93, 84, 93, 76, 84, 76, 75, 101, 92, 109, 101,
|
|
109, 110, 101, 110, 93, 101, 93, 92, 118, 109, 126, 118, 126, 127, 118, 127,
|
|
110, 118, 110, 109, 135, 126, 143, 135, 143, 144, 135, 144, 127, 135, 127, 126,
|
|
|
|
// lod
|
|
0, 34, 18, 18, 34, 36, 18, 36, 2, 18, 2, 0, 34, 68, 52, 52,
|
|
68, 70, 52, 70, 36, 52, 36, 34, 68, 102, 86, 86, 102, 104, 86, 104,
|
|
70, 86, 70, 68, 102, 136, 120, 120, 136, 138, 120, 138, 104, 120, 104, 102,
|
|
2, 36, 20, 20, 36, 38, 20, 38, 4, 20, 4, 2, 36, 70, 54, 54,
|
|
70, 72, 54, 72, 38, 54, 38, 36, 70, 104, 88, 88, 104, 106, 88, 106,
|
|
72, 88, 72, 70, 104, 138, 122, 122, 138, 140, 122, 140, 106, 122, 106, 104,
|
|
4, 38, 22, 22, 38, 40, 22, 40, 6, 22, 6, 4, 38, 72, 56, 56,
|
|
72, 74, 56, 74, 40, 56, 40, 38, 72, 106, 90, 90, 106, 108, 90, 108,
|
|
74, 90, 74, 72, 106, 140, 124, 124, 140, 142, 124, 142, 108, 124, 108, 106,
|
|
6, 40, 24, 24, 40, 42, 24, 42, 8, 24, 8, 6, 40, 74, 58, 58,
|
|
74, 76, 58, 76, 42, 58, 42, 40, 74, 108, 92, 92, 108, 110, 92, 110,
|
|
76, 92, 76, 74, 108, 142, 126, 126, 142, 144, 126, 144, 110, 126, 110, 108};
|
|
|
|
/*
|
|
// indices
|
|
std::uint16_t indices[768];
|
|
int flat_index = 0;
|
|
|
|
for (int x = 0; x<8; ++x)
|
|
{
|
|
for (int y = 0; y<8; ++y)
|
|
{
|
|
indices[flat_index++] = MapChunk::indexLoD(y, x); //9
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y, x); //0
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y + 1, x); //17
|
|
indices[flat_index++] = MapChunk::indexLoD(y, x); //9
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y + 1, x); //17
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y + 1, x + 1); //18
|
|
indices[flat_index++] = MapChunk::indexLoD(y, x); //9
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y + 1, x + 1); //18
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y, x + 1); //1
|
|
indices[flat_index++] = MapChunk::indexLoD(y, x); //9
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y, x + 1); //1
|
|
indices[flat_index++] = MapChunk::indexNoLoD(y, x); //0
|
|
}
|
|
}
|
|
|
|
*/
|
|
|
|
{
|
|
OpenGL::Scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> const _ (_mapchunk_index);
|
|
gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, (768 + 192) * sizeof(std::uint16_t), indices.data(), GL_STATIC_DRAW);
|
|
}
|
|
|
|
// tex coords
|
|
glm::vec2 temp[mapbufsize], *vt;
|
|
float tx, ty;
|
|
|
|
// init texture coordinates for detail map:
|
|
vt = temp;
|
|
const float detail_half = 0.5f * detail_size / 8.0f;
|
|
for (int j = 0; j < 17; ++j)
|
|
{
|
|
bool is_lod = j % 2;
|
|
|
|
for (int i = 0; i< (is_lod ? 8 : 9); ++i)
|
|
{
|
|
tx = detail_size / 8.0f * i;
|
|
ty = detail_size / 8.0f * j * 0.5f;
|
|
|
|
if (is_lod)
|
|
tx += detail_half;
|
|
|
|
*vt++ = glm::vec2(tx, ty);
|
|
}
|
|
}
|
|
|
|
gl.bufferData<GL_ARRAY_BUFFER> (_mapchunk_texcoord, sizeof(temp), temp, GL_STATIC_DRAW);
|
|
|
|
}
|
|
|
|
void WorldRender::setupLiquidChunkVAO(OpenGL::Scoped::use_program& water_shader)
|
|
{
|
|
ZoneScoped;
|
|
OpenGL::Scoped::vao_binder const _ (_liquid_chunk_vao);
|
|
|
|
{
|
|
OpenGL::Scoped::buffer_binder<GL_ARRAY_BUFFER> const binder(_liquid_chunk_vertex);
|
|
water_shader.attrib("position", 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
}
|
|
|
|
void WorldRender::setupLiquidChunkBuffers()
|
|
{
|
|
ZoneScoped;
|
|
|
|
// vertices
|
|
glm::vec2 vertices[768 / 2];
|
|
glm::vec2* vt = vertices;
|
|
|
|
for (int z = 0; z < 8; ++z)
|
|
{
|
|
for (int x = 0; x < 8; ++x)
|
|
{
|
|
// first triangle
|
|
*vt++ = glm::vec2(UNITSIZE * x, UNITSIZE * z);
|
|
*vt++ = glm::vec2(UNITSIZE * x, UNITSIZE * (z + 1));
|
|
*vt++ = glm::vec2(UNITSIZE * (x + 1), UNITSIZE * z);
|
|
|
|
// second triangle
|
|
*vt++ = glm::vec2(UNITSIZE * (x + 1), UNITSIZE * z);
|
|
*vt++ = glm::vec2(UNITSIZE * x, UNITSIZE * (z + 1));
|
|
*vt++ = glm::vec2(UNITSIZE * (x + 1), UNITSIZE * (z + 1));
|
|
}
|
|
}
|
|
|
|
gl.bufferData<GL_ARRAY_BUFFER> (_liquid_chunk_vertex, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorldRender::setupOccluderBuffers()
|
|
{
|
|
ZoneScoped;
|
|
static constexpr std::array<std::uint16_t, 36> indices
|
|
{
|
|
/*Above ABC,BCD*/
|
|
0,1,2,
|
|
1,2,3,
|
|
/*Following EFG,FGH*/
|
|
4,5,6,
|
|
5,6,7,
|
|
/*Left ABF,AEF*/
|
|
1,0,5,
|
|
0,4,5,
|
|
/*Right side CDH,CGH*/
|
|
3,2,7,
|
|
2,6,7,
|
|
/*ACG,AEG*/
|
|
2,0,6,
|
|
0,4,6,
|
|
/*Behind BFH,BDH*/
|
|
5,1,7,
|
|
1,3,7
|
|
};
|
|
|
|
{
|
|
OpenGL::Scoped::buffer_binder<GL_ELEMENT_ARRAY_BUFFER> const _ (_occluder_index);
|
|
gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, 36 * sizeof(std::uint16_t), indices.data(), GL_STATIC_DRAW);
|
|
}
|
|
|
|
}
|
|
|
|
void WorldRender::drawMinimap ( MapTile *tile
|
|
, glm::mat4x4 const& model_view
|
|
, glm::mat4x4 const& projection
|
|
, glm::vec3 const& camera_pos
|
|
, MinimapRenderSettings* settings
|
|
)
|
|
{
|
|
ZoneScoped;
|
|
|
|
// Also load a tile above the current one to correct the lookat approximation
|
|
TileIndex m_tile = TileIndex(camera_pos);
|
|
m_tile.z -= 1;
|
|
|
|
bool unload = !_world->mapIndex.has_unsaved_changes(m_tile);
|
|
|
|
MapTile* mTile = _world->mapIndex.loadTile(m_tile);
|
|
|
|
if (mTile)
|
|
{
|
|
mTile->wait_until_loaded();
|
|
mTile->waitForChildrenLoaded();
|
|
|
|
}
|
|
|
|
draw(model_view, projection, glm::vec3(), 0, glm::vec4(),
|
|
CursorType::NONE, 0.f, false, false, false, 0.3f, 0.f, glm::vec3(), 0.f, 0.f, false, false, false, editing_mode::minimap, camera_pos, true, false, true, settings->draw_wmo, settings->draw_water, false, settings->draw_m2, false, false, true, settings, false, eTerrainType::eTerrainType_Linear, 0, display_mode::in_3D, false, true);
|
|
|
|
|
|
if (unload)
|
|
{
|
|
_world->mapIndex.unloadTile(m_tile);
|
|
}
|
|
}
|
|
|
|
bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings* settings, std::optional<QImage>& combined_image)
|
|
{
|
|
ZoneScoped;
|
|
// Setup framebuffer
|
|
QOpenGLFramebufferObjectFormat fmt;
|
|
fmt.setSamples(0);
|
|
fmt.setInternalTextureFormat(GL_RGBA8);
|
|
fmt.setAttachment(QOpenGLFramebufferObject::Depth);
|
|
|
|
QOpenGLFramebufferObject pixel_buffer(settings->resolution, settings->resolution, fmt);
|
|
pixel_buffer.bind();
|
|
|
|
gl.viewport(0, 0, settings->resolution, settings->resolution);
|
|
gl.clearColor(.0f, .0f, .0f, 1.f);
|
|
gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
// Load tile
|
|
bool unload = !_world->mapIndex.has_unsaved_changes(tile_idx);
|
|
|
|
if (!_world->mapIndex.tileLoaded(tile_idx) && !_world->mapIndex.tileAwaitingLoading(tile_idx))
|
|
{
|
|
MapTile* tile = _world->mapIndex.loadTile(tile_idx);
|
|
tile->wait_until_loaded();
|
|
_world->wait_for_all_tile_updates();
|
|
tile->waitForChildrenLoaded();
|
|
}
|
|
|
|
MapTile* mTile = _world->mapIndex.getTile(tile_idx);
|
|
|
|
if (mTile)
|
|
{
|
|
unsigned counter = 0;
|
|
constexpr unsigned TIMEOUT = 5000;
|
|
|
|
while (AsyncLoader::instance().is_loading() || !mTile->finishedLoading())
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
counter++;
|
|
|
|
if (counter >= TIMEOUT)
|
|
break;
|
|
}
|
|
|
|
float max_height = std::max(_world->getMaxTileHeight(tile_idx), 200.f);
|
|
|
|
// setup view matrices
|
|
auto projection = glm::ortho( -TILESIZE / 2.0f,TILESIZE / 2.0f,-TILESIZE / 2.0f,TILESIZE / 2.0f,0.f,100000.0f);
|
|
|
|
auto eye = glm::vec3(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 10.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f);
|
|
auto center = glm::vec3(TILESIZE * tile_idx.x + TILESIZE / 2.0f, max_height + 5.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0 - 0.005f);
|
|
auto up = glm::vec3(0.f, 1.f, 0.f);
|
|
|
|
glm::vec3 const z = glm::normalize(eye - center);
|
|
glm::vec3 const x = glm::normalize(glm::cross(up, z));
|
|
glm::vec3 const y = glm::normalize(glm::cross(z, x));
|
|
|
|
auto look_at = glm::transpose(glm::mat4x4(x.x, x.y, x.z, glm::dot(x, glm::vec3(-eye.x, -eye.y, -eye.z))
|
|
, y.x, y.y, y.z, glm::dot(y, glm::vec3(-eye.x, -eye.y, -eye.z))
|
|
, z.x, z.y, z.z, glm::dot(z, glm::vec3(-eye.x, -eye.y, -eye.z))
|
|
, 0.f, 0.f, 0.f, 1.f
|
|
));
|
|
|
|
glFinish();
|
|
|
|
drawMinimap(mTile
|
|
, look_at
|
|
, projection
|
|
, glm::vec3(TILESIZE * tile_idx.x + TILESIZE / 2.0f
|
|
, max_height + 15.0f, TILESIZE * tile_idx.z + TILESIZE / 2.0f)
|
|
, settings);
|
|
|
|
// Clearing alpha from image
|
|
gl.colorMask(false, false, false, true);
|
|
gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
gl.clear(GL_COLOR_BUFFER_BIT);
|
|
gl.colorMask(true, true, true, true);
|
|
|
|
assert(pixel_buffer.isValid() && pixel_buffer.isBound());
|
|
|
|
QImage image = pixel_buffer.toImage();
|
|
|
|
image = image.convertToFormat(QImage::Format_RGBA8888);
|
|
|
|
QSettings app_settings;
|
|
QString str = QString(Noggit::Project::CurrentProject::get()->ProjectPath.c_str());
|
|
if (!(str.endsWith('\\') || str.endsWith('/')))
|
|
{
|
|
str += "/";
|
|
}
|
|
|
|
QDir dir(str + "/textures/minimap/");
|
|
if (!dir.exists())
|
|
dir.mkpath(".");
|
|
|
|
std::string tex_name = std::string(_world->basename + "_" + std::to_string(tile_idx.x) + "_" + std::to_string(tile_idx.z) + ".blp");
|
|
|
|
if (settings->file_format == ".png")
|
|
{
|
|
image.save(dir.filePath(std::string(_world->basename + "_" + std::to_string(tile_idx.x) + "_" + std::to_string(tile_idx.z) + ".png").c_str()));
|
|
}
|
|
else if (settings->file_format == ".blp")
|
|
{
|
|
QByteArray bytes;
|
|
QBuffer buffer( &bytes );
|
|
buffer.open( QIODevice::WriteOnly );
|
|
|
|
image.save( &buffer, "PNG" );
|
|
|
|
auto blp = Png2Blp();
|
|
blp.load(reinterpret_cast<const void*>(bytes.constData()), bytes.size());
|
|
|
|
uint32_t file_size;
|
|
void* blp_image = blp.createBlpDxtInMemory(true, FORMAT_DXT5, file_size);
|
|
|
|
QFile file(dir.filePath(tex_name.c_str()));
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
QDataStream out(&file);
|
|
out.writeRawData(reinterpret_cast<char*>(blp_image), file_size);
|
|
|
|
file.close();
|
|
}
|
|
|
|
// Write combined file
|
|
if (settings->combined_minimap && combined_image.has_value())
|
|
{
|
|
QImage scaled_image = image.scaled(128, 128, Qt::KeepAspectRatio);
|
|
|
|
for (int i = 0; i < 128; ++i)
|
|
{
|
|
for (int j = 0; j < 128; ++j)
|
|
{
|
|
combined_image->setPixelColor(static_cast<int>(tile_idx.x) * 128 + j, static_cast<int>(tile_idx.z) * 128 + i, scaled_image.pixelColor(j, i));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Register in md5translate.trs
|
|
std::string map_name = gMapDB.getByID(_world->mapIndex._map_id).getString(MapDB::InternalName);
|
|
|
|
auto sstream = std::stringstream();
|
|
sstream << map_name << "\\map" << std::setfill('0') << std::setw(2) << tile_idx.x << "_" << std::setfill('0') << std::setw(2) << tile_idx.z << ".blp";
|
|
std::string tilename_left = sstream.str();
|
|
_world->mapIndex._minimap_md5translate[map_name][tilename_left] = tex_name;
|
|
|
|
if (unload)
|
|
{
|
|
_world->mapIndex.unloadTile(tile_idx);
|
|
}
|
|
|
|
}
|
|
|
|
pixel_buffer.release();
|
|
|
|
return true;
|
|
}
|