From a0813cba0ce480a8620a00f9f0572f0f0a7cd0c8 Mon Sep 17 00:00:00 2001 From: Mikulas Florek Date: Sat, 14 Dec 2024 22:26:53 +0100 Subject: [PATCH] fixed fbx -> prefab --- external/openfbx/ofbx.cpp | 36 +- external/openfbx/ofbx.h | 4 +- src/core/job_system.cpp | 44 +- src/core/math.cpp | 16 + src/core/math.h | 1 + src/core/string.cpp | 5 + src/core/string.h | 1 + src/core/win/simple_win.h | 4 + src/editor/file_system_watcher.h | 1 + src/engine/file_system.cpp | 1 + src/renderer/editor/fbx_importer.cpp | 416 +++++++++++------- src/renderer/editor/model_importer.cpp | 565 ++++++++++++++----------- src/renderer/editor/model_importer.h | 47 +- src/renderer/editor/model_meta.h | 6 + src/renderer/editor/render_plugins.cpp | 29 +- 15 files changed, 730 insertions(+), 446 deletions(-) diff --git a/external/openfbx/ofbx.cpp b/external/openfbx/ofbx.cpp index 93210fadbe..83ba9e2afe 100644 --- a/external/openfbx/ofbx.cpp +++ b/external/openfbx/ofbx.cpp @@ -1182,7 +1182,10 @@ struct GeometryDataImpl : GeometryData { Vec4Attributes getColors() const override { return patchAttributes(colors); } Vec3Attributes getTangents() const override { return patchAttributes(tangents); } int getPartitionCount() const override { return (int)partitions.size(); } - + + const int getMaterialMapSize() const override { return (int)materials.size(); } + const int* getMaterialMap() const override { return materials.empty() ? nullptr : &materials[0]; } + GeometryPartition getPartition(int index) const override { if (index >= partitions.size()) return {nullptr, 0, 0, 0}; return { @@ -3123,26 +3126,28 @@ static OptionalError parseAnimationCurve(const Scene& scene, const Elem return curve; } -static OptionalError parseGeometry(const Element& element, GeometryImpl& geom, std::vector &jobs, Allocator& allocator) { +static OptionalError parseGeometry(const Element& element, GeometryImpl& geom, std::vector &jobs, bool ignore_geometry, Allocator& allocator) { assert(element.first_property); + if (!parseGeometryMaterials(geom, element, jobs)) return Error("Invalid materials"); + const Element* vertices_element = findChild(element, "Vertices"); - if (!vertices_element || !vertices_element->first_property) - { + if (!vertices_element || !vertices_element->first_property) { return &geom; } const Element* polys_element = findChild(element, "PolygonVertexIndex"); if (!polys_element || !polys_element->first_property) return Error("Indices missing"); - if (!pushJob(jobs, *vertices_element->first_property, geom.positions.values)) return Error("Invalid vertices"); - if (!pushJob(jobs, *polys_element->first_property, geom.positions.indices)) return Error("Invalid vertices"); + if (!ignore_geometry) { + if (!pushJob(jobs, *vertices_element->first_property, geom.positions.values)) return Error("Invalid vertices"); + if (!pushJob(jobs, *polys_element->first_property, geom.positions.indices)) return Error("Invalid vertices"); - if (!parseGeometryMaterials(geom, element, jobs)) return Error("Invalid materials"); - if (!parseGeometryUVs(geom, element, jobs)) return Error("Invalid vertex attributes"); - if (!parseGeometryTangents(geom, element, jobs)) return Error("Invalid vertex attributes"); - if (!parseGeometryColors(geom, element, jobs)) return Error("Invalid vertex attributes"); - if (!parseGeometryNormals(geom, element, jobs)) return Error("Invalid vertex attributes"); + if (!parseGeometryUVs(geom, element, jobs)) return Error("Invalid vertex attributes"); + if (!parseGeometryTangents(geom, element, jobs)) return Error("Invalid vertex attributes"); + if (!parseGeometryColors(geom, element, jobs)) return Error("Invalid vertex attributes"); + if (!parseGeometryNormals(geom, element, jobs)) return Error("Invalid vertex attributes"); + } return &geom; } @@ -3406,6 +3411,7 @@ static bool parseObjects(const Element& root, Scene& scene, u16 flags, Allocator const bool ignore_limbs = (flags & (u16)LoadFlags::IGNORE_LIMBS) != 0; const bool ignore_meshes = (flags & (u16)LoadFlags::IGNORE_MESHES) != 0; const bool ignore_models = (flags & (u16)LoadFlags::IGNORE_MODELS) != 0; + const bool keep_matertial_map = (flags & (u16)LoadFlags::KEEP_MATERIAL_MAP) != 0; const Element* objs = findChild(root, "Objects"); if (!objs) return true; @@ -3438,18 +3444,20 @@ static bool parseObjects(const Element& root, Scene& scene, u16 flags, Allocator if (iter.second.object == scene.m_root) continue; - if (iter.second.element->id == "Geometry" && !ignore_geometry) + if (iter.second.element->id == "Geometry") { Property* last_prop = iter.second.element->first_property; while (last_prop->next) last_prop = last_prop->next; if (last_prop && last_prop->value == "Mesh") { GeometryImpl* geom = allocator.allocate(scene, *iter.second.element); - parseGeometry(*iter.second.element, *geom, jobs, allocator); + if (!ignore_geometry || keep_matertial_map) { + parseGeometry(*iter.second.element, *geom, jobs, ignore_geometry, allocator); + } obj = geom; scene.m_geometries.push_back(geom); } - else if (last_prop && last_prop->value == "Shape") + else if (last_prop && last_prop->value == "Shape" && !ignore_geometry) { obj = allocator.allocate(scene, *iter.second.element); } diff --git a/external/openfbx/ofbx.h b/external/openfbx/ofbx.h index 6c0edef1e2..28154572c7 100644 --- a/external/openfbx/ofbx.h +++ b/external/openfbx/ofbx.h @@ -30,7 +30,7 @@ using JobProcessor = void (*)(JobFunction, void*, void*, u32, u32); enum class LoadFlags : u16 { NONE = 0, - UNUSED = 1 << 0, // can be reused + KEEP_MATERIAL_MAP = 1 << 0, // keep material map even if IGNORE_GEOMETRY is used IGNORE_GEOMETRY = 1 << 1, IGNORE_BLEND_SHAPES = 1 << 2, IGNORE_CAMERAS = 1 << 3, @@ -544,6 +544,8 @@ struct GeometryData { virtual Vec2Attributes getUVs(int index = 0) const = 0; virtual Vec4Attributes getColors() const = 0; virtual Vec3Attributes getTangents() const = 0; + virtual const int getMaterialMapSize() const = 0; + virtual const int* getMaterialMap() const = 0; virtual int getPartitionCount() const = 0; virtual GeometryPartition getPartition(int partition_index) const = 0; }; diff --git a/src/core/job_system.cpp b/src/core/job_system.cpp index 13b2bd5dbd..0a7767641f 100644 --- a/src/core/job_system.cpp +++ b/src/core/job_system.cpp @@ -302,6 +302,7 @@ struct WorkerTask : Thread { Signal* m_signal_to_check = nullptr; WaitingFiber* m_waiting_fiber_to_push = nullptr; + i32 m_deferred_push_to_worker = -1; Fiber::Handle m_primary_fiber; System& m_system; @@ -412,6 +413,17 @@ LUMIX_FORCE_INLINE static bool popWork(Work& work, WorkerTask* worker) { static void afterSwitch() { WorkerTask* worker = getWorker(); + if (worker->m_deferred_push_to_worker >= 0) { + if (worker->m_deferred_push_to_worker != ANY_WORKER) { + WorkerTask* dst_worker = g_system->m_workers[worker->m_deferred_push_to_worker % g_system->m_workers.size()]; + dst_worker->m_work_queue.pushAndWake(worker->m_waiting_fiber_to_push->fiber, dst_worker); + } + else { + g_system->m_global_queue.pushAndWake(worker->m_waiting_fiber_to_push->fiber, nullptr); + } + worker->m_deferred_push_to_worker = -1; + } + if (!worker->m_signal_to_check) return; Signal* signal = worker->m_signal_to_check; @@ -737,31 +749,29 @@ void exit(Mutex* mutex) { // TODO race condition in both moveJobToWorker and yield, we could be poppped while we are still inside void moveJobToWorker(u8 worker_index) { - FiberJobPair* this_fiber = getWorker()->m_current_fiber; - WorkerTask* worker = g_system->m_workers[worker_index % g_system->m_workers.size()]; - worker->m_work_queue.pushAndWake(this_fiber, worker); - + WorkerTask* worker = getWorker(); + if (worker->m_worker_index == worker_index) return; + + FiberJobPair* this_fiber = worker->m_current_fiber; + WaitingFiber waiting_fiber; + waiting_fiber.fiber = worker->m_current_fiber; + waiting_fiber.next = nullptr; + worker->m_waiting_fiber_to_push = &waiting_fiber; + worker->m_deferred_push_to_worker = worker_index; + FiberJobPair* new_fiber = popFreeFiber(); - getWorker()->m_current_fiber = new_fiber; + worker->m_current_fiber = new_fiber; this_fiber->current_job.worker_index = worker_index; Fiber::switchTo(&this_fiber->fiber, new_fiber->fiber); afterSwitch(); - getWorker()->m_current_fiber = this_fiber; - ASSERT(getWorker()->m_worker_index == worker_index); + worker = getWorker(); + worker->m_current_fiber = this_fiber; + ASSERT(worker->m_worker_index == worker_index || worker_index == ANY_WORKER); } void yield() { - FiberJobPair* this_fiber = getWorker()->m_current_fiber; - WorkerTask* worker = getWorker(); - g_system->m_global_queue.pushAndWake(this_fiber, nullptr); - - FiberJobPair* new_fiber = popFreeFiber(); - this_fiber->current_job.worker_index = ANY_WORKER; - getWorker()->m_current_fiber = new_fiber; - Fiber::switchTo(&this_fiber->fiber, new_fiber->fiber); - afterSwitch(); - getWorker()->m_current_fiber = this_fiber; + moveJobToWorker(ANY_WORKER); } void run(void* data, void(*task)(void*), Counter* on_finished, u8 worker_index) diff --git a/src/core/math.cpp b/src/core/math.cpp index 8d3f581563..e89d57501b 100644 --- a/src/core/math.cpp +++ b/src/core/math.cpp @@ -976,6 +976,22 @@ void Matrix::setPerspective(float fov, float ratio, float near_plane, float far_ } } +// assumes ortho matrix +void Matrix::decompose(Vec3& position, Quat& rotation, Vec3& scale) const { + position = getTranslation(); + Vec3 x = getXVector(); + Vec3 y = getYVector(); + Vec3 z = getZVector(); + scale.x = length(x); + scale.y = length(y); + scale.z = length(z); + Matrix mtx = Matrix::IDENTITY; + mtx.setXVector(x / scale.x); + mtx.setYVector(y / scale.y); + mtx.setZVector(z / scale.z); + rotation = mtx.getRotation(); +} + void Matrix::decompose(Vec3& position, Quat& rotation, float& scale) const { position = getTranslation(); scale = length(getXVector()); diff --git a/src/core/math.h b/src/core/math.h index 31d68a240e..9e10c05306 100644 --- a/src/core/math.h +++ b/src/core/math.h @@ -338,6 +338,7 @@ struct alignas(16) LUMIX_CORE_API Matrix { Matrix(const Vec3& pos, const Quat& rot); void decompose(Vec3& position, Quat& rotation, float& scale) const; + void decompose(Vec3& position, Quat& rotation, Vec3& scale) const; float operator[](int index) const { return (&columns[0].x)[index]; } float& operator[](int index) { return (&columns[0].x)[index]; } diff --git a/src/core/string.cpp b/src/core/string.cpp index 713b28b2a3..97e808ed74 100644 --- a/src/core/string.cpp +++ b/src/core/string.cpp @@ -809,4 +809,9 @@ bool startsWithInsensitive(StringView str, StringView prefix) { return equalIStrings(str, prefix); } +bool StringView::operator==(const StringView& rhs) const { + return equalStrings(*this, rhs); +} + + } // namespace Lumix diff --git a/src/core/string.h b/src/core/string.h index 322cea155d..f7589fdbae 100644 --- a/src/core/string.h +++ b/src/core/string.h @@ -30,6 +30,7 @@ struct LUMIX_CORE_API StringView { void removeSuffix(u32 count) { ASSERT(count <= size()); end -= count; } void removePrefix(u32 count) { ASSERT(count <= size()); begin += count; } bool empty() const { return begin == end || !begin[0]; } + bool operator==(const StringView& rhs) const; const char* begin = nullptr; const char* end = nullptr; diff --git a/src/core/win/simple_win.h b/src/core/win/simple_win.h index 700e745373..0b809e9385 100644 --- a/src/core/win/simple_win.h +++ b/src/core/win/simple_win.h @@ -667,6 +667,10 @@ __inline DWORD GetCurrentDirectory(DWORD nBufferLength, LPSTR lpBuffer) { return GetCurrentDirectoryA(nBufferLength, lpBuffer); } + +WINBASEAPI DWORD WINAPI GetFinalPathNameByHandleA(HANDLE hFile, LPSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); +WINBASEAPI DWORD WINAPI GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart); + WINBASEAPI HANDLE WINAPI FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData); WINUSERAPI BOOL WINAPI PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); diff --git a/src/editor/file_system_watcher.h b/src/editor/file_system_watcher.h index b720c848d9..56edb4316f 100644 --- a/src/editor/file_system_watcher.h +++ b/src/editor/file_system_watcher.h @@ -13,6 +13,7 @@ struct LUMIX_EDITOR_API FileSystemWatcher virtual ~FileSystemWatcher() {} static UniquePtr create(const char* path, struct IAllocator& allocator); + // TODO on windows, this always returns lower case path virtual Delegate& getCallback() = 0; }; diff --git a/src/engine/file_system.cpp b/src/engine/file_system.cpp index 191bfa12c0..02724814cb 100644 --- a/src/engine/file_system.cpp +++ b/src/engine/file_system.cpp @@ -102,6 +102,7 @@ struct FileSystemImpl : FileSystem { } bool getContentSync(const Path& path, OutputMemoryStream& content) override { + PROFILE_FUNCTION(); os::InputFile file; const Path full_path(m_base_path, path); diff --git a/src/renderer/editor/fbx_importer.cpp b/src/renderer/editor/fbx_importer.cpp index 64f834eaae..c7f60373e5 100644 --- a/src/renderer/editor/fbx_importer.cpp +++ b/src/renderer/editor/fbx_importer.cpp @@ -13,6 +13,7 @@ #include "core/math.h" #include "core/os.h" #include "core/path.h" +#include "core/stack_array.h" #include "engine/prefab.h" #include "core/profiler.h" #include "engine/resource_manager.h" @@ -35,6 +36,36 @@ namespace Lumix { +namespace { + +struct FBXImportGeometry { + const ofbx::GeometryData* geom; + const ofbx::Mesh* mesh; + i32 bone_idx = -1; +}; + +struct GeomPartition { + const ofbx::GeometryData* geom; + u32 partition; + u32 material; + bool flip_handness; + + bool operator==(const GeomPartition& rhs) const { + return geom == rhs.geom + && partition == rhs.partition + && material == rhs.material + && flip_handness == rhs.flip_handness; + } +}; + +} + +template<> struct HashFunc { + static u32 get(const GeomPartition& k) { + return k.partition ^ k.material ^ u32(u64(k.geom)) ^ u32(u64(k.geom) >> 32); + } +}; + struct FBXImporter : ModelImporter { struct Skin { float weights[4]; @@ -43,10 +74,10 @@ struct FBXImporter : ModelImporter { }; struct VertexLayout { - u32 size; - u32 normal_offset; - u32 uv_offset; - u32 tangent_offset; + i32 size = -1; + i32 normal_offset = -1; + i32 uv_offset = -1; + i32 tangent_offset = -1; }; enum class Orientation { @@ -113,39 +144,6 @@ struct FBXImporter : ModelImporter { return un.ui32; } - static u32 packF4u(const Vec3& vec) { - const i8 xx = i8(clamp((vec.x * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); - const i8 yy = i8(clamp((vec.y * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); - const i8 zz = i8(clamp((vec.z * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); - const i8 ww = i8(0); - - union { - u32 ui32; - i8 arr[4]; - } un; - - un.arr[0] = xx; - un.arr[1] = yy; - un.arr[2] = zz; - un.arr[3] = ww; - - return un.ui32; - } - - static Vec3 unpackF4u(u32 packed) { - union { - u32 ui32; - i8 arr[4]; - } un; - - un.ui32 = packed; - Vec3 res; - res.x = un.arr[0]; - res.y = un.arr[1]; - res.z = un.arr[2]; - return ((res + Vec3(128.f)) / 255) * 2.f - 0.5f; - } - static bool doesFlipHandness(const Matrix& mtx) { Vec3 x(1, 0, 0); Vec3 y(0, 1, 0); @@ -153,14 +151,13 @@ struct FBXImporter : ModelImporter { return z.z < 0; } - static bool hasTangents(const ofbx::Mesh& mesh) { - const ofbx::GeometryData& geom = mesh.getGeometryData(); + static bool hasTangents(const ofbx::GeometryData& geom) { if (geom.getTangents().values) return true; if (geom.getUVs().values) return true; return false; } - static int getVertexSize(const ofbx::Mesh& mesh, bool is_skinned, const ModelMeta& meta) { + static int getVertexSize(const ofbx::GeometryData& geom, bool is_skinned, const ModelMeta& meta) { static const int POSITION_SIZE = sizeof(float) * 3; static const int NORMAL_SIZE = sizeof(u8) * 4; static const int TANGENT_SIZE = sizeof(u8) * 4; @@ -170,18 +167,16 @@ struct FBXImporter : ModelImporter { static const int BONE_INDICES_WEIGHTS_SIZE = sizeof(float) * 4 + sizeof(u16) * 4; int size = POSITION_SIZE + NORMAL_SIZE; - const ofbx::GeometryData& geom = mesh.getGeometryData(); - if (geom.getUVs().values) size += UV_SIZE; if (meta.bake_vertex_ao) size += AO_SIZE; if (geom.getColors().values && meta.import_vertex_colors) size += meta.vertex_color_is_ao ? AO_SIZE : COLOR_SIZE; - if (hasTangents(mesh)) size += TANGENT_SIZE; + if (hasTangents(geom)) size += TANGENT_SIZE; if (is_skinned) size += BONE_INDICES_WEIGHTS_SIZE; return size; } - static bool areIndices16Bit(const ImportMesh& mesh) { + static bool areIndices16Bit(const ImportGeometry& mesh) { int vertex_size = mesh.vertex_size; return mesh.vertex_buffer.size() / vertex_size < (1 << 16); } @@ -208,7 +203,7 @@ struct FBXImporter : ModelImporter { } } - static void computeTangents(ImportMesh& mesh, OutputMemoryStream& unindexed_triangles, const VertexLayout& layout, const Path& path) { + static void computeTangents(OutputMemoryStream& unindexed_triangles, const VertexLayout& layout, const Path& path) { PROFILE_FUNCTION(); struct { @@ -281,7 +276,7 @@ struct FBXImporter : ModelImporter { } } - static void computeTangentsSimple(ImportMesh& mesh, OutputMemoryStream& unindexed_triangles, const VertexLayout& layout) { + static void computeTangentsSimple(OutputMemoryStream& unindexed_triangles, const VertexLayout& layout) { PROFILE_FUNCTION(); const u32 vertex_size = layout.size; const int vertex_count = int(unindexed_triangles.size() / vertex_size); @@ -321,7 +316,7 @@ struct FBXImporter : ModelImporter { } } - static void remap(const OutputMemoryStream& unindexed_triangles, ImportMesh& mesh) { + static void remap(const OutputMemoryStream& unindexed_triangles, ImportGeometry& mesh) { PROFILE_FUNCTION(); const u32 vertex_count = u32(unindexed_triangles.size() / mesh.vertex_size); mesh.indices.resize(vertex_count); @@ -330,46 +325,48 @@ struct FBXImporter : ModelImporter { mesh.vertex_buffer.resize(unique_vertex_count * mesh.vertex_size); + u8* vb = mesh.vertex_buffer.getMutableData(); for (u32 i = 0; i < vertex_count; ++i) { const u8* src = unindexed_triangles.data() + i * mesh.vertex_size; - u8* dst = mesh.vertex_buffer.getMutableData() + mesh.indices[i] * mesh.vertex_size; + u8* dst = vb + mesh.indices[i] * mesh.vertex_size; memcpy(dst, src, mesh.vertex_size); } } // convert from ofbx to runtime vertex data, compute missing info (normals, tangents, ao, ...) void postprocess(const ModelMeta& meta, const Path& path) { - AtomicI32 mesh_idx_getter = 0; + AtomicI32 geom_idx_getter = 0; jobs::runOnWorkers([&](){ Array skinning(m_allocator); OutputMemoryStream unindexed_triangles(m_allocator); Array tri_indices_tmp(m_allocator); for (;;) { - i32 mesh_idx = mesh_idx_getter.inc(); - if (mesh_idx >= m_meshes.size()) break; + i32 geom_idx = geom_idx_getter.inc(); + if (geom_idx >= m_geometries.size()) break; - ImportMesh& import_mesh = m_meshes[mesh_idx]; - const ofbx::Mesh* mesh = m_fbx_meshes[import_mesh.mesh_index]; - import_mesh.vertex_size = getVertexSize(*mesh, import_mesh.is_skinned, meta); - const ofbx::GeometryData& geom = mesh->getGeometryData(); - ofbx::GeometryPartition partition = geom.getPartition(import_mesh.submesh == -1 ? 0 : import_mesh.submesh); + ImportGeometry& import_geom = m_geometries[geom_idx]; + const FBXImportGeometry* fbx_geom = (const FBXImportGeometry*)import_geom.user_data; + + const ofbx::GeometryData& geom = *fbx_geom->geom; + import_geom.vertex_size = getVertexSize(geom, import_geom.is_skinned, meta); + ofbx::GeometryPartition partition = geom.getPartition(import_geom.submesh == -1 ? 0 : import_geom.submesh); if (partition.polygon_count == 0) continue; - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::POSITION, .type = gpu::AttributeType::FLOAT, .num_components = 3 }); - - import_mesh.attributes.push({ + + import_geom.attributes.push({ .semantic = AttributeSemantic::NORMAL, .type = gpu::AttributeType::I8, .num_components = 4 }); if (geom.getUVs().values) { - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::TEXCOORD0, .type = gpu::AttributeType::FLOAT, .num_components = 2 @@ -377,7 +374,7 @@ struct FBXImporter : ModelImporter { } if (meta.bake_vertex_ao) { - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::AO, .type = gpu::AttributeType::U8, .num_components = 4 // 1+3 because of padding @@ -385,36 +382,36 @@ struct FBXImporter : ModelImporter { } if (geom.getColors().values && meta.import_vertex_colors) { if (meta.vertex_color_is_ao) { - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::AO, .type = gpu::AttributeType::U8, .num_components = 4 // 1+3 because of padding }); } else { - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::COLOR0, .type = gpu::AttributeType::U8, .num_components = 4 }); } } - - if (hasTangents(*mesh)) { - import_mesh.attributes.push({ + + if (hasTangents(geom)) { + import_geom.attributes.push({ .semantic = AttributeSemantic::TANGENT, .type = gpu::AttributeType::I8, .num_components = 4 }); } - if (import_mesh.is_skinned) { - import_mesh.attributes.push({ + if (import_geom.is_skinned) { + import_geom.attributes.push({ .semantic = AttributeSemantic::JOINTS, .type = gpu::AttributeType::U16, .num_components = 4 }); - import_mesh.attributes.push({ + import_geom.attributes.push({ .semantic = AttributeSemantic::WEIGHTS, .type = gpu::AttributeType::FLOAT, .num_components = 4 @@ -424,10 +421,6 @@ struct FBXImporter : ModelImporter { PROFILE_BLOCK("FBX convert vertex data") profiler::pushInt("Triangle count", partition.triangles_count); - Matrix transform_matrix = Matrix::IDENTITY; - Matrix geometry_matrix = toLumix(mesh->getGeometricMatrix()); - transform_matrix = toLumix(mesh->getGlobalTransform()) * geometry_matrix; - ofbx::Vec3Attributes normals = geom.getNormals(); ofbx::Vec3Attributes tangents = geom.getTangents(); ofbx::Vec4Attributes colors = meta.import_vertex_colors ? geom.getColors() : ofbx::Vec4Attributes{}; @@ -435,7 +428,7 @@ struct FBXImporter : ModelImporter { VertexLayout vertex_layout; - const bool compute_tangents = !tangents.values && uvs.values; + const bool compute_tangents = !tangents.values && uvs.values || meta.force_recompute_tangents; vertex_layout.size = sizeof(Vec3); // position vertex_layout.normal_offset = vertex_layout.size; @@ -446,30 +439,53 @@ struct FBXImporter : ModelImporter { if (colors.values && meta.import_vertex_colors) vertex_layout.size += sizeof(u32); vertex_layout.tangent_offset = vertex_layout.size; if (tangents.values || compute_tangents) vertex_layout.size += sizeof(u32); - if (import_mesh.is_skinned) vertex_layout.size += sizeof(Vec4) + 4 * sizeof(u16); - - if (import_mesh.is_skinned) fillSkinInfo(skinning, import_mesh, m_fbx_meshes[import_mesh.mesh_index]); + if (import_geom.is_skinned) vertex_layout.size += sizeof(Vec4) + 4 * sizeof(u16); - triangulate(unindexed_triangles, import_mesh, partition, skinning, meta, transform_matrix, tri_indices_tmp); + if (import_geom.is_skinned) { + const FBXImportGeometry* g = (const FBXImportGeometry*)import_geom.user_data; + fillSkinInfo(skinning, g->bone_idx, fbx_geom->mesh); + triangulate(unindexed_triangles, import_geom, partition, geom, &skinning, meta, tri_indices_tmp); + } + else { + triangulate(unindexed_triangles, import_geom, partition, geom, nullptr, meta, tri_indices_tmp); + } if (!normals.values || meta.force_recompute_normals) computeNormals(unindexed_triangles, vertex_layout); if (compute_tangents) { if (meta.use_mikktspace) { - computeTangents(import_mesh, unindexed_triangles, vertex_layout, path); + computeTangents(unindexed_triangles, vertex_layout, path); } else { - computeTangentsSimple(import_mesh, unindexed_triangles, vertex_layout); + computeTangentsSimple(unindexed_triangles, vertex_layout); } } - remap(unindexed_triangles, import_mesh); - import_mesh.index_size = areIndices16Bit(import_mesh) ? 2 : 4; + remap(unindexed_triangles, import_geom); + import_geom.index_size = areIndices16Bit(import_geom) ? 2 : 4; + + if (import_geom.flip_handness) { + const u32 num_vertices = u32(import_geom.vertex_buffer.size() / import_geom.vertex_size); + u8* data = import_geom.vertex_buffer.getMutableData(); + auto transform_vec = [&](u32 offset){ + u32 packed; + memcpy(&packed, data + offset, sizeof(packed)); + Vec3 v = unpackF4u(packed); + v.x *= -1; + packed = packF4u(v); + memcpy(data + offset, &packed, sizeof(packed)); + }; + for (u32 i = 0; i < num_vertices; ++i) { + Vec3 p; + memcpy(&p, data + i * import_geom.vertex_size, sizeof(p)); + p.x *= -1; + memcpy(data + i * import_geom.vertex_size, &p, sizeof(p)); + transform_vec(i * import_geom.vertex_size + vertex_layout.normal_offset); + transform_vec(i * import_geom.vertex_size + vertex_layout.tangent_offset); + } - const bool flip_handness = doesFlipHandness(transform_matrix); - if (flip_handness) { - for (i32 i = 0, n = import_mesh.indices.size(); i < n; i += 3) { - swap(import_mesh.indices[i], import_mesh.indices[i+1]); + for (i32 i = 0, n = import_geom.indices.size(); i < n; i += 3) { + swap(import_geom.indices[i], import_geom.indices[i + 1]); } } } @@ -489,10 +505,10 @@ struct FBXImporter : ModelImporter { } - ofbx::DMatrix getBindPoseMatrix(const ImportMesh* mesh, const ofbx::Object* node) const { + ofbx::DMatrix getBindPoseMatrix(const ofbx::Mesh* mesh, const ofbx::Object* node) const { if (!mesh) return node->getGlobalTransform(); - auto* skin = m_fbx_meshes[mesh->mesh_index]->getSkin(); + auto* skin = mesh->getSkin(); if (!skin) return node->getGlobalTransform(); for (int i = 0, c = skin->getClusterCount(); i < c; ++i) { @@ -541,15 +557,15 @@ struct FBXImporter : ModelImporter { sortBones(); if (force_skinned) { - for (ImportMesh& m : m_meshes) { - m.bone_idx = m_bones.find([&](const Bone& bone){ return bone.id == u64(m_fbx_meshes[m.mesh_index]); }); - m.is_skinned = true; + for (ImportGeometry& g : m_geometries) { + FBXImportGeometry* fbx_geom = (FBXImportGeometry*)g.user_data; + fbx_geom->bone_idx = m_bones.find([&](const Bone& bone){ return bone.id == u64(fbx_geom->mesh); }); } } for (Bone& bone : m_bones) { const ofbx::Object* node = (const ofbx::Object*)bone.id; - const ImportMesh* mesh = getAnyMeshFromBone(node, i32(&bone - m_bones.begin())); + const ofbx::Mesh* mesh = getAnyMeshFromBone(node, i32(&bone - m_bones.begin())); Matrix tr = toLumix(getBindPoseMatrix(mesh, node)); tr.normalizeScale(); // TODO why? tr.setTranslation(tr.getTranslation() * m_scene_scale); @@ -585,7 +601,15 @@ struct FBXImporter : ModelImporter { LUMIX_FORCE_INLINE Matrix fixOrientation(const Matrix& m) const { switch (m_orientation) { case Orientation::Y_UP: return m; - case Orientation::Z_UP: + case Orientation::Z_UP:{ + Matrix mtx = Matrix( + Vec4(1, 0, 0, 0), + Vec4(0, 0, -1, 0), + Vec4(0, 1, 0, 0), + Vec4(0, 0, 0, 1) + ); + return mtx * m; + } case Orientation::Z_MINUS_UP: case Orientation::X_MINUS_UP: case Orientation::X_UP: @@ -596,27 +620,24 @@ struct FBXImporter : ModelImporter { return m; } - LUMIX_FORCE_INLINE u32 getPackedVec3(ofbx::Vec3 vec, const Matrix& mtx) const { + LUMIX_FORCE_INLINE u32 getPackedVec3(ofbx::Vec3 vec) const { Vec3 v = toLumixVec3(vec); - v = normalize(mtx.transformVector(v)); - // TODO put fixOrientation in mtx - v = fixOrientation(v); return packF4u(v); } - void fillSkinInfo(Array& skinning, const ImportMesh& import_mesh, const ofbx::Mesh* mesh) const { + void fillSkinInfo(Array& skinning, i32 force_bone_idx, const ofbx::Mesh* mesh) const { const ofbx::Skin* fbx_skin = mesh->getSkin(); const ofbx::GeometryData& geom = mesh->getGeometryData(); skinning.resize(geom.getPositions().values_count); memset(&skinning[0], 0, skinning.size() * sizeof(skinning[0])); if (!fbx_skin) { - ASSERT(import_mesh.bone_idx >= 0); + ASSERT(force_bone_idx >= 0); for (Skin& skin : skinning) { skin.count = 1; skin.weights[0] = 1; skin.weights[1] = skin.weights[2] = skin.weights[3] = 0; - skin.joints[0] = skin.joints[1] = skin.joints[2] = skin.joints[3] = import_mesh.bone_idx; + skin.joints[0] = skin.joints[1] = skin.joints[2] = skin.joints[3] = force_bone_idx; } return; } @@ -668,16 +689,14 @@ struct FBXImporter : ModelImporter { } void triangulate(OutputMemoryStream& unindexed_triangles - , ImportMesh& mesh + , ImportGeometry& mesh , const ofbx::GeometryPartition& partition - , const Array& skinning + , const ofbx::GeometryData& geom + , const Array* skinning , const ModelMeta& meta - , const Matrix& matrix , Array& tri_indices) const { PROFILE_FUNCTION(); - const ofbx::Mesh* fbx_mesh = m_fbx_meshes[mesh.mesh_index]; - const ofbx::GeometryData& geom = fbx_mesh->getGeometryData(); ofbx::Vec3Attributes positions = geom.getPositions(); ofbx::Vec3Attributes normals = geom.getNormals(); ofbx::Vec3Attributes tangents = geom.getTangents(); @@ -701,11 +720,9 @@ struct FBXImporter : ModelImporter { u32 tri_count = ofbx::triangulate(geom, polygon, tri_indices.begin()); for (u32 i = 0; i < tri_count; ++i) { ofbx::Vec3 cp = positions.get(tri_indices[i]); - Vec3 pos = matrix.transformPoint(toLumixVec3(cp)) * m_scene_scale; - pos = fixOrientation(pos); - write(pos); + write(toLumixVec3(cp)); - if (normals.values) write(getPackedVec3(normals.get(tri_indices[i]), matrix)); + if (normals.values) write(getPackedVec3(normals.get(tri_indices[i]))); else write(u32(0)); if (uvs.values) { ofbx::Vec2 uv = uvs.get(tri_indices[i]); @@ -722,16 +739,16 @@ struct FBXImporter : ModelImporter { write(packColor(color)); } } - if (tangents.values) write(getPackedVec3(tangents.get(tri_indices[i]), matrix)); + if (tangents.values) write(getPackedVec3(tangents.get(tri_indices[i]))); else if (compute_tangents) write(u32(0)); - if (mesh.is_skinned) { + if (skinning) { if (positions.indices) { - write(skinning[positions.indices[tri_indices[i]]].joints); - write(skinning[positions.indices[tri_indices[i]]].weights); + write((*skinning)[positions.indices[tri_indices[i]]].joints); + write((*skinning)[positions.indices[tri_indices[i]]].weights); } else { - write(skinning[tri_indices[i]].joints); - write(skinning[tri_indices[i]].weights); + write((*skinning)[tri_indices[i]].joints); + write((*skinning)[tri_indices[i]].weights); } } } @@ -761,18 +778,18 @@ struct FBXImporter : ModelImporter { } } - const ImportMesh* getAnyMeshFromBone(const ofbx::Object* node, int bone_idx) const { - for (const ImportMesh& mesh : m_meshes) { - if (mesh.bone_idx == bone_idx) { - return &mesh; + const ofbx::Mesh* getAnyMeshFromBone(const ofbx::Object* node, i32 bone_idx) const { + for (const ImportGeometry& geom : m_geometries) { + FBXImportGeometry* fbx_geom = (FBXImportGeometry*)geom.user_data; + if (fbx_geom->bone_idx == bone_idx) { + return fbx_geom->mesh; } - const ofbx::Mesh* fbx_mesh = m_fbx_meshes[mesh.mesh_index]; - auto* skin = fbx_mesh->getSkin(); + auto* skin = fbx_geom->mesh->getSkin(); if (!skin) continue; for (int j = 0, c = skin->getClusterCount(); j < c; ++j) { - if (skin->getCluster(j)->getLink() == node) return &mesh; + if (skin->getCluster(j)->getLink() == node) return fbx_geom->mesh; } } return nullptr; @@ -997,12 +1014,13 @@ struct FBXImporter : ModelImporter { makeLowercase(Span(out), out); } - // TODO names should be unique, since they are used for prefab saving - void getImportMeshName(ImportMesh& mesh, const ofbx::Mesh* fbx_mesh) const { + // TODO optimize this + void getImportMeshName(ImportMesh& mesh, const ofbx::Mesh* fbx_mesh, HashMap& names, i32 submesh) const { const char* name = fbx_mesh->name; if (name[0] == '\0' && fbx_mesh->getParent()) name = fbx_mesh->getParent()->name; - if (name[0] == '\0') name = m_materials[mesh.material_index].name.c_str(); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + if (name[0] == '\0') name = m_materials[geom.material_index].name.c_str(); mesh.name = name; char* chars = mesh.name.getMutableData(); @@ -1012,11 +1030,28 @@ struct FBXImporter : ModelImporter { if (chars[i] == ':') chars[i] = '_'; } - if (mesh.submesh >= 0) { + if (submesh >= 0) { char tmp[32]; - toCString(mesh.submesh, Span(tmp)); + toCString(submesh, Span(tmp)); mesh.name.append("_", tmp); } + + u32 collision = 0; + StaticString<1024> tmp_name(mesh.name); + for (;;) { + bool found_collision = false; + if (names.find(StringView(tmp_name)).isValid()) { + ++collision; + found_collision = true; + tmp_name = mesh.name; + tmp_name.append(".", collision); + } + else { + mesh.name = tmp_name; + names.insert(mesh.name, true); + break; + } + } } static i32 detectMeshLOD(StringView mesh_name) { @@ -1036,6 +1071,7 @@ struct FBXImporter : ModelImporter { for (i32 i = 0, c = m_scene->getLightCount(); i < c; ++i) { const ofbx::Light* light = m_scene->getLight(i); const Matrix mtx = toLumix(light->getGlobalTransform()); + // TODO check if meta.scene_scale is applied everywhere Vec3 v = mtx.getTranslation() * meta.scene_scale * m_scene_scale; v = fixOrientation(v); const DVec3 pos = DVec3(v); @@ -1043,34 +1079,64 @@ struct FBXImporter : ModelImporter { } } - void gatherMeshes(StringView fbx_filename, StringView src_dir) { + void gatherMeshes(StringView fbx_filename, StringView src_dir, const ModelMeta* meta, bool ignore_geometry) { PROFILE_FUNCTION(); - int c = m_scene->getMeshCount(); + const i32 c = m_scene->getMeshCount(); Array materials(m_allocator); + HashMap names(m_allocator); + names.reserve(c); + m_meshes.reserve(c); + + HashMap geom_map(m_allocator); + geom_map.reserve(c); for (int mesh_idx = 0; mesh_idx < c; ++mesh_idx) { - const ofbx::Mesh* fbx_mesh = (const ofbx::Mesh*)m_scene->getMesh(mesh_idx); + const ofbx::Mesh* fbx_mesh = m_scene->getMesh(mesh_idx); const int mat_count = fbx_mesh->getMaterialCount(); - for (int j = 0; j < mat_count; ++j) { - const ofbx::GeometryData& geom = fbx_mesh->getGeometryData(); - ofbx::GeometryPartition partition = geom.getPartition(mat_count > 1 ? j : 0); + + bool is_skinned = meta && meta->force_skin; + const ofbx::Skin* skin = fbx_mesh->getSkin(); + if (skin) { + for (int i = 0; i < skin->getClusterCount(); ++i) { + if (skin->getCluster(i)->getIndicesCount() > 0) { + is_skinned = true; + break; + } + } + } + + const ofbx::GeometryData& geom = fbx_mesh->getGeometryData(); + Span material_map(geom.getMaterialMap(), geom.getMaterialMapSize()); + + // mesh can have materials, which are not used by any triangles + // we don't want to create empty meshes for them + // mark used materials/partitions + StackArray used_materials(m_allocator); + used_materials.resize(mat_count); + if (mat_count == 1) { + used_materials[0] = true; + } + else { + memset(used_materials.begin(), 0, used_materials.byte_size()); + for (i32 m : material_map) { + if (m < 0) continue; + if (m >= used_materials.size()) continue; + + used_materials[m] = true; + } + } + + for (int fbx_mat_index = 0; fbx_mat_index < mat_count; ++fbx_mat_index) { + ofbx::GeometryPartition partition = geom.getPartition(mat_count > 1 ? fbx_mat_index : 0); + + if (material_map.length() != 0 && !used_materials[fbx_mat_index]) continue; + + const ofbx::Material* fbx_mat = fbx_mesh->getMaterial(fbx_mat_index); ImportMesh& mesh = m_meshes.emplace(m_allocator); mesh.mesh_index = m_meshes.size() - 1; m_fbx_meshes.push(fbx_mesh); - mesh.is_skinned = false; - const ofbx::Skin* skin = fbx_mesh->getSkin(); - if (skin) { - for (int i = 0; i < skin->getClusterCount(); ++i) { - if (skin->getCluster(i)->getIndicesCount() > 0) { - mesh.is_skinned = true; - break; - } - } - } - const ofbx::Material* fbx_mat = fbx_mesh->getMaterial(j); - mesh.submesh = mat_count > 1 ? j : -1; i32 mat_idx = materials.indexOf(fbx_mat); if (mat_idx < 0) { @@ -1080,9 +1146,59 @@ struct FBXImporter : ModelImporter { mat.diffuse_color = { diffuse_color.r, diffuse_color.g, diffuse_color.b }; materials.push(fbx_mat); } - mesh.material_index = mat_idx; - getImportMeshName(mesh, fbx_mesh); + + Matrix transform_matrix; + Matrix geometry_matrix = toLumix(fbx_mesh->getGeometricMatrix()); + transform_matrix = toLumix(fbx_mesh->getGlobalTransform()) * geometry_matrix; + transform_matrix.multiply3x3(m_scene_scale); + transform_matrix.setTranslation(transform_matrix.getTranslation() * m_scene_scale); + mesh.matrix = fixOrientation(transform_matrix); + const bool flip_handness = doesFlipHandness(mesh.matrix); + + if (is_skinned) { + ImportGeometry& import_geom = m_geometries.emplace(m_allocator); + mesh.geometry_idx = m_geometries.size() - 1; + import_geom.flip_handness = flip_handness; + import_geom.is_skinned = is_skinned; + import_geom.material_index = mat_idx; + import_geom.submesh = mat_count > 1 ? fbx_mat_index : -1; + FBXImportGeometry* fbx_geom = new (NewPlaceholder(), import_geom.user_data) FBXImportGeometry; + fbx_geom->geom = &geom; + fbx_geom->mesh = fbx_mesh; + } + else { + const GeomPartition match = { + .geom = &geom, + .partition = u32(fbx_mat_index), + .material = u32(mat_idx), + .flip_handness = flip_handness + }; + auto iter = geom_map.find(match); + if (iter.isValid()) { + mesh.geometry_idx = iter.value(); + ASSERT(!m_geometries[mesh.geometry_idx].is_skinned); + } + else { + geom_map.insert(match, (i32)m_geometries.size()); + ImportGeometry& import_geom = m_geometries.emplace(m_allocator); + import_geom.flip_handness = flip_handness; + import_geom.is_skinned = false; + mesh.geometry_idx = m_geometries.size() - 1; + import_geom.material_index = mat_idx; + import_geom.submesh = mat_count > 1 ? fbx_mat_index : -1; + FBXImportGeometry* fbx_geom = new (NewPlaceholder(), import_geom.user_data) FBXImportGeometry; + fbx_geom->geom = &geom; + fbx_geom->mesh = fbx_mesh; + } + } + + getImportMeshName(mesh, fbx_mesh, names, mat_count > 1 ? fbx_mat_index : -1); + m_geometries[mesh.geometry_idx].name = mesh.name; // TODO name from ofbx::Geometry mesh.lod = detectMeshLOD(mesh.name); + + if (doesFlipHandness(mesh.matrix)) { + mesh.matrix.setXVector(mesh.matrix.getXVector() * -1); + } } } @@ -1274,7 +1390,7 @@ struct FBXImporter : ModelImporter { if (!fs.getContentSync(Path(filename), data)) return false; } - const ofbx::LoadFlags flags = ignore_geometry ? ofbx::LoadFlags::IGNORE_GEOMETRY : ofbx::LoadFlags::NONE; + const ofbx::LoadFlags flags = ignore_geometry ? ofbx::LoadFlags::IGNORE_GEOMETRY | ofbx::LoadFlags::KEEP_MATERIAL_MAP : ofbx::LoadFlags::NONE; { PROFILE_BLOCK("ofbx::load"); m_scene = ofbx::load(data.data(), (i32)data.size(), static_cast(flags), &ofbx_job_processor, nullptr); @@ -1298,13 +1414,13 @@ struct FBXImporter : ModelImporter { StringView src_dir = Path::getDir(filename); if (!ignore_geometry) extractEmbedded(*m_scene, src_dir, m_allocator); - gatherMeshes(filename, src_dir); + gatherMeshes(filename, src_dir, meta, ignore_geometry); if(!meta || !meta->ignore_animations) gatherAnimations(filename); if (meta) gatherLights(*meta); if (!ignore_geometry) { bool any_skinned = false; - for (const ImportMesh& m : m_meshes) any_skinned = any_skinned || m.is_skinned; + for (const ImportGeometry& g : m_geometries) any_skinned = any_skinned || g.is_skinned; // TODO why do we need this here? gatherBones(meta->force_skin || any_skinned); } diff --git a/src/renderer/editor/model_importer.cpp b/src/renderer/editor/model_importer.cpp index 3e017d64b8..5a2fa40b96 100644 --- a/src/renderer/editor/model_importer.cpp +++ b/src/renderer/editor/model_importer.cpp @@ -180,7 +180,7 @@ static bool hasAutoLOD(const ModelMeta& meta, u32 idx) { return meta.autolod_mask & (1 << idx); } -static bool areIndices16Bit(const ModelImporter::ImportMesh& mesh) { +static bool areIndices16Bit(const ModelImporter::ImportGeometry& mesh) { int vertex_size = mesh.vertex_size; return mesh.vertex_buffer.size() / vertex_size < (1 << 16); } @@ -224,62 +224,6 @@ static Vec2 computeImpostorHalfExtents(Vec2 bounding_cylinder) { }; } -static void computeBoundingShapes(ModelImporter::ImportMesh& mesh) { - PROFILE_FUNCTION(); - AABB aabb(Vec3(FLT_MAX), Vec3(-FLT_MAX)); - float max_squared_dist = 0; - const u32 vertex_count = u32(mesh.vertex_buffer.size() / mesh.vertex_size); - const u8* positions = mesh.vertex_buffer.data(); - for (u32 i = 0; i < vertex_count; ++i) { - Vec3 p; - memcpy(&p, positions, sizeof(p)); - positions += mesh.vertex_size; - aabb.addPoint(p); - float d = squaredLength(p); - max_squared_dist = maximum(d, max_squared_dist); - } - - mesh.aabb = aabb; - mesh.origin_radius_squared = max_squared_dist; -} - -static AABB computeMeshAABB(const ModelImporter::ImportMesh& mesh) { - const u32 vertex_count = u32(mesh.vertex_buffer.size() / mesh.vertex_size); - if (vertex_count <= 0) return { Vec3(0), Vec3(0) }; - - const u8* ptr = mesh.vertex_buffer.data(); - - Vec3 min(FLT_MAX); - Vec3 max(-FLT_MAX); - - for (u32 i = 0; i < vertex_count; ++i) { - Vec3 v; - memcpy(&v, ptr + mesh.vertex_size * i, sizeof(v)); - - min = minimum(min, v); - max = maximum(max, v); - } - - return { min, max }; -} - -static void offsetMesh(ModelImporter::ImportMesh& mesh, const Vec3& offset) { - const u32 vertex_count = u32(mesh.vertex_buffer.size() / mesh.vertex_size); - if (vertex_count <= 0) return; - - u8* ptr = mesh.vertex_buffer.getMutableData(); - mesh.origin = offset; - - for (u32 i = 0; i < vertex_count; ++i) { - Vec3 v; - memcpy(&v, ptr + mesh.vertex_size * i, sizeof(v)); - v -= offset; - memcpy(ptr + mesh.vertex_size * i, &v, sizeof(v)); - } - - computeBoundingShapes(mesh); -} - ModelImporter::ModelImporter(struct StudioApp& app) : m_app(app) , m_allocator(app.getAllocator()) @@ -288,54 +232,79 @@ ModelImporter::ModelImporter(struct StudioApp& app) , m_bones(app.getAllocator()) , m_meshes(app.getAllocator()) , m_animations(app.getAllocator()) + , m_geometries(app.getAllocator()) , m_lights(app.getAllocator()) {} + +u32 ModelImporter::packF4u(const Vec3& vec) { + const i8 xx = i8(clamp((vec.x * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); + const i8 yy = i8(clamp((vec.y * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); + const i8 zz = i8(clamp((vec.z * 0.5f + 0.5f) * 255, 0.f, 255.f) - 128); + const i8 ww = i8(0); + + union { + u32 ui32; + i8 arr[4]; + } un; + + un.arr[0] = xx; + un.arr[1] = yy; + un.arr[2] = zz; + un.arr[3] = ww; + + return un.ui32; +} + +Vec3 ModelImporter::unpackF4u(u32 packed) { + union { + u32 ui32; + i8 arr[4]; + } un; + + un.ui32 = packed; + Vec3 res; + res.x = un.arr[0]; + res.y = un.arr[1]; + res.z = un.arr[2]; + return ((res + Vec3(128.f)) / 255) * 2.f - 1.f; +} + + + void ModelImporter::writeString(const char* str) { m_out_file.write(str, stringLength(str)); } -void ModelImporter::centerMeshes() { - jobs::forEach(m_meshes.size(), 1, [&](i32 mesh_idx, i32){ - ImportMesh& mesh = m_meshes[mesh_idx]; - const AABB aabb = computeMeshAABB(mesh); - const Vec3 offset = (aabb.max + aabb.min) * 0.5f; - offsetMesh(mesh, offset); - }); -} +static i32 getAttributeOffset(const ModelImporter::ImportGeometry& mesh, AttributeSemantic semantic) { + i32 offset = 0; + for (const auto& attr : mesh.attributes) { + if (attr.semantic == semantic) return offset; + offset += gpu::getSize(attr.type) * attr.num_components; + } + return -1; +} void ModelImporter::postprocessCommon(const ModelMeta& meta) { jobs::forEach(m_meshes.size(), 1, [&](i32 mesh_idx, i32){ - computeBoundingShapes(m_meshes[mesh_idx]); - }); - - AABB merged_aabb(Vec3(FLT_MAX), Vec3(-FLT_MAX)); - for (const ImportMesh& m : m_meshes) { - merged_aabb.merge(m.aabb); - } + // TODO this can process the same geom multiple times - jobs::forEach(m_meshes.size(), 1, [&](i32 mesh_idx, i32){ ImportMesh& mesh = m_meshes[mesh_idx]; - - if (meta.origin != ModelMeta::Origin::SOURCE) { - Vec3 offset = (merged_aabb.max + merged_aabb.min) * 0.5f; - if (meta.origin == ModelMeta::Origin::BOTTOM) offset.y = merged_aabb.min.y; - offsetMesh(mesh, offset); - } + ImportGeometry& geom = m_geometries[mesh.geometry_idx]; for (u32 i = 0; i < meta.lod_count; ++i) { if ((meta.autolod_mask & (1 << i)) == 0) continue; if (mesh.lod != 0) continue; - mesh.autolod_indices[i].create(m_allocator); - mesh.autolod_indices[i]->resize(mesh.indices.size()); - const size_t lod_index_count = meshopt_simplifySloppy(mesh.autolod_indices[i]->begin() - , mesh.indices.begin() - , mesh.indices.size() - , (const float*)mesh.vertex_buffer.data() - , u32(mesh.vertex_buffer.size() / mesh.vertex_size) - , mesh.vertex_size - , size_t(mesh.indices.size() * meta.autolod_coefs[i]) + geom.autolod_indices[i].create(m_allocator); + geom.autolod_indices[i]->resize(geom.indices.size()); + const size_t lod_index_count = meshopt_simplifySloppy(geom.autolod_indices[i]->begin() + , geom.indices.begin() + , geom.indices.size() + , (const float*)geom.vertex_buffer.data() + , u32(geom.vertex_buffer.size() / geom.vertex_size) + , geom.vertex_size + , size_t(geom.indices.size() * meta.autolod_coefs[i]) ); - mesh.autolod_indices[i]->resize((u32)lod_index_count); + geom.autolod_indices[i]->resize((u32)lod_index_count); } }); @@ -343,27 +312,25 @@ void ModelImporter::postprocessCommon(const ModelMeta& meta) { if (meta.bake_vertex_ao) bakeVertexAO(meta.min_bake_vertex_ao); u32 mesh_data_size = 0; - for (const ImportMesh& m : m_meshes) { - mesh_data_size += u32(m.vertex_buffer.size() + m.indices.byte_size()); + for (const ImportGeometry& g : m_geometries) { + mesh_data_size += u32(g.vertex_buffer.size() + g.indices.byte_size()); } m_out_file.reserve(128 * 1024 + mesh_data_size); } bool ModelImporter::writeSubmodels(const Path& src, const ModelMeta& meta) { PROFILE_FUNCTION(); - for (int i = 0; i < m_meshes.size(); ++i) { + HashMap map(m_allocator); + map.reserve(m_geometries.size()); + + for (i32 i = 0; i < m_geometries.size(); ++i) { m_out_file.clear(); writeModelHeader(); const BoneNameHash root_motion_bone(meta.root_motion_bone.c_str()); write(root_motion_bone); - writeMeshes(src, i, meta); + writeSubmesh(src, i, meta); writeGeometry(i); - if (m_meshes[i].is_skinned) { - writeSkeleton(meta); - } - else { - write((i32)0); - } + write((i32)0); // lods const i32 lod_count = 1; @@ -373,7 +340,7 @@ bool ModelImporter::writeSubmodels(const Path& src, const ModelMeta& meta) { write(to_mesh); write(factor); - Path path(m_meshes[i].name, ".fbx:", src); + Path path(m_geometries[i].name, ".fbx:", src); AssetCompiler& compiler = m_app.getAssetCompiler(); if (!compiler.writeCompiledResource(path, Span(m_out_file.data(), (i32)m_out_file.size()))) { @@ -582,13 +549,15 @@ void ModelImporter::createImpostorTextures(Model* model, ImpostorTexturesContext bool ModelImporter::write(const Path& src, const ModelMeta& meta) { const Path filepath = Path(ResourcePath::getResource(src)); - if (!meta.split && !writeModel(src, meta)) return false; - if (!writeMaterials(filepath, meta, false)) return false; - if (!writeAnimations(filepath, meta)) return false; if (meta.split) { - centerMeshes(); if (!writeSubmodels(filepath, meta)) return false; + if (!writeDummyModel(src)) return false; + } + else { + if (!writeModel(src, meta)) return false; } + if (!writeMaterials(filepath, meta, false)) return false; + if (!writeAnimations(filepath, meta)) return false; if (!writePhysics(filepath, meta)) return false; if (meta.split || meta.create_prefab_with_physics) { jobs::moveJobToWorker(0); @@ -657,7 +626,7 @@ bool ModelImporter::writeMaterials(const Path& src, const ModelMeta& meta, bool blob << "texture \"\"\n"; } - if (!material.textures[0].import) { + if (!material.textures[0].import && !meta.ignore_material_colors) { const Vec3 color = material.diffuse_color; blob << "uniform \"Material color\", {" << powf(color.x, 2.2f) << "," << powf(color.x, 2.2f) @@ -721,46 +690,58 @@ void ModelImporter::writeImpostorVertices(float center_y, Vec2 bounding_cylinder } } -void ModelImporter::writeGeometry(int mesh_idx) { +void ModelImporter::writeGeometry(u32 geom_idx) { PROFILE_FUNCTION(); // TODO lods - OutputMemoryStream vertices_blob(m_allocator); - const ImportMesh& import_mesh = m_meshes[mesh_idx]; + const ImportGeometry& geom = m_geometries[geom_idx]; - const bool are_indices_16_bit = import_mesh.index_size == sizeof(u16); - write(import_mesh.index_size); + const bool are_indices_16_bit = geom.index_size == sizeof(u16); + write(geom.index_size); if (are_indices_16_bit) { - write(import_mesh.indices.size()); - for (int i : import_mesh.indices) { + write(geom.indices.size()); + for (int i : geom.indices) { ASSERT(i <= (1 << 16)); write((u16)i); } } else { - ASSERT(import_mesh.index_size == sizeof(u32)); - write(import_mesh.indices.size()); - write(&import_mesh.indices[0], sizeof(import_mesh.indices[0]) * import_mesh.indices.size()); + ASSERT(geom.index_size == sizeof(u32)); + write(geom.indices.size()); + write(&geom.indices[0], sizeof(geom.indices[0]) * geom.indices.size()); } - const Vec3 center = (import_mesh.aabb.max + import_mesh.aabb.min) * 0.5f; - float max_center_dist_squared = 0; - const u8* positions = import_mesh.vertex_buffer.data(); - const i32 vertex_size = import_mesh.vertex_size; - const u32 vertex_count = u32(import_mesh.vertex_buffer.size() / vertex_size); + AABB aabb = {{FLT_MAX, FLT_MAX, FLT_MAX}, {-FLT_MAX, -FLT_MAX, -FLT_MAX}}; + float origin_radius_squared = 0; + const u8* positions = geom.vertex_buffer.data(); + const i32 vertex_size = geom.vertex_size; + const u32 vertex_count = u32(geom.vertex_buffer.size() / vertex_size); + for (u32 i = 0; i < vertex_count; ++i) { + Vec3 p; + memcpy(&p, positions, sizeof(p)); + positions += vertex_size; + const float d = squaredLength(p); + origin_radius_squared = maximum(d, origin_radius_squared); + aabb.addPoint(p); + } + + float center_radius_squared = 0; + const Vec3 center = (aabb.max + aabb.min) * 0.5f; + + positions = geom.vertex_buffer.data(); for (u32 i = 0; i < vertex_count; ++i) { Vec3 p; memcpy(&p, positions, sizeof(p)); positions += vertex_size; - float d = squaredLength(p - center); - max_center_dist_squared = maximum(d, max_center_dist_squared); + const float d = squaredLength(p - center); + center_radius_squared = maximum(d, center_radius_squared); } - write((i32)import_mesh.vertex_buffer.size()); - write(import_mesh.vertex_buffer.data(), import_mesh.vertex_buffer.size()); + write((i32)geom.vertex_buffer.size()); + write(geom.vertex_buffer.data(), geom.vertex_buffer.size()); - write(sqrtf(import_mesh.origin_radius_squared)); - write(sqrtf(max_center_dist_squared)); - write(import_mesh.aabb); + write(sqrtf(origin_radius_squared)); + write(sqrtf(center_radius_squared)); + write(aabb); } bool ModelImporter::writePrefab(const Path& src, const ModelMeta& meta) { @@ -784,25 +765,23 @@ bool ModelImporter::writePrefab(const Path& src, const ModelMeta& meta) { PhysicsModule* pmodule = (PhysicsModule*)world.getModule(RIGID_ACTOR_TYPE); const EntityRef root = world.createEntity({0, 0, 0}, Quat::IDENTITY); - if (!meta.split) { - world.createComponent(MODEL_INSTANCE_TYPE, root); - rmodule->setModelInstancePath(root, src); - - ASSERT(with_physics); - world.createComponent(RIGID_ACTOR_TYPE, root); - pmodule->setMeshGeomPath(root, Path(".phy:", src)); - } - else { + if (meta.split) { for(int i = 0; i < m_meshes.size(); ++i) { - const EntityRef e = world.createEntity(DVec3(m_meshes[i].origin), Quat::IDENTITY); + Vec3 pos; + Quat rot; + Vec3 scale; + m_meshes[i].matrix.decompose(pos, rot, scale); + const EntityRef e = world.createEntity(DVec3(pos), rot); + world.setScale(e, scale); world.createComponent(MODEL_INSTANCE_TYPE, e); world.setParent(root, e); - Path mesh_path(m_meshes[i].name, ".fbx:", src); + const ImportGeometry& geom = m_geometries[m_meshes[i].geometry_idx]; + Path mesh_path(geom.name, ".fbx:", src); rmodule->setModelInstancePath(e, mesh_path); if (with_physics) { world.createComponent(RIGID_ACTOR_TYPE, e); - pmodule->setMeshGeomPath(e, Path(m_meshes[i].name, ".phy:", src)); + pmodule->setMeshGeomPath(e, Path(geom.name, ".phy:", src)); } } @@ -812,8 +791,17 @@ bool ModelImporter::writePrefab(const Path& src, const ModelMeta& meta) { const EntityRef e = world.createEntity(pos, Quat::IDENTITY); world.createComponent(POINT_LIGHT_TYPE, e); world.setParent(root, e); + world.setEntityName(e, "light"); } } + else { + world.createComponent(MODEL_INSTANCE_TYPE, root); + rmodule->setModelInstancePath(root, src); + + ASSERT(with_physics); + world.createComponent(RIGID_ACTOR_TYPE, root); + pmodule->setMeshGeomPath(root, Path(".phy:", src)); + } world.serialize(blob, WorldSerializeFlags::NONE); engine.destroyWorld(world); @@ -826,76 +814,51 @@ bool ModelImporter::writePrefab(const Path& src, const ModelMeta& meta) { return true; } +static bool isIdentity(const Matrix& mtx) { + for (u32 i = 0; i < 4; ++i) { + for (u32 j = 0; j < 4; ++j) { + if (fabs(mtx.columns[i][j] - Matrix::IDENTITY.columns[i][j]) > 0.001f) return false; + } + } + return true; +} + void ModelImporter::writeGeometry(const ModelMeta& meta) { PROFILE_FUNCTION(); - AABB aabb = {{0, 0, 0}, {0, 0, 0}}; - float origin_radius_squared = 0; float center_radius_squared = 0; - OutputMemoryStream vertices_blob(m_allocator); Vec2 bounding_cylinder = Vec2(0); - for (const ImportMesh& import_mesh : m_meshes) { - if (import_mesh.lod != 0) continue; - - origin_radius_squared = maximum(origin_radius_squared, import_mesh.origin_radius_squared); - aabb.merge(import_mesh.aabb); - } - const Vec3 center = (aabb.min + aabb.max) * 0.5f; - const Vec3 center_xz0(0, center.y, 0); - for (const ImportMesh& import_mesh : m_meshes) { - if (import_mesh.lod != 0) continue; - - const u8* positions = import_mesh.vertex_buffer.data(); - const i32 vertex_size = import_mesh.vertex_size; - const u32 vertex_count = u32(import_mesh.vertex_buffer.size() / vertex_size); - for (u32 i = 0; i < vertex_count; ++i) { - Vec3 p; - memcpy(&p, positions, sizeof(p)); - positions += vertex_size; - float d = squaredLength(p - center); - center_radius_squared = maximum(d, center_radius_squared); - - p -= center_xz0; - float xz_squared = p.x * p.x + p.z * p.z; - bounding_cylinder.x = maximum(bounding_cylinder.x, xz_squared); - bounding_cylinder.y = maximum(bounding_cylinder.y, fabsf(p.y)); - } - } - bounding_cylinder.x = sqrtf(bounding_cylinder.x); - for (u32 lod = 0; lod < meta.lod_count - (meta.create_impostor ? 1 : 0); ++lod) { for (const ImportMesh& import_mesh : m_meshes) { - - const bool are_indices_16_bit = areIndices16Bit(import_mesh); + const ImportGeometry& geom = m_geometries[import_mesh.geometry_idx]; + const bool are_indices_16_bit = areIndices16Bit(geom); if (import_mesh.lod == lod && !hasAutoLOD(meta, lod)) { if (are_indices_16_bit) { const i32 index_size = sizeof(u16); write(index_size); - write(import_mesh.indices.size()); - for (int i : import_mesh.indices) - { + write(geom.indices.size()); + for (u32 i : geom.indices) { ASSERT(i <= (1 << 16)); u16 index = (u16)i; write(index); } } else { - int index_size = sizeof(import_mesh.indices[0]); + int index_size = sizeof(geom.indices[0]); write(index_size); - write(import_mesh.indices.size()); - write(&import_mesh.indices[0], sizeof(import_mesh.indices[0]) * import_mesh.indices.size()); + write(geom.indices.size()); + write(&geom.indices[0], sizeof(geom.indices[0]) * geom.indices.size()); } } else if (import_mesh.lod == 0 && hasAutoLOD(meta, lod)) { - const auto& lod_indices = *import_mesh.autolod_indices[lod].get(); + const auto& lod_indices = *geom.autolod_indices[lod].get(); if (are_indices_16_bit) { const i32 index_size = sizeof(u16); write(index_size); write(lod_indices.size()); - for (u32 i : lod_indices) - { + for (u32 i : lod_indices) { ASSERT(i <= (1 << 16)); u16 index = (u16)i; write(index); @@ -905,7 +868,7 @@ void ModelImporter::writeGeometry(const ModelMeta& meta) { i32 index_size = sizeof(lod_indices[0]); write(index_size); write(lod_indices.size()); - write(lod_indices.begin(), import_mesh.autolod_indices[lod]->byte_size()); + write(lod_indices.begin(), geom.autolod_indices[lod]->byte_size()); } } } @@ -920,20 +883,97 @@ void ModelImporter::writeGeometry(const ModelMeta& meta) { write(indices, sizeof(indices)); } + float origin_radius_squared = 0; + AABB aabb = { {FLT_MAX, FLT_MAX, FLT_MAX}, {-FLT_MAX, -FLT_MAX, -FLT_MAX} }; + for (u32 lod = 0; lod < meta.lod_count - (meta.create_impostor ? 1 : 0); ++lod) { for (const ImportMesh& import_mesh : m_meshes) { - if ((import_mesh.lod == lod && !hasAutoLOD(meta, lod)) || (import_mesh.lod == 0 && hasAutoLOD(meta, lod))) { - write((i32)import_mesh.vertex_buffer.size()); - write(import_mesh.vertex_buffer.data(), import_mesh.vertex_buffer.size()); + if (!((import_mesh.lod == lod && !hasAutoLOD(meta, lod)) || (import_mesh.lod == 0 && hasAutoLOD(meta, lod)))) continue; + + const ImportGeometry& geom = m_geometries[import_mesh.geometry_idx]; + write((i32)geom.vertex_buffer.size()); + + const u32 vertex_size = geom.vertex_size; + const u32 vertex_count = u32(geom.vertex_buffer.size() / geom.vertex_size); + const i32 normal_offset = getAttributeOffset(geom, AttributeSemantic::NORMAL); + const i32 tangent_offset = getAttributeOffset(geom, AttributeSemantic::TANGENT); + const i32 bitangent_offset = getAttributeOffset(geom, AttributeSemantic::BITANGENT); + const u8* in = geom.vertex_buffer.data(); + u8* out = (u8*)m_out_file.skip(geom.vertex_buffer.size()); + const Matrix mtx = import_mesh.matrix; + + memcpy(out, in, geom.vertex_buffer.size()); + if (isIdentity(mtx)) { + for (u32 i = 0; i < vertex_count; ++i) { + Vec3 p; + memcpy(&p, out + vertex_size * i, sizeof(p)); + aabb.addPoint(p); + const float d = squaredLength(p); + origin_radius_squared = maximum(d, origin_radius_squared); + } + } + else { + Vec3 scale; + Vec3 pos; + Quat rot; + mtx.decompose(pos, rot, scale); + ASSERT(fabsf(rot.x * rot.x + rot.y * rot.y + rot.z * rot.z + rot.w * rot.w - 1) < 0.0001f); + + auto transform_vector = [&](u32 offset){ + u32 packed_vec; + memcpy(&packed_vec, out + offset, sizeof(packed_vec)); + Vec3 vec = unpackF4u(packed_vec); + vec = rot.rotate(vec); + packed_vec = packF4u(vec); + memcpy(out + offset, &packed_vec, sizeof(packed_vec)); + }; + + for (u32 i = 0; i < vertex_count; ++i) { + Vec3 p; + memcpy(&p, out + vertex_size * i, sizeof(p)); + p = mtx.transformPoint(p); + memcpy(out + i * vertex_size, &p, sizeof(p)); + + aabb.addPoint(p); + const float d = squaredLength(p); + origin_radius_squared = maximum(d, origin_radius_squared); + + if (normal_offset >= 0) transform_vector(normal_offset + vertex_size * i); + if (tangent_offset >= 0) transform_vector(tangent_offset + vertex_size * i); + if (bitangent_offset >= 0) transform_vector(bitangent_offset + vertex_size * i); + } } } } + const Vec3 center = (aabb.min + aabb.max) * 0.5f; + const Vec3 center_xz0(0, center.y, 0); + for (const ImportMesh& import_mesh : m_meshes) { + if (import_mesh.lod != 0) continue; + + const ImportGeometry& geom = m_geometries[import_mesh.geometry_idx]; + const u8* positions = geom.vertex_buffer.data(); + const i32 vertex_size = geom.vertex_size; + const u32 vertex_count = u32(geom.vertex_buffer.size() / vertex_size); + for (u32 i = 0; i < vertex_count; ++i) { + Vec3 p; + memcpy(&p, positions, sizeof(p)); + positions += vertex_size; + float d = squaredLength(p - center); + center_radius_squared = maximum(d, center_radius_squared); + + p -= center_xz0; + float xz_squared = p.x * p.x + p.z * p.z; + bounding_cylinder.x = maximum(bounding_cylinder.x, xz_squared); + bounding_cylinder.y = maximum(bounding_cylinder.y, fabsf(p.y)); + } + } + bounding_cylinder.x = sqrtf(bounding_cylinder.x); + if (meta.create_impostor) writeImpostorVertices((aabb.max.y + aabb.min.y) * 0.5f, bounding_cylinder); if (m_meshes.empty()) { for (const Bone& bone : m_bones) { - // TODO check if this works with different values of m_orientation const Vec3 p = bone.bind_pose_matrix.getTranslation(); origin_radius_squared = maximum(origin_radius_squared, squaredLength(p)); aabb.addPoint(p); @@ -972,35 +1012,55 @@ void ModelImporter::writeImpostorMesh(StringView dir, StringView model_name) } -void ModelImporter::writeMeshes(const Path& src, int mesh_idx, const ModelMeta& meta) { +void ModelImporter::writeSubmesh(const Path& src, i32 geom_idx, const ModelMeta& meta) { PROFILE_FUNCTION(); const PathInfo src_info(src); - i32 mesh_count = 0; - if (mesh_idx >= 0) { - mesh_count = 1; + write((u32)1); + + const ImportGeometry& geom = m_geometries[geom_idx]; + write((u32)geom.attributes.size()); + + for (const AttributeDesc& desc : geom.attributes) { + write(desc.semantic); + write(desc.type); + write(desc.num_components); } - else { - for (ImportMesh& mesh : m_meshes) { - if (mesh.lod >= meta.lod_count - (meta.create_impostor ? 1 : 0)) continue; - if (mesh.lod == 0 || !hasAutoLOD(meta, mesh.lod)) ++mesh_count; - for (u32 i = 1; i < meta.lod_count - (meta.create_impostor ? 1 : 0); ++i) { - if (mesh.lod == 0 && hasAutoLOD(meta, i)) ++mesh_count; - } + + const ImportMaterial& material = m_materials[geom.material_index]; + const Path mat_path(src_info.dir, material.name, ".mat"); + const i32 len = mat_path.length(); + write(len); + write(mat_path.c_str(), len); + + write((u32)geom.name.length()); + write(geom.name.c_str(), geom.name.length()); +} + +void ModelImporter::writeMeshes(const Path& src, const ModelMeta& meta) { + PROFILE_FUNCTION(); + const PathInfo src_info(src); + i32 mesh_count = 0; + for (ImportMesh& mesh : m_meshes) { + if (mesh.lod >= meta.lod_count - (meta.create_impostor ? 1 : 0)) continue; + if (mesh.lod == 0 || !hasAutoLOD(meta, mesh.lod)) ++mesh_count; + for (u32 i = 1; i < meta.lod_count - (meta.create_impostor ? 1 : 0); ++i) { + if (mesh.lod == 0 && hasAutoLOD(meta, i)) ++mesh_count; } - if (meta.create_impostor) ++mesh_count; } + if (meta.create_impostor) ++mesh_count; write(mesh_count); auto writeMesh = [&](const ImportMesh& mesh ) { - write((u32)mesh.attributes.size()); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + write((u32)geom.attributes.size()); - for (const AttributeDesc& desc : mesh.attributes) { + for (const AttributeDesc& desc : geom.attributes) { write(desc.semantic); write(desc.type); write(desc.num_components); } - const ImportMaterial& material = m_materials[mesh.material_index]; + const ImportMaterial& material = m_materials[geom.material_index]; const Path mat_path(src_info.dir, material.name, ".mat"); const i32 len = mat_path.length(); write(len); @@ -1010,19 +1070,14 @@ void ModelImporter::writeMeshes(const Path& src, int mesh_idx, const ModelMeta& write(mesh.name.c_str(), mesh.name.length()); }; - if(mesh_idx >= 0) { - writeMesh(m_meshes[mesh_idx]); - } - else { - for (u32 lod = 0; lod < meta.lod_count - (meta.create_impostor ? 1 : 0); ++lod) { - for (ImportMesh& import_mesh : m_meshes) { - if (import_mesh.lod == lod && !hasAutoLOD(meta, lod)) writeMesh(import_mesh); - else if (import_mesh.lod == 0 && hasAutoLOD(meta, lod)) writeMesh(import_mesh); - } + for (u32 lod = 0; lod < meta.lod_count - (meta.create_impostor ? 1 : 0); ++lod) { + for (ImportMesh& import_mesh : m_meshes) { + if (import_mesh.lod == lod && !hasAutoLOD(meta, lod)) writeMesh(import_mesh); + else if (import_mesh.lod == 0 && hasAutoLOD(meta, lod)) writeMesh(import_mesh); } } - if (mesh_idx < 0 && meta.create_impostor) { + if (meta.create_impostor) { writeImpostorMesh(src_info.dir, src_info.basename); } } @@ -1090,9 +1145,10 @@ void ModelImporter::bakeVertexAO(float min_ao) { AABB aabb(Vec3(FLT_MAX), Vec3(-FLT_MAX)); for (ImportMesh& mesh : m_meshes) { - const u8* positions = mesh.vertex_buffer.data(); - const i32 vertex_size = mesh.vertex_size; - const i32 vertex_count = i32(mesh.vertex_buffer.size() / vertex_size); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + const u8* positions = geom.vertex_buffer.data(); + const i32 vertex_size = geom.vertex_size; + const i32 vertex_count = i32(geom.vertex_buffer.size() / vertex_size); for (i32 i = 0; i < vertex_count; ++i) { Vec3 p; memcpy(&p, positions + i * vertex_size, sizeof(p)); @@ -1103,10 +1159,11 @@ void ModelImporter::bakeVertexAO(float min_ao) { Voxels voxels(m_allocator); voxels.beginRaster(aabb, 64); for (ImportMesh& mesh : m_meshes) { - const u8* positions = mesh.vertex_buffer.data(); - const i32 vertex_size = mesh.vertex_size; - const i32 count = mesh.indices.size(); - const u32* indices = mesh.indices.data(); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + const u8* positions = geom.vertex_buffer.data(); + const i32 vertex_size = geom.vertex_size; + const i32 count = geom.indices.size(); + const u32* indices = geom.indices.data(); for (i32 i = 0; i < count; i += 3) { Vec3 p[3]; @@ -1120,16 +1177,17 @@ void ModelImporter::bakeVertexAO(float min_ao) { voxels.blurAO(); for (ImportMesh& mesh : m_meshes) { - const u8* positions = mesh.vertex_buffer.data(); + ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + const u8* positions = geom.vertex_buffer.data(); u32 ao_offset = 0; - for (const AttributeDesc& desc : mesh.attributes) { + for (const AttributeDesc& desc : geom.attributes) { if (desc.semantic == AttributeSemantic::AO) break; ao_offset += desc.num_components * gpu::getSize(desc.type); } - u8* AOs = mesh.vertex_buffer.getMutableData() + ao_offset; - const i32 vertex_size = mesh.vertex_size; - const i32 vertex_count = i32(mesh.vertex_buffer.size() / vertex_size); + u8* AOs = geom.vertex_buffer.getMutableData() + ao_offset; + const i32 vertex_size = geom.vertex_size; + const i32 vertex_count = i32(geom.vertex_buffer.size() / vertex_size); for (i32 i = 0; i < vertex_count; ++i) { Vec3 p; @@ -1172,17 +1230,21 @@ bool ModelImporter::writePhysics(const Path& src, const ModelMeta& meta) { if (meta.split) { for (const ImportMesh& mesh : m_meshes) { + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; m_out_file.clear(); m_out_file.write(&header, sizeof(header)); verts.clear(); - int vertex_size = mesh.vertex_size; - int vertex_count = (i32)(mesh.vertex_buffer.size() / vertex_size); + int vertex_size = geom.vertex_size; + int vertex_count = (i32)(geom.vertex_buffer.size() / vertex_size); - const u8* vd = mesh.vertex_buffer.data(); + const u8* vd = geom.vertex_buffer.data(); for (int i = 0; i < vertex_count; ++i) { - verts.push(*(Vec3*)(vd + i * vertex_size)); + Vec3 p; + memcpy(&p, vd + i * vertex_size, sizeof(p)); + p = mesh.matrix.transformPoint(p); + verts.push(p); } if (to_convex) { @@ -1192,7 +1254,7 @@ bool ModelImporter::writePhysics(const Path& src, const ModelMeta& meta) { } } else { - if (!ps->cookTriMesh(verts, mesh.indices, m_out_file)) { + if (!ps->cookTriMesh(verts, geom.indices, m_out_file)) { logError("Failed to cook ", src); return false; } @@ -1212,18 +1274,24 @@ bool ModelImporter::writePhysics(const Path& src, const ModelMeta& meta) { i32 total_vertex_count = 0; for (const ImportMesh& mesh : m_meshes) { - total_vertex_count += (i32)(mesh.vertex_buffer.size() / mesh.vertex_size); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + total_vertex_count += (i32)(geom.vertex_buffer.size() / geom.vertex_size); } verts.reserve(total_vertex_count); for (const ImportMesh& mesh : m_meshes) { - int vertex_size = mesh.vertex_size; - int vertex_count = (i32)(mesh.vertex_buffer.size() / vertex_size); + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + + int vertex_size = geom.vertex_size; + int vertex_count = (i32)(geom.vertex_buffer.size() / vertex_size); - const u8* src = mesh.vertex_buffer.data(); + const u8* src = geom.vertex_buffer.data(); for (int i = 0; i < vertex_count; ++i) { - verts.push(*(Vec3*)(src + i * vertex_size)); + Vec3 p; + memcpy(&p, src + i * vertex_size, sizeof(p)); + p = mesh.matrix.transformPoint(p); + verts.push(p); } } @@ -1235,17 +1303,19 @@ bool ModelImporter::writePhysics(const Path& src, const ModelMeta& meta) { } else { Array indices(m_allocator); i32 count = 0; - for (auto& mesh : m_meshes) { - count += mesh.indices.size(); + for (const ImportMesh& mesh : m_meshes) { + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + count += geom.indices.size(); } indices.reserve(count); int offset = 0; for (const ImportMesh& mesh : m_meshes) { - for (unsigned int j = 0, c = mesh.indices.size(); j < c; ++j) { - u32 index = mesh.indices[j] + offset; + const ImportGeometry& geom = m_geometries[mesh.geometry_idx]; + for (unsigned int j = 0, c = geom.indices.size(); j < c; ++j) { + u32 index = geom.indices[j] + offset; indices.push(index); } - int vertex_count = (i32)(mesh.vertex_buffer.size() / mesh.vertex_size); + int vertex_count = (i32)(geom.vertex_buffer.size() / geom.vertex_size); offset += vertex_count; } @@ -1260,6 +1330,20 @@ bool ModelImporter::writePhysics(const Path& src, const ModelMeta& meta) { return compiler.writeCompiledResource(phy_path, Span(m_out_file.data(), (i32)m_out_file.size())); } +// if we split the model into multiple meshes, we still create a dummy file for the asset +// so that we can edit source's metadata +bool ModelImporter::writeDummyModel(const Path& src) { + m_out_file.clear(); + writeModelHeader(); + write(BoneNameHash()); + write((u32)0); + write((u32)0); + write((u32)0); + + AssetCompiler& compiler = m_app.getAssetCompiler(); + return compiler.writeCompiledResource(Path(src), Span(m_out_file.data(), m_out_file.size())); +} + bool ModelImporter::writeModel(const Path& src, const ModelMeta& meta) { PROFILE_FUNCTION(); if (m_meshes.empty() && m_animations.empty()) return false; @@ -1268,7 +1352,7 @@ bool ModelImporter::writeModel(const Path& src, const ModelMeta& meta) { writeModelHeader(); const BoneNameHash root_motion_bone(meta.root_motion_bone.c_str()); write(root_motion_bone); - writeMeshes(src, -1, meta); + writeMeshes(src, meta); writeGeometry(meta); writeSkeleton(meta); writeLODs(meta); @@ -1322,7 +1406,6 @@ bool ModelImporter::writeAnimations(const Path& src, const ModelMeta& meta) { bind_pos = (m_bones[parent_idx].bind_pose_matrix.inverted() * m_bones[bone_idx].bind_pose_matrix).getTranslation(); } - // TODO check if this works with different values of m_orientation if (isBindPosePositionTrack(keys.size(), keys, bind_pos)) continue; const BoneNameHash name_hash(bone.name.c_str()); diff --git a/src/renderer/editor/model_importer.h b/src/renderer/editor/model_importer.h index 397ba35f42..93d97b3a4c 100644 --- a/src/renderer/editor/model_importer.h +++ b/src/renderer/editor/model_importer.h @@ -57,29 +57,36 @@ struct ModelImporter { struct ImportMesh { ImportMesh(IAllocator& allocator) - : vertex_buffer(allocator) - , indices(allocator) - , name(allocator) - , attributes(allocator) - { - } + : name(allocator) + {} + Matrix matrix = Matrix::IDENTITY; u32 mesh_index = 0xFFffFFff; - u32 material_index = 0xffFFffFF; - bool is_skinned = false; - i32 bone_idx = -1; + u32 geometry_idx = 0xffFFffFF; u32 lod = 0; - i32 submesh = -1; + String name; + }; + + struct ImportGeometry { + ImportGeometry(IAllocator& allocator) + : vertex_buffer(allocator) + , indices(allocator) + , attributes(allocator) + , name(allocator) + {} + OutputMemoryStream vertex_buffer; u32 vertex_size = 0xffFFffFF; Array attributes; Array indices; u32 index_size = 0; Local> autolod_indices[4]; - AABB aabb; - float origin_radius_squared; - Vec3 origin = Vec3(0); + i32 submesh = -1; + u32 material_index; + bool is_skinned; + bool flip_handness; String name; + alignas(8) u8 user_data[64]; }; struct Bone { @@ -108,8 +115,10 @@ struct ModelImporter { // TODO fix this (remove meta from these functions?) bool write(const Path& src, const ModelMeta& meta); bool writeMaterials(const Path& src, const ModelMeta& meta, bool force); + bool writePrefab(const Path& src, const ModelMeta& meta); void createImpostorTextures(struct Model* model, ImpostorTexturesContext& ctx, bool bake_normals); + const Array& getGeometries() const { return m_geometries; } const Array& getMeshes() const { return m_meshes; } const Array& getAnimations() const { return m_animations; } @@ -121,6 +130,9 @@ struct ModelImporter { void write(const void* ptr, size_t size) { m_out_file.write(ptr, size); } void writeString(const char* str); + static u32 packF4u(const Vec3& vec); + static Vec3 unpackF4u(u32 packed); + // this is called when writing animations, importer must fill tracks array with keyframes virtual void fillTracks(const ImportAnimation& anim , Array>& tracks @@ -132,19 +144,19 @@ struct ModelImporter { void writeModelHeader(); void writeImpostorVertices(float center_y, Vec2 bounding_cylinder); void writeImpostorMesh(StringView dir, StringView model_name); - void writeMeshes(const Path& src, int mesh_idx, const ModelMeta& meta); + void writeSubmesh(const Path& src, i32 geom_idx, const ModelMeta& meta); + void writeMeshes(const Path& src, const ModelMeta& meta); void writeLODs(const ModelMeta& meta); void writeGeometry(const ModelMeta& meta); - void writeGeometry(int mesh_idx); + void writeGeometry(u32 geom_idx); void writeSkeleton(const ModelMeta& meta); - bool writePrefab(const Path& src, const ModelMeta& meta); bool findTexture(StringView src_dir, StringView ext, ImportTexture& tex) const; void bakeVertexAO(float min_ao); bool writeSubmodels(const Path& src, const ModelMeta& meta); + bool writeDummyModel(const Path& src); bool writeModel(const Path& src, const ModelMeta& meta); bool writeAnimations(const Path& src, const ModelMeta& meta); bool writePhysics(const Path& src, const ModelMeta& meta); - void centerMeshes(); // compute AO, auto LODs, etc. // call this from parse when appropriate @@ -159,6 +171,7 @@ struct ModelImporter { Array m_bones; // parent must be before children Array m_materials; Array m_meshes; + Array m_geometries; Array m_animations; Array m_lights; }; diff --git a/src/renderer/editor/model_meta.h b/src/renderer/editor/model_meta.h index bee4229a9b..34d7e6505c 100644 --- a/src/renderer/editor/model_meta.h +++ b/src/renderer/editor/model_meta.h @@ -85,6 +85,7 @@ struct ModelMeta { WRITE_BOOL(create_impostor, false); WRITE_BOOL(use_mikktspace, false); WRITE_BOOL(force_recompute_normals, false); + WRITE_BOOL(force_recompute_tangents, false); WRITE_BOOL(force_skin, false); WRITE_BOOL(bake_vertex_ao, false); WRITE_BOOL(bake_impostor_normals, false); @@ -94,6 +95,7 @@ struct ModelMeta { WRITE_BOOL(import_vertex_colors, false); WRITE_BOOL(vertex_color_is_ao, false); WRITE_BOOL(ignore_animations, false); + WRITE_BOOL(ignore_material_colors, false); WRITE_VALUE(min_bake_vertex_ao, 0.f); WRITE_VALUE(anim_translation_error, 1.f); WRITE_VALUE(anim_rotation_error, 1.f); @@ -150,6 +152,7 @@ struct ModelMeta { { "root_motion_flags", (i32*)&root_motion_flags }, { "use_mikktspace", &use_mikktspace }, { "force_recompute_normals", &force_recompute_normals }, + { "force_recompute_tangents", &force_recompute_tangents }, { "force_skin", &force_skin }, { "anim_rotation_error", &anim_rotation_error }, { "anim_translation_error", &anim_translation_error }, @@ -164,6 +167,7 @@ struct ModelMeta { { "use_specular_as_roughness", &use_specular_as_roughness }, { "use_specular_as_metallic", &use_specular_as_metallic }, { "ignore_animations", &ignore_animations }, + { "ignore_material_colors", &ignore_material_colors }, { "vertex_color_is_ao", &vertex_color_is_ao }, { "lod_count", &lod_count }, { "create_prefab_with_physics", &create_prefab_with_physics }, @@ -273,6 +277,7 @@ struct ModelMeta { Path skeleton; bool force_recompute_normals = false; + bool force_recompute_tangents = false; bool use_mikktspace = false; bool import_vertex_colors = false; bool bake_vertex_ao = false; @@ -300,6 +305,7 @@ struct ModelMeta { bool split = false; bool force_skin = false; bool ignore_animations = false; + bool ignore_material_colors = false; bool create_prefab_with_physics = false; }; diff --git a/src/renderer/editor/render_plugins.cpp b/src/renderer/editor/render_plugins.cpp index 78b1b1f50a..c10b63d5bf 100644 --- a/src/renderer/editor/render_plugins.cpp +++ b/src/renderer/editor/render_plugins.cpp @@ -2090,12 +2090,29 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin { saveUndo(ImGui::Checkbox("##mikktspace", &m_meta.use_mikktspace)); ImGuiEx::Label("Recompute normals"); saveUndo(ImGui::Checkbox("##recomputenormals", &m_meta.force_recompute_normals)); + ImGuiEx::Label("Recompute tangents"); + saveUndo(ImGui::Checkbox("##recomputetangents", &m_meta.force_recompute_tangents)); ImGuiEx::Label("Force skinned"); saveUndo(ImGui::Checkbox("##frcskn", &m_meta.force_skin)); ImGuiEx::Label("Split"); saveUndo(ImGui::Checkbox("##split", &m_meta.split)); + if (m_meta.split && ImGui::Button("Recreate prefab")) { + ModelImporter* fbx_importer = createFBXImporter(m_app, m_app.getAllocator()); + if (fbx_importer->parseSimple(m_resource->getPath())) { + if (!fbx_importer->writePrefab(m_resource->getPath(), m_meta)) { + logError("Failed to write materials for ", m_resource->getPath()); + } + } + else { + logError("Failed to load ", m_resource->getPath()); + } + destroyFBXImporter(*fbx_importer); + } + ImGuiEx::Label("Ignore animations"); saveUndo(ImGui::Checkbox("##ignoreanim", &m_meta.ignore_animations)); + ImGuiEx::Label("Ignore material colors"); + saveUndo(ImGui::Checkbox("##ignorematcol", &m_meta.ignore_material_colors)); ImGuiEx::Label("Create impostor mesh"); saveUndo(ImGui::Checkbox("##creimp", &m_meta.create_impostor)); if (m_meta.create_impostor) { @@ -2446,11 +2463,6 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin { ImGui::EndMenuBar(); } - if (m_resource->isEmpty()) { - ImGui::TextUnformatted("Loading..."); - return; - } - if (!ImGui::BeginTable("tab", 2, ImGuiTableFlags_Resizable)) return; ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 250); @@ -2481,6 +2493,11 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin { } void previewGUI() { + if (m_resource->isEmpty()) { + ImGui::TextUnformatted("Loading..."); + return; + } + if (!m_resource->isReady()) return; if (ImGui::Checkbox("Wireframe", &m_wireframe)) enableWireframe(*m_resource, m_wireframe); @@ -2581,7 +2598,7 @@ struct ModelPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin { } if(meta.split) { - const Array& meshes = importer->getMeshes(); + const Array& meshes = importer->getGeometries(); for (int i = 0; i < meshes.size(); ++i) { Path tmp(meshes[i].name, ".fbx:", path); compiler.addResource(Model::TYPE, tmp);