Files
filament/tools/filamesh/src/main.cpp
2020-01-06 14:38:32 -08:00

409 lines
14 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MeshWriter.h"
#include <fstream>
#include <iostream>
#include <math/half.h>
#include <math/mat3.h>
#include <math/norm.h>
#include <math/quat.h>
#include <math/vec3.h>
#include <utils/algorithm.h>
#include <utils/Path.h>
#include <filameshio/filamesh.h>
#include <getopt/getopt.h>
using namespace filamesh;
using namespace filament::math;
using namespace utils;
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/cimport.h>
#include <assimp/scene.h>
using Assimp::Importer;
// configuration
bool g_interleaved = false;
bool g_snormUVs = false;
bool g_compression = false;
Mesh g_mesh;
float2 g_minUV = float2(std::numeric_limits<float>::max());
float2 g_maxUV = float2(std::numeric_limits<float>::lowest());
template<bool SNORMUVS>
static ushort2 convertUV(float2 uv) {
if (SNORMUVS) {
short2 uvshort(packSnorm16(uv));
return bit_cast<ushort2>(uvshort);
} else {
half2 uvhalf(uv);
return bit_cast<ushort2>(uvhalf);
}
}
template<typename VECTOR, typename INDEX>
static Box computeAABB(VECTOR const* positions, INDEX const* indices,
size_t count, size_t stride) noexcept {
filament::math::float3 bmin(std::numeric_limits<float>::max());
filament::math::float3 bmax(std::numeric_limits<float>::lowest());
for (size_t i = 0; i < count; ++i) {
VECTOR const* p = reinterpret_cast<VECTOR const *>(
(char const*) positions + indices[i] * stride);
const filament::math::float3 v(p->x, p->y, p->z);
bmin = min(bmin, v);
bmax = max(bmax, v);
}
return Box().set(bmin, bmax);
}
void preprocessNode(const aiScene* scene, const aiNode* node) {
for (size_t i = 0; i < node->mNumMeshes; ++i) {
const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
if (!mesh->HasNormals()) {
std::cerr << "Error: mesh " << i << " does not have normals" << std::endl;
continue;
}
if (!mesh->HasTextureCoords(0)) {
std::cerr << "Warning: mesh " << i << " does not have texture coordinates"
<< std::endl;
continue;
}
const float3* uv0 = reinterpret_cast<const float3*>(mesh->mTextureCoords[0]);
const float3* uv1 = reinterpret_cast<const float3*>(mesh->mTextureCoords[1]);
if (!mesh->HasTextureCoords(1)) {
uv1 = nullptr;
}
const size_t numVertices = mesh->mNumVertices;
const size_t numFaces = mesh->mNumFaces;
if (numVertices == 0 || numFaces == 0) {
continue;
}
for (size_t j = 0; j < numVertices; j++) {
g_minUV = min(uv0[j].xy, g_minUV);
g_maxUV = max(uv0[j].xy, g_maxUV);
if (uv1) {
g_minUV = min(uv1[j].xy, g_minUV);
g_maxUV = max(uv1[j].xy, g_maxUV);
}
}
}
for (size_t i = 0; i < node->mNumChildren; ++i) {
preprocessNode(scene, node->mChildren[i]);
}
}
template<bool INTERLEAVED, bool SNORMUVS>
void processNode(const aiScene* scene, const aiNode* node, std::vector<Part>& meshes) {
for (size_t i = 0; i < node->mNumMeshes; ++i) {
const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
if (!mesh->HasNormals()) {
continue;
}
const float3* vertices = reinterpret_cast<const float3*>(mesh->mVertices);
const float3* tangents = reinterpret_cast<const float3*>(mesh->mTangents);
const float3* bitangents = reinterpret_cast<const float3*>(mesh->mBitangents);
const float3* normals = reinterpret_cast<const float3*>(mesh->mNormals);
const float4* colors = reinterpret_cast<const float4*>(mesh->mColors[0]);
const float3* uv0 = reinterpret_cast<const float3*>(mesh->mTextureCoords[0]);
const float3* uv1 = reinterpret_cast<const float3*>(mesh->mTextureCoords[1]);
if (!mesh->HasVertexColors(0)) {
colors = nullptr;
}
if (!mesh->HasTextureCoords(0)) {
uv0 = nullptr;
}
if (!mesh->HasTextureCoords(1)) {
uv1 = nullptr;
}
float4 color = {};
const size_t numVertices = mesh->mNumVertices;
if (numVertices > 0) {
const aiFace* faces = mesh->mFaces;
const size_t numFaces = mesh->mNumFaces;
if (numFaces > 0) {
uint32_t indicesOffset = g_mesh.vertexCount;
g_mesh.vertexCount += numVertices;
if (INTERLEAVED) {
g_mesh.vertices.reserve(g_mesh.vertexCount);
} else {
g_mesh.positions.reserve(g_mesh.vertexCount);
g_mesh.tangents.reserve(g_mesh.vertexCount);
g_mesh.uv0.reserve(g_mesh.vertexCount);
}
for (size_t j = 0; j < numVertices; j++) {
quatf q;
if (uv0) {
q = mat3f::packTangentFrame({tangents[j], bitangents[j], normals[j]});
} else {
q = quatf(0, 0, 0, 1);
}
color = colors ? colors[j] : float4(1.0f);
Vertex vertex {
.position = half4(vertices[j], 1.0_h),
.tangents = short4(filament::math::packSnorm16(q.xyzw)),
.color = ubyte4(clamp(color, 0.0f, 1.0f) * 255.0f),
.uv0 = uv0 ? convertUV<SNORMUVS>(uv0[j].xy) : ushort2(0),
};
if (INTERLEAVED) {
g_mesh.vertices.emplace_back(vertex);
} else {
g_mesh.positions.emplace_back(vertex.position);
g_mesh.tangents.emplace_back(vertex.tangents);
g_mesh.colors.emplace_back(vertex.color);
g_mesh.uv0.emplace_back(vertex.uv0);
if (uv1 != nullptr) {
g_mesh.uv1.emplace_back(convertUV<SNORMUVS>(uv1[j].xy));
}
}
}
// all faces should be triangles since we configure assimp to triangulate faces
uint32_t indicesCount = numFaces * faces[0].mNumIndices;
uint32_t indexBufferOffset = g_mesh.indices.size();
g_mesh.indices.reserve(g_mesh.indices.size() + indicesCount);
for (size_t j = 0; j < numFaces; ++j) {
const aiFace& face = faces[j];
for (size_t k = 0; k < face.mNumIndices; ++k) {
g_mesh.indices.push_back(uint32_t(face.mIndices[k] + indicesOffset));
}
}
size_t stride = INTERLEAVED ? sizeof(Vertex) : sizeof(Vertex::position);
const decltype(Vertex::position)* positions =
INTERLEAVED ? &g_mesh.vertices.data()->position : g_mesh.positions.data();
const Box aabb(computeAABB(positions,
g_mesh.indices.data() + indexBufferOffset, indicesCount, stride));
meshes.emplace_back(Part {
.offset = indexBufferOffset,
.indexCount = indicesCount,
.minIndex = indicesOffset,
.maxIndex = (indicesOffset + indicesCount - 1),
.material = mesh->mMaterialIndex,
.aabb = aabb
});
}
}
}
for (size_t i = 0 ; i < node->mNumChildren ; ++i) {
processNode<INTERLEAVED, SNORMUVS>(scene, node->mChildren[i], meshes);
}
}
static void printUsage(const char* name) {
std::string execName(utils::Path(name).getName());
std::string usage(
"FILAMESH is a tool to convert meshes into an optimized binary format\n"
"Usage:\n"
" FILAMESH [options] <source mesh> <destination file>\n"
"\n"
"Supported mesh formats:\n"
" FBX, OBJ\n"
"\n"
"Input meshes must have texture coordinates.\n"
"\n"
"Options:\n"
" --help, -h\n"
" print this message\n\n"
" --license\n"
" Print copyright and license information\n\n"
" --interleaved, -i\n"
" interleaves mesh attributes\n\n"
" --compress, -c\n"
" enable compression\n\n"
);
const std::string from("FILAMESH");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), execName);
}
printf("%s", usage.c_str());
}
static void license() {
static const char *license[] = {
#include "licenses/licenses.inc"
nullptr
};
const char **p = &license[0];
while (*p)
std::cout << *p++ << std::endl;
}
static int handleArguments(int argc, char* argv[]) {
static constexpr const char* OPTSTR = "hilc";
static const struct option OPTIONS[] = {
{ "help", no_argument, 0, 'h' },
{ "license", no_argument, 0, 'l' },
{ "interleaved", no_argument, 0, 'i' },
{ "compress", no_argument, 0, 'c' },
{ 0, 0, 0, 0 } // termination of the option list
};
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
// std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
// break;
case 'l':
license();
exit(0);
// break;
case 'i':
g_interleaved = true;
break;
case 'c':
g_compression = true;
break;
}
}
return optind;
}
int main(int argc, char* argv[]) {
int optionIndex = handleArguments(argc, argv);
int numArgs = argc - optionIndex;
if (numArgs < 2) {
printUsage(argv[0]);
return 1;
}
Path src(argv[optionIndex]);
if (!src.exists()) {
std::cerr << "The source mesh " << src << " does not exist." << std::endl;
return 1;
}
Importer importer;
importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE,
aiPrimitiveType_LINE | aiPrimitiveType_POINT);
importer.SetPropertyBool(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, true);
importer.SetPropertyBool(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, true);
const aiScene* scene = importer.ReadFile(src,
// normals and tangents
aiProcess_GenSmoothNormals |
aiProcess_CalcTangentSpace |
// topology optimization
aiProcess_FindInstances |
aiProcess_OptimizeMeshes |
aiProcess_JoinIdenticalVertices |
// misc optimization
aiProcess_ImproveCacheLocality |
aiProcess_PreTransformVertices |
aiProcess_SortByPType |
// we only support triangles
aiProcess_Triangulate);
if (!scene) {
std::cerr << "Unknown mesh format in " << src << std::endl;
return 1;
}
const aiNode* node = scene->mRootNode;
// Check for acceptable assimp data and determine UV bounds.
preprocessNode(scene, node);
g_snormUVs = g_minUV.x >= -1.0f && g_minUV.x <= 1.0f && g_maxUV.x >= -1.0f && g_maxUV.x <= 1.0f &&
g_minUV.y >= -1.0f && g_minUV.y <= 1.0f && g_maxUV.y >= -1.0f && g_maxUV.y <= 1.0f;
// Consume assimp data and produce filamesh data.
if (g_interleaved) {
if (g_snormUVs) {
processNode<true, true>(scene, node, g_mesh.parts);
} else {
processNode<true, false>(scene, node, g_mesh.parts);
}
} else {
if (g_snormUVs) {
processNode<false, true>(scene, node, g_mesh.parts);
} else {
processNode<false, false>(scene, node, g_mesh.parts);
}
}
uint32_t materialCount = scene->mNumMaterials;
for (uint32_t i = 0; i < materialCount; i++) {
const aiMaterial* material = scene->mMaterials[i];
aiString name;
if (material->Get(AI_MATKEY_NAME, name) != AI_SUCCESS) {
std::cerr << "Unnamed material replaced with 'default'" << std::endl;
g_mesh.materials.emplace_back("default");
} else {
g_mesh.materials.emplace_back(name.C_Str());
}
}
Path dst(argv[optionIndex + 1]);
const Path outputDir(dst.getParent());
if (!outputDir.exists()) {
outputDir.mkdirRecursive();
}
std::ofstream out(dst, std::ios::binary | std::ios::trunc);
if (!out.good()) {
std::cerr << "Could not write to " << dst << std::endl;
out.close();
return 1;
}
uint32_t flags = 0;
if (g_interleaved) {
flags |= filamesh::INTERLEAVED;
}
if (g_snormUVs) {
flags |= filamesh::TEXCOORD_SNORM16;
}
if (g_compression) {
flags |= filamesh::COMPRESSION;
}
MeshWriter(flags).serialize(out, g_mesh);
out.flush();
out.close();
return 0;
}