// This file is part of Noggit3, licensed under GNU General Public License (version 3). #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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::max()) , vmax(std::numeric_limits::lowest()) , _chunk_update_flags(ChunkUpdateFlags::VERTEX | ChunkUpdateFlags::ALPHAMAP | ChunkUpdateFlags::SHADOW | ChunkUpdateFlags::MCCV | ChunkUpdateFlags::NORMALS| ChunkUpdateFlags::HOLES | ChunkUpdateFlags::AREA_ID| ChunkUpdateFlags::FLAGS | ChunkUpdateFlags::GROUND_EFFECT) { 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 = nullptr; 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; 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(this, f, base, maintile, 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 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 (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 MapChunk::compressed_shadow_map() const { std::vector 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(zdiff / (UNITSIZE * 0.5f) + 0.5f); const int column = static_cast((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(zdiff / (UNITSIZE * 0.5f) + 0.5f); const int column = static_cast((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& area_id_colors , int animtime , display_mode display , std::array& 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 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::max(); vmax.y = std::numeric_limits::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) { 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; } changed = true; header_flags.flags.has_mccv = 1; hasMCCV = 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) { 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; } changed = true; header_flags.flags.has_mccv = 1; hasMCCV = 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 (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 tx = pos.x + k*UNITSIZE + (j % 2) * UNITSIZE / 2.0f; float dist2 = misc::dist (tx, tz, mVertices[i].x, mVertices[i].z); if (dist2 > radius) continue; 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); } 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(sExtendableArray& lADTFile , int& lCurrentPosition , int& lMCIN_Position , std::map &lTextures , std::vector &lObjectInstances , std::vector& lModelInstances) { 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(lMCIN_Position + 8)->mEntries[py * 16 + px].offset = lCurrentPosition; // check this // MCNK data // lADTFile.Insert(lCurrentPosition + 8, 0x80, reinterpret_cast(&(header))); MapChunkHeader *lMCNK_header = lADTFile.GetPointer(lCurrentPosition + 8); header_flags.flags.do_not_fix_alpha_map = 1; lMCNK_header->flags = header_flags.value; lMCNK_header->ix = px; lMCNK_header->iy = py; lMCNK_header->zpos = zbase; lMCNK_header->xpos = xbase; 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) { std::copy(texture_set->getDoodadMappingBase(), texture_set->getDoodadMappingBase() + 8 , lMCNK_header->doodadMapping); *reinterpret_cast(lMCNK_header->doodadStencil) = *reinterpret_cast(texture_set->getDoodadStencilBase()); } else { std::fill(lMCNK_header->doodadMapping, lMCNK_header->doodadMapping + 8, 0); *reinterpret_cast(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(lMCNK_Position + 8)->ofsHeight = lCurrentPosition - lMCNK_Position; float* lHeightmap = lADTFile.GetPointer(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); lADTFile.GetPointer(lMCNK_Position + 8)->ofsMCCV = lCurrentPosition - lMCNK_Position; unsigned int *lmccv = lADTFile.GetPointer(lCurrentPosition + 8); for (int i = 0; i < mapbufsize; ++i) { *lmccv++ = ((unsigned char)(mccv[i].z * 127.0f) & 0xFF) + (((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 { lADTFile.GetPointer(lMCNK_Position + 8)->ofsMCCV = 0; } // MCNR int lMCNR_Size = mapbufsize * 3; lADTFile.Extend(8 + lMCNR_Size); SetChunkHeader(lADTFile, lCurrentPosition, 'MCNR', lMCNR_Size); lADTFile.GetPointer(lMCNK_Position + 8)->ofsNormal = lCurrentPosition - lMCNK_Position; char * lNormals = lADTFile.GetPointer(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(tile_buffer[pixel_start] * 127); lNormals[i * 3 + 1] = static_cast(tile_buffer[pixel_start + 2] * 127); lNormals[i * 3 + 2] = static_cast(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(8 + lMCLY_Size)); SetChunkHeader(lADTFile, lCurrentPosition, 'MCLY', static_cast(lMCLY_Size)); lADTFile.GetPointer(lMCNK_Position + 8)->ofsLayer = lCurrentPosition - lMCNK_Position; lADTFile.GetPointer(lMCNK_Position + 8)->nLayers = texture_set ? static_cast(texture_set->num()) : 0; std::vector> 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) { ENTRY_MCLY * lLayer = lADTFile.GetPointer(lCurrentPosition + 8 + 0x10 * static_cast(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(alphamaps[j - 1].size()); } } } lCurrentPosition += 8 + static_cast(lMCLY_Size); lMCNK_Size += 8 + static_cast(lMCLY_Size); // } // MCRF // { std::list lDoodadIDs; std::list lObjectIDs; std::array 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(4 * (lDoodadIDs.size() + lObjectIDs.size())); lADTFile.Extend(8 + lMCRF_Size); SetChunkHeader(lADTFile, lCurrentPosition, 'MCRF', lMCRF_Size); lADTFile.GetPointer(lMCNK_Position + 8)->ofsRefs = lCurrentPosition - lMCNK_Position; lADTFile.GetPointer(lMCNK_Position + 8)->nDoodadRefs = static_cast(lDoodadIDs.size()); lADTFile.GetPointer(lMCNK_Position + 8)->nMapObjRefs = static_cast(lObjectIDs.size()); // MCRF data int *lReferences = lADTFile.GetPointer(lCurrentPosition + 8); lID = 0; for (std::list::iterator it = lDoodadIDs.begin(); it != lDoodadIDs.end(); ++it) { lReferences[lID] = *it; lID++; } for (std::list::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; int lMCSH_Size = 0x200; lADTFile.Extend(8 + lMCSH_Size); SetChunkHeader(lADTFile, lCurrentPosition, 'MCSH', lMCSH_Size); lADTFile.GetPointer(lMCNK_Position + 8)->ofsShadow = lCurrentPosition - lMCNK_Position; lADTFile.GetPointer(lMCNK_Position + 8)->sizeShadow = 0x200; char * lLayer = lADTFile.GetPointer(lCurrentPosition + 8); auto shadow_map = compressed_shadow_map(); memcpy(lLayer, shadow_map.data(), 0x200); lCurrentPosition += 8 + lMCSH_Size; lMCNK_Size += 8 + lMCSH_Size; } else { header_flags.flags.has_mcsh = 0; lADTFile.GetPointer(lMCNK_Position + 8)->ofsShadow = 0; lADTFile.GetPointer(lMCNK_Position + 8)->sizeShadow = 0; } // MCAL lADTFile.Extend(8 + lMCAL_Size); SetChunkHeader(lADTFile, lCurrentPosition, 'MCAL', lMCAL_Size); lADTFile.GetPointer(lMCNK_Position + 8)->ofsAlpha = lCurrentPosition - lMCNK_Position; lADTFile.GetPointer(lMCNK_Position + 8)->sizeAlpha = 8 + lMCAL_Size; char * lAlphaMaps = lADTFile.GetPointer(lCurrentPosition + 8); for (auto& alpha : alphamaps) { memcpy(lAlphaMaps, alpha.data(), alpha.size()); lAlphaMaps += alpha.size(); } lCurrentPosition += 8 + lMCAL_Size; lMCNK_Size += 8 + lMCAL_Size; // } //! Don't write anything MCLQ related anymore... // MCSE int lMCSE_Size = sizeof(ENTRY_MCSE) * sound_emitters.size(); lADTFile.Extend(8 + lMCSE_Size); SetChunkHeader(lADTFile, lCurrentPosition, 'MCSE', lMCSE_Size); lADTFile.GetPointer(lMCNK_Position + 8)->ofsSndEmitters = lCurrentPosition - lMCNK_Position; lADTFile.GetPointer(lMCNK_Position + 8)->nSndEmitters = lMCSE_Size / 0x1C; lCurrentPosition += 8; for (auto& sound_emitter : sound_emitters) { ENTRY_MCSE* MCSE = lADTFile.GetPointer(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(lMCNK_Position)->mSize = lMCNK_Size; lADTFile.GetPointer(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& 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& selected) { std::vector 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& 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(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).value(); 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(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; } 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()->value(); for (int i = 0; i < 64; ++i) { for (int j = 0; j < 64; ++j) { temp_alphamaps[layer][64 * j + i] = static_cast(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(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(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::registerChunkUpdate(unsigned flags) { _chunk_update_flags |= flags; mt->registerChunkUpdate(flags); }