Commit 9318ab92 authored by Georg Schaefer's avatar Georg Schaefer
Browse files

Add level converter and collision mesh generation.

parent a045f869
......@@ -50,7 +50,7 @@ if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif ()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1z")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -g -Wall -Wextra -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -g -Wall -Wextra")
#include directory of project
include_directories(
......@@ -149,3 +149,17 @@ file(GLOB_RECURSE skinned_mesh_converter_HDR
add_executable(skinned_mesh_converter ${skinned_mesh_converter_SRC} ${skinned_mesh_converter_HDR})
target_link_libraries(skinned_mesh_converter ${LINKED_LIBRARIES})
################################################################################
# level_converter settings
################################################################################
file(GLOB_RECURSE level_converter_SRC
${CMAKE_CURRENT_SOURCE_DIR}/src/gdw/level_converter/*.cpp
)
file(GLOB_RECURSE level_converter_HDR
${CMAKE_CURRENT_SOURCE_DIR}/src/gdw/level_converter/*.hpp
)
add_executable(level_converter ${level_converter_SRC} ${level_converter_HDR})
target_link_libraries(level_converter ${LINKED_LIBRARIES})
#pragma once
#include <fstream>
#include <string>
#include <vector>
#include <glm/vec3.hpp>
#include <glm/gtc/quaternion.hpp>
namespace gdw {
namespace level_converter {
struct level_t {
uint64_t name_length;
std::string name;
};
struct entity_t {
uint64_t name_length;
std::string name;
glm::vec3 position;
glm::quat rotation;
glm::vec3 scale;
};
struct point_light_t {
glm::vec3 color;
glm::vec3 position;
};
struct directional_light_t {
glm::vec3 color;
glm::vec3 direction;
};
struct header_t {
const uint64_t magic = 0x313030564c574447;
level_t level;
uint64_t point_light_count;
uint64_t directional_light_count;
uint64_t entity_count;
};
inline std::ofstream& operator<<(std::ofstream& ofstream, const level_t& level) {
ofstream.write(reinterpret_cast<const char*>(&level.name_length), sizeof(uint64_t));
ofstream.write(level.name.c_str(), level.name_length);
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream, const header_t& header) {
ofstream.write(reinterpret_cast<const char*>(&header.magic), sizeof(uint64_t));
ofstream << header.level;
ofstream.write(reinterpret_cast<const char*>(&header.point_light_count), sizeof(uint64_t));
ofstream.write(reinterpret_cast<const char*>(&header.directional_light_count), sizeof(uint64_t));
ofstream.write(reinterpret_cast<const char*>(&header.entity_count), sizeof(uint64_t));
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream, const point_light_t& point_light) {
ofstream.write(reinterpret_cast<const char*>(&point_light.color), sizeof(glm::vec3));
ofstream.write(reinterpret_cast<const char*>(&point_light.position), sizeof(glm::vec3));
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream, const std::vector<point_light_t>& point_lights) {
for(auto& point_light : point_lights) {
ofstream << point_light;
}
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream, const directional_light_t& directional_light) {
ofstream.write(reinterpret_cast<const char*>(&directional_light.color), sizeof(glm::vec3));
ofstream.write(reinterpret_cast<const char*>(&directional_light.direction), sizeof(glm::vec3));
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream,
const std::vector<directional_light_t>& directional_lights) {
for(auto& directional_light : directional_lights) {
ofstream << directional_light;
}
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream, const entity_t& entity) {
ofstream.write(reinterpret_cast<const char*>(&entity.name_length), sizeof(uint64_t));
ofstream.write(entity.name.c_str(), entity.name_length);
ofstream.write(reinterpret_cast<const char*>(&entity.position), sizeof(glm::vec3));
ofstream.write(reinterpret_cast<const char*>(&entity.rotation), sizeof(glm::quat));
ofstream.write(reinterpret_cast<const char*>(&entity.scale), sizeof(glm::vec3));
return ofstream;
}
inline std::ofstream& operator<<(std::ofstream& ofstream,
const std::vector<entity_t>& entities) {
for(auto& entity : entities) {
ofstream << entity;
}
return ofstream;
}
}
}
#include <iostream>
#include <stdexcept>
#include <unordered_map>
#include <vector>
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <glm/gtc/constants.hpp>
#include <glm/gtx/hash.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/mat2x3.hpp>
#include <glm/gtx/matrix_decompose.hpp>
#include <gdw/level_converter/level.hpp>
#include <gdw/level_converter/level_converter.hpp>
#include <gdw/util/adjacency.hpp>
#include <gdw/util/conversion.hpp>
namespace gdw::level_converter {
level_converter::level_converter(const boost::filesystem::path& filename,
const boost::filesystem::path& output_directory) {
Assimp::Importer importer;
importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
auto scene = importer.ReadFile(filename.native(), aiProcessPreset_TargetRealtime_MaxQuality);
if(!scene) {
std::cout << importer.GetErrorString() << std::endl;
return;
}
check_scene_requirements(*scene);
auto stripped_filename = strip_filename(filename) + ".lvl";
auto output_filename = (output_directory / stripped_filename).native();
std::ofstream ofstream{output_filename, std::ios_base::trunc | std::ios_base::binary};
if(!ofstream) {
throw std::runtime_error{"could not open " + output_directory.native() + stripped_filename};
}
std::cout << "converting " + filename.string<std::string>() << " => " << output_filename << std::endl;
header_t header;
std::vector<point_light_t> point_lights;
std::vector<directional_light_t> directional_lights;
std::vector<entity_t> entities;
std::unordered_map<std::string, aiNode*> potential_empty_nodes;
auto root = scene->mRootNode;
std::stack<aiNode*> nodes;
nodes.push(root);
while(!nodes.empty()) {
auto node = nodes.top();
nodes.pop();
auto node_name = util::to_string(node->mName);
if(node->mNumMeshes == 0 && node->mNumChildren == 0) {
potential_empty_nodes.insert(std::make_pair(node_name, node));
} else if (node->mNumMeshes != 0 && node_name != "level") {
glm::mat4 model = util::to_glm(node->mTransformation);
glm::vec3 scale;
glm::quat orientation;
glm::vec3 translation;
glm::vec3 skew;
glm::vec4 perspective;
if (!glm::decompose(model, scale, orientation, translation, skew, perspective)) {
std::cout << "matrix decomposition failed" << std::endl;
}
auto offset_rotation_x = glm::angleAxis(glm::radians(-90.f), glm::vec3{1.f, 0.f, 0.f});
auto offset_rotation_y = glm::angleAxis(glm::radians(180.f), glm::vec3{0.f, 0.f, 1.f});
auto offset_rotation = glm::normalize(offset_rotation_x);
orientation = glm::normalize(offset_rotation * orientation);
auto name_postfix_position = node_name.rfind(".");
if (name_postfix_position != std::string::npos) {
node_name = node_name.substr(0, name_postfix_position);
}
std::cout << "entity name " << node_name << std::endl;
entities.emplace_back(entity_t{
node_name.size(),
node_name,
translation,
orientation,
scale
});
}
for(auto child_index = 0u; child_index < node->mNumChildren; ++child_index) {
nodes.push(node->mChildren[child_index]);
}
}
for(auto light_index = 0u; light_index < scene->mNumLights; ++light_index) {
auto& light = *scene->mLights[light_index];
auto light_name = util::to_string(light.mName);
auto light_node = potential_empty_nodes.find(light_name);
if(light_node != potential_empty_nodes.end()) {
potential_empty_nodes.erase(light_node);
}
auto transformation = util::to_glm(light_node->second->mTransformation);
if(light.mType == aiLightSource_POINT) {
point_lights.emplace_back(
point_light_t{util::to_glm(light.mColorDiffuse), glm::vec3{transformation[3]}});
} else if(light.mType == aiLightSource_DIRECTIONAL) {
auto orientation = glm::normalize(glm::quat_cast(transformation));
auto direction = glm::rotate(orientation, glm::vec3{0.f, 1.f, 0.f});
directional_lights.emplace_back(
directional_light_t{util::to_glm(light.mColorDiffuse), direction});
}
}
for(auto camera_index = 0u; camera_index < scene->mNumCameras; ++camera_index) {
auto& camera = *scene->mCameras[camera_index];
auto camera_name = util::to_string(camera.mName);
auto camera_node = potential_empty_nodes.find(camera_name);
if(camera_node != potential_empty_nodes.end()) {
potential_empty_nodes.erase(camera_node);
}
}
header.level.name = "level";
header.level.name_length = header.level.name.size();
header.point_light_count = point_lights.size();
header.directional_light_count = directional_lights.size();
header.entity_count = entities.size();
ofstream << header;
ofstream << point_lights;
ofstream << directional_lights;
ofstream << entities;
}
void level_converter::check_scene_requirements(const aiScene& scene) noexcept {
if(!scene.HasMaterials()) {
throw std::runtime_error{"file contains no material data"};
}
if(!scene.HasMeshes()) {
std::runtime_error{"file contains no mesh data"};
}
if(!scene.HasTextures()) {
std::runtime_error{"file contains no texture data"};
}
}
std::string level_converter::strip_filename(const boost::filesystem::path& path) {
auto filename = path.filename().replace_extension();
return filename.native();
}
void level_converter::check_mesh_requirements(const aiMesh& mesh) noexcept {
std::string mesh_name = util::to_string(mesh.mName);
if(mesh.HasBones()) {
throw std::runtime_error{"mesh " + mesh_name + " contains bone data"};
}
if(!mesh.HasFaces()) {
throw std::runtime_error{"mesh " + mesh_name + " contains no faces"};
}
if(!mesh.HasNormals()) {
throw std::runtime_error{"mesh " + mesh_name + " contains no normals"};
}
if(!mesh.HasPositions()) {
throw std::runtime_error{"mesh " + mesh_name + " contains no positions."};
}
if(!mesh.HasTangentsAndBitangents()) {
throw std::runtime_error{"mesh " + mesh_name + " contains no tangents and bitangents"};
}
if(!mesh.HasTextureCoords(0)) {
throw std::runtime_error{"mesh " + mesh_name + " contains no texture coordinates"};
}
}
}
#pragma once
#include <fstream>
#include <string>
#define BOOST_FILESYSTEM_NO_DEPRECATED
#include <boost/filesystem.hpp>
#include <assimp/mesh.h>
#include <assimp/scene.h>
namespace gdw::level_converter {
struct header;
/**
* This class parses an arbitiary scene file via assimp and converts it to the engines internal format.
*/
class level_converter {
public:
/**
* This constructor converts the input file into the engines internal format and writes it to the output
* directory.
* @param filename the name of the input scene that shall be converted
* @param output_directory the path to which the converted mesh shall be written
*/
level_converter(const boost::filesystem::path& filename, const boost::filesystem::path& output_directory);
~level_converter() = default;
level_converter(const level_converter&) = delete;
level_converter& operator=(const level_converter&) = delete;
level_converter(level_converter&&) = default;
level_converter& operator=(level_converter&&) = default;
private:
/**
* This helper function checks if a scene has all of the required data.
* @param scene the scene that shall be checked
*/
void check_scene_requirements(const aiScene& scene) noexcept;
/**
* This helper function strips the passed filepath to the bare filename without extension.
* @param path the filepath that shall be stripped
* @return the stripped file path as {@link std::string}
*/
std::string strip_filename(const boost::filesystem::path& path);
/**
* This helper function checks if a mesh has all of the required data.
* @param mesh the mesh that shall be checked
*/
void check_mesh_requirements(const aiMesh& mesh) noexcept;
};
}
#include <iostream>
#include <string>
#define BOOST_FILESYSTEM_NO_DEPRECATED
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <gdw/level_converter/level_converter.hpp>
int main(int argc, char* argv[]) {
boost::program_options::options_description description(
"GameDevWeek/C++ Level Converter\nUsage: level_converter -i FILE -o DIRECTORY\n\nConverts the "
"scene in the specified file into the GameDevWeek/C++ binary scene format");
description.add_options()("help", "Display available options")("version",
"Display the version of this program")(
"input,i", boost::program_options::value<std::string>()->required(),
"The input file which is to be converted. The supported mesh formats are:\n*.fbx Filmbox\n*.dae "
"Collada")("output,o", boost::program_options::value<std::string>()->required(),
"The output directory where the converted scene is being put.");
boost::program_options::variables_map options;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, description),
options);
if(options.count("help")) {
std::cout << description << std::endl;
return 0;
}
constexpr auto version = "1.0.0";
if(options.count("version")) {
std::cout << "GameDevWeek/C++ Level Converter " << version << std::endl;
return 0;
}
boost::program_options::notify(options);
auto input_file = options["input"].as<std::string>();
auto output_directory = options["output"].as<std::string>();
if(!boost::filesystem::exists(input_file)) {
std::cout << input_file << ": no such file or directory" << std::endl;
return -1;
} else {
if(!boost::filesystem::is_regular_file(input_file)) {
std::cout << input_file << " is not a file" << std::endl;
return -2;
}
}
if(!boost::filesystem::exists(output_directory)) {
std::cout << output_directory << ": no such file or directory" << std::endl;
return -1;
} else {
if(!boost::filesystem::is_directory(output_directory)) {
std::cout << output_directory << " is not a directory" << std::endl;
return -3;
}
}
gdw::level_converter::level_converter level_converter{boost::filesystem::path{input_file},
boost::filesystem::path{output_directory}};
return 0;
}
......@@ -17,7 +17,7 @@ int main(int argc, char* argv[]) {
"The input file which is to be converted. The supported mesh formats are:\n*.fbx Filmbox\n*.dae "
"Collada")("output,o", boost::program_options::value<std::string>()->required(),
"The output directory where the converted meshe is being put.")(
"transparent,t", "Marks the mesh as non opaque");
"transparent,t", "Marks the mesh as non opaque")("collision,c", "Generate collision mesh");
boost::program_options::variables_map options;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, description),
......@@ -39,6 +39,11 @@ int main(int argc, char* argv[]) {
transparent = true;
}
bool collision = false;
if (options.count("collision")) {
collision = true;
}
boost::program_options::notify(options);
auto input_file = options["input"].as<std::string>();
......@@ -65,7 +70,7 @@ int main(int argc, char* argv[]) {
}
gdw::mesh_converter::mesh_converter mesh_converter{
boost::filesystem::path{input_file}, boost::filesystem::path{output_directory}, transparent};
boost::filesystem::path{input_file}, boost::filesystem::path{output_directory}, transparent, collision};
return 0;
}
......@@ -20,7 +20,7 @@
namespace gdw::mesh_converter {
mesh_converter::mesh_converter(const boost::filesystem::path& filename,
const boost::filesystem::path& output_directory, bool transparent) {
const boost::filesystem::path& output_directory, bool transparent, bool collision) {
const auto offset_rotation = glm::angleAxis(glm::radians(-90.f), glm::vec3{1.f, 0.f, 0.f});
Assimp::Importer importer;
......@@ -35,7 +35,12 @@ mesh_converter::mesh_converter(const boost::filesystem::path& filename,
check_scene_requirements(*scene);
auto stripped_filename = strip_filename(filename) + ".msh";
auto stripped_filename = strip_filename(filename);
if (collision) {
stripped_filename += ".col";
} else {
stripped_filename += ".msh";
}
auto output_filename = (output_directory / stripped_filename).native();
std::ofstream ofstream{output_filename, std::ios_base::trunc | std::ios_base::binary};
if(!ofstream) {
......@@ -119,14 +124,25 @@ mesh_converter::mesh_converter(const boost::filesystem::path& filename,
header.submesh_count = submeshes.size();
header.transparent = transparent;
ofstream << header;
if (!collision) {
ofstream << header;
for(auto& submesh : submeshes) {
ofstream << submesh;
for(auto& submesh : submeshes) {
ofstream << submesh;
}
} else {
ofstream.write(reinterpret_cast<char*>(&header.vertex_count), sizeof(uint64_t));
ofstream.write(reinterpret_cast<char*>(&header.index_count), sizeof(uint64_t));
}
for(auto& vertex : vertices) {
ofstream << vertex;
if (!collision) {
for(auto& vertex : vertices) {
ofstream << vertex;
}
} else {
for(auto& vertex : vertices) {
ofstream.write(reinterpret_cast<char*>(&vertex.position), sizeof(glm::vec3));
}
}
for(auto& index : indices) {
......
......@@ -24,7 +24,7 @@ public:
* @param output_directory the path to which the converted mesh shall be written
*/
mesh_converter(const boost::filesystem::path& filename, const boost::filesystem::path& output_directory,
bool transparent);
bool transparent, bool collision);
~mesh_converter() = default;
mesh_converter(const mesh_converter&) = delete;
......
......@@ -27,6 +27,10 @@ glm::vec3 to_glm(const aiVector3D& vec3) {
return glm::vec3{vec3.x, vec3.y, vec3.z};
}
glm::vec3 to_glm(const aiColor3D& color) {
return glm::vec3{color.r, color.g, color.b} / 255.f;
}
/**
* Converts an assimp quaternion into a glm quaternion.
* @param quat the quaternion to convert
......
#include <iostream>
#include <stdexcept>
#include <unordered_map>
#include <vector>
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <glm/gtc/constants.hpp>
#include <glm/gtx/hash.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/mat2x3.hpp>
#include <gdw/skeleton_converter/skeleton.hpp>
#include <gdw/skeleton_converter/skeleton_converter.hpp>
#include <gdw/util/adjacency.hpp>
#include <gdw/util/conversion.hpp>
namespace gdw::skeleton_converter {
skeleton_converter::skeleton_converter(const boost::filesystem::path& filename,
const boost::filesystem::path& output_directory) {
Assimp::Importer importer;
importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
auto scene = importer.ReadFile(filename.native(), aiProcessPreset_TargetRealtime_MaxQuality);
if(!scene) {
std::cout << importer.GetErrorString() << std::endl;
return;
}
check_scene_requirements(*scene);
auto stripped_filename = strip_filename(filename) + ".skl";
auto output_filename = (output_directory / stripped_filename).native();
std::ofstream ofstream{output_filename, std::ios_base::trunc | std::ios_base::binary};
if(!ofstream) {
throw std::runtime_error{"could not open " + output_directory.native() + stripped_filename};
}
std::cout << "converting " + filename.string<std::string>() << " => " << output_filename << std::endl;
header header;
for(auto mesh_index = 0; mesh_index < scene