mekhanoidy-tools/src/mod_converter/model.cpp

499 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <fbxsdk.h>
//#include <Eigen/Core>
//#include <Eigen/Dense>
#include <iostream>
using namespace std;
const float scale_mult = 30.0f;
const std::string texture_extension = ".TM.bmp";
const map<char, string> transliteration =
{
{ 'à',"a" },
{ 'á',"b" },
{ 'â',"v" },
{ 'ã',"g" },
{ 'ä',"d" },
{ 'å',"e" },
{ '¸',"yo" },
{ 'æ',"zh" },
{ 'ç',"z" },
{ 'è',"i" },
{ 'é',"y" },
{ 'ê',"k" },
{ 'ë',"l" },
{ 'ì',"m" },
{ 'í',"n" },
{ 'î',"o" },
{ 'ï',"p" },
{ 'ð',"r" },
{ 'ñ',"s" },
{ 'ò',"t" },
{ 'ó',"u" },
{ 'ô',"f" },
{ 'õ',"kh" },
{ 'ö',"ts" },
{ '÷',"ch" },
{ 'ø',"sh" },
{ 'ù',"shch" },
{ 'ú',"_" },
{ 'û',"y" },
{ 'ü',"_" },
{ 'ý',"e" },
{ 'þ',"yu" },
{ 'ÿ',"ya" },
{ 'À',"A" },
{ 'Á',"B" },
{ 'Â',"V" },
{ 'Ã',"G" },
{ 'Ä',"D" },
{ 'Å',"E" },
{ '¨',"Yo" },
{ 'Æ',"Zh" },
{ 'Ç',"Z" },
{ 'È',"I" },
{ 'É',"Y" },
{ 'Ê',"K" },
{ 'Ë',"L" },
{ 'Ì',"M" },
{ 'Í',"N" },
{ 'Î',"O" },
{ 'Ï',"P" },
{ 'Ð',"R" },
{ 'Ñ',"S" },
{ 'Ò',"T" },
{ 'Ó',"U" },
{ 'Ô',"F" },
{ 'Õ',"Kh" },
{ 'Ö',"Ts" },
{ '×',"Ch" },
{ 'Ø',"Sh" },
{ 'Ù',"Shch" },
{ 'Ú',"_" },
{ 'Û',"Y" },
{ 'Ü',"_" },
{ 'Ý',"E" },
{ 'Þ',"Yu" },
{ 'ß',"Ya" },
{ ' ',"_" },
}
;
std::string version();
// UE does not recognize russian strings in .obj
string translate(const string &s)
{
string o;
for (auto c : s)
{
auto i = transliteration.find(c);
if (i == transliteration.end())
o += c;
else
o += i->second;
}
return o;
}
template <typename T>
void aim_vector3<T>::load(const buffer &b)
{
READ(b, x);
READ(b, z);
READ(b, y);
}
void aim_vector4::load(const buffer &b, uint32_t flags)
{
aim_vector3::load(b);
if (flags & F_USE_W_COORDINATE)
READ(b, w);
}
std::string aim_vector4::print() const
{
string s;
s += to_string(x) + " " + to_string(y) + " " + to_string(z);
return s;
}
void vertex::load(const buffer &b, uint32_t flags)
{
coordinates.load(b, flags);
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;*/
string s;
if (rotate_x_90)
{
// that rotation is really equivalent to exchanging y and z and z sign
s = "v " +
to_string(-coordinates.x * scale_mult) + " " +
to_string(coordinates.z * scale_mult) + " " +
to_string(coordinates.y * scale_mult) + " " +
to_string(coordinates.w)
;
}
else
{
s = "v " +
to_string(-coordinates.x * scale_mult) + " " +
to_string(coordinates.y * scale_mult) + " " +
to_string(-coordinates.z * scale_mult) + " " +
to_string(coordinates.w)
;
}
return s;
}
std::string vertex::printNormal(bool rotate_x_90) const
{
string s;
if (rotate_x_90)
s = "vn " + to_string(-normal.x) + " " + to_string(-normal.z) + " " + to_string(normal.y);
else
s = "vn " + to_string(-normal.x) + " " + to_string(normal.y) + " " + to_string(-normal.z);
return s;
}
std::string vertex::printTex() const
{
string s;
float i;
auto u = modf(fabs(texture_coordinates.u), &i);
auto v = modf(fabs(texture_coordinates.v), &i);
s = "vt " + to_string(u) + " " + 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);
}
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)
{
v.x /= 3.0f;
v.y /= 3.0f;
v.z /= 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);
READ_STRING_N(b, name, 0xC);
for (auto &s : segments)
s.loadHeader(b);
//if (segments[0].n)
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
{
string s;
s += "newmtl " + name + "\n";
s += "\n";
s += "Ka " + mat.ambient.print() + "\n";
s += "Kd " + mat.diffuse.print() + "\n";
s += "Ks " + mat.specular.print() + "\n";
s += " Ns " + to_string(mat.power) + "\n";
// d 1.0
// illum
s += "\n";
if (string(tex_mask) != "_DEFAULT_")
s += "map_Ka " + string(tex_mask) + texture_extension + "\n";
if (string(tex_mask) != "_DEFAULT_")
s += "map_Kd " + string(tex_mask) + texture_extension + "\n";
if (string(tex_spec) != "_DEFAULT_")
s += "map_Ks " + string(tex_spec) + texture_extension + "\n";
if (string(tex_spec) != "_DEFAULT_")
s += "map_Ns " + string(tex_spec) + texture_extension + "\n";
s += "\n";
return s;
}
std::string block::printObj(int group_offset, bool rotate_x_90) const
{
string s;
s += "usemtl " + name + "\n";
s += "\n";
s += "g " + 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 = to_string(t.x + 1 + group_offset);
auto y = to_string(t.y + 1 + group_offset);
auto z = 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::load(const buffer &b)
{
// header
READ(b, type);
READ_STRING(b, name);
name = translate(name);
READ_STRING(b, tex_mask);
READ_STRING(b, tex_spec);
READ_STRING(b, tex3);
READ_STRING(b, tex4);
READ(b, all_lods);
READ(b, unk2);
READ(b, unk3);
READ(b, size);
READ(b, unk4);
if (size == 0) // critical error!!! cannot survive
throw std::runtime_error("model file has bad block size field (size == 0)");
// data
buffer data = buffer(b, size);
// we cannot process this type at the moment
if (type == BlockType::ParticleEmitter)
return;
uint32_t n_animations;
READ(data, n_animations);
animations.resize(n_animations);
mat.load(data);
// unk
READ(data, effect);
READ(data, unk7);
READ(data, unk9);
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);
//
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;
READ(data, n_vertex);
vertices.resize(n_vertex);
READ(data, n_faces);
if (triangles_mult_7 && (flags & F_USE_W_COORDINATE) && !unk11)
n_faces *= 7;
for (auto &v : vertices)
v.load(data, flags);
faces.resize(n_faces / 3);
for (auto &t : faces)
READ(data, t);
// animations
for (auto &a : animations)
a.load(data);
for (auto &dm : damage_models)
dm.load(data);
string s = "extraction error: block #" + std::string(name);
if (!data.eof())
{
cerr << s << "\n";
return;
}
// unknown how to proceed
if (!data.eof() && triangles_mult_7)
{
// unknown end of block
auto triangles2 = faces;
triangles2.resize((data.size() - data.index()) / sizeof(face));
for (auto &t : triangles2)
READ(data, t);
}
if (!data.eof())
throw std::logic_error(s);
}
bool block::canPrint() const
{
if (type == BlockType::ParticleEmitter)
return false;
if (type != BlockType::VisibleObject)
return false;
if (!(all_lods == 15 || LODs.lod1))
return false;
return true;
}
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)
{
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)
{
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";
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);
}