mekhanoidy-tools/src/model/model.cpp
2019-03-10 22:39:16 +03:00

711 lines
18 KiB
C++

/*
* AIM mod_converter
* 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 <http://www.gnu.org/licenses/>.
*/
#include "model.h"
#include <algorithm>
#include <fstream>
#include <map>
#include <set>
#include <string>
#include <math.h>
#include <buffer.h>
//#include <Eigen/Core>
//#include <Eigen/Dense>
#include <boost/algorithm/string.hpp>
#include <primitives/sw/cl.h>
#include <unicode/translit.h>
#include <unicode/errorcode.h>
#include <iostream>
cl::opt<float> scale_multiplier("s", cl::desc("Model scale multiplier"), cl::init(1.0f));
template <typename T>
inline bool replace_all(T &str, const T &from, const T &to)
{
bool replaced = false;
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != T::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length();
replaced = true;
}
return replaced;
}
inline bool replace_all(std::string &str, const std::string &from, const std::string &to)
{
return replace_all<std::string>(str, from, to);
}
std::string version();
float scale_mult()
{
return scale_multiplier;
}
// UE does not recognize russian strings in .obj
// what about fbx?
// TODO: what to do with signs: soft sign -> ' ?
std::string translate(const std::string &s)
{
UErrorCode ec = UErrorCode::U_ZERO_ERROR;
auto tr = icu::Transliterator::createInstance("Lower; Any-Latin; NFC; Latin-ASCII;", UTransDirection::UTRANS_FORWARD, ec);
if (!tr || ec)
throw std::runtime_error("Cannot create translator, ec = " + std::to_string(ec));
icu::UnicodeString s2(s.c_str());
tr->transliterate(s2);
std::string s3;
s2.toUTF8String<std::string>(s3);
replace_all(s3, " ", "");
// remove soft signs
replace_all(s3, "'", "");
return s3;
}
enum AxisSystem
{
MayaYUpZFrontRH,
Windows3dViewer = MayaYUpZFrontRH,
AIM,
UE4 = AIM, // Do not use 'Convert scene' during UE4 import!
ax_aim = AIM,
ax_ue4,
ax_maya_y = MayaYUpZFrontRH,
ax_win_3d_viewer,
};
cl::opt<AxisSystem> AS(cl::desc("Choose axis system:"),
cl::values(
clEnumVal(ax_ue4, "Original AIM or UE4 axis system"),
clEnumVal(ax_maya_y, "Default MAYA Y-Up Z-Front or Windows 3d Viewer axis system")
)
, cl::init(AxisSystem::UE4)
);
int get_x_coordinate_id()
{
switch (AS)
{
case AxisSystem::AIM:
return 1;
default:
return 0;
}
}
template <typename T>
void aim_vector3<T>::load(const buffer &b)
{
/*
Our coord system:
^ z
|
---> y
/
v x
AIM Coordinates:
1st number: +Y (left) (or -Y (right)?)
2nd number: +X (front) - 100% sure
3rd number: +Z (up) - 100% sure
This is Z UP, LH axis system.
Also see https://twitter.com/FreyaHolmer/status/644881436982575104
*/
switch (AS)
{
case AxisSystem::MayaYUpZFrontRH:
// Y UP, Z FRONT (RH)
READ(b, base::x);
READ(b, base::z);
READ(b, base::y);
break;
case AxisSystem::AIM:
// Z UP, X FRONT (LH)
READ(b, base::y);
READ(b, base::x);
READ(b, base::z);
break;
default:
throw SW_RUNTIME_ERROR("Unknown Axis System");
}
/*
// Y UP, X FRONT (LH?) (blender accepts such fbx)
z,x,y
*/
}
void aim_vector4::load(const buffer &b, uint32_t flags)
{
base::load(b);
if (flags & F_USE_W_COORDINATE)
READ(b, w);
}
std::string aim_vector4::print() const
{
std::string s;
s += std::to_string(x) + " " + std::to_string(y) + " " + std::to_string(z);
return s;
}
void vertex::load(const buffer &b, uint32_t flags)
{
coordinates.load(b, flags);
normal.load(b);
//READ(b, normal);
READ(b, texture_coordinates);
}
std::string vertex::printVertex(bool rotate_x_90) const
{
// rotate by 90 grad over Ox axis
/*#define M_PI_2 1.57079632679489661923
Eigen::Vector3f x;
x << -coordinates.x, coordinates.y, -coordinates.z;
Eigen::AngleAxis<float> rx(M_PI_2, Eigen::Vector3f(1, 0, 0));
auto x2 = rx * x;*/
std::string s;
if (rotate_x_90)
{
// that rotation is really equivalent to exchanging y and z and z sign
s = "v " +
std::to_string(-coordinates.x * scale_mult()) + " " +
std::to_string(coordinates.z * scale_mult()) + " " +
std::to_string(coordinates.y * scale_mult()) + " " +
std::to_string(coordinates.w)
;
}
else
{
s = "v " +
std::to_string(-coordinates.x * scale_mult()) + " " +
std::to_string(coordinates.y * scale_mult()) + " " +
std::to_string(-coordinates.z * scale_mult()) + " " +
std::to_string(coordinates.w)
;
}
return s;
}
std::string vertex::printNormal(bool rotate_x_90) const
{
std::string s;
if (rotate_x_90)
s = "vn " + std::to_string(-normal.x) + " " + std::to_string(-normal.z) + " " + std::to_string(normal.y);
else
s = "vn " + std::to_string(-normal.x) + " " + std::to_string(normal.y) + " " + std::to_string(-normal.z);
return s;
}
std::string vertex::printTex() const
{
std::string s;
float i;
auto u = modf(fabs(texture_coordinates.u), &i);
auto v = modf(fabs(texture_coordinates.v), &i);
s = "vt " + std::to_string(u) + " " + std::to_string(1 - v);
return s;
}
void damage_model::load(const buffer &b)
{
uint32_t n_polygons;
READ(b, n_polygons);
model_polygons.resize(n_polygons);
READ(b, unk8);
READ_STRING_N(b, name, 0x3C);
for (auto &t : model_polygons)
READ(b, t);
READ(b, unk6);
READ(b, flags);
uint32_t n_vertex;
uint32_t n_faces;
READ(b, n_vertex);
vertices.resize(n_vertex);
READ(b, n_faces);
faces.resize(n_faces / 3);
for (auto &v : vertices)
v.load(b, flags);
for (auto &t : faces)
READ(b, t);
}
std::string mat_color::print() const
{
std::string s;
s += std::to_string(r) + " " + std::to_string(g) + " " + std::to_string(b);
return s;
}
void material::load(const buffer &b)
{
READ(b, diffuse);
READ(b, ambient);
READ(b, specular);
READ(b, emissive);
READ(b, power);
auto delim_by_3 = [](auto &v)
{
//if (v.r > 1)
//v.r /= 3.0f;
//if (v.g > 1)
//v.g /= 3.0f;
//if (v.b > 1)
//v.b /= 3.0f;
};
// in aim - those values lie in interval [0,3] instead of [0,1]
delim_by_3(diffuse);
delim_by_3(ambient);
delim_by_3(specular);
}
void animation::load(const buffer &b)
{
READ(b, type); // seen: 1, 3
READ_STRING_N(b, name, 0xC);
for (auto &s : segments)
s.loadHeader(b);
if (segments[0].n == 0)
return;
for (auto &s : segments)
s.loadData(b);
}
void animation::segment::loadHeader(const buffer &b)
{
READ(b, n);
READ(b, unk0);
READ(b, unk1);
}
void animation::segment::loadData(const buffer &b)
{
if (n == 0)
return;
if (unk0)
{
model_polygons.resize(n);
for (auto &t : model_polygons)
READ(b, t);
}
unk2.resize(n);
for (auto &unk : unk2)
READ(b, unk);
}
std::string block::printMtl() const
{
std::string s;
s += "newmtl " + h.name + "\n";
s += "\n";
s += "Ka " + mat.ambient.print() + "\n";
s += "Kd " + mat.diffuse.print() + "\n";
s += "Ks " + mat.specular.print() + "\n";
s += " Ns " + std::to_string(mat.power) + "\n";
// d 1.0
// illum
s += "\n";
if (h.mask.name != "_DEFAULT_")
s += "map_Ka " + h.mask.name + texture_extension + "\n";
if (h.mask.name != "_DEFAULT_")
s += "map_Kd " + h.mask.name + texture_extension + "\n";
if (h.spec.name != "_DEFAULT_")
s += "map_Ks " + h.spec.name + texture_extension + "\n";
if (h.spec.name != "_DEFAULT_")
s += "map_Ns " + h.spec.name + texture_extension + "\n";
s += "\n";
return s;
}
std::string block::printObj(int group_offset, bool rotate_x_90) const
{
std::string s;
s += "usemtl " + h.name + "\n";
s += "\n";
s += "g " + h.name + "\n";
s += "s 1\n"; // still unk how to use
s += "\n";
for (auto &v : vertices)
s += v.printVertex(rotate_x_90) + "\n";
s += "\n";
for (auto &v : vertices)
s += v.printNormal(rotate_x_90) + "\n";
s += "\n";
for (auto &v : vertices)
s += v.printTex() + "\n";
s += "\n";
for (auto &t : faces)
{
auto x = std::to_string(t.x + 1 + group_offset);
auto y = std::to_string(t.y + 1 + group_offset);
auto z = std::to_string(t.z + 1 + group_offset);
x += "/" + x + "/" + x;
y += "/" + y + "/" + y;
z += "/" + z + "/" + z;
s += "f " + x + " " + z + " " + y + "\n";
}
s += "\n";
s += "\n";
return s;
}
void block::header::texture::load(const buffer &b)
{
READ_STRING(b, name);
if (gameType == GameType::AimR)
READ(b, number);
}
void block::header::load(const buffer &b)
{
READ(b, type);
READ_STRING(b, name);
name = translate(name);
mask.load(b);
spec.load(b);
tex3.load(b);
tex4.load(b);
READ(b, all_lods);
if (gameType == GameType::AimR)
{
READ(b, unk2[0]);
READ(b, unk2[1]);
READ(b, size);
}
else
READ(b, unk2);
READ(b, unk3);
if (gameType != GameType::AimR)
READ(b, size);
else
READ(b, unk2[2]); // unk4_0 - related to unk4 - some vector3f
READ(b, unk4);
}
void block::load(const buffer &b)
{
h.load(b);
//if (h.size == 0) // critical error!!! cannot survive
// throw std::runtime_error("model file has bad block size field (size == 0)");
// data
buffer data = buffer(b, h.size);
// we cannot process this type at the moment
if (h.type == BlockType::ParticleEmitter)
return;
if (h.type == BlockType::BitmapAlpha)
return;
// if we have size - create new buffer
// else - pass current
// no copy when buffer is created before
loadPayload(h.size == 0 ? b : data);
}
void block::loadPayload(const buffer &data)
{
// anims
uint32_t n_animations;
READ(data, n_animations);
animations.resize(n_animations);
// mat
mat.load(data);
READ(data, mat_type);
// check mat type - warn if unknown
switch (mat_type)
{
case MaterialType::Texture:
case MaterialType::TextureWithGlareMap:
case MaterialType::AlphaTextureNoGlare:
case MaterialType::AlphaTextureWithOverlap:
case MaterialType::TextureWithGlareMap2:
case MaterialType::AlphaTextureDoubleSided:
case MaterialType::DetalizationObjectGrass:
case MaterialType::Fire:
case MaterialType::MaterialOnly:
case MaterialType::TextureWithDetalizationMap:
case MaterialType::DetalizationObjectStone:
case MaterialType::TextureWithDetalizationMapWithoutModulation:
case MaterialType::TiledTexture:
case MaterialType::TextureWithGlareMapAndMask:
case MaterialType::TextureWithMask:
case MaterialType::Fire2:
break;
default:
std::cout << h.name << ": " << "warning: unknown material type " << (int)mat_type << " \n";
break;
}
// unk
// seen: 2,3,4,8,9,516
READ(data, unk7);
// seen: 0.0222222, 0.0444444, 0.0555556, 0.03125, 0.0375, 0.0625, 0.1, 0.125, 100, inf
READ(data, unk9); // scale? probably no
READ(data, unk10);
READ(data, auto_animation);
READ(data, animation_cycle);
READ(data, unk8);
READ(data, unk11);
READ(data, unk12);
READ(data, triangles_mult_7); // particle system?
/*if (unk7 != 0)
std::cout << "nonzero unk7 = " << unk7 << " in block " << h.name << "\n";
if (unk9 != 0)
std::cout << "nonzero unk9 = " << unk9 << " in block " << h.name << "\n";*/
//
READ(data, additional_params);
uint32_t n_damage_models;
READ(data, n_damage_models);
damage_models.resize(n_damage_models);
READ(data, rot);
READ(data, flags);
uint32_t n_vertex;
uint32_t n_faces; // ??? edges? polygons?
READ(data, n_vertex);
vertices.resize(n_vertex);
READ(data, n_faces);
auto n_triangles = n_faces / 3;
for (auto &v : vertices)
v.load(data, flags);
faces.resize(n_triangles);
for (auto &t : faces)
t.load(data);
// animations
for (auto &a : animations)
a.load(data);
for (auto &dm : damage_models)
dm.load(data);
auto read_more_faces = [&]()
{
n_faces *= 6; // 7
auto faces2 = faces;
faces2.resize(n_faces / 3);
for (auto &t : faces2)
READ(data, t);
};
// maybe two winds anims?
// small wind and big wind?
if (triangles_mult_7 && !unk11 &&
((flags & F_USE_W_COORDINATE) || flags == 0x112)
)
{
read_more_faces();
}
if (unk7 != 0)
return;
std::string s = "extraction error: block: " + std::string(h.name);
// unknown how to proceed
if (!data.eof() && triangles_mult_7)
{
if (unk11)
{
read_more_faces();
}
else
{
// unknown end of block
auto triangles2 = faces;
auto d = data.end() - data.index();
triangles2.resize(d / sizeof(face));
for (auto &t : triangles2)
READ(data, t);
uint16_t t;
while (!data.eof())
READ(data, t);
}
}
if (!data.eof() && h.size)
throw std::logic_error(s);
}
bool block::isEngineFx() const
{
return h.type == BlockType::HelperObject && h.name.find(boost::to_lower_copy(std::string("FIRE"))) == 0;
}
bool block::canPrint() const
{
// block all lods except 0
if (!(h.all_lods == 15 || h.LODs.lod1))
return false;
// lods
if (h.type == BlockType::VisibleObject)
return true;
// particles
if (h.type == BlockType::ParticleEmitter)
return false;
// collision object
if (h.name == "SHAPE")
return false;
// default
return false;
}
block::block_info block::save(yaml &root) const
{
aim_vector4 min{ 1e6, 1e6, 1e6, 1e6 }, max{ -1e6, -1e6, -1e6, -1e6 };
for (auto &v : vertices)
{
auto mm = [&v](auto &m, auto f)
{
m.x = f(m.x, v.coordinates.x);
m.y = f(m.y, v.coordinates.y);
m.z = f(m.z, v.coordinates.z);
};
mm(min, [](auto x, auto y) {return std::min(x,y); });
mm(max, [](auto x, auto y) {return std::max(x,y); });
}
root["xlen"] = max.x - min.x;
root["ylen"] = max.y - min.y;
root["zlen"] = max.z - min.z;
// convex hull length
/*float len = 0;
for (auto &v1 : vertices)
{
for (auto &v2 : vertices)
{
len = std::max(len, sqrt(
pow(v2.coordinates.x - v1.coordinates.x, 2) +
pow(v2.coordinates.y - v1.coordinates.y, 2) +
pow(v2.coordinates.z - v1.coordinates.z, 2)
));
}
}
root["len"] = len;*/
return {min,max};
}
void model::load(const buffer &b)
{
int n_blocks;
READ(b, n_blocks);
if (n_blocks > 1000) // probably bad file
throw std::runtime_error("Model file has bad block count (should be <= 1000). Probably not a model.");
char header[0x40];
READ(b, header);
blocks.resize(n_blocks);
for (auto &f : blocks)
f.load(b);
}
void model::print(const std::string &fn) const
{
auto title = [](auto &o)
{
o << "#" << "\n";
o << "# A.I.M. Model Converter (ver. " << version() << ")\n";
o << "#" << "\n";
o << "\n";
};
auto print_obj = [&](const auto &n, bool rotate_x_90 = false)
{
std::ofstream o(n);
title(o);
o << "mtllib " + fn + ".mtl\n\n";
o << "o " << fn << "\n\n";
int n_vert = 0;
for (auto &b : blocks)
{
if (!b.canPrint())
continue;
o << b.printObj(n_vert, rotate_x_90) << "\n";
n_vert += b.vertices.size();
}
};
auto mtl_fn = fn + ".mtl";
std::ofstream m(mtl_fn);
title(m);
for (auto &b : blocks)
m << b.printMtl() << "\n";
print_obj(fn + "_fbx.obj");
print_obj(fn + "_ue4.obj", true);
}
void model::save(yaml &root) const
{
aim_vector4 min{ 1e6, 1e6, 1e6, 1e6 }, max{ -1e6, -1e6, -1e6, -1e6 };
for (auto &b : blocks)
{
if (!b.canPrint())
continue;
auto [bmin, bmax] = b.save(root["lods"][b.h.name]);
auto mm = [](auto &v, auto &m, auto f)
{
m.x = f(m.x, v.x);
m.y = f(m.y, v.y);
m.z = f(m.z, v.z);
};
mm(bmin, min, [](auto x, auto y) {return std::min(x,y); });
mm(bmax, max, [](auto x, auto y) {return std::max(x,y); });
}
root["full"]["xlen"] = max.x - min.x;
root["full"]["ylen"] = max.y - min.y;
root["full"]["zlen"] = max.z - min.z;
//root["full"]["len"] = sqrt(pow(max.x - min.x, 2) + pow(max.y - min.y, 2) + pow(max.z - min.z, 2));
}