/*
* AIM mmp_extractor
* Copyright (C) 2015 lzwdgc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "mmp.h"
#include
#include
#include
#include
#include
#include
#include
#include
cl::list extend("e",
cl::desc("Try to extend map for ue4. Use <63> <4> or <127> <1> values."),
cl::value_desc(" "),
cl::multi_val(2)//,
// defaults
//cl::init(std::vector{127,1})
//cl::init(std::vector{63,4})
);
void water_segment::load(const buffer &b)
{
wg.load(b);
}
void weather_segment::load(const buffer &b)
{
wg.load(b);
}
header_segment *header::create_segment(const buffer &b)
{
HeaderSegmentType type;
READ(b, type);
header_segment *segment = 0;
switch (type)
{
case HeaderSegmentType::water:
segment = new water_segment;
break;
case HeaderSegmentType::weather:
segment = new weather_segment;
break;
default:
throw std::logic_error("unknown header segment type " + std::to_string((int)type));
break;
}
if (segment)
{
segment->type = type;
READ(b, segment->unk0);
READ(b, segment->len);
}
return segment;
}
void header::load(const buffer &b)
{
READ(b, unk0);
READ_WSTRING(b, name1);
READ_WSTRING(b, name2);
READ(b, width);
READ(b, length);
READ(b, n_header_segs);
segments.resize(n_header_segs);
READ_STRING_N(b, name, 0xA0);
for (auto &s : segments)
{
s = create_segment(b);
buffer b2(b, s->len);
if (!b2.eof())
s->load(b2);
}
}
void segment::load(const buffer &b)
{
uint32_t offset;
READ(b, offset);
READ(b, desc);
buffer b2(b);
b2.seek(offset);
READ(b2, d);
b2.seek(offset);
READ(b2, d2);
}
void mmp::load(const buffer &b)
{
h.load(b);
xsegs = (h.width - 1) / 64;
if ((h.width - 1) % 64 != 0)
xsegs++;
ysegs = (h.length - 1) / 64;
if ((h.length - 1) % 64 != 0)
ysegs++;
int n_segs = xsegs * ysegs;
segments.resize(n_segs);
for (auto &s : segments)
s.load(b);
if (segments.empty())
return;
// check whether all segments were read
auto len = b.index() + segments.size() * sizeof(segment::data);
if (len != b.size())
throw std::logic_error("Some segments were not read");
}
void mmp::load(const path &fn)
{
filename = fn;
buffer b(read_file(filename));
load(b);
}
void mmp::loadTextureNames(const path &fn)
{
std::ifstream ifile(fn);
while (ifile)
{
int id;
std::string name;
ifile >> id >> name;
textures_names[id] = name;
}
}
static bool is_power_of_two(int x)
{
return x != 0 && (x & (x - 1)) == 0;
}
void mmp::process()
{
for (auto &s : segments)
{
for (auto &i : s.d.Infomap)
textures[i.getTexture()]++;
}
textures.erase(0);
auto textures_per_color = std::max(1U, textures.size() / 3);
auto color_step = 200 / std::max(1U, textures.size());
for (size_t i = 0; i < textures.size(); i++)
{
int color_id = i / textures_per_color;
color c = { 0 };
if (color_id == 0)
c.r = 255 - 200 / textures_per_color * (i % textures_per_color);
else if (color_id == 1)
c.g = 255 - 200 / textures_per_color * (i % textures_per_color);
else if (color_id == 2)
c.b = 255 - 200 / textures_per_color * (i % textures_per_color);
auto iter = textures.begin();
std::advance(iter, i);
textures_map_colored[iter->first] = c;
color c2 = { 0 };
c2.g = 255 - i * color_step;
textures_map[iter->first] = c2;
}
alpha_maps[0] = mat(h.width, h.length);
for (auto &t : textures_map)
{
alpha_maps[t.second.g] = mat(h.width, h.length);
}
auto wnew = h.width;
auto lnew = h.length;
if (extend.empty())
{
// defaults
//extend.push_back(63);
//extend.push_back(4);
extend.push_back(127);
extend.push_back(1);
}
if (!extend.empty())
{
int quads_per_sect = extend[0] + 1;
int sections_per_comp = extend[1];
if (!is_power_of_two(quads_per_sect))
throw SW_RUNTIME_ERROR("quads per section must be 2^n-1");
if (sections_per_comp != 1 && sections_per_comp != 4)
throw SW_RUNTIME_ERROR("sections per component must be 1 or 4");
if (sections_per_comp > 1)
sections_per_comp = sqrt(sections_per_comp);
double mult = quads_per_sect * sections_per_comp - sections_per_comp;
int wcomps = ceil(h.width / mult);
int lcomps = ceil(h.length / mult);
if (wcomps * lcomps > 1024)
{
throw SW_RUNTIME_ERROR("ue4 allows maximum 1024 components while you have: " +
std::to_string(wcomps) + "x" + std::to_string(lcomps) + " = " + std::to_string(wcomps * lcomps) + " components");
}
wnew = wcomps * mult + 1;
lnew = lcomps * mult + 1;
}
// merge
heightmap = decltype(heightmap)(wnew, lnew);
heightmap32 = decltype(heightmap32)(h.width, h.length);
//heightmap_segmented = decltype(heightmap)(segment::len, h.length);
texmap = decltype(texmap)(h.width, h.length);
texmap_colored = decltype(texmap_colored)(h.width, h.length);
colormap = decltype(colormap)(h.width, h.length);
shadowmap = decltype(shadowmap)(h.width, h.length);
normalmap = decltype(normalmap)(h.width, h.length);
h_min = std::numeric_limits::max();
h_max = std::numeric_limits::min();
for (auto &s : segments)
{
const auto &data = s.d;
int y1 = s.desc.min.y / 10;
int y2 = s.desc.max.y / 10;
if (y2 > (int)h.length)
y2 = h.length;
for (int y = 0; y1 < y2; y1++, y++)
{
int x1 = s.desc.min.x / 10;
int x2 = s.desc.max.x / 10;
auto dx = x2 - x1;
if (x2 >(int)h.width)
x2 = h.width;
for (int x = 0; x1 < x2; x1++, x++)
{
auto p = y * dx + x;
auto y_rev = h.length - y1 - 1; // for bmp reversion
//auto y_rev = y1;
auto t = data.Infomap[p].getTexture();
auto t_norm = textures_map.find(t);
if (t_norm != textures_map.end())
{
texmap(y_rev, x1) = t_norm->second;
alpha_maps[t_norm->second.g](y_rev, x1) = color{ 0,255,0,0 };
}
texmap_colored(y_rev, x1) = textures_map_colored[t];
colormap(y_rev, x1) = data.Colormap[p];
shadowmap(y_rev, x1) = *(uint32_t*)&data.Shadowmap[p];
normalmap(y_rev, x1) = *(uint32_t*)&data.Normalmap[p];
auto length = data.Heightmap[p];
h_min = std::min(h_min, length);
h_max = std::max(h_max, length);
}
}
}
alpha_maps.erase(0);
// heightmap calculations
// https://docs.unrealengine.com/en-US/BuildingWorlds/Landscape/TechnicalGuide/index.html
// "Unreal Engine 4 calculates your heightmap's height by using values between -256 to 255.992 and stored with 16 bit precision."
const auto _2pow15 = 1 << 15;
const auto ue4_zero_level = _2pow15 - 1;
static const auto min_ue4_heightmap_height_units = 256;
static const auto ue4_height_per_i16_tick = float(min_ue4_heightmap_height_units) / _2pow15;
static const auto ue4_max_plus_height_level_units = _2pow15 * ue4_height_per_i16_tick;
static const auto ue4_max_minus_height_level_units = (_2pow15 - 1) * ue4_height_per_i16_tick;
bool calc_by_plus = std::fabs(h_max) > std::fabs(h_min);
const auto number_of_height_levels = calc_by_plus ? _2pow15 - 1 : _2pow15;
const auto ue4_max_height_level_units = calc_by_plus ? ue4_max_plus_height_level_units : ue4_max_minus_height_level_units;
const auto absmaxheight = std::max(std::fabs(h_max), std::fabs(h_min));
this->ue4_z_scale = absmaxheight / ue4_max_height_level_units;
auto aim_height_to_ue4_heightmap = [&number_of_height_levels](float h, float hmax)
{
return h * number_of_height_levels / hmax;
};
// make heightmap
for (auto &s : segments)
{
int y1 = s.desc.min.y / 10;
int y2 = s.desc.max.y / 10;
if (y2 > (int)h.length)
y2 = h.length;
for (int y = 0; y1 < y2; y1++, y++)
{
int x1 = s.desc.min.x / 10;
int x2 = s.desc.max.x / 10;
auto dx = x2 - x1;
if (x2 > (int)h.width)
x2 = h.width;
for (int x = 0; x1 < x2; x1++, x++)
{
auto height = s.d.Heightmap[y * dx + x];
//heightmap32(y1, x1) = height; // dunno what is right
//heightmap32(y1, x1) = height - h_min; // dunno what is right
heightmap(y1, x1) = ue4_zero_level + aim_height_to_ue4_heightmap(height, absmaxheight);
}
}
}
}
void mmp::writeFileInfo()
{
auto fn = filename;
std::ofstream ofile(fn += ".info.txt");
if (!ofile)
return;
ofile << "width: " << h.width << "\n";
ofile << "length: " << h.length << "\n";
ofile << "x segments: " << xsegs << "\n";
ofile << "y segments: " << ysegs << "\n";
ofile << "h_min: " << h_min << "\n";
ofile << "h_max: " << h_max << "\n";
ofile << "h_diff: " << h_max - h_min << "\n";
ofile << "ue4_z_scale: " << std::setprecision(10) << ue4_z_scale << "\n";
}
void mmp::writeTexturesList()
{
auto fn = filename;
std::ofstream ofile(fn += ".textures.txt");
if (!ofile)
return;
for (auto &t : textures)
{
ofile << t.first;
if (!textures_names.empty())
{
int c = textures_map[t.first].g;
ofile << "\tcount: " << t.second;
ofile << "\thex: 0x00" << std::hex << c << "0000" << std::dec;
ofile << "\tg: " << c;
ofile << "\tg: " << std::setprecision(3) << std::setw(3) << c / 256.f;
ofile << "\t" << textures_names[t.first];
}
ofile << "\n";
}
}
static cv::Mat toCvMat(const mat &in)
{
int cols = in.getWidth();
int rows = in.getHeight();
cv::Mat m(in.getHeight(), in.getWidth(), CV_16UC1);
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
auto &o = in(row * cols + col);
m.ptr(row)[col] = o;
}
}
return m;
}
static cv::Mat toCvMat(const mat &in)
{
int cols = in.getWidth();
int rows = in.getHeight();
cv::Mat m(in.getHeight(), in.getWidth(), CV_8UC3);
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
auto &o = in(row * cols + col);
m.ptr(row)[3 * col + 2] = (o >> 16) & 0xFF;
m.ptr(row)[3 * col + 1] = (o >> 8) & 0xFF;
m.ptr(row)[3 * col + 0] = (o >> 0) & 0xFF;
}
}
return m;
}
void mmp::writeHeightMap()
{
auto write_hm = [this](const String &name, const auto &v, auto sz)
{
auto fn = filename;
fn += name;
auto f = primitives::filesystem::fopen(fn, "wb");
if (f == nullptr)
return;
fwrite(&v(0, 0), v.size() * sz, 1, f);
fclose(f);
};
cv::imwrite((const char *)to_path_string(path(filename) += ".heightmap16.png").c_str(), toCvMat(heightmap));
write_hm(".heightmap16.r16", heightmap, sizeof(decltype(heightmap)::type));
//write_hm(".heightmap32.r32", heightmap32, sizeof(decltype(heightmap32)::type));
}
void mmp::writeHeightMapSegmented()
{
/*auto fn = filename + ".heightmap.r16s";
FILE *f = fopen(fn.c_str(), "wb");
if (f == nullptr)
return;
fwrite(&heightmap_segmented(0, 0), heightmap_segmented.size() * sizeof(decltype(heightmap_segmented)::type), 1, f);
fclose(f);*/
}
void mmp::writeTextureMap()
{
auto fn = filename;
fn += ".texmap.bmp";
write_mat_bmp(fn, texmap);
}
void mmp::writeTextureAlphaMaps()
{
for (auto &t : alpha_maps)
{
int tex_id = 0;
for (auto &[tid, tex] : textures_map)
{
if (tex.g == t.first)
{
tex_id = tid;
break;
}
}
auto fn = filename;
fn += ".texmap." + std::to_string(t.first) + "." + std::to_string(tex_id) + ".bmp";
write_mat_bmp(fn, t.second);
}
}
void mmp::writeTextureMapColored()
{
auto fn = filename;
fn += ".texmap.colored.bmp";
write_mat_bmp(fn, texmap_colored);
}
void mmp::writeColorMap()
{
auto fn = filename;
fn += ".colormap.bmp";
write_mat_bmp(fn, colormap);
}
void mmp::writeSplitColormap() const
{
std::set colors;
for (auto &pixel : colormap)
colors.insert(pixel);
int i = 0;
for (auto &color : colors)
{
++i;
auto fn = filename;
std::ostringstream ss;
ss << "0x";
ss.fill('0');
ss.width(8);
ss << std::hex << std::uppercase << color;
fn += ".colormap." + ss.str() + ".png";
if (fs::exists(fn))
continue;
auto m = colormap;
for (auto &pixel : m)
pixel = pixel == color ? 0x0000FF00 : 0;
std::cout << "\r[" << i << "/" << colors.size() << "] Processing color " << ss.str();
cv::imwrite((const char *)to_path_string(fn).c_str(), toCvMat(m));
}
}
void mmp::writeShadowMap()
{
auto fn = filename;
fn += ".shadowmap.bmp";
write_mat_bmp(fn, shadowmap);
}
void mmp::writeNormalMap()
{
auto fn = filename;
fn += ".normalmap.bmp";
write_mat_bmp(fn, normalmap);
}