Files
noggit-red/src/noggit/MapChunk.cpp

2116 lines
58 KiB
C++

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/frustum.hpp>
#include <noggit/Brush.h>
#include <noggit/TileWater.hpp>
#include <noggit/Log.h>
#include <noggit/MapChunk.h>
#include <noggit/MapHeaders.h>
#include <noggit/Misc.h>
#include <noggit/World.h>
#include <noggit/Alphamap.hpp>
#include <noggit/texture_set.hpp>
#include <noggit/tool_enums.hpp>
#include <noggit/ui/TexturingGUI.h>
#include <noggit/ActionManager.hpp>
#include <noggit/Action.hpp>
#include <opengl/scoped.hpp>
#include <external/tracy/Tracy.hpp>
#include <glm/glm.hpp>
#include <ClientFile.hpp>
#include <algorithm>
#include <iostream>
#include <map>
#include <QImage>
#include <limits>
MapChunk::MapChunk(MapTile* maintile, BlizzardArchive::ClientFile* f, bool bigAlpha,tile_mode mode
, Noggit::NoggitRenderContext context, bool init_empty, int chunk_idx, bool load_textures)
: _mode(mode)
, mt(maintile)
, use_big_alphamap(bigAlpha)
, _context(context)
, vmin(std::numeric_limits<float>::max())
, vmax(std::numeric_limits<float>::lowest())
, _chunk_update_flags(ChunkUpdateFlags::VERTEX | ChunkUpdateFlags::ALPHAMAP
| ChunkUpdateFlags::SHADOW | ChunkUpdateFlags::MCCV
| ChunkUpdateFlags::NORMALS| ChunkUpdateFlags::HOLES
| ChunkUpdateFlags::AREA_ID| ChunkUpdateFlags::FLAGS
| ChunkUpdateFlags::GROUND_EFFECT | ChunkUpdateFlags::DETAILDOODADS_EXCLUSION)
{
if (init_empty)
{
// header.flags = 0;
px = chunk_idx / 16;
py = chunk_idx % 16;
zbase = ZEROPOINT - (maintile->zbase + py * CHUNKSIZE);
xbase = ZEROPOINT - (maintile->xbase + px * CHUNKSIZE);
ybase = 0.0f;
areaID = 0;
// zbase = header.zpos;
// xbase = header.xpos;
// ybase = header.ypos;
texture_set.reset();
auto& tile_buffer = mt->getChunkHeightmapBuffer();
int chunk_start = (px * 16 + py) * mapbufsize * 4;
// Generate normals
for (int i = 0; i < mapbufsize; ++i)
{
int pixel_start = chunk_start + i * 4;
tile_buffer[pixel_start] = 0.0f;
tile_buffer[pixel_start + 1] = 1.0f;
tile_buffer[pixel_start + 2] = 0.0f;
}
// Clear shadows
memset(_shadow_map, 0, 64 * 64);
// Do not write MCCV
hasMCCV = false;
holes = 0;
zbase = zbase*-1.0f + ZEROPOINT;
xbase = xbase*-1.0f + ZEROPOINT;
glm::vec3 *ttv = mVertices;
for (int j = 0; j < 17; ++j) {
for (int i = 0; i < ((j % 2) ? 8 : 9); ++i) {
float xpos, zpos;
xpos = i * UNITSIZE;
zpos = j * 0.5f * UNITSIZE;
if (j % 2) {
xpos += UNITSIZE*0.5f;
}
glm::vec3 v = glm::vec3(xbase + xpos, ybase + 0.0f, zbase + zpos);
*ttv++ = v;
vmin.y = std::min(vmin.y, v.y);
vmax.y = std::max(vmax.y, v.y);
}
}
vmin.x = xbase;
vmin.z = zbase;
vmax.x = xbase + 8 * UNITSIZE;
vmax.z = zbase + 8 * UNITSIZE;
update_intersect_points();
// use absolute y pos in vertices
ybase = 0.0f;
vcenter = (vmin + vmax) * 0.5f;
return;
}
uint32_t fourcc;
uint32_t size;
size_t base = f->getPos();
hasMCCV = false;
MapChunkHeader tmp_chunk_header;
// - MCNK ----------------------------------------------
{
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCNK');
f->read(&tmp_chunk_header, sizeof(MapChunkHeader));
header_flags.value = tmp_chunk_header.flags.value;
areaID = tmp_chunk_header.areaid;
zbase = tmp_chunk_header.zpos;
xbase = tmp_chunk_header.xpos;
ybase = tmp_chunk_header.ypos;
px = tmp_chunk_header.ix;
py = tmp_chunk_header.iy;
holes = tmp_chunk_header.holes;
// correct the x and z values ^_^
zbase = zbase*-1.0f + ZEROPOINT;
xbase = xbase*-1.0f + ZEROPOINT;
}
if (!load_textures)
{
tmp_chunk_header.nLayers = 0;
}
texture_set = std::make_unique<TextureSet>(this, f, base, bigAlpha,
!!header_flags.flags.do_not_fix_alpha_map, mode == tile_mode::uid_fix_all, _context, tmp_chunk_header);
// - MCVT ----------------------------------------------
{
f->seek(base + tmp_chunk_header.ofsHeight);
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCVT');
glm::vec3 *ttv = mVertices;
// vertices
for (int j = 0; j < 17; ++j) {
for (int i = 0; i < ((j % 2) ? 8 : 9); ++i) {
float h, xpos, zpos;
f->read(&h, 4);
xpos = i * UNITSIZE;
zpos = j * 0.5f * UNITSIZE;
if (j % 2) {
xpos += UNITSIZE*0.5f;
}
glm::vec3 v = glm::vec3(xbase + xpos, ybase + h, zbase + zpos);
*ttv++ = v;
vmin.y = std::min(vmin.y, v.y);
vmax.y = std::max(vmax.y, v.y);
}
}
vmin.x = xbase;
vmin.z = zbase;
vmax.x = xbase + 8 * UNITSIZE;
vmax.z = zbase + 8 * UNITSIZE;
update_intersect_points();
// use absolute y pos in vertices
ybase = 0.0f;
tmp_chunk_header.ypos = 0.0f;
}
// - MCNR ----------------------------------------------
{
f->seek(base + tmp_chunk_header.ofsNormal);
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCNR');
auto& tile_buffer = mt->getChunkHeightmapBuffer();
int chunk_start = (px * 16 + py) * mapbufsize * 4;
char nor[3];
for (int i = 0; i < mapbufsize; ++i)
{
f->read(nor, 3);
int pixel_start = chunk_start + i * 4;
tile_buffer[pixel_start] = nor[0] / 127.0f;
tile_buffer[pixel_start + 1] = nor[2] / 127.0f;
tile_buffer[pixel_start + 2] = nor[1] / 127.0f;
}
}
// - MCSH ----------------------------------------------
if((header_flags.flags.has_mcsh) && tmp_chunk_header.ofsShadow && tmp_chunk_header.sizeShadow)
{
f->seek(base + tmp_chunk_header.ofsShadow);
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCSH');
char compressed_shadow_map[64 * 64 / 8];
// shadow map 64 x 64
f->read(&compressed_shadow_map, 0x200);
f->seekRelative(-0x200);
uint8_t *p;
char *c;
p = _shadow_map;
c = compressed_shadow_map;
for (int i = 0; i<64 * 8; ++i)
{
for (int b = 0x01; b != 0x100; b <<= 1)
{
*p++ = ((*c) & b) ? 85 : 0;
}
c++;
}
if (!header_flags.flags.do_not_fix_alpha_map)
{
for (std::size_t i(0); i < 64; ++i)
{
_shadow_map[i * 64 + 63] = _shadow_map[i * 64 + 62];
_shadow_map[63 * 64 + i] = _shadow_map[62 * 64 + i];
}
_shadow_map[63 * 64 + 63] = _shadow_map[62 * 64 + 62];
}
}
else
{
/** We have no shadow map (MCSH), so we got no shadows at all! **
** This results in everything being black.. Yay. Lets fake it! **/
memset(_shadow_map, 0, 64 * 64);
}
// - MCCV ----------------------------------------------
if(tmp_chunk_header.ofsMCCV)
{
f->seek(base + tmp_chunk_header.ofsMCCV);
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCCV');
if (!(header_flags.flags.has_mccv))
{
header_flags.flags.has_mccv = 1;
}
hasMCCV = true;
unsigned char t[4];
for (int i = 0; i < mapbufsize; ++i)
{
f->read(t, 4);
mccv[i] = glm::vec3((float)t[2] / 127.0f, (float)t[1] / 127.0f, (float)t[0] / 127.0f);
}
}
else
{
glm::vec3 mccv_default(1.f, 1.f, 1.f);
for (int i = 0; i < mapbufsize; ++i)
{
mccv[i] = mccv_default;
}
}
if (tmp_chunk_header.sizeLiquid > 8)
{
f->seek(base + tmp_chunk_header.ofsLiquid);
f->read(&fourcc, 4);
f->seekRelative(4); // ignore the size here, the valid size is in the header
assert(fourcc == 'MCLQ');
int layer_count = (tmp_chunk_header.sizeLiquid - 8) / sizeof(mclq);
std::vector<mclq> layers(layer_count);
f->read(layers.data(), sizeof(mclq)*layer_count);
mt->Water.getChunk(px, py)->from_mclq(layers);
// remove the liquid flags as it'll be saved as MH2O
header_flags.value &= ~(0xF << 2);
}
vcenter = (vmin + vmax) * 0.5f;
if (tmp_chunk_header.nSndEmitters)
{
f->seek(base + tmp_chunk_header.ofsSndEmitters);
f->read(&fourcc, 4);
f->read(&size, 4);
assert(fourcc == 'MCSE');
for (unsigned int i = 0; i < tmp_chunk_header.nSndEmitters; i++)
{
ENTRY_MCSE sound_emitter;
f->read(&sound_emitter, sizeof(ENTRY_MCSE));
float pos_x = sound_emitter.pos[1] * -1.0f + ZEROPOINT;
float pos_y = sound_emitter.pos[2];
float pos_z = sound_emitter.pos[0] * -1.0f + ZEROPOINT;
sound_emitter.pos[0] = pos_x;
sound_emitter.pos[1] = pos_y;
sound_emitter.pos[2] = pos_z;
sound_emitters.emplace_back(sound_emitter);
}
}
}
int MapChunk::indexLoD(int x, int y)
{
return (x + 1) * 9 + x * 8 + y;
}
int MapChunk::indexNoLoD(int x, int y)
{
return x * 8 + x * 9 + y;
}
void MapChunk::update_intersect_points()
{
// update the center of the chunk and visibility when the vertices changed
vcenter = (vmin + vmax) * 0.5f;
}
std::vector<uint8_t> MapChunk::compressed_shadow_map() const
{
std::vector<uint8_t> shadow_map(64 * 64 / 8);
for (int i = 0; i < 64 * 64; ++i)
{
if (_shadow_map[i])
{
shadow_map[i / 8] |= 1 << i % 8;
}
}
return shadow_map;
}
bool MapChunk::has_shadows() const
{
for (int i = 0; i < 64 * 64; ++i)
{
if (_shadow_map[i])
{
return true;
}
}
return false;
}
bool MapChunk::GetVertex(float x, float z, glm::vec3 *V)
{
float xdiff, zdiff;
xdiff = x - xbase;
zdiff = z - zbase;
const int row = static_cast<int>(zdiff / (UNITSIZE * 0.5f) + 0.5f);
const int column = static_cast<int>((xdiff - UNITSIZE * 0.5f * (row % 2)) / UNITSIZE + 0.5f);
if ((row < 0) || (column < 0) || (row > 16) || (column >((row % 2) ? 8 : 9)))
return false;
*V = mVertices[17 * (row / 2) + ((row % 2) ? 9 : 0) + column];
return true;
}
void MapChunk::getVertexInternal(float x, float z, glm::vec3* v)
{
float xdiff, zdiff;
xdiff = x - xbase;
zdiff = z - zbase;
const int row = static_cast<int>(zdiff / (UNITSIZE * 0.5f) + 0.5f);
const int column = static_cast<int>((xdiff - UNITSIZE * 0.5f * (row % 2)) / UNITSIZE + 0.5f);
*v = mVertices[17 * (row / 2) + ((row % 2) ? 9 : 0) + column];
}
float MapChunk::getHeight(int x, int z)
{
if (x > 9 || z > 9 || x < 0 || z < 0) return 0.0f;
return mVertices[indexNoLoD(x, z)].y;
}
void MapChunk::clearHeight()
{
for (int i = 0; i < mapbufsize; ++i)
{
mVertices[i].y = 0.0f;
}
vmin.y = 0.0f;
vmax.y = 0.0f;
update_intersect_points();
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
void MapChunk::draw ( math::frustum const& frustum
, OpenGL::Scoped::use_program& mcnk_shader
, const float& cull_distance
, const glm::vec3& camera
, bool need_visibility_update
, bool show_unpaintable_chunks
, bool draw_paintability_overlay
, bool draw_chunk_flag_overlay
, bool draw_areaid_overlay
, std::map<int, misc::random_color>& area_id_colors
, int animtime
, display_mode display
, std::array<int, 4>& textures_bound
)
{
ZoneScopedN("MapChunk::draw()");
/*
bool cantPaint = show_unpaintable_chunks
&& draw_paintability_overlay
&& Noggit::Ui::selected_texture::get()
&& !canPaintTexture(*Noggit::Ui::selected_texture::get());
{
ZoneScopedN("MapChunk::draw() : Binding textures");
if (texture_set->num())
{
texture_set->bind_alpha(0);
for (int i = 0; i < texture_set->num(); ++i)
{
//texture_set->bindTexture(i, i + 1, textures_bound);
//mcnk_shader.uniform("tex_temp_" + std::to_string(i), (*texture_set->getTextures())[i]->array_index());
if (texture_set->is_animated(i))
{
mcnk_shader.uniform("tex_anim_"+ std::to_string(i), texture_set->anim_uv_offset(i, animtime));
}
}
}
OpenGL::texture::set_active_texture(5);
shadow.bind();
}
{
ZoneScopedN("MapChunk::draw() : Setting uniforms");
mcnk_shader.uniform("layer_count", (int)texture_set->num());
mcnk_shader.uniform("cant_paint", (int)cantPaint);
if (draw_chunk_flag_overlay)
{
mcnk_shader.uniform ("draw_impassible_flag", (int)header_flags.flags.impass);
}
if (draw_areaid_overlay)
{
mcnk_shader.uniform("areaid_color", (math::vector_4d)area_id_colors[areaID]);
}
}
{
ZoneScopedN("MapChunk::draw() : VAO/Buffer bindings");
}
{
ZoneScopedN("MapChunk::draw() : Draw call");
gl.drawElements(GL_TRIANGLES, _lod_level_indice_count, GL_UNSIGNED_SHORT, nullptr);
}
{
ZoneScopedN("MapChunk::draw() : Defaulting tex anim uniforms");
for (int i = 0; i < texture_set->num(); ++i)
{
if (texture_set->is_animated(i))
{
mcnk_shader.uniform("tex_anim_" + std::to_string(i), math::vector_2d());
}
}
}
*/
}
bool MapChunk::intersect (math::ray const& ray, selection_result* results)
{
if (!ray.intersect_bounds (vmin, vmax))
{
return false;
}
static constexpr std::array<std::uint16_t, 768> 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
};
bool intersection_found = false;
for (int i (0); i < indices.size(); i += 3)
{
if ( auto distance = ray.intersect_triangle ( mVertices[indices[i + 0]]
, mVertices[indices[i + 1]]
, mVertices[indices[i + 2]]
)
)
{
results->emplace_back
(*distance, selected_chunk_type (this, std::make_tuple(indices[i], indices[i + 1],
indices[i + 2]), ray.position (*distance)));
intersection_found = true;
}
}
return intersection_found;
}
void MapChunk::updateVerticesData()
{
// This method assumes tile's heightmap texture is currently bound to the active tex unit
if (!(_chunk_update_flags & ChunkUpdateFlags::VERTEX))
return;
vmin.y = std::numeric_limits<float>::max();
vmax.y = std::numeric_limits<float>::lowest();
auto& tile_buffer = mt->getChunkHeightmapBuffer();
int chunk_start = (px * 16 + py) * mapbufsize * 4;
for (int i(0); i < mapbufsize; ++i)
{
vmin.y = std::min(vmin.y, mVertices[i].y);
vmax.y = std::max(vmax.y, mVertices[i].y);
tile_buffer[chunk_start + i * 4 + 3] = mVertices[i].y;
}
mt->markExtentsDirty();
update_intersect_points();
}
void MapChunk::recalcExtents()
{
for (int i(0); i < mapbufsize; ++i)
{
vmin.y = std::min(vmin.y, mVertices[i].y);
vmax.y = std::max(vmax.y, mVertices[i].y);
}
}
glm::vec3 MapChunk::getNeighborVertex(int i, unsigned dir)
{
// i - vertex index
// 0 - up_left
// 1 - up_right
// 2 - down_left
// 3 - down_right
// Returns a neighboring vertex,
// if one does not exist,
// returns a virtual vertex with the height that equals to the height of MapChunks's vertex i.
constexpr float half_unit = UNITSIZE / 2.f;
static constexpr std::array xdiff{-half_unit, half_unit, half_unit, -half_unit};
static constexpr std::array zdiff{-half_unit, -half_unit, half_unit, half_unit};
static constexpr std::array idiff{-9, -8, 9, 8};
float vertex_x = mVertices[i].x + xdiff[dir];
float vertex_z = mVertices[i].z + zdiff[dir];
TileIndex tile({vertex_x, 0, vertex_z});
glm::vec3 result{};
if (tile.x == mt->index.x && tile.z == mt->index.z)
{
mt->getVertexInternal(vertex_x, vertex_z, &result);
}
else if (mt->_world->mapIndex.tileLoaded(tile))
{
mt->_world->mapIndex.getTile(tile)->getVertexInternal(vertex_x, vertex_z, &result);
}
else
{
result = {vertex_x, mVertices[i].y, vertex_z};
}
return result;
/*
if ((i >= 1 && i <= 7 && dir < 2 && !py)
|| (i >= 137 && i <= 143 && (dir == 2 || dir == 3) && py == 15)
|| (!i && (dir < 2 && !py || (!dir || dir == 3) && !px))
|| (i == 8 && ((dir == 1 || dir == 2) && px == 15) || dir < 2 && !py)
|| (i == 136 && ((dir == 3 || dir == 2) && py == 15) || (!dir || dir == 3) && !px)
|| (i == 144 && ((dir == 3 || dir == 2) && py == 15) || ((dir == 1 || dir == 2) && px == 15))
|| (!(i % 17) && (!dir || dir == 3) && !px)
|| (!(i % 25) && (dir == 1 || dir == 2) && px == 15))
{
float vertex_x = mVertices[i].x + xdiff[dir];
float vertex_z = mVertices[i].z + zdiff[dir];
tile_index tile({vertex_x, 0, vertex_z});
if (!mt->_world->mapIndex.tileLoaded(tile))
{
return glm::vec3( mVertices[i].x + xdiff[dir], mVertices[i].y, mVertices[i].z + zdiff[dir]);
}
glm::vec3 vertex{};
mt->_world->mapIndex.getTile(tile)->getVertexInternal(mVertices[i].x + xdiff[dir], mVertices[i].z + zdiff[dir], &vertex);
return glm::vec3( mVertices[i].x + xdiff[dir], vertex.y, mVertices[i].z + zdiff[dir]);
}
switch (dir)
{
case 0:
{
if (!i)
return mt->getChunk(px - 1, py - 1)->mVertices[135];
else if (i == 136)
return mt->getChunk(px - 1, py)->mVertices[135];
else if (i == 8)
return mt->getChunk(px, py - 1)->mVertices[135];
else if (i >= 1 && i <= 7)
return mt->getChunk(px, py - 1)->mVertices[127 + i];
else if (!(i % 17))
return mt->getChunk(px - 1, py)->mVertices[i - 1];
break;
}
case 1:
{
if (!i)
return mt->getChunk(px, py - 1)->mVertices[128];
else if (i == 144)
return mt->getChunk(px + 1, py)->mVertices[128];
else if (i == 8)
return mt->getChunk(px + 1, py - 1)->mVertices[128];
else if (i >= 1 && i <= 7)
return mt->getChunk(px, py - 1)->mVertices[128 + i];
else if (!(i % 17 % 8) && i % 17 != 16 && i % 17)
return mt->getChunk(px + 1, py)->mVertices[i - 8];
break;
}
case 2:
{
if (i == 136)
return mt->getChunk(px, py + 1)->mVertices[9];
else if (i == 144)
return mt->getChunk(px + 1, py + 1)->mVertices[9];
else if (i == 8)
return mt->getChunk(px + 1, py)->mVertices[9];
else if (!(i % 17 % 8) && i % 17 != 16 && i && i % 17)
return mt->getChunk(px + 1, py)->mVertices[i + 9];
else if (i >= 137 && i <= 143)
return mt->getChunk(px, py + 1)->mVertices[i - 127];
break;
}
case 3:
{
if (!i)
return mt->getChunk(px - 1, py)->mVertices[16];
else if (i == 136)
return mt->getChunk(px - 1, py + 1)->mVertices[16];
else if (i == 144)
return mt->getChunk(px, py + 1)->mVertices[16];
else if (!(i % 17))
return mt->getChunk(px - 1, py)->mVertices[i + 16];
else if (i >= 137 && i <= 143)
return mt->getChunk(px, py + 1)->mVertices[i - 128];
break;
}
}
return mVertices[i + idiff[dir]];
*/
}
glm::uvec2 MapChunk::getUnitIndextAt(glm::vec3 pos)
{
glm::uvec2 unit_index = glm::uvec2(floor((pos.x - xbase) / UNITSIZE), floor((pos.z - zbase) / UNITSIZE));
if (unit_index.x > 8 || unit_index.y > 8)
return glm::uvec2(8, 8);
return unit_index;
}
void MapChunk::recalcNorms()
{
// 0 - up_left
// 1 - up_right
// 2 - down_left
// 3 - down_right
auto& tile_buffer = mt->getChunkHeightmapBuffer();
int chunk_start = (px * 16 + py) * mapbufsize * 4;
for (int i = 0; i < mapbufsize; ++i)
{
glm::vec3 const P1 (getNeighborVertex(i, 0)); // up_left
glm::vec3 const P2 (getNeighborVertex(i, 1)); // up_right
glm::vec3 const P3 (getNeighborVertex(i, 2)); // down_left
glm::vec3 const P4 (getNeighborVertex(i, 3)); // down_right
glm::vec3 const N1 = glm::cross((P2 - mVertices[i]) , (P1 - mVertices[i]));
glm::vec3 const N2 = glm::cross((P3 - mVertices[i]) , (P2 - mVertices[i]));
glm::vec3 const N3 = glm::cross((P4 - mVertices[i]) , (P3 - mVertices[i]));
glm::vec3 const N4 = glm::cross((P1 - mVertices[i]) , (P4 - mVertices[i]));
glm::vec3 Norm (N1 + N2 + N3 + N4);
Norm = glm::normalize(Norm);
Norm.x = std::floor(Norm.x * 127) / 127;
Norm.y = std::floor(Norm.y * 127) / 127;
Norm.z = std::floor(Norm.z * 127) / 127;
//! \todo: find out why recalculating normals without changing the terrain result in slightly different normals
int pixel_start = chunk_start + i * 4;
tile_buffer[pixel_start] = -Norm.z;
tile_buffer[pixel_start + 1] = Norm.y;
tile_buffer[pixel_start + 2] = -Norm.x;
}
registerChunkUpdate(ChunkUpdateFlags::NORMALS);
}
void MapChunk::updateNormalsData()
{
//gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, px * 16 + py, mapbufsize, 1, GL_RGB, GL_FLOAT, mNormals);
}
bool MapChunk::changeTerrain(glm::vec3 const& pos, float change, float radius, int BrushType, float inner_radius)
{
//float dist, xdiff, zdiff;
bool changed = false;
for (int i = 0; i < mapbufsize; ++i)
{
float dt = change;
if (changeTerrainProcessVertex(pos, mVertices[i], dt, radius, inner_radius, BrushType))
{
changed = true;
mVertices[i].y += dt;
}
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
return changed;
}
bool MapChunk::ChangeMCCV(glm::vec3 const& pos, glm::vec4 const& color, float change, float radius, bool editMode)
{
float dist;
bool changed = false;
if (!hasMCCV)
{
initMCCV();
changed = true;
}
for (int i = 0; i < mapbufsize; ++i)
{
dist = misc::dist(mVertices[i], pos);
if (dist <= radius)
{
float edit = change * (1.0f - dist / radius);
if (editMode)
{
mccv[i].x += (color.x / 0.5f - mccv[i].x)* edit;
mccv[i].y += (color.y / 0.5f - mccv[i].y)* edit;
mccv[i].z += (color.z / 0.5f - mccv[i].z)* edit;
}
else
{
mccv[i].x += (1.0f - mccv[i].x) * edit;
mccv[i].y += (1.0f - mccv[i].y) * edit;
mccv[i].z += (1.0f - mccv[i].z) * edit;
}
mccv[i].x = std::min(std::max(mccv[i].x, 0.0f), 2.0f);
mccv[i].y = std::min(std::max(mccv[i].y, 0.0f), 2.0f);
mccv[i].z = std::min(std::max(mccv[i].z, 0.0f), 2.0f);
changed = true;
}
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::MCCV);
}
return changed;
}
bool MapChunk::stampMCCV(glm::vec3 const& pos, glm::vec4 const& color, float change, float radius, bool editMode, QImage* img, bool paint, bool use_image_colors)
{
float dist;
bool changed = false;
if (!hasMCCV)
{
initMCCV();
changed = true;
}
for (int i = 0; i < mapbufsize; ++i)
{
dist = misc::dist(mVertices[i], pos);
if(std::abs(pos.x - mVertices[i].x) > radius || std::abs(pos.z - mVertices[i].z) > radius)
continue;
glm::vec3 const diff{mVertices[i] - pos};
int pixel_x = std::round(((diff.x + radius) / (2.f * radius)) * img->width());
int pixel_y = std::round(((diff.z + radius) / (2.f * radius)) * img->height());
if (use_image_colors)
{
QColor image_color;
if (pixel_x >= 0 && pixel_x < img->width() && pixel_y >= 0 && pixel_y < img->height())
{
image_color = img->pixelColor(pixel_x, pixel_y);
}
else
{
image_color = QColor(Qt::black);
}
mccv[i].x = image_color.redF() / 0.5f;
mccv[i].y = image_color.greenF() / 0.5f;
mccv[i].z = image_color.blueF() / 0.5f;
mccv[i].x = std::min(std::max(mccv[i].x, 0.0f), 2.0f);
mccv[i].y = std::min(std::max(mccv[i].y, 0.0f), 2.0f);
mccv[i].z = std::min(std::max(mccv[i].z, 0.0f), 2.0f);
}
else
{
float image_factor;
if (pixel_x >= 0 && pixel_x < img->width() && pixel_y >= 0 && pixel_y < img->height())
{
auto mask_color = img->pixelColor(pixel_x, pixel_y);
image_factor = (mask_color.redF() + mask_color.greenF() + mask_color.blueF()) / 3.0f;
}
else
{
image_factor = 0.f;
}
float edit = image_factor * (paint ? ((change * (1.0f - dist / radius))) : (change * 20.f));
if (editMode)
{
mccv[i].x += (color.x / 0.5f - mccv[i].x)* edit;
mccv[i].y += (color.y / 0.5f - mccv[i].y)* edit;
mccv[i].z += (color.z / 0.5f - mccv[i].z)* edit;
}
else
{
mccv[i].x += (1.0f - mccv[i].x) * edit;
mccv[i].y += (1.0f - mccv[i].y) * edit;
mccv[i].z += (1.0f - mccv[i].z) * edit;
}
mccv[i].x = std::min(std::max(mccv[i].x, 0.0f), 2.0f);
mccv[i].y = std::min(std::max(mccv[i].y, 0.0f), 2.0f);
mccv[i].z = std::min(std::max(mccv[i].z, 0.0f), 2.0f);
}
changed = true;
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::MCCV);
}
return changed;
}
void MapChunk::update_vertex_colors()
{
if (_chunk_update_flags & ChunkUpdateFlags::MCCV)
gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, px * 16 + py, mapbufsize, 1, GL_RGB, GL_FLOAT, mccv);
}
glm::vec3 MapChunk::pickMCCV(glm::vec3 const& pos)
{
float dist;
float cur_dist = UNITSIZE;
if (!hasMCCV)
{
return glm::vec3(1.0f, 1.0f, 1.0f);
}
int v_index = 0;
for (int i = 0; i < mapbufsize; ++i)
{
dist = misc::dist(mVertices[i], pos);
if (dist <= cur_dist)
{
cur_dist = dist;
v_index = i;
}
}
return mccv[v_index];
}
bool MapChunk::flattenTerrain ( glm::vec3 const& pos
, float remain
, float radius
, int BrushType
, flatten_mode const& mode
, glm::vec3 const& origin
, math::degrees angle
, math::degrees orientation
)
{
bool changed (false);
for (int i(0); i < mapbufsize; ++i)
{
float const dist(misc::dist(mVertices[i], pos));
if (dist >= radius)
{
continue;
}
float const ah(origin.y
+ ((mVertices[i].x - origin.x) * glm::cos(math::radians(orientation)._)
+ (mVertices[i].z - origin.z) * glm::sin(math::radians(orientation)._)
) * glm::tan(math::radians(angle)._)
);
if ((!mode.lower && ah < mVertices[i].y)
|| (!mode.raise && ah > mVertices[i].y)
)
{
continue;
}
if (BrushType == eFlattenType_Origin)
{
mVertices[i].y = origin.y;
changed = true;
continue;
}
mVertices[i].y = glm::mix
(
mVertices[i].y
, ah
, BrushType == eFlattenType_Flat ? remain
: BrushType == eFlattenType_Linear ? remain * (1.f - dist / radius)
: BrushType == eFlattenType_Smooth ? pow (remain, 1.f + dist / radius)
: throw std::logic_error ("bad brush type")
);
changed = true;
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
return changed;
}
bool MapChunk::blurTerrain ( glm::vec3 const& pos
, float remain
, float radius
, int BrushType
, flatten_mode const& mode
/*, std::function<std::optional<float>(float, float)> height*/
)
{
bool changed = false;
if (BrushType == eFlattenType_Origin)
{
return false;
}
for (int i (0); i < mapbufsize; ++i)
{
float const dist = misc::dist(mVertices[i], pos);
if (dist >= radius)
{
continue;
}
int Rad = (int)(radius / UNITSIZE);
float TotalHeight = 0;
float TotalWeight = 0;
for (int j = -Rad * 2; j <= Rad * 2; ++j)
{
float tz = pos.z + j * UNITSIZE / 2;
for (int k = -Rad; k <= Rad; ++k)
{
float const tx = pos.x + k*UNITSIZE + (j % 2) * UNITSIZE / 2.0f;
float const dist2 = misc::dist (tx, tz, mVertices[i].x, mVertices[i].z);
if (dist2 > radius)
continue;
glm::vec3 vec;
bool res = mt->getWorld()->GetVertex(tx, tz, &vec);
if (res)
{
// float h = vec.y;
TotalHeight += (1.0f - dist2 / radius) * vec.y;
TotalWeight += (1.0f - dist2 / radius);
}
/*
// auto h (height (tx, tz));
auto h = height(tx, tz);
if (h)
{
TotalHeight += (1.0f - dist2 / radius) * h.value();
TotalWeight += (1.0f - dist2 / radius);
}*/
}
}
float target = TotalHeight / TotalWeight;
float& y = mVertices[i].y;
if ((target > y && !mode.raise) || (target < y && !mode.lower))
{
continue;
}
y = glm::mix
(
y,
target,
BrushType == eFlattenType_Flat ? remain
: BrushType == eFlattenType_Linear ? remain * (1.f - dist / radius)
: BrushType == eFlattenType_Smooth ? pow (remain, 1.f + dist / radius)
: throw std::logic_error ("bad brush type")
);
changed = true;
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
return changed;
}
bool MapChunk::changeTerrainProcessVertex(glm::vec3 const& pos, glm::vec3 const& vertex, float& dt,
float radiusOuter, float radiusInner, int brushType)
{
float dist, xdiff, zdiff;
bool changed = false;
xdiff = vertex.x - pos.x;
zdiff = vertex.z - pos.z;
if (brushType == eTerrainType_Quadra)
{
if ((std::abs(xdiff) < std::abs(radiusOuter / 2)) && (std::abs(zdiff) < std::abs(radiusOuter / 2)))
{
dist = std::sqrt(xdiff*xdiff + zdiff*zdiff);
dt = dt * (1.0f - dist * radiusInner / radiusOuter);
changed = true;
}
}
else
{
dist = std::sqrt(xdiff*xdiff + zdiff*zdiff);
if (dist < radiusOuter)
{
changed = true;
switch (brushType)
{
case eTerrainType_Flat:
break;
case eTerrainType_Linear:
dt = dt * (1.0f - dist * (1.0f - radiusInner) / radiusOuter);
break;
case eTerrainType_Smooth:
dt = dt / (1.0f + dist / radiusOuter);
break;
case eTerrainType_Polynom:
dt = dt * ((dist / radiusOuter)*(dist / radiusOuter) + dist / radiusOuter + 1.0f);
break;
case eTerrainType_Trigo:
dt = dt * cos(dist / radiusOuter);
break;
case eTerrainType_Gaussian:
dt = dist < radiusOuter * radiusInner ? dt * std::exp(-(std::pow(radiusOuter * radiusInner / radiusOuter, 2) / (2 * std::pow(0.39f, 2)))) : dt * std::exp(-(std::pow(dist / radiusOuter, 2) / (2 * std::pow(0.39f, 2))));
break;
default:
LogError << "Invalid terrain edit type (" << brushType << ")" << std::endl;
changed = false;
break;
}
}
}
return changed;
}
auto MapChunk::stamp(glm::vec3 const& pos, float dt, QImage const* img, float radiusOuter
, float radiusInner, int brushType, bool sculpt) -> void
{
if (sculpt)
{
for(int i{}; i < mapbufsize; ++i)
{
if(std::abs(pos.x - mVertices[i].x) > radiusOuter || std::abs(pos.z - mVertices[i].z) > radiusOuter)
continue;
float delta = dt;
changeTerrainProcessVertex(pos, mVertices[i], delta, radiusOuter, radiusInner, brushType);
glm::vec3 const diff{mVertices[i] - pos};
int pixel_x = std::round(((diff.x + radiusOuter) / (2.f * radiusOuter)) * img->width());
int pixel_y = std::round(((diff.z + radiusOuter) / (2.f * radiusOuter)) * img->height());
float image_factor;
if (pixel_x >= 0 && pixel_x < img->width() && pixel_y >= 0 && pixel_y < img->height())
{
auto color = img->pixelColor(pixel_x, pixel_y);
image_factor = (color.redF() + color.greenF() + color.blueF()) / 3.0f;
}
else
{
image_factor = 0;
}
mVertices[i].y += delta * image_factor;
}
}
else
{
auto cur_action = NOGGIT_CUR_ACTION;
float* original_heightmap = cur_action->getChunkTerrainOriginalData(this);
if (!original_heightmap)
return;
for(int i{}; i < mapbufsize; ++i)
{
if(std::abs(pos.x - mVertices[i].x) > radiusOuter || std::abs(pos.z - mVertices[i].z) > radiusOuter)
continue;
float delta = cur_action->getDelta();
changeTerrainProcessVertex(pos, mVertices[i], delta, radiusOuter, radiusInner, brushType);
glm::vec3 const diff{glm::vec3{original_heightmap[i * 3], original_heightmap[i * 3 + 1], original_heightmap[i * 3 + 2]} - pos};
int pixel_x = std::round(((diff.x + radiusOuter) / (2.f * radiusOuter)) * img->width());
int pixel_y = std::round(((diff.z + radiusOuter) / (2.f * radiusOuter)) * img->height());
float image_factor;
if (pixel_x >= 0 && pixel_x < img->width() && pixel_y >= 0 && pixel_y < img->height())
{
auto color = img->pixelColor(pixel_x, pixel_y);
image_factor = (color.redF() + color.greenF() + color.blueF()) / 3.0f;
}
else
{
image_factor = 0;
}
mVertices[i].y = original_heightmap[i * 3 + 1] + (delta * image_factor);
}
}
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
void MapChunk::eraseTextures()
{
texture_set->eraseTextures();
}
void MapChunk::eraseTexture(scoped_blp_texture_reference const& tex)
{
int textureindex = texture_set->get_texture_index_or_add(tex, 0);
if (textureindex != -1)
{
texture_set->eraseTexture(textureindex);
}
}
void MapChunk::change_texture_flag(scoped_blp_texture_reference const& tex, std::size_t flag, bool add)
{
texture_set->change_texture_flag(tex, flag, add);
}
int MapChunk::addTexture(scoped_blp_texture_reference texture)
{
return texture_set->addTexture(std::move (texture));
}
bool MapChunk::switchTexture(scoped_blp_texture_reference const& oldTexture, scoped_blp_texture_reference newTexture)
{
bool changed = texture_set->replace_texture(oldTexture, std::move (newTexture));
if (changed)
registerChunkUpdate(ChunkUpdateFlags::ALPHAMAP);
return changed;
}
bool MapChunk::paintTexture(glm::vec3 const& pos, Brush* brush, float strength, float pressure, scoped_blp_texture_reference texture)
{
return texture_set->paintTexture(xbase, zbase, pos.x, pos.z, brush, strength, pressure, std::move (texture));
}
bool MapChunk::stampTexture(glm::vec3 const& pos, Brush *brush, float strength, float pressure, scoped_blp_texture_reference texture, QImage* img, bool paint)
{
return texture_set->stampTexture(xbase, zbase, pos.x, pos.z, brush, strength, pressure, std::move (texture), img, paint);
}
bool MapChunk::replaceTexture(glm::vec3 const& pos, float radius, scoped_blp_texture_reference const& old_texture, scoped_blp_texture_reference new_texture, bool entire_chunk)
{
return texture_set->replace_texture(xbase, zbase, pos.x, pos.z, radius, old_texture, std::move (new_texture), entire_chunk);
}
bool MapChunk::canPaintTexture(scoped_blp_texture_reference texture)
{
return texture_set->canPaintTexture(texture);
}
void MapChunk::update_shadows()
{
if (_chunk_update_flags & ChunkUpdateFlags::SHADOW)
gl.texSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, px * 16 + py, 64, 64, 1, GL_RED, GL_UNSIGNED_BYTE, _shadow_map);
}
void MapChunk::clear_shadows()
{
memset(_shadow_map, 0, 64 * 64);
registerChunkUpdate(ChunkUpdateFlags::SHADOW);
}
void MapChunk::paintDetailDoodadsExclusion(glm::vec3 const& pos, float radius, bool exclusion)
{
texture_set->setDetailDoodadsExclusion(xbase, zbase, pos, radius, false, exclusion);
}
bool MapChunk::isHole(int i, int j)
{
return (holes & ((1 << ((j * 4) + i)))) != 0;
}
void MapChunk::setHole(glm::vec3 const& pos, float radius, bool big, bool add)
{
if (big)
{
holes = add ? 0xFFFFFFFF : 0x0;
}
else
{
for (int x = 0; x < 4; ++x)
{
for (int y = 0; y < 4; ++y)
{
if (misc::getShortestDist(pos.x, pos.z, xbase + (MINICHUNKSIZE * x),
zbase + (MINICHUNKSIZE * y), MINICHUNKSIZE) <= radius)
{
int v = 1 << (y * 4 + x);
holes = add ? (holes | v) : (holes & ~v);
}
}
}
}
registerChunkUpdate(ChunkUpdateFlags::HOLES);
}
void MapChunk::setAreaID(int ID)
{
areaID = ID;
registerChunkUpdate(ChunkUpdateFlags::AREA_ID);
}
int MapChunk::getAreaID()
{
return areaID;
}
void MapChunk::setFlag(bool changeto, uint32_t flag)
{
if (changeto)
{
header_flags.value |= flag;
}
else
{
header_flags.value &= ~flag;
}
registerChunkUpdate(ChunkUpdateFlags::FLAGS);
}
void MapChunk::save(util::sExtendableArray& lADTFile
, int& lCurrentPosition
, int& lMCIN_Position
, std::map<std::string, int> &lTextures
, std::vector<WMOInstance*> &lObjectInstances
, std::vector<ModelInstance*>& lModelInstances
, bool use_mclq_liquids)
{
int lID;
int lMCNK_Size = 0x80;
int lMCNK_Position = lCurrentPosition;
lADTFile.Extend(8 + 0x80); // This is only the size of the header. More chunks will increase the size.
SetChunkHeader(lADTFile, lCurrentPosition, 'MCNK', lMCNK_Size);
lADTFile.GetPointer<MCIN>(lMCIN_Position + 8)->mEntries[py * 16 + px].offset = lCurrentPosition; // check this
// MCNK data
// lADTFile.Insert(lCurrentPosition + 8, 0x80, reinterpret_cast<char*>(&(header)));
auto const lMCNK_header = lADTFile.GetPointer<MapChunkHeader>(lCurrentPosition + 8);
header_flags.flags.do_not_fix_alpha_map = use_mclq_liquids ? 0 : 1;
lMCNK_header->flags = header_flags;
lMCNK_header->ix = px;
lMCNK_header->iy = py;
lMCNK_header->zpos = zbase * -1.0f + ZEROPOINT;
lMCNK_header->xpos = xbase * -1.0f + ZEROPOINT;
lMCNK_header->ypos = ybase;
lMCNK_header->holes = holes;
lMCNK_header->areaid = areaID;
lMCNK_header->nLayers = 0;
lMCNK_header->nDoodadRefs = 0;
lMCNK_header->ofsHeight = 0;
lMCNK_header->ofsNormal = 0;
lMCNK_header->ofsLayer = 0;
lMCNK_header->ofsRefs = 0;
lMCNK_header->ofsAlpha = 0;
lMCNK_header->sizeAlpha = 0;
lMCNK_header->ofsShadow = 0;
lMCNK_header->sizeShadow = 0;
lMCNK_header->nMapObjRefs = 0;
lMCNK_header->ofsMCCV = 0;
//! \todo Implement sound emitter support. Or not.
lMCNK_header->ofsSndEmitters = 0;
lMCNK_header->nSndEmitters = 0;
lMCNK_header->ofsLiquid = 0;
//! \todo Is this still 8 if no chunk is present? Or did they correct that?
lMCNK_header->sizeLiquid = 8;
lMCNK_header->ypos = mVertices[0].y;
if(texture_set)
{
// hackfix -- temp hackfix to bruteforce update + save
texture_set->apply_alpha_changes();
texture_set->updateDoodadMapping();
std::copy(texture_set->getDoodadMappingBase(), texture_set->getDoodadMappingBase() + 8
, lMCNK_header->doodadMapping);
*reinterpret_cast<std::uint64_t*>(lMCNK_header->doodadStencil)
= *reinterpret_cast<std::uint64_t const*>(texture_set->getDoodadStencilBase());
}
else
{
std::fill(lMCNK_header->doodadMapping, lMCNK_header->doodadMapping + 8, 0);
*reinterpret_cast<std::uint64_t*>(lMCNK_header->doodadStencil) = 0;
}
lCurrentPosition += 8 + 0x80;
// MCVT
int lMCVT_Size = mapbufsize * 4;
lADTFile.Extend(8 + lMCVT_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCVT', lMCVT_Size);
// lADTFile.GetPointer<MapChunkHeader>(lMCNK_Position + 8)->ofsHeight = lCurrentPosition - lMCNK_Position;
auto header_ptr = lADTFile.GetPointer<MapChunkHeader>(lMCNK_Position + 8);
header_ptr->ofsHeight = lCurrentPosition - lMCNK_Position;
auto const lHeightmap = lADTFile.GetPointer<float>(lCurrentPosition + 8);
for (int i = 0; i < mapbufsize; ++i)
lHeightmap[i] = mVertices[i].y - mVertices[0].y;
lCurrentPosition += 8 + lMCVT_Size;
lMCNK_Size += 8 + lMCVT_Size;
// MCCV
int lMCCV_Size = 0;
if (hasMCCV)
{
lMCCV_Size = mapbufsize * sizeof(unsigned int);
lADTFile.Extend(8 + lMCCV_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCCV', lMCCV_Size);
header_ptr->ofsMCCV = lCurrentPosition - lMCNK_Position;
auto const lmccv = lADTFile.GetPointer<unsigned int>(lCurrentPosition + 8);
for (int i = 0; i < mapbufsize; ++i)
{
lmccv[i] = (((unsigned char)(mccv[i].z * 127.0f) & 0xFF) << 0)
+ (((unsigned char)(mccv[i].y * 127.0f) & 0xFF) << 8)
+ (((unsigned char)(mccv[i].x * 127.0f) & 0xFF) << 16);
}
lCurrentPosition += 8 + lMCCV_Size;
lMCNK_Size += 8 + lMCCV_Size;
}
else
{
header_ptr->ofsMCCV = 0;
}
// MCNR
int lMCNR_Size = mapbufsize * 3;
lADTFile.Extend(8 + lMCNR_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCNR', lMCNR_Size);
header_ptr->ofsNormal = lCurrentPosition - lMCNK_Position;
auto const lNormals = lADTFile.GetPointer<char>(lCurrentPosition + 8);
auto& tile_buffer = mt->getChunkHeightmapBuffer();
int chunk_start = (px * 16 + py) * mapbufsize * 4;
for (int i = 0; i < mapbufsize; ++i)
{
int pixel_start = chunk_start + i * 4;
lNormals[i * 3 + 0] = static_cast<char>(tile_buffer[pixel_start] * 127);
lNormals[i * 3 + 1] = static_cast<char>(tile_buffer[pixel_start + 2] * 127);
lNormals[i * 3 + 2] = static_cast<char>(tile_buffer[pixel_start + 1] * 127);
}
lCurrentPosition += 8 + lMCNR_Size;
lMCNK_Size += 8 + lMCNR_Size;
// }
// Unknown MCNR bytes
// These are not in as we have data or something but just to make the files more blizzlike.
// {
lADTFile.Extend(13);
lCurrentPosition += 13;
lMCNK_Size += 13;
// }
// MCLY
// {
size_t lMCLY_Size = texture_set ? texture_set->num() * 0x10 : 0;
lADTFile.Extend(static_cast<long>(8 + lMCLY_Size));
SetChunkHeader(lADTFile, lCurrentPosition, 'MCLY', static_cast<int>(lMCLY_Size));
header_ptr->ofsLayer = lCurrentPosition - lMCNK_Position;
header_ptr->nLayers = texture_set ? static_cast<std::uint32_t>(texture_set->num()) : 0;
std::vector<std::vector<uint8_t>> alphamaps;
int lMCAL_Size = 0;
if (texture_set)
{
alphamaps = texture_set->save_alpha(use_big_alphamap);
// MCLY data
for (size_t j = 0; j < texture_set->num(); ++j)
{
auto const lLayer = lADTFile.GetPointer<ENTRY_MCLY>(lCurrentPosition + 8 + 0x10 * static_cast<unsigned long>(j));
lLayer->textureID = lTextures.find(texture_set->filename(j))->second;
lLayer->flags = texture_set->flag(j);
lLayer->ofsAlpha = lMCAL_Size;
lLayer->effectID = texture_set->effect(j);
if (j == 0)
{
lLayer->flags &= ~(FLAG_USE_ALPHA | FLAG_ALPHA_COMPRESSED);
}
else
{
lLayer->flags |= FLAG_USE_ALPHA;
//! \todo find out why compression fuck up textures ingame
lLayer->flags &= ~FLAG_ALPHA_COMPRESSED;
lMCAL_Size += static_cast<int>(alphamaps[j - 1].size());
}
}
}
lCurrentPosition += 8 + static_cast<int>(lMCLY_Size);
lMCNK_Size += 8 + static_cast<int>(lMCLY_Size);
// }
// MCRF
// {
std::list<int> lDoodadIDs;
std::list<int> lObjectIDs;
std::array<glm::vec3, 2> lChunkExtents;
lChunkExtents[0] = glm::vec3(xbase, 0.0f, zbase);
lChunkExtents[1] = glm::vec3(xbase + CHUNKSIZE, 0.0f, zbase + CHUNKSIZE);
// search all wmos that are inside this chunk
lID = 0;
for(auto const& wmo : lObjectInstances)
{
if (wmo->isInsideRect(&lChunkExtents))
{
lObjectIDs.push_back(lID);
}
lID++;
}
// search all models that are inside this chunk
lID = 0;
for(auto const& model : lModelInstances)
{
if (model->isInsideRect(&lChunkExtents))
{
lDoodadIDs.push_back(lID);
}
lID++;
}
int lMCRF_Size = static_cast<int>(4 * (lDoodadIDs.size() + lObjectIDs.size()));
lADTFile.Extend(8 + lMCRF_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCRF', lMCRF_Size);
header_ptr->ofsRefs = lCurrentPosition - lMCNK_Position;
header_ptr->nDoodadRefs = static_cast<std::uint32_t>(lDoodadIDs.size());
header_ptr->nMapObjRefs = static_cast<std::uint32_t>(lObjectIDs.size());
// MCRF data
auto const lReferences = lADTFile.GetPointer<int>(lCurrentPosition + 8);
lID = 0;
for (std::list<int>::iterator it = lDoodadIDs.begin(); it != lDoodadIDs.end(); ++it)
{
lReferences[lID] = *it;
lID++;
}
for (std::list<int>::iterator it = lObjectIDs.begin(); it != lObjectIDs.end(); ++it)
{
lReferences[lID] = *it;
lID++;
}
lCurrentPosition += 8 + lMCRF_Size;
lMCNK_Size += 8 + lMCRF_Size;
// }
// MCSH
if (has_shadows())
{
// header_flags.flags.has_mcsh = 1;
lMCNK_header->flags.flags.has_mcsh = 1;
int lMCSH_Size = 0x200;
lADTFile.Extend(8 + lMCSH_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCSH', lMCSH_Size);
header_ptr->ofsShadow = lCurrentPosition - lMCNK_Position;
header_ptr->sizeShadow = 0x200;
auto const lLayer = lADTFile.GetPointer<char>(lCurrentPosition + 8);
auto shadow_map = compressed_shadow_map();
memcpy(lLayer.get(), shadow_map.data(), 0x200);
lCurrentPosition += 8 + lMCSH_Size;
lMCNK_Size += 8 + lMCSH_Size;
}
else
{
lMCNK_header->flags.flags.has_mcsh = 0;
// header_flags.flags.has_mcsh = 0;
header_ptr->ofsShadow = 0;
header_ptr->sizeShadow = 0;
}
// MCAL
lADTFile.Extend(8 + lMCAL_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCAL', lMCAL_Size);
header_ptr->ofsAlpha = lCurrentPosition - lMCNK_Position;
header_ptr->sizeAlpha = 8 + lMCAL_Size;
auto lAlphaMaps = lADTFile.GetPointer<char>(lCurrentPosition + 8);
for (auto& alpha : alphamaps)
{
memcpy(lAlphaMaps.get(), alpha.data(), alpha.size());
lAlphaMaps += alpha.size();
}
lCurrentPosition += 8 + lMCAL_Size;
lMCNK_Size += 8 + lMCAL_Size;
if (use_mclq_liquids)
{
auto liquids = liquid_chunk();
if (liquids && liquids->layer_count() > 0)
{
int liquids_size = 8 + liquids->layer_count() * sizeof(mclq);
lMCNK_Size += liquids_size;
header_ptr->sizeLiquid = liquids_size;
header_ptr->ofsLiquid = lCurrentPosition - lMCNK_Position;
// current position updated inside
liquids->save_mclq(lADTFile, lMCNK_Position, lCurrentPosition);
}
// no liquid, vanilla adt still have an empty chunk
else
{
lADTFile.Extend(8);
// size seems to be 0 in vanilla adts in the mclq chunk's header and set right in the mcnk header (layer_size * n_layer + 8)
SetChunkHeader(lADTFile, lCurrentPosition, 'MCLQ', 0);
header_ptr->sizeLiquid = 8;
header_ptr->ofsLiquid = lCurrentPosition - lMCNK_Position;
// When saving a tile that had MLCQ, also remove flags in MCNK
// Client sees the flag and loads random data as if it were MCLQ, which we dont save.
// clear MCLQ liquid flags (0x4, 0x8, 0x10, 0x20)
header_ptr->flags.value &= 0xFFFFFFC3;
lCurrentPosition += 8;
lMCNK_Size += 8;
}
}
// MCSE
int lMCSE_Size = sizeof(ENTRY_MCSE) * sound_emitters.size();
lADTFile.Extend(8 + lMCSE_Size);
SetChunkHeader(lADTFile, lCurrentPosition, 'MCSE', lMCSE_Size);
header_ptr->ofsSndEmitters = lCurrentPosition - lMCNK_Position;
header_ptr->nSndEmitters = lMCSE_Size / 0x1C;
lCurrentPosition += 8;
for (auto& sound_emitter : sound_emitters)
{
auto const MCSE = lADTFile.GetPointer<ENTRY_MCSE>(lCurrentPosition);
MCSE->soundId = sound_emitter.soundId;
MCSE->pos[0] = ZEROPOINT - sound_emitter.pos[2];
MCSE->pos[1] = ZEROPOINT - sound_emitter.pos[0];
MCSE->pos[2] = sound_emitter.pos[1];
MCSE->size[0] = sound_emitter.size[0];
MCSE->size[1] = sound_emitter.size[1];
MCSE->size[2] = sound_emitter.size[2];
lCurrentPosition += 0x1C;
}
// lCurrentPosition += 8 + lMCSE_Size;
lMCNK_Size += 8 + lMCSE_Size;
lADTFile.GetPointer<sChunkHeader>(lMCNK_Position)->mSize = lMCNK_Size;
lADTFile.GetPointer<MCIN>(lMCIN_Position + 8)->mEntries[py * 16 + px].size = lMCNK_Size + sizeof (sChunkHeader);
}
bool MapChunk::fixGapLeft(const MapChunk* chunk)
{
if (!chunk)
return false;
bool changed = false;
for (size_t i = 0; i <= 136; i+= 17)
{
float h = chunk->mVertices[i + 8].y;
if (mVertices[i].y != h)
{
mVertices[i].y = h;
changed = true;
}
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
return changed;
}
bool MapChunk::fixGapAbove(const MapChunk* chunk)
{
if (!chunk)
return false;
bool changed = false;
for (size_t i = 0; i < 9; i++)
{
float h = chunk->mVertices[i + 136].y;
if (mVertices[i].y != h)
{
mVertices[i].y = h;
changed = true;
}
}
if (changed)
{
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
return changed;
}
void MapChunk::selectVertex(glm::vec3 const& pos, float radius, std::unordered_set<glm::vec3*>& vertices)
{
if (misc::getShortestDist(pos.x, pos.z, xbase, zbase, CHUNKSIZE) > radius)
{
return;
}
for (int i = 0; i < mapbufsize; ++i)
{
if (misc::dist(pos.x, pos.z, mVertices[i].x, mVertices[i].z) <= radius)
{
vertices.emplace(&mVertices[i]);
}
}
}
void MapChunk::fixVertices(std::unordered_set<glm::vec3*>& selected)
{
std::vector<int> ids ={ 0, 1, 17, 18 };
// iterate through each "square" of vertices
for (int i = 0; i < 64; ++i)
{
int not_selected = 0, count = 0, mid_vertex = ids[0] + 9;
float h = 0.0f;
for (int& index : ids)
{
if (selected.find(&mVertices[index]) == selected.end())
{
not_selected = index;
}
else
{
count++;
}
h += mVertices[index].y;
index += (((i+1) % 8) == 0) ? 10 : 1;
}
if (count == 2)
{
mVertices[mid_vertex].y = h * 0.25f;
}
else if (count == 3)
{
mVertices[mid_vertex].y = (h - mVertices[not_selected].y) / 3.0f;
}
}
}
bool MapChunk::isBorderChunk(std::unordered_set<glm::vec3*>& selected)
{
for (int i = 0; i < mapbufsize; ++i)
{
// border chunk if at least a vertex isn't selected
if (selected.find(&mVertices[i]) == selected.end())
{
return true;
}
}
return false;
}
QImage MapChunk::getHeightmapImage(float min_height, float max_height)
{
glm::vec3* heightmap = getHeightmap();
QImage image(17, 17, QImage::Format_RGBA64);
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
for (unsigned y = 0; y < SUM; ++y)
for (unsigned x = 0; x < SUM; ++x)
{
unsigned const plain {y * SUM + x};
bool const is_virtual {static_cast<bool>(plain % 2)};
bool const erp = plain % DSUM / SUM;
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
float value = is_virtual ? (heightmap[idx].y + heightmap[idx + (erp ? SUM : 1)].y) / 2.f : heightmap[idx].y;
value = std::min(1.0f, std::max(0.0f, ((value - min_height) / (max_height - min_height))));
image.setPixelColor(x, y, QColor::fromRgbF(value, value, value, 1.0));
}
return std::move(image);
}
QImage MapChunk::getAlphamapImage(unsigned layer)
{
texture_set->apply_alpha_changes();
auto& alphamaps = *texture_set->getAlphamaps();
auto& alpha_layer = *alphamaps.at(layer - 1);
QImage image(64, 64, QImage::Format_RGBA8888);
for (int i = 0; i < 64; ++i)
{
for (int j = 0; j < 64; ++j)
{
int value = alpha_layer.getAlpha(64 * j + i);
image.setPixelColor(i, j, QColor(value, value, value, 255));
}
}
return std::move(image);
}
void MapChunk::setHeightmapImage(QImage const& image, float multiplier, int mode)
{
glm::vec3* heightmap = getHeightmap();
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
for (unsigned y = 0; y < SUM; ++y)
for (unsigned x = 0; x < SUM; ++x)
{
unsigned const plain {y * SUM + x};
bool const is_virtual {static_cast<bool>(plain % 2)};
if (is_virtual)
continue;
bool const erp = plain % DSUM / SUM;
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
switch (mode)
{
case 0: // Set
heightmap[idx].y = qGray(image.pixel(x, y)) / 255.0f * multiplier;
break;
case 1: // Add
heightmap[idx].y += qGray(image.pixel(x, y)) / 255.0f * multiplier;
break;
case 2: // Subtract
heightmap[idx].y -= qGray(image.pixel(x, y)) / 255.0f * multiplier;
break;
case 3: // Multiply
heightmap[idx].y *= qGray(image.pixel(x, y)) / 255.0f * multiplier;
break;
}
}
registerChunkUpdate(ChunkUpdateFlags::VERTEX);
}
ChunkWater* MapChunk::liquid_chunk() const
{
return mt->Water.getChunk(px, py);
}
void MapChunk::unload()
{
_chunk_update_flags = ChunkUpdateFlags::VERTEX | ChunkUpdateFlags::ALPHAMAP
| ChunkUpdateFlags::SHADOW | ChunkUpdateFlags::MCCV
| ChunkUpdateFlags::NORMALS| ChunkUpdateFlags::HOLES
| ChunkUpdateFlags::AREA_ID| ChunkUpdateFlags::FLAGS
| ChunkUpdateFlags::GROUND_EFFECT | ChunkUpdateFlags::DETAILDOODADS_EXCLUSION;
}
void MapChunk::setAlphamapImage(const QImage &image, unsigned int layer)
{
if (!layer)
return;
texture_set->create_temporary_alphamaps_if_needed();
auto& temp_alphamaps = *texture_set->getTempAlphamaps();
for (int i = 0; i < 64; ++i)
{
for (int j = 0; j < 64; ++j)
{
temp_alphamaps[layer][64 * j + i] = static_cast<float>(qGray(image.pixel(i, j))) / 255.0f;
}
}
texture_set->markDirty();
}
QImage MapChunk::getVertexColorImage()
{
glm::vec3* colors = getVertexColors();
QImage image(17, 17, QImage::Format_RGBA8888);
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
for (unsigned y = 0; y < SUM; ++y)
for (unsigned x = 0; x < SUM; ++x)
{
unsigned const plain {y * SUM + x};
bool const is_virtual {static_cast<bool>(plain % 2)};
bool const erp = plain % DSUM / SUM;
unsigned const idx {(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
float r = is_virtual ? (colors[idx].x + colors[idx + (erp ? SUM : 1)].x) / 4.f : colors[idx].x / 2.f;
float g = is_virtual ? (colors[idx].y + colors[idx + (erp ? SUM : 1)].y) / 4.f : colors[idx].y / 2.f;
float b = is_virtual ? (colors[idx].z + colors[idx + (erp ? SUM : 1)].z) / 4.f : colors[idx].z / 2.f;
image.setPixelColor(x, y, QColor::fromRgbF(r, g, b, 1.0));
}
return std::move(image);
}
void MapChunk::setVertexColorImage(const QImage &image)
{
glm::vec3* colors = getVertexColors();
unsigned const LONG{9}, SHORT{8}, SUM{LONG + SHORT}, DSUM{SUM * 2};
for (unsigned y = 0; y < SUM; ++y)
for (unsigned x = 0; x < SUM; ++x)
{
unsigned const plain{y * SUM + x};
bool const is_virtual{static_cast<bool>(plain % 2)};
if (is_virtual)
continue;
bool const erp = plain % DSUM / SUM;
unsigned const idx{(plain - (is_virtual ? (erp ? SUM : 1) : 0)) / 2};
QColor color = image.pixelColor(x, y);
colors[idx].x = color.redF() * 2.f;
colors[idx].y = color.greenF() * 2.f;
colors[idx].z = color.blueF() * 2.f;
}
registerChunkUpdate(ChunkUpdateFlags::MCCV);
}
void MapChunk::initMCCV()
{
if (!hasMCCV)
{
for (int i = 0; i < mapbufsize; ++i)
{
mccv[i].x = 1.0f; // set default shaders
mccv[i].y = 1.0f;
mccv[i].z = 1.0f;
}
header_flags.flags.has_mccv = 1;
hasMCCV = true;
}
}
void MapChunk::registerChunkUpdate(unsigned flags)
{
_chunk_update_flags |= flags;
mt->registerChunkUpdate(flags);
}