From f1e0c1b47b237cd4dbd0956b6e31457c23fd0ec3 Mon Sep 17 00:00:00 2001 From: i-saint Date: Wed, 10 Mar 2021 21:51:49 +0900 Subject: [PATCH] initial commit --- .gitattributes | 10 + .gitignore | 15 + LICENSE.txt | 21 ++ src/CMakeLists.txt | 55 +++ src/SmallFBX.h | 15 + src/SmallFBX.vcxproj | 131 +++++++ src/SmallFBX/CMakeLists.txt | 9 + src/SmallFBX/pch.cpp | 1 + src/SmallFBX/pch.h | 19 ++ src/SmallFBX/sfbx.natvis | 92 +++++ src/SmallFBX/sfbxAlgorithm.h | 138 ++++++++ src/SmallFBX/sfbxAnimation.cpp | 513 ++++++++++++++++++++++++++++ src/SmallFBX/sfbxAnimation.h | 140 ++++++++ src/SmallFBX/sfbxDeformer.cpp | 479 ++++++++++++++++++++++++++ src/SmallFBX/sfbxDeformer.h | 198 +++++++++++ src/SmallFBX/sfbxDocument.cpp | 604 +++++++++++++++++++++++++++++++++ src/SmallFBX/sfbxDocument.h | 98 ++++++ src/SmallFBX/sfbxGeometry.cpp | 351 +++++++++++++++++++ src/SmallFBX/sfbxGeometry.h | 106 ++++++ src/SmallFBX/sfbxInternal.h | 189 +++++++++++ src/SmallFBX/sfbxMaterial.cpp | 37 ++ src/SmallFBX/sfbxMaterial.h | 33 ++ src/SmallFBX/sfbxMath.h | 334 ++++++++++++++++++ src/SmallFBX/sfbxMeta.h | 32 ++ src/SmallFBX/sfbxModel.cpp | 404 ++++++++++++++++++++++ src/SmallFBX/sfbxModel.h | 207 +++++++++++ src/SmallFBX/sfbxNode.cpp | 265 +++++++++++++++ src/SmallFBX/sfbxNode.h | 61 ++++ src/SmallFBX/sfbxObject.cpp | 257 ++++++++++++++ src/SmallFBX/sfbxObject.h | 128 +++++++ src/SmallFBX/sfbxProperty.cpp | 353 +++++++++++++++++++ src/SmallFBX/sfbxProperty.h | 93 +++++ src/SmallFBX/sfbxRawVector.h | 235 +++++++++++++ src/SmallFBX/sfbxTokens.h | 209 ++++++++++++ src/SmallFBX/sfbxTypes.h | 358 +++++++++++++++++++ src/SmallFBX/sfbxUtil.h | 18 + src/SmallFBX/sfbxUtils.cpp | 167 +++++++++ src/Test.vcxproj | 111 ++++++ src/Test/Test.cpp | 210 ++++++++++++ src/Test/Test.h | 55 +++ src/Test/TestFBX.cpp | 305 +++++++++++++++++ src/Test/pch.cpp | 1 + src/Test/pch.h | 35 ++ src/setup.bat | 14 + src/setup.vcxproj | 45 +++ 45 files changed, 7151 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/SmallFBX.h create mode 100644 src/SmallFBX.vcxproj create mode 100644 src/SmallFBX/CMakeLists.txt create mode 100644 src/SmallFBX/pch.cpp create mode 100644 src/SmallFBX/pch.h create mode 100644 src/SmallFBX/sfbx.natvis create mode 100644 src/SmallFBX/sfbxAlgorithm.h create mode 100644 src/SmallFBX/sfbxAnimation.cpp create mode 100644 src/SmallFBX/sfbxAnimation.h create mode 100644 src/SmallFBX/sfbxDeformer.cpp create mode 100644 src/SmallFBX/sfbxDeformer.h create mode 100644 src/SmallFBX/sfbxDocument.cpp create mode 100644 src/SmallFBX/sfbxDocument.h create mode 100644 src/SmallFBX/sfbxGeometry.cpp create mode 100644 src/SmallFBX/sfbxGeometry.h create mode 100644 src/SmallFBX/sfbxInternal.h create mode 100644 src/SmallFBX/sfbxMaterial.cpp create mode 100644 src/SmallFBX/sfbxMaterial.h create mode 100644 src/SmallFBX/sfbxMath.h create mode 100644 src/SmallFBX/sfbxMeta.h create mode 100644 src/SmallFBX/sfbxModel.cpp create mode 100644 src/SmallFBX/sfbxModel.h create mode 100644 src/SmallFBX/sfbxNode.cpp create mode 100644 src/SmallFBX/sfbxNode.h create mode 100644 src/SmallFBX/sfbxObject.cpp create mode 100644 src/SmallFBX/sfbxObject.h create mode 100644 src/SmallFBX/sfbxProperty.cpp create mode 100644 src/SmallFBX/sfbxProperty.h create mode 100644 src/SmallFBX/sfbxRawVector.h create mode 100644 src/SmallFBX/sfbxTokens.h create mode 100644 src/SmallFBX/sfbxTypes.h create mode 100644 src/SmallFBX/sfbxUtil.h create mode 100644 src/SmallFBX/sfbxUtils.cpp create mode 100644 src/Test.vcxproj create mode 100644 src/Test/Test.cpp create mode 100644 src/Test/Test.h create mode 100644 src/Test/TestFBX.cpp create mode 100644 src/Test/pch.cpp create mode 100644 src/Test/pch.h create mode 100644 src/setup.bat create mode 100644 src/setup.vcxproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2dc3ed8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto +*.h text +*.c text +*.hpp text +*.cpp text + +*.bat text eol=crlf +*.sln text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj.filters text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cdc2d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.user +*.png +*.fbx +*.abc +*.usd +*.usda +*.mqoz +*.wasm + +.vs/ +_out/ +_tmp/ +_dist/ +_build_* +Externals/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8371e9a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 i-saint + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..a772653 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,55 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.5) +PROJECT(WebAlembicViewer) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE PATH "" FORCE) +endif() +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/_dist" CACHE PATH "" FORCE) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -std=c++17") +if (EMSCRIPTEN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FORCE_FILESYSTEM=1 -s ALLOW_MEMORY_GROWTH=1 -s DISABLE_EXCEPTION_CATCHING=0 -s USE_GLFW=3 -s EXIT_RUNTIME=0") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --bind") + + # download external libraries + set(EXTERNALS_URL "https://github.com/i-saint/WebAlembicViewer/releases/download/data/Externals.7z") + set(EXTERNALS_ARCHIVE "${CMAKE_SOURCE_DIR}/Externals/Externals.7z") + if(NOT EXISTS ${EXTERNALS_ARCHIVE}) + file(DOWNLOAD ${EXTERNALS_URL} ${EXTERNALS_ARCHIVE} SHOW_PROGRESS) + execute_process(COMMAND 7za x -y -o${CMAKE_SOURCE_DIR}/Externals ${EXTERNALS_ARCHIVE}) + endif() + + set(ext_includes + ${CMAKE_SOURCE_DIR}/Externals/include + ${CMAKE_SOURCE_DIR}/Externals/include/OpenEXR + ) + file(GLOB ext_libs ${CMAKE_SOURCE_DIR}/Externals/lib_emscripten/*.a) +else() + option(DISABLE_GL "disable OpenGL. mainly for benchmark test." OFF) + if(DISABLE_GL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DwabcDisableGL") + endif() + + set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + find_package(OpenEXR REQUIRED) + find_package(Alembic REQUIRED) + set(ext_includes ${ALEMBIC_INCLUDE_DIRS}) + set(ext_libs ${ALEMBIC_LIBRARIES}) +endif() + +add_subdirectory(SmallFBX) + +file(GLOB sources *.cpp *.h) +add_executable(WebAlembicViewer ${sources}) +target_include_directories(WebAlembicViewer + PRIVATE + ${CMAKE_SOURCE_DIR} + ${ext_includes} +) +target_link_libraries(WebAlembicViewer + PRIVATE + SmallFBX + ${ext_libs} +) \ No newline at end of file diff --git a/src/SmallFBX.h b/src/SmallFBX.h new file mode 100644 index 0000000..4a453de --- /dev/null +++ b/src/SmallFBX.h @@ -0,0 +1,15 @@ +#pragma once + +#include "SmallFBX/sfbxTypes.h" +#include "SmallFBX/sfbxProperty.h" +#include "SmallFBX/sfbxNode.h" +#include "SmallFBX/sfbxObject.h" +#include "SmallFBX/sfbxModel.h" +#include "SmallFBX/sfbxGeometry.h" +#include "SmallFBX/sfbxDeformer.h" +#include "SmallFBX/sfbxMaterial.h" +#include "SmallFBX/sfbxAnimation.h" +#include "SmallFBX/sfbxDocument.h" + +#include "SmallFBX/sfbxMath.h" +#include "SmallFBX/sfbxUtil.h" diff --git a/src/SmallFBX.vcxproj b/src/SmallFBX.vcxproj new file mode 100644 index 0000000..eebbad4 --- /dev/null +++ b/src/SmallFBX.vcxproj @@ -0,0 +1,131 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {1c5de91b-7ae9-4304-9fa1-0de1aba8c02d} + + + + + + + {3C7D88E0-B0D0-4E63-9F3F-75530B2DA797} + Win32Proj + 10.0 + + + + v142 + StaticLibrary + Unicode + + + v142 + StaticLibrary + true + Unicode + + + + + + + $(ProjectDir);$(ProjectDir)Externals\include;$(IncludePath) + $(ProjectDir)Externals\lib_win64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(SolutionDir)_build_msvc\$(Platform)_$(Configuration)\ + $(SolutionDir)_build_msvc\obj\$(Platform)_$(Configuration)\$(ProjectName)\ + + + + Use + pch.h + true + stdcpp17 + + + + + %(PreprocessorDefinitions) + Disabled + + + true + true + + + + + /Zo %(AdditionalOptions) + %(PreprocessorDefinitions) + Full + AnySuitable + true + Speed + false + true + false + false + true + Fast + false + MultiThreadedDLL + true + + + true + true + true + true + UseLinkTimeCodeGeneration + + + + + + \ No newline at end of file diff --git a/src/SmallFBX/CMakeLists.txt b/src/SmallFBX/CMakeLists.txt new file mode 100644 index 0000000..df27e4a --- /dev/null +++ b/src/SmallFBX/CMakeLists.txt @@ -0,0 +1,9 @@ +find_package(ZLIB REQUIRED) + +file(GLOB sources *.h *.cpp) +add_library(SmallFBX STATIC ${sources}) +target_include_directories(SmallFBX + PRIVATE + ${CMAKE_SOURCE_DIR} + ${ZLIB_INCLUDE_DIRS} +) diff --git a/src/SmallFBX/pch.cpp b/src/SmallFBX/pch.cpp new file mode 100644 index 0000000..1d9f38c --- /dev/null +++ b/src/SmallFBX/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/SmallFBX/pch.h b/src/SmallFBX/pch.h new file mode 100644 index 0000000..5daa222 --- /dev/null +++ b/src/SmallFBX/pch.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_span + #include +#endif diff --git a/src/SmallFBX/sfbx.natvis b/src/SmallFBX/sfbx.natvis new file mode 100644 index 0000000..36c8ad4 --- /dev/null +++ b/src/SmallFBX/sfbx.natvis @@ -0,0 +1,92 @@ + + + + {{ size={m_size} + + m_size + m_capacity + m_data + + m_size + m_data + + + + + + {{ value={m_scalar.b.value} type={m_type} }} + {{ value={m_scalar.i16} type={m_type} }} + {{ value={m_scalar.i32} type={m_type} }} + {{ value={m_scalar.i64} type={m_type} }} + {{ value={m_scalar.f32} type={m_type} }} + {{ value={m_scalar.f64} type={m_type} }} + + {{ size={m_data.m_size/1} type={m_type} }} + {{ size={m_data.m_size/2} type={m_type} }} + {{ size={m_data.m_size/4} type={m_type} }} + {{ size={m_data.m_size/8} type={m_type} }} + {{ size={m_data.m_size/8} type={m_type} }} + {{ size={m_data.m_size/4} type={m_type} }} + {{ size={m_data.m_size/8} type={m_type} }} + + {{ value={m_data.m_data,s} type={m_type} }} + {{ value={m_data.m_data,s} type={m_type} }} + + + m_scalar.b.value + m_scalar.i16 + m_scalar.i32 + m_scalar.i64 + m_scalar.f32 + m_scalar.f64 + + m_data.m_size + + m_data.m_size + (sfbx::boolean*)m_data.m_data + + + m_data.m_size/2 + + m_data.m_size/2 + (int16*)m_data.m_data + + + m_data.m_size/4 + + m_data.m_size/4 + (int32*)m_data.m_data + + + m_data.m_size/8 + + m_data.m_size/8 + (int64*)m_data.m_data + + + m_data.m_size/4 + + m_data.m_size/4 + (float32*)m_data.m_data + + + m_data.m_size/8 + + m_data.m_size/8 + (float64*)m_data.m_data + + + m_data.m_size + + m_data.m_size + (char*)m_data.m_data + + + m_data.m_size + + m_data.m_size + (char*)m_data.m_data + + + + diff --git a/src/SmallFBX/sfbxAlgorithm.h b/src/SmallFBX/sfbxAlgorithm.h new file mode 100644 index 0000000..ff3c03b --- /dev/null +++ b/src/SmallFBX/sfbxAlgorithm.h @@ -0,0 +1,138 @@ +#pragma once + +#include "sfbxTypes.h" + +namespace sfbx { + +// call resize() if Cont has. otherwise do nothing. +template +inline void resize(Cont& dst, size_t n) +{ + if constexpr (has_resize) { + dst.resize(n); + } +} + + +template +inline void each(Container& val, const Body& body) +{ + for (auto& v : val) + body(v); +} + +template +inline void each_indexed(Container& val, Indices& idx, const Body& body) +{ + for (auto i : idx) + body(val[i]); +} + + +template +inline void copy(D* dst, const S* src, size_t n) +{ + for (size_t i = 0; i < n; ++i) + *dst++ = *src++; +} +template +inline void copy(span dst, span src) +{ + copy(dst.data(), src.data(), src.size()); +} +template +inline void copy(Dst& dst, const Src& src) +{ + resize(dst, src.size()); + copy(make_span(dst), make_span(src)); +} + +template +inline void copy_indexed(Dst& dst, const Src& src, const Indices& idx) +{ + resize(dst, idx.size()); + auto* d = dst.data(); + for (auto i : idx) + *d++ = src[i]; +} + + +template +inline void transform(Dst& dst, const Src& src, const Body& body) +{ + resize(dst, src.size()); + auto* d = dst.data(); + for (const auto& v : src) + *d++ = body(v); +} + +template +inline void transform_indexed(Dst& dst, const Src& src, const Indices& idx, const Body& body) +{ + resize(dst, idx.size()); + auto* d = dst.data(); + for (auto i : idx) + *d++ = body(src[i]); +} + + +template +inline void join(String& dst, const Container& cont, typename String::const_pointer sep, const ToString& to_s) +{ + bool first = true; + for (auto& v : cont) { + if (!first) + dst += sep; + dst += to_s(v); + first = false; + } +} + +template +inline void join(std::string& dst, const Container& cont, const char* sep) +{ + join(dst, cont, sep, + [](typename Container::const_reference v) { return std::to_string(v); }); +} + + +template +inline size_t count(const Container& cont, const Body& body) +{ + size_t r = 0; + for (auto& v : cont) + if (body(v)) + ++r; + return r; +} + +template +inline bool erase(Container& cont, get_value_type v) +{ + auto it = std::find(cont.begin(), cont.end(), v); + if (it != cont.end()) { + cont.erase(it); + return true; + } + return false; +} + +template +inline bool erase_if(Container& cont, const Body& v) +{ + auto it = std::remove_if(cont.begin(), cont.end(), v); + if (it != cont.end()) { + cont.erase(it, cont.end()); + return true; + } + return false; +} + + +// substitution for std::string_view::starts_with() (which require C++20) +inline bool starts_with(string_view str, string_view v) +{ + return std::strncmp(str.data(), v.data(), v.size()) == 0; +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxAnimation.cpp b/src/SmallFBX/sfbxAnimation.cpp new file mode 100644 index 0000000..63ef35a --- /dev/null +++ b/src/SmallFBX/sfbxAnimation.cpp @@ -0,0 +1,513 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxAnimation.h" +#include "sfbxModel.h" +#include "sfbxDeformer.h" +#include "sfbxMaterial.h" +#include "sfbxDocument.h" + +namespace sfbx { + + +ObjectClass AnimationStack::getClass() const { return ObjectClass::AnimationStack; } + +void AnimationStack::importFBXObjects() +{ + super::importFBXObjects(); + + EnumerateProperties(getNode(), [this](Node* n) { + if (GetPropertyString(n, 0) == sfbxS_LocalStart) + m_local_start = FromTicks(GetPropertyValue(n, 4)); + else if (GetPropertyString(n, 0) == sfbxS_LocalStop) + m_local_stop = FromTicks(GetPropertyValue(n, 4)); + else if (GetPropertyString(n, 0) == sfbxS_ReferenceStart) + m_reference_start = FromTicks(GetPropertyValue(n, 4)); + else if (GetPropertyString(n, 0) == sfbxS_ReferenceStop) + m_reference_stop = FromTicks(GetPropertyValue(n, 4)); + }); +} + +void AnimationStack::exportFBXObjects() +{ + super::exportFBXObjects(); + + float start{}, stop{}; + bool first = true; + for (auto* layer : getAnimationLayers()) { + for (auto* node : layer->getAnimationCurveNodes()) { + if (first) { + start = node->getStartTime(); + stop = node->getStopTime(); + } + else { + start = std::min(start, node->getStartTime()); + stop = std::min(start, node->getStopTime()); + } + } + } + + m_reference_start = m_local_start = start; + m_reference_stop = m_local_stop = stop; + auto props = getNode()->createChild(sfbxS_Properties70); + if (m_local_start != 0.0f) + props->createChild(sfbxS_P, sfbxS_LocalStart, sfbxS_KTime, sfbxS_Time, "", ToTicks(m_local_start)); + if (m_local_stop != 0.0f) + props->createChild(sfbxS_P, sfbxS_LocalStop, sfbxS_KTime, sfbxS_Time, "", ToTicks(m_local_stop)); + if (m_reference_start != 0.0f) + props->createChild(sfbxS_P, sfbxS_ReferenceStart, sfbxS_KTime, sfbxS_Time, "", ToTicks(m_reference_start)); + if (m_reference_stop != 0.0f) + props->createChild(sfbxS_P, sfbxS_ReferenceStop, sfbxS_KTime, sfbxS_Time, "", ToTicks(m_reference_stop)); +} + +void AnimationStack::addChild(Object* v) +{ + super::addChild(v); + if (auto l = as(v)) + m_anim_layers.push_back(l); +} + +void AnimationStack::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto l = as(v)) + erase(m_anim_layers, l); +} + +float AnimationStack::getLocalStart() const { return m_local_start; } +float AnimationStack::getLocalStop() const { return m_local_stop; } +float AnimationStack::getReferenceStart() const { return m_reference_start; } +float AnimationStack::getReferenceStop() const { return m_reference_stop; } +span AnimationStack::getAnimationLayers() const { return m_anim_layers; } + +AnimationLayer* AnimationStack::createLayer(string_view name) +{ + return createChild(name); +} + +void AnimationStack::applyAnimation(float time) +{ + for (auto layer : m_anim_layers) + layer->applyAnimation(time); +} + +bool AnimationStack::remap(Document* doc) +{ + for (size_t i = 0; i < m_anim_layers.size(); /**/) { + auto layer = m_anim_layers[i]; + if (!layer->remap(doc)) + eraseChild(layer); + else + ++i; + } + if (m_anim_layers.empty()) + return false; + + if (auto t = doc->findAnimationStack(getFullName())) + t->merge(this); + + if (!doc->findObject(getFullName())) + doc->addObject(shared_from_this()); + for (auto layer : getAnimationLayers()) { + if (!doc->findObject(layer->getName())) + doc->addObject(layer->shared_from_this()); + + for (auto node : layer->getAnimationCurveNodes()) { + doc->addObject(node->shared_from_this()); + for (auto curve : node->getAnimationCurves()) + doc->addObject(curve->shared_from_this()); + } + } + + return true; +} + +void AnimationStack::merge(AnimationStack* src) +{ + for (auto layer : src->getAnimationLayers()) { + if (auto l = as(findChild(layer->getName()))) + l->merge(layer); + else + addChild(layer); + } +} + + + +ObjectClass AnimationLayer::getClass() const { return ObjectClass::AnimationLayer; } + +void AnimationLayer::importFBXObjects() +{ + super::importFBXObjects(); +} + +void AnimationLayer::exportFBXObjects() +{ + super::exportFBXObjects(); +} + +void AnimationLayer::addChild(Object* v) +{ + super::addChild(v); + if (auto acn = as(v)) + m_anim_nodes.push_back(acn); +} + +void AnimationLayer::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto acn = as(v)) + erase(m_anim_nodes, acn); +} + +span AnimationLayer::getAnimationCurveNodes() const +{ + return make_span(m_anim_nodes); +} + +AnimationCurveNode* AnimationLayer::createCurveNode(AnimationKind kind, Object* target) +{ + auto ret = createChild(); + ret->initialize(kind, target); + return ret; +} + +void AnimationLayer::applyAnimation(float time) +{ + for (auto n : m_anim_nodes) + n->applyAnimation(time); +} + +bool AnimationLayer::remap(Document* doc) +{ + for (size_t i = 0; i < m_anim_nodes.size(); /**/) { + auto node = m_anim_nodes[i]; + if (!node->remap(doc)) + eraseChild(node); + else + ++i; + } + return !m_anim_nodes.empty(); +} + +void AnimationLayer::merge(AnimationLayer* src) +{ + for (auto node : src->getAnimationCurveNodes()) + addChild(node); +} + + + +struct AnimationKindData +{ + AnimationKind kind; + string_view object_name; + string_view link_name; + std::vector curve_names; +}; +static const AnimationKindData g_akdata[] = { + {AnimationKind::Position, sfbxS_T, sfbxS_LclTranslation, {"d|X", "d|Y", "d|Z"}}, + {AnimationKind::Rotation, sfbxS_R, sfbxS_LclRotation, {"d|X", "d|Y", "d|Z"}}, + {AnimationKind::Scale, sfbxS_S, sfbxS_LclScale, {"d|X", "d|Y", "d|Z"}}, + {AnimationKind::DeformWeight, sfbxS_DeformPercent, sfbxS_DeformPercent, {"d|" sfbxS_DeformPercent}}, + {AnimationKind::FocalLength, sfbxS_FocalLength, sfbxS_FocalLength, {"d|" sfbxS_FocalLength}}, +}; +static const AnimationKindData* FindAnimationKindData(AnimationKind v) +{ + for (auto& akd : g_akdata) + if (akd.kind == v) + return &akd; + return nullptr; +} +static const AnimationKindData* FindAnimationKindData(string_view name) +{ + for (auto& akd : g_akdata) + if (akd.object_name == name) + return &akd; + return nullptr; +} + + +ObjectClass AnimationCurveNode::getClass() const { return ObjectClass::AnimationCurveNode; } + +void AnimationCurveNode::importFBXObjects() +{ + super::importFBXObjects(); + + auto name = getName(); + auto* akd = FindAnimationKindData(name); + if (akd) { + m_kind = akd->kind; + EnumerateProperties(getNode(), [this, akd](Node* p) { + // todo + }); + } + else { + sfbxPrint("sfbx::AnimationCurveNode: unrecognized animation target \"%s\"\n", std::string(name).c_str()); + } +} + +void AnimationCurveNode::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto props = getNode()->createChild(sfbxS_Properties70); + if (auto* akd = FindAnimationKindData(getName())) { + size_t n = m_curves.size(); + if (akd->curve_names.size() == n) { + for (size_t i = 0; i < n; ++i) { + float v = m_curves[i]->evaluate(m_curves[i]->getStartTime()); + props->createChild(sfbxS_P, akd->curve_names[i], sfbxS_Number, "", sfbxS_A, (float64)v); + } + } + } +} + +void AnimationCurveNode::exportFBXConnections() +{ + // ignore super::constructLinks() + + m_document->createLinkOO(this, getParent()); + + auto* acd = FindAnimationKindData(m_kind); + if (acd && m_curves.size() == acd->curve_names.size()) { + if (auto* target = getAnimationTarget()) + m_document->createLinkOP(this, target, acd->link_name); + for (size_t i = 0; i < m_curves.size(); ++i) + m_document->createLinkOP(m_curves[i], this, acd->curve_names[i]); + } +} + +void AnimationCurveNode::addChild(Object* v) +{ + super::addChild(v); + if (auto curve = as(v)) + m_curves.push_back(curve); +} + +void AnimationCurveNode::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto curve = as(v)) + erase(m_curves, curve); +} + +AnimationKind AnimationCurveNode::getAnimationKind() const +{ + return m_kind; +} + +Object* AnimationCurveNode::getAnimationTarget() const +{ + for (auto p : getParents()) + if (!as(p)) + return p; + return nullptr; +} + +span AnimationCurveNode::getAnimationCurves() const +{ + return make_span(m_curves); +} + +float AnimationCurveNode::getStartTime() const +{ + return m_curves.empty() ? 0.0f : m_curves[0]->getStartTime(); +} + +float AnimationCurveNode::getStopTime() const +{ + return m_curves.empty() ? 0.0f : m_curves[0]->getStopTime(); +} + +float AnimationCurveNode::evaluate(float time) const +{ + if (m_curves.empty()) + return 0.0f; + return m_curves[0]->evaluate(time); +} + +float3 AnimationCurveNode::evaluate3(float time) const +{ + if (m_curves.size() != 3) + return float3::zero(); + + return float3{ + m_curves[0]->evaluate(time), + m_curves[1]->evaluate(time), + m_curves[2]->evaluate(time), + }; +} + +void AnimationCurveNode::applyAnimation(float time) const +{ + if (m_curves.empty() || m_kind == AnimationKind::Unknown) + return; + + auto* target = getAnimationTarget(); + switch (m_kind) { + case AnimationKind::Position: + if (auto* model = as(target)) + model->setPosition(evaluate3(time)); + break; + case AnimationKind::Rotation: + if (auto* model = as(target)) + model->setRotation(evaluate3(time)); + break; + case AnimationKind::Scale: + if (auto* model = as(target)) + model->setScale(evaluate3(time)); + break; + case AnimationKind::DeformWeight: + if (auto* bsc = as(target)) + bsc->setWeight(evaluate(time)); + break; + case AnimationKind::FocalLength: + if (auto cam = as(target)) { + // todo + } + break; + default: + // should not be here + sfbxPrint("sfbx::AnimationCurveNode: something wrong\n"); + break; + } +} + +void AnimationCurveNode::initialize(AnimationKind kind, Object* target) +{ + m_kind = kind; + if (target) + target->addChild(this); + + if (auto* acd = FindAnimationKindData(m_kind)) { + setName(acd->object_name); + for (auto& cn : acd->curve_names) + createChild(); + } +} + +void AnimationCurveNode::addValue(float time, float value) +{ + if (m_curves.size() != 1) { + sfbxPrint("afbx::AnimationCurveNode::addValue() curve count mismatch\n"); + return; + } + m_curves[0]->addValue(time, value); +} + +void AnimationCurveNode::addValue(float time, float3 value) +{ + if (m_curves.size() != 3) { + sfbxPrint("afbx::AnimationCurveNode::addValue() curve count mismatch\n"); + return; + } + m_curves[0]->addValue(time, value.x); + m_curves[1]->addValue(time, value.y); + m_curves[2]->addValue(time, value.z); +} + +bool AnimationCurveNode::remap(Document* doc) +{ + if (m_kind == AnimationKind::Unknown || m_curves.empty()) + return false; + for (auto& p : getParents()) { + if (!as(p)) { + if (auto np = doc->findObject(p->getName())) + p = np; + else + return false; + } + } + return true; +} + + +ObjectClass AnimationCurve::getClass() const { return ObjectClass::AnimationCurve; } + +void AnimationCurve::importFBXObjects() +{ + super::importFBXObjects(); + + for (auto n : getNode()->getChildren()) { + auto name = n->getName(); + if (name == sfbxS_Default) { + m_default = (float32)GetPropertyValue(n); + } + else if (name == sfbxS_KeyTime) { + RawVector times_i64; + GetPropertyValue(times_i64, n); + transform(m_times, times_i64, [](int64 v) { return FromTicks(v); }); + } + else if (name == sfbxS_KeyValueFloat) { + GetPropertyValue(m_values, n); + } + } +} + +void AnimationCurve::exportFBXObjects() +{ + super::exportFBXObjects(); + + RawVector times_i64; + transform(times_i64, m_times, [](float v) { return ToTicks(v); }); + + auto n = getNode(); + n->createChild(sfbxS_Default, (float64)m_default); + n->createChild(sfbxS_KeyVer, sfbxI_KeyVer); + n->createChild(sfbxS_KeyTime, times_i64); + n->createChild(sfbxS_KeyValueFloat, m_values); // float array + + int attr_flags[] = { 24836 }; + float attr_data[] = { 0, 0, 0, 0 }; + int attr_refcount[] = { (int)m_times.size() }; + n->createChild(sfbxS_KeyAttrFlags, make_span(attr_flags)); + n->createChild(sfbxS_KeyAttrDataFloat, make_span(attr_data)); + n->createChild(sfbxS_KeyAttrRefCount, make_span(attr_refcount)); +} + +void AnimationCurve::exportFBXConnections() +{ + // do nothing +} + +span AnimationCurve::getTimes() const { return make_span(m_times); } +span AnimationCurve::getValues() const { return make_span(m_values); } + +float AnimationCurve::getStartTime() const { return m_times.empty() ? 0.0f : m_times.front(); } +float AnimationCurve::getStopTime() const { return m_times.empty() ? 0.0f : m_times.back(); } + +float AnimationCurve::evaluate(float time) const +{ + if (m_times.empty()) + return m_default; + else if (time <= m_times.front()) + return m_values.front(); + else if (time >= m_times.back()) + return m_values.back(); + else { + // lerp + auto it = std::lower_bound(m_times.begin(), m_times.end(), time); + size_t i = std::distance(m_times.begin(), it); + + float t2 = m_times[i]; + float v2 = m_values[i]; + if (time == t2) + return v2; + + float t1 = m_times[i - 1]; + float v1 = m_values[i - 1]; + float w = (time - t1) / (t2 - t1); + return v1 + (v2 - v1) * w; + } +} + +void AnimationCurve::setTimes(span v) { m_times = v; } +void AnimationCurve::setValues(span v) { m_values = v; } + +void AnimationCurve::addValue(float time, float value) +{ + m_times.push_back(time); + m_values.push_back(value); +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxAnimation.h b/src/SmallFBX/sfbxAnimation.h new file mode 100644 index 0000000..c5bcf65 --- /dev/null +++ b/src/SmallFBX/sfbxAnimation.h @@ -0,0 +1,140 @@ +#pragma once +#include "sfbxObject.h" + +namespace sfbx { + +// animation-related classes +// (AnimationStack, AnimationLayer, AnimationCurveNode, AnimationCurve) + +enum class AnimationKind +{ + Unknown, + Position, // float3 + Rotation, // float3 + Scale, // float3 + DeformWeight, // float + FocalLength, // float + filmboxTypeID, // int16 + lockInfluenceWeights, // int32 +}; + +class AnimationStack : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + float getLocalStart() const; + float getLocalStop() const; + float getReferenceStart() const; + float getReferenceStop() const; + span getAnimationLayers() const; + + AnimationLayer* createLayer(string_view name = {}); + + void applyAnimation(float time); + + bool remap(Document* doc); + bool remap(DocumentPtr doc) { return remap(doc.get()); } + void merge(AnimationStack* src); // merge src into this + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + float m_local_start{}; + float m_local_stop{}; + float m_reference_start{}; + float m_reference_stop{}; + std::vector m_anim_layers; +}; + +class AnimationLayer : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + span getAnimationCurveNodes() const; + + AnimationCurveNode* createCurveNode(AnimationKind kind, Object* target); + + void applyAnimation(float time); + + bool remap(Document* doc); + void merge(AnimationLayer* src); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + std::vector m_anim_nodes; +}; + +class AnimationCurveNode : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + AnimationKind getAnimationKind() const; + Object* getAnimationTarget() const; + span getAnimationCurves() const; + float getStartTime() const; + float getStopTime() const; + + // evaluate curve(s) + float evaluate(float time) const; + float3 evaluate3(float time) const; + + // apply evaluated value to target + void applyAnimation(float time) const; + + void initialize(AnimationKind kind, Object* target); + void addValue(float time, float value); + void addValue(float time, float3 value); + + bool remap(Document* doc); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + void exportFBXConnections() override; + + AnimationKind m_kind = AnimationKind::Unknown; + std::vector m_curves; +}; + +class AnimationCurve : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + + span getTimes() const; + span getValues() const; + float getStartTime() const; + float getStopTime() const; + float evaluate(float time) const; + + void setTimes(span v); + void setValues(span v); + void addValue(float time, float value); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + void exportFBXConnections() override; + + float m_default{}; + RawVector m_times; + RawVector m_values; +}; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxDeformer.cpp b/src/SmallFBX/sfbxDeformer.cpp new file mode 100644 index 0000000..ec19afa --- /dev/null +++ b/src/SmallFBX/sfbxDeformer.cpp @@ -0,0 +1,479 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxModel.h" +#include "sfbxGeometry.h" +#include "sfbxDeformer.h" +#include "sfbxDocument.h" + +namespace sfbx { + +ObjectClass Deformer::getClass() const { return ObjectClass::Deformer; } + +GeomMesh* Deformer::getBaseMesh() const +{ + for (auto* p = getParent(); p; p = p->getParent()) + if (auto geom = as(p)) + return geom; + return nullptr; +} + +void Deformer::deformPoints(span dst) const {} +void Deformer::deformNormals(span dst) const {} + + +ObjectClass SubDeformer::getClass() const { return ObjectClass::Deformer; } + + +ObjectSubClass Skin::getSubClass() const { return ObjectSubClass::Skin; } + +void Skin::importFBXObjects() +{ + super::importFBXObjects(); +} + +void Skin::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto n = getNode(); + n->createChild(sfbxS_Version, sfbxI_SkinVersion); + n->createChild(sfbxS_Link_DeformAcuracy, (float64)50.0); + +} + +void Skin::addParent(Object* v) +{ + super::addParent(v); + if (auto mesh = as(v)) + m_mesh = mesh; +} + +void Skin::addChild(Object* v) +{ + super::addChild(v); + if (auto cluster = as(v)) { + m_clusters.push_back(cluster); + + // clear cached skin data + m_weights = {}; + m_joint_matrices = {}; + } +} + +void Skin::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto cluster = as(v)) + erase(m_clusters, cluster); +} + +GeomMesh* Skin::getMesh() const { return m_mesh; } +span Skin::getClusters() const { return make_span(m_clusters); } + +const JointWeights& Skin::getJointWeights() const +{ + auto& ret = m_weights; + if (!ret.counts.empty()) + return ret; + + auto mesh = getBaseMesh(); + if (!mesh) + return ret; + + size_t cclusters = m_clusters.size(); + size_t cpoints = mesh->getPoints().size(); + size_t total_weights = 0; + + // setup counts + ret.counts.resize(cpoints, 0); + for (auto cluster : m_clusters) { + auto indices = cluster->getIndices(); + for (int vi : indices) + ret.counts[vi]++; + total_weights += indices.size(); + } + + // setup offsets + ret.offsets.resize(cpoints); + size_t offset = 0; + int max_joints_per_vertex = 0; + for (size_t pi = 0; pi < cpoints; ++pi) { + ret.offsets[pi] = offset; + int c = ret.counts[pi]; + offset += c; + max_joints_per_vertex = std::max(max_joints_per_vertex, c); + } + ret.max_joints_per_vertex = max_joints_per_vertex; + + // setup weights + ret.counts.zeroclear(); // clear to recount + ret.weights.resize(total_weights); + for (size_t ci = 0; ci < cclusters; ++ci) { + auto cluster = m_clusters[ci]; + auto indices = cluster->getIndices(); + auto weights = cluster->getWeights(); + size_t cweights = weights.size(); + for (size_t wi = 0; wi < cweights; ++wi) { + int vi = indices[wi]; + float weight = weights[wi]; + int pos = ret.offsets[vi] + ret.counts[vi]++; + ret.weights[pos] = { (int)ci, weight }; + } + } + + // sort weights + { + auto* w = ret.weights.data(); + for (int c : ret.counts) { + std::sort(w, w + c, [](auto& a, auto& b) { return b.weight < a.weight; }); + w += c; + } + } + return ret; +} + +JointWeights Skin::createFixedJointWeights(int joints_per_vertex) const +{ + auto& tmp = getJointWeights(); + if (tmp.weights.empty()) + return tmp; + + JointWeights ret; + size_t cpoints = tmp.counts.size(); + ret.max_joints_per_vertex = std::min(joints_per_vertex, tmp.max_joints_per_vertex); + ret.counts.resize(cpoints); + ret.offsets.resize(cpoints); + ret.weights.resize(cpoints * joints_per_vertex); + ret.weights.zeroclear(); + + auto normalize_weights = [](span weights) { + float total = 0.0f; + for (auto& w : weights) + total += w.weight; + + if (total != 0.0f) { + float rcp_total = 1.0f / total; + for (auto& w : weights) + w.weight *= rcp_total; + } + }; + + for (size_t pi = 0; pi < cpoints; ++pi) { + int count = tmp.counts[pi]; + ret.counts[pi] = std::min(count, joints_per_vertex); + ret.offsets[pi] = joints_per_vertex * pi; + + auto* src = &tmp.weights[tmp.offsets[pi]]; + auto* dst = &ret.weights[ret.offsets[pi]]; + if (count < joints_per_vertex) { + copy(dst, src, size_t(count)); + } + else { + copy(dst, src, size_t(joints_per_vertex)); + normalize_weights(make_span(dst, joints_per_vertex)); + } + } + return ret; +} + +const JointMatrices& Skin::getJointMatrices() const +{ + auto& ret = m_joint_matrices; + + // todo: cache result + size_t cclusters = m_clusters.size(); + ret.bindpose.resize(cclusters); + ret.global_transform.resize(cclusters); + ret.joint_transform.resize(cclusters); + for (size_t ci = 0; ci < cclusters; ++ci) { + auto bindpose = m_clusters[ci]->getTransform(); + ret.bindpose[ci] = bindpose; + if (auto trans = as(m_clusters[ci]->getChild())) { + auto global_matrix = trans->getGlobalMatrix(); + ret.global_transform[ci] = global_matrix; + ret.joint_transform[ci] = bindpose * global_matrix; + } + else { + // should not be here + sfbxPrint("sfbx::Deformer::skinMakeJointMatrices(): Cluster has non-Model child\n"); + ret.global_transform[ci] = ret.joint_transform[ci] = float4x4::identity(); + } + } + return ret; +} + + +Cluster* Skin::createCluster(Model* joint) +{ + if (!joint) + return nullptr; + auto r = createChild(); + r->setName(joint->getName()); + r->addChild(joint); + return r; +} + +void Skin::deformPoints(span dst) const +{ + auto& weights = getJointWeights(); + auto& matrices = getJointMatrices(); + DeformPoints(dst, weights, matrices, dst); +} + +void Skin::deformNormals(span dst) const +{ + auto& weights = getJointWeights(); + auto& matrices = getJointMatrices(); + DeformVectors(dst, weights, matrices, dst); +} + + + +ObjectSubClass Cluster::getSubClass() const { return ObjectSubClass::Cluster; } + +void Cluster::importFBXObjects() +{ + super::importFBXObjects(); + + auto n = getNode(); + GetChildPropertyValue(m_indices, n, sfbxS_Indexes); + GetChildPropertyValue(m_weights, n, sfbxS_Weights); + GetChildPropertyValue(m_transform, n, sfbxS_Transform); + GetChildPropertyValue(m_transform_link, n, sfbxS_TransformLink); +} + +void Cluster::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto n = getNode(); + n->createChild(sfbxS_Version, sfbxI_ClusterVersion); + n->createChild(sfbxS_Mode, sfbxS_Total1); + n->createChild(sfbxS_UserData, "", ""); + if (!m_indices.empty()) + n->createChild(sfbxS_Indexes, m_indices); + if (!m_weights.empty()) + n->createChild(sfbxS_Weights, make_adaptor(m_weights)); + if (m_transform != float4x4::identity()) + n->createChild(sfbxS_Transform, (double4x4)m_transform); + if (m_transform_link != float4x4::identity()) + n->createChild(sfbxS_TransformLink, (double4x4)m_transform_link); +} + +span Cluster::getIndices() const { return make_span(m_indices); } +span Cluster::getWeights() const { return make_span(m_weights); } +float4x4 Cluster::getTransform() const { return m_transform; } +float4x4 Cluster::getTransformLink() const { return m_transform_link; } + +void Cluster::setIndices(span v) { m_indices = v; } +void Cluster::setWeights(span v) { m_weights = v; } +void Cluster::setBindMatrix(float4x4 v) +{ + m_transform_link = v; + m_transform = invert(v); +} + + +ObjectSubClass BlendShape::getSubClass() const { return ObjectSubClass::BlendShape; } + +void BlendShape::importFBXObjects() +{ + super::importFBXObjects(); +} + +void BlendShape::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto n = getNode(); + n->createChild(sfbxS_Version, sfbxI_BlendShapeVersion); +} + +void BlendShape::addChild(Object* v) +{ + super::addChild(v); + if (auto ch = as(v)) + m_channels.push_back(ch); +} + +void BlendShape::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto ch = as(v)) + erase(m_channels, ch); +} + +span BlendShape::getChannels() const +{ + return make_span(m_channels); +} + +BlendShapeChannel* BlendShape::createChannel(string_view name) +{ + return createChild(name); +} + +BlendShapeChannel* BlendShape::createChannel(Shape* shape) +{ + if (!shape) + return nullptr; + auto ret = createChannel(shape->getName()); + ret->addShape(shape); + return ret; +} + +void BlendShape::deformPoints(span dst) const +{ + for (auto ch : m_channels) + ch->deformPoints(dst); +} + +void BlendShape::deformNormals(span dst) const +{ + for (auto ch : m_channels) + ch->deformNormals(dst); +} + + +ObjectSubClass BlendShapeChannel::getSubClass() const { return ObjectSubClass::BlendShapeChannel; } + +void BlendShapeChannel::importFBXObjects() +{ + super::importFBXObjects(); + + for (auto c : getChildren()) { + if (auto shape = as(c)) + m_shape_data.push_back({ shape, 1.0f }); + } + if (auto n = getNode()->findChild(sfbxS_FullWeights)) { + RawVector weights; + GetPropertyValue(weights, n); + if (weights.size() == m_shape_data.size()) { + size_t n = weights.size(); + for (size_t i = 0; i < n; ++i) + m_shape_data[i].weight = weights[i] * 0.01f; // percent to 0-1 + } + } +} + +void BlendShapeChannel::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto n = getNode(); + n->createChild(sfbxS_Version, sfbxI_BlendShapeChannelVersion); + n->createChild(sfbxS_DeformPercent, (float64)0); + if (!m_shape_data.empty()) { + auto full_weights = n->createChild(sfbxS_FullWeights); + auto* dst = full_weights->createProperty()->allocateArray(m_shape_data.size()).data(); + for (auto& data : m_shape_data) + *dst++ = float64(data.weight) * 100.0; // 0-1 to percent + } +} + +float BlendShapeChannel::getWeight() const +{ + return m_weight; +} + +span BlendShapeChannel::getShapeData() const +{ + return make_span(m_shape_data); +} + +void BlendShapeChannel::addShape(Shape* shape, float weight) +{ + if (shape) { + addChild(shape); + m_shape_data.push_back({ shape, weight }); + } +} + +void BlendShapeChannel::setWeight(float v) +{ + m_weight = v; +} + +void BlendShapeChannel::deformPoints(span dst) const +{ + if (m_weight <= 0.0f || m_shape_data.empty()) + return; + + if (m_shape_data.size() == 1) { + auto& sd = m_shape_data[0]; + auto shape = sd.shape; + auto indices = shape->getIndices(); + auto delta = shape->getDeltaPoints(); + size_t n = indices.size(); + float w = m_weight / sd.weight; + for (size_t i = 0; i < n; ++i) + dst[indices[i]] += delta[i] * w; + } + else { + // todo + } +} + +void BlendShapeChannel::deformNormals(span dst) const +{ + if (m_weight == 0.0f) + return; + if (m_shape_data.size() == 1) { + auto& sd = m_shape_data[0]; + auto shape = sd.shape; + auto indices = shape->getIndices(); + auto delta = shape->getDeltaNormals(); + size_t n = indices.size(); + float w = m_weight / sd.weight; + for (size_t i = 0; i < n; ++i) + dst[indices[i]] += delta[i] * w; + } + else if (m_shape_data.size() > 1) { + // todo + } +} + + +ObjectClass Pose::getClass() const { return ObjectClass::Pose; } + +ObjectSubClass BindPose::getSubClass() const { return ObjectSubClass::BindPose; } + +void BindPose::importFBXObjects() +{ + super::importFBXObjects(); + + for (auto n : getNode()->getChildren()) { + if (n->getName() == sfbxS_PoseNode) { + auto nid = GetChildPropertyValue(n, sfbxS_Node); + auto model = as(m_document->findObject(nid)); + if (model) { + float4x4 mat; + GetChildPropertyValue(mat, n, sfbxS_Matrix); + m_pose_data.push_back({ model, float4x4(mat) }); + } + else { + sfbxPrint("sfbx::Pose::constructObject(): non-Model joint object\n"); + } + } + } +} + +void BindPose::exportFBXObjects() +{ + super::exportFBXObjects(); + + auto n = getNode(); + n->createChild(sfbxS_Type, sfbxS_BindPose); + n->createChild(sfbxS_Version, sfbxI_BindPoseVersion); + n->createChild(sfbxS_NbPoseNodes, (int32)m_pose_data.size()); + for (auto& d : m_pose_data) { + auto pn = n->createChild(sfbxS_PoseNode); + pn->createChild(sfbxS_Node, (int64)d.object); + pn->createChild(sfbxS_Matrix, (double4x4)d.matrix); + } +} + +span BindPose::getPoseData() const { return make_span(m_pose_data); } +void BindPose::addPoseData(Model* joint, float4x4 bind_matrix) { m_pose_data.push_back({ joint, bind_matrix }); } + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxDeformer.h b/src/SmallFBX/sfbxDeformer.h new file mode 100644 index 0000000..9a2af8f --- /dev/null +++ b/src/SmallFBX/sfbxDeformer.h @@ -0,0 +1,198 @@ +#pragma once +#include "sfbxObject.h" + +namespace sfbx { + +// Deformer and its subclasses: +// (Skin, Cluster, BlendShape, BlendShapeChannel) + +class Deformer : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + + GeomMesh* getBaseMesh() const; + + // apply deform to dst. size of dst must be equal with base mesh. + virtual void deformPoints(span dst) const; + virtual void deformNormals(span dst) const; +}; + +class SubDeformer : public Object +{ +using super = Object; +public: +protected: + ObjectClass getClass() const override; +}; + + +struct JointWeight +{ + int index; // index of joint/cluster + float weight; +}; + +struct JointWeights // copyable +{ + int max_joints_per_vertex = 0; + RawVector counts; // per-vertex. counts of affected joints. + RawVector offsets; // per-vertex. offset to weights. + RawVector weights; // vertex * affected joints (total of counts). weights of affected joints. +}; + +struct JointMatrices +{ + RawVector bindpose; + RawVector global_transform; + RawVector joint_transform; +}; + +class Skin : public Deformer +{ +using super = Deformer; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + GeomMesh* getMesh() const; + span getClusters() const; + const JointWeights& getJointWeights() const; + JointWeights createFixedJointWeights(int joints_per_vertex) const; + const JointMatrices& getJointMatrices() const; + + // joint should be Null, Root or LimbNode + Cluster* createCluster(Model* joint); + + // apply deform to dst. size of dst must be equal with base mesh. + void deformPoints(span dst) const override; + void deformNormals(span dst) const override; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + void addParent(Object* v) override; + + GeomMesh* m_mesh{}; + std::vector m_clusters; + mutable JointWeights m_weights; + mutable JointMatrices m_joint_matrices; +}; + + +class Cluster : public SubDeformer +{ +using super = SubDeformer; +public: + ObjectSubClass getSubClass() const override; + + span getIndices() const; + span getWeights() const; + float4x4 getTransform() const; + float4x4 getTransformLink() const; + + void setIndices(span v); + void setWeights(span v); + void setBindMatrix(float4x4 v); // v: global matrix of the joint (not inverted) + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + RawVector m_indices; + RawVector m_weights; + float4x4 m_transform = float4x4::identity(); + float4x4 m_transform_link = float4x4::identity(); +}; + + +class BlendShape : public Deformer +{ +using super = Deformer; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + span getChannels() const; + BlendShapeChannel* createChannel(string_view name); + BlendShapeChannel* createChannel(Shape* shape); + + void deformPoints(span dst) const override; + void deformNormals(span dst) const override; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + std::vector m_channels; +}; + + +class BlendShapeChannel : public SubDeformer +{ +using super = SubDeformer; +public: + struct ShapeData + { + Shape* shape; + float weight; + }; + + ObjectSubClass getSubClass() const override; + + float getWeight() const; + span getShapeData() const; + // weight: 0.0f - 100.0f + void addShape(Shape* shape, float weight = 100.0f); + + void setWeight(float v); + void deformPoints(span dst) const; + void deformNormals(span dst) const; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + std::vector m_shape_data; + float m_weight = 0.0f; +}; + + + +// Pose and its subclasses: +// (BindPose only for now. probably RestPose will be added) + +class Pose : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; +}; + +class BindPose : public Pose +{ +using super = Pose; +public: + struct PoseData + { + Model* object; + float4x4 matrix; + }; + + ObjectSubClass getSubClass() const override; + + span getPoseData() const; + void addPoseData(Model* joint, float4x4 bind_matrix); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + std::vector m_pose_data; +}; + + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxDocument.cpp b/src/SmallFBX/sfbxDocument.cpp new file mode 100644 index 0000000..0f99850 --- /dev/null +++ b/src/SmallFBX/sfbxDocument.cpp @@ -0,0 +1,604 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxObject.h" +#include "sfbxModel.h" +#include "sfbxGeometry.h" +#include "sfbxDeformer.h" +#include "sfbxMaterial.h" +#include "sfbxAnimation.h" +#include "sfbxDocument.h" + +namespace sfbx { + +static const char* g_creator = "SmallFBX 1.0.0"; + +static const uint8_t g_fbx_header_magic[23]{ + 'K', 'a', 'y', 'd', 'a', 'r', 'a', ' ', 'F', 'B', 'X', ' ', 'B', 'i', 'n', 'a', 'r', 'y', ' ', ' ', 0x00, 0x1a, 0x00, +}; + +// *** these must not be changed. it leads to CRC check failure. *** +static const char g_fbx_time_id[] = "1970-01-01 10:00:00:000"; +static const uint8_t g_fbx_file_id[16]{ 0x28, 0xb3, 0x2a, 0xeb, 0xb6, 0x24, 0xcc, 0xc2, 0xbf, 0xc8, 0xb0, 0x2a, 0xa9, 0x2b, 0xfc, 0xf1 }; +static const uint8_t g_fbx_footer_magic1[16]{ 0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e }; +static const uint8_t g_fbx_footer_magic2[16]{ 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b }; + + +Document::Document() +{ + initialize(); +} + +void Document::initialize() +{ + m_root_model = createObject("Scene"); + m_root_model->setID(0); +} + +bool Document::read(std::istream& is) +{ + unload(); + initialize(); + + char magic[std::size(g_fbx_header_magic)]; + readv(is, magic, std::size(g_fbx_header_magic)); + if (memcmp(magic, g_fbx_header_magic, std::size(g_fbx_header_magic)) != 0) { + sfbxPrint("sfbx::Document::read(): not a fbx file\n"); + return false; + } + m_version = (FileVersion)read1(is); + + + try { + uint64_t pos = std::size(g_fbx_header_magic) + 4; + for (;;) { + auto node = createNode(); + pos += node->read(is, pos); + if (node->isNull()) { + eraseNode(node); + break; + } + } + + if (Node* objects = findNode(sfbxS_Objects)) { + for (Node* n : objects->getChildren()) { + if (Object* obj = createObject(GetObjectClass(n), GetObjectSubClass(n))) { + obj->setNode(n); + } + } + } + + if (Node* connections = findNode(sfbxS_Connections)) { + for (Node* n : connections->getChildren()) { + auto name = n->getName(); + auto ct = GetPropertyString(n, 0); + if (name == sfbxS_C && ct == sfbxS_OO) { + Object* child = findObject(GetPropertyValue(n, 1)); + Object* parent = findObject(GetPropertyValue(n, 2)); + if (child && parent) + parent->addChild(child); + } + else if (name == sfbxS_C && ct == sfbxS_OP) { + auto name = GetPropertyString(n, 3); // todo + Object* child = findObject(GetPropertyValue(n, 1)); + Object* parent = findObject(GetPropertyValue(n, 2)); + if (child && parent) { + parent->addChild(child); + } + } +#ifdef sfbxEnableLegacyFormatSupport + else if (name == sfbxS_Connect && ct == sfbxS_OO) { + Object* child = findObject(GetPropertyString(n, 1)); + Object* parent = findObject(GetPropertyString(n, 2)); + if (child && parent) + parent->addChild(child); + } +#endif + else { + sfbxPrint("sfbx::Document::read(): unrecognized connection type %s %s\n", + std::string(name).c_str(), std::string(ct).c_str()); + } + } + } + + // index based loop because m_objects maybe push_backed in the loop + for (size_t i = 0; i < m_objects.size(); ++i) { + auto obj = m_objects[i]; + obj->importFBXObjects(); + if (obj->getParents().empty()) + m_root_objects.push_back(obj.get()); + } + + if (Node* takes = findNode(sfbxS_Takes)) { + auto current = GetChildPropertyString(takes, sfbxS_Current); + if (auto* t = findAnimationStack(current)) + m_current_take = t; + } + } + catch (const std::runtime_error& e) { + sfbxPrint("sfbx::Document::read(): exception %s\n", e.what()); + return false; + } + return true; +} + +bool Document::read(const std::string& path) +{ + unload(); + + std::ifstream file; + file.open(path, std::ios::in | std::ios::binary); + if (file) + return read(file); + return false; +} + + +bool Document::writeBinary(std::ostream& os) +{ + writev(os, g_fbx_header_magic); + writev(os, m_version); + + uint64_t pos = std::size(g_fbx_header_magic) + 4; + for (Node* node : m_root_nodes) + pos += node->write(os, pos); + { + Node null_node; + null_node.m_document = this; + pos += null_node.write(os, pos); + } + + // footer + + writev(os, g_fbx_footer_magic1); + pos += std::size(g_fbx_footer_magic1); + + // add padding to 16 byte align + uint64_t pad = 16 - (pos % 16); + for (uint64_t i = 0; i < pad; ++i) + writev(os, (int8)0); + + writev(os, (int32)0); + writev(os, m_version); + + // 120 byte space + for (uint64_t i = 0; i < 120; ++i) + writev(os, (int8)0); + + writev(os, g_fbx_footer_magic2); + + return true; +} + +bool Document::writeBinary(const std::string& path) +{ + std::ofstream file; + file.open(path, std::ios::out | std::ios::binary); + if (file) + return writeBinary(file); + return false; +} + +bool Document::writeAscii(std::ostream& output) +{ + auto data = toString(); + output.write(data.data(), data.size()); + return true; +} + +bool Document::writeAscii(const std::string& path) +{ + std::ofstream file; + file.open(path, std::ios::out | std::ios::binary); + if (file) + return writeAscii(file); + return false; +} + + + +void Document::unload() +{ + m_version = FileVersion::Default; + m_nodes.clear(); + m_root_nodes.clear(); + m_objects.clear(); + m_root_objects.clear(); + m_root_model = {}; +} + +FileVersion Document::getVersion() +{ + return m_version; +} + + +void Document::setVersion(FileVersion v) +{ + m_version = v; +} + + +Node* Document::createNode(string_view name) +{ + auto n = createChildNode(name); + m_root_nodes.push_back(n); + return n; +} +Node* Document::createChildNode(string_view name) +{ + auto n = new Node(); + n->m_document = this; + n->setName(name); + m_nodes.push_back(NodePtr(n)); + return n; +} + +void Document::eraseNode(Node* n) +{ + { + auto it = std::find_if(m_nodes.begin(), m_nodes.end(), + [n](const NodePtr& p) { return p.get() == n; }); + if (it != m_nodes.end()) + m_nodes.erase(it); + } + { + auto it = std::find(m_root_nodes.begin(), m_root_nodes.end(), n); + if (it != m_root_nodes.end()) + m_root_nodes.erase(it); + } +} + +Node* Document::findNode(string_view name) const +{ + auto it = std::find_if(m_nodes.begin(), m_nodes.end(), + [&name](const NodePtr& p) { return p->getName() == name; }); + return it != m_nodes.end() ? it->get() : nullptr; +} + +span Document::getAllNodes() const { return make_span(m_nodes); } +span Document::getRootNodes() const { return make_span(m_root_nodes); } + +void Document::createLinkOO(Object* child, Object* parent) +{ + if (!child || !parent) + return; + if (auto c = findNode(sfbxS_Connections)) + c->createChild(sfbxS_C, sfbxS_OO, child->getID(), parent->getID()); +} + +void Document::createLinkOP(Object* child, Object* parent, string_view target) +{ + if (!child || !parent) + return; + if (auto c = findNode(sfbxS_Connections)) + c->createChild(sfbxS_C, sfbxS_OP, child->getID(), parent->getID(), target); +} + +Object* Document::createObject(ObjectClass c, ObjectSubClass s) +{ + Object* r{}; + switch (c) { + case ObjectClass::NodeAttribute: + switch (s) { + case ObjectSubClass::Null: r = new NullAttribute(); break; + case ObjectSubClass::Root: r = new RootAttribute(); break; + case ObjectSubClass::LimbNode: r = new LimbNodeAttribute(); break; + case ObjectSubClass::Light: r = new LightAttribute(); break; + case ObjectSubClass::Camera: r = new CameraAttribute(); break; + default: r = new NodeAttribute(); break; + } + break; + case ObjectClass::Model: + switch (s) { + case ObjectSubClass::Null: r = new Null(); break; + case ObjectSubClass::Root: r = new Root(); break; + case ObjectSubClass::LimbNode: r = new LimbNode(); break; + case ObjectSubClass::Mesh: r = new Mesh(); break; + case ObjectSubClass::Light: r = new Light(); break; + case ObjectSubClass::Camera: r = new Camera(); break; + default: r = new Model(); break; + } + break; + case ObjectClass::Geometry: + switch (s) { + case ObjectSubClass::Mesh: r = new GeomMesh(); break; + case ObjectSubClass::Shape: r = new Shape(); break; + default: r = new Geometry(); break; + } + break; + case ObjectClass::Deformer: + switch (s) { + case ObjectSubClass::Skin: r = new Skin(); break; + case ObjectSubClass::Cluster: r = new Cluster(); break; + case ObjectSubClass::BlendShape: r = new BlendShape(); break; + case ObjectSubClass::BlendShapeChannel: r = new BlendShapeChannel(); break; + default: r = new Deformer(); break; + } + break; + case ObjectClass::Pose: + switch (s) { + case ObjectSubClass::BindPose: r = new BindPose(); break; + default: r = new Pose(); break; + } + break; + case ObjectClass::Video: r = new Video(); break; + case ObjectClass::Material: r = new Material(); break; + case ObjectClass::AnimationStack: r = new AnimationStack(); break; + case ObjectClass::AnimationLayer: r = new AnimationLayer(); break; + case ObjectClass::AnimationCurveNode:r = new AnimationCurveNode(); break; + case ObjectClass::AnimationCurve: r = new AnimationCurve(); break; + default: break; + } + + if (r) { + addObject(ObjectPtr(r)); + } + else { + sfbxPrint("sfbx::Document::createObject(): unrecongnized type \"%s\"\n", GetObjectClassName(c).data()); + } + return r; +} + +template +T* Document::createObject(string_view name) +{ + T* r = new T(); + r->setName(name); + addObject(ObjectPtr(r)); + return r; +} + +#define Body(T) template T* Document::createObject(string_view name); +sfbxEachObjectType(Body) +#undef Body + +void Document::addObject(ObjectPtr obj) +{ + if (obj) { + m_objects.push_back(obj); + if (auto take = as(obj.get())) + m_anim_stacks.push_back(take); + obj->m_document = this; + } +} + + +void Document::eraseObject(Object* obj) +{ + { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [obj](const ObjectPtr& p) { return p.get() == obj; }); + if (it != m_objects.end()) + m_objects.erase(it); + } + { + auto it = std::find(m_root_objects.begin(), m_root_objects.end(), obj); + if (it != m_root_objects.end()) + m_root_objects.erase(it); + } + { + auto it = std::find(m_anim_stacks.begin(), m_anim_stacks.end(), obj); + if (it != m_anim_stacks.end()) + m_anim_stacks.erase(it); + } +} + +Object* Document::findObject(int64 id) const +{ + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [id](auto& p) { return p->getID() == id; }); + return it != m_objects.end() ? it->get() : nullptr; +} + +Object* Document::findObject(string_view name) const +{ + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [&name](auto& p) { return p->getName() == name; }); + return it != m_objects.end() ? it->get() : nullptr; +} + +span Document::getAllObjects() const { return make_span(m_objects); } +span Document::getRootObjects() const { return make_span(m_root_objects); } +Model* Document::getRootModel() const { return m_root_model; } + +span Document::getAnimationStacks() const +{ + return make_span(m_anim_stacks); +} + +AnimationStack* Document::findAnimationStack(string_view name) const +{ + for (auto take : m_anim_stacks) + if (take->getName() == name || take->getFullName() == name) + return take; + return nullptr; +} + +AnimationStack* Document::getCurrentTake() const { return m_current_take; } +void Document::setCurrentTake(AnimationStack* v) { m_current_take = v; } + +void Document::constructNodes() +{ + m_nodes.clear(); + m_root_nodes.clear(); + + std::time_t t = std::time(nullptr); + std::tm* now = std::localtime(&t); + std::string take_name{ m_current_take ? m_current_take->getName() : "" }; + + auto header_extension = createNode(sfbxS_FBXHeaderExtension); + { + header_extension->createChild(sfbxS_FBXHeaderVersion, (int32_t)1003); + header_extension->createChild(sfbxS_FBXVersion, (int32_t)getVersion()); + header_extension->createChild(sfbxS_EncryptionType, (int32_t)0); + { + auto timestamp = header_extension->createChild(sfbxS_CreationTimeStamp); + timestamp->createChild(sfbxS_Version, 1000); + timestamp->createChild(sfbxS_Year, now->tm_year); + timestamp->createChild(sfbxS_Month, now->tm_mon); + timestamp->createChild(sfbxS_Day, now->tm_mday); + timestamp->createChild(sfbxS_Hour, now->tm_hour); + timestamp->createChild(sfbxS_Minute, now->tm_min); + timestamp->createChild(sfbxS_Second, now->tm_sec); + timestamp->createChild(sfbxS_Millisecond, 0); + } + header_extension->createChild(sfbxS_Creator, g_creator); + { + auto other_flags = header_extension->createChild(sfbxS_OtherFlags); + other_flags->createChild(sfbxS_TCDefinition, sfbxI_TCDefinition); + } + { + auto scene_info = header_extension->createChild(sfbxS_SceneInfo, MakeFullName(sfbxS_GlobalInfo, sfbxS_SceneInfo), sfbxS_UserData); + scene_info->createChild(sfbxS_Type, sfbxS_UserData); + scene_info->createChild(sfbxS_Version, 100); + { + auto meta = scene_info->createChild(sfbxS_MetaData); + meta->createChild(sfbxS_Version, 100); + meta->createChild(sfbxS_Title, ""); + meta->createChild(sfbxS_Subject, ""); + meta->createChild(sfbxS_Author, ""); + meta->createChild(sfbxS_Keywords, ""); + meta->createChild(sfbxS_Revision, ""); + meta->createChild(sfbxS_Comment, ""); + } + { + auto prop = scene_info->createChild(sfbxS_Properties70); + prop->createChild(sfbxS_P, sfbxS_DocumentUrl, sfbxS_KString, sfbxS_Url, "", "a.fbx"); + prop->createChild(sfbxS_P, sfbxS_SrcDocumentUrl, sfbxS_KString, sfbxS_Url, "", "a.fbx"); + prop->createChild(sfbxS_P, sfbxS_Original, sfbxS_Compound, "", ""); + prop->createChild(sfbxS_P, sfbxS_OriginalApplicationVendor, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_OriginalApplicationName, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_OriginalApplicationVersion, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_OriginalDateTime_GMT, sfbxS_DateTime, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_OriginalFileName, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_LastSaved, sfbxS_Compound, "", ""); + prop->createChild(sfbxS_P, sfbxS_LastSavedApplicationVendor, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_LastSavedApplicationName, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_LastSavedApplicationVersion, sfbxS_KString, "", "", ""); + prop->createChild(sfbxS_P, sfbxS_LastSavedDateTime_GMT, sfbxS_DateTime, "", "", ""); + } + } + } + + createNode(sfbxS_FileId)->addProperties(make_span(g_fbx_file_id)); + createNode(sfbxS_CreationTime)->addProperties(g_fbx_time_id); + createNode(sfbxS_Creator)->addProperties(g_creator); + + auto global_settings = createNode(sfbxS_GlobalSettings); + { + global_settings->createChild(sfbxS_Version, sfbxI_GlobalSettingsVersion); + auto prop = global_settings->createChild(sfbxS_Properties70); + prop->createChild(sfbxS_P, "UpAxis", "int", "Integer", "", 1); + prop->createChild(sfbxS_P, "UpAxisSign", "int", "Integer", "", 1); + prop->createChild(sfbxS_P, "FrontAxis", "int", "Integer", "", 2); + prop->createChild(sfbxS_P, "FrontAxisSign", "int", "Integer", "", 1); + prop->createChild(sfbxS_P, "CoordAxis", "int", "Integer", "", 0); + prop->createChild(sfbxS_P, "CoordAxisSign", "int", "Integer", "", 1); + prop->createChild(sfbxS_P, "OriginalUpAxis", "int", "Integer", "", -1); + prop->createChild(sfbxS_P, "OriginalUpAxisSign", "int", "Integer", "", 1); + prop->createChild(sfbxS_P, "UnitScaleFactor", "double", "Number", "", 1.000000); + prop->createChild(sfbxS_P, "OriginalUnitScaleFactor", "double", "Number", "", 1.000000); + prop->createChild(sfbxS_P, "AmbientColor", "ColorRGB", "Color", "", 0.000000, 0.000000, 0.000000); + prop->createChild(sfbxS_P, "DefaultCamera", "KString", "", "", "Producer Perspective"); + prop->createChild(sfbxS_P, "TimeMode", "enum", "", "", 0); + prop->createChild(sfbxS_P, "TimeProtocol", "enum", "", "", 2); + prop->createChild(sfbxS_P, "SnapOnFrameMode", "enum", "", "", 0); + prop->createChild(sfbxS_P, "TimeSpanStart", "KTime", "Time", "", (int64)0); + prop->createChild(sfbxS_P, "TimeSpanStop", "KTime", "Time", "", (int64)sfbxI_TicksPerSecond); + prop->createChild(sfbxS_P, "CustomFrameRate", "double", "Number", "", -1.000000); + prop->createChild(sfbxS_P, "TimeMarker", "Compound", "", ""); + prop->createChild(sfbxS_P, "CurrentTimeMarker", "int", "Integer", "", -1); + } + + auto documents = createNode(sfbxS_Documents); + { + documents->createChild(sfbxS_Count, (int32)1); + auto doc = documents->createChild(sfbxS_Document); + { + doc->addProperties((int64)this, "My Scene", "Scene"); + + auto prop = doc->createChild(sfbxS_Properties70); + prop->createChild(sfbxS_P, "SourceObject", "object", "", ""); + prop->createChild(sfbxS_P, "ActiveAnimStackName", "KString", "", "", take_name); + + doc->createChild(sfbxS_RootNode, 0); + } + } + + auto references = createNode(sfbxS_References); + + auto definitions = createNode(sfbxS_Definitions); + + createNode(sfbxS_Objects); + createNode(sfbxS_Connections); + + // index based loop because m_objects maybe push_backed in the loop + for (size_t i = 0; i < m_objects.size(); ++i) + m_objects[i]->exportFBXObjects(); + for (size_t i = 0; i < m_objects.size(); ++i) + m_objects[i]->exportFBXConnections(); + + { + auto add_object_type = [definitions](size_t n, const char* type) -> Node* { + if (n == 0) + return nullptr; + auto ot = definitions->createChild(sfbxS_ObjectType); + ot->addProperty(type); + ot->createChild(sfbxS_Count, (int32)n); + return ot; + }; + + add_object_type(1, sfbxS_GlobalSettings); + + add_object_type(countObjects(), sfbxS_NodeAttribute); + add_object_type(countObjects(), sfbxS_Model); + add_object_type(countObjects(), sfbxS_Geometry); + add_object_type(countObjects(), sfbxS_Deformer); + add_object_type(countObjects(), sfbxS_Pose); + + add_object_type(countObjects(), sfbxS_AnimationStack); + add_object_type(countObjects(), sfbxS_AnimationLayer); + add_object_type(countObjects(), sfbxS_AnimationCurveNode); + add_object_type(countObjects(), sfbxS_AnimationCurve); + + add_object_type(countObjects(), sfbxS_Material); + } + + auto takes = createNode(sfbxS_Takes); + takes->createChild(sfbxS_Current, take_name); + for (auto* t : m_anim_stacks) { + auto take = takes->createChild(sfbxS_Take, t->getName()); + take->createChild(sfbxS_FileName, std::string(t->getName()) + ".tak"); + + float lstart = t->getLocalStart(); + float lstop = t->getLocalStop(); + float rstart = t->getReferenceStart(); + float rstop = t->getReferenceStop(); + if (lstart != 0.0f || lstop != 0.0f) + take->createChild(sfbxS_LocalTime, ToTicks(lstart), ToTicks(lstop)); + if (rstart != 0.0f || rstop != 0.0f) + take->createChild(sfbxS_ReferenceTime, ToTicks(rstart), ToTicks(rstop)); + } +} + +std::string Document::toString() +{ + char version[128]; + sprintf(version, "; FBX %d.%d.0 project file\n", (int)m_version / 1000 % 10, (int)m_version / 100 % 10); + + std::string s; + s += version; + s += "; ----------------------------------------------------\n\n"; + + for (auto node : getRootNodes()) { + // these nodes seem required only in binary format. + if (node->getName() == sfbxS_FileId || + node->getName() == sfbxS_CreationTime || + node->getName() == sfbxS_Creator) + continue; + s += node->toString(); + } + return s; +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxDocument.h b/src/SmallFBX/sfbxDocument.h new file mode 100644 index 0000000..de4f041 --- /dev/null +++ b/src/SmallFBX/sfbxDocument.h @@ -0,0 +1,98 @@ +#pragma once +#include "sfbxObject.h" +# +namespace sfbx { + +enum class FileVersion : int +{ + Unknown = 0, + + Fbx2014 = 7400, + Fbx2015 = Fbx2014, + + Fbx2016 = 7500, + Fbx2017 = Fbx2016, + Fbx2018 = Fbx2016, + + Fbx2019 = 7700, + Fbx2020 = Fbx2019, + + Default = Fbx2020, +}; + +class Document +{ +public: + Document(); + bool read(std::istream &input); + bool read(const std::string& path); + bool writeBinary(std::ostream& output); + bool writeBinary(const std::string& path); + bool writeAscii(std::ostream& output); + bool writeAscii(const std::string& path); + void unload(); + + FileVersion getVersion(); + void setVersion(FileVersion v); + + Node* createNode(string_view name = {}); + Node* createChildNode(string_view name = {}); + void eraseNode(Node* n); + Node* findNode(string_view name) const; + span getAllNodes() const; + span getRootNodes() const; + + void createLinkOO(Object* child, Object* parent); + void createLinkOP(Object* child, Object* parent, string_view target); + + + Object* createObject(ObjectClass t, ObjectSubClass s); + template T* createObject(string_view name = {}); + void addObject(ObjectPtr obj); + void eraseObject(Object* objv); + + Object* findObject(int64 id) const; + Object* findObject(string_view name) const; // name must be in node name format (e.g. "hoge\x00\x01Mesh") + span getAllObjects() const; + span getRootObjects() const; + Model* getRootModel() const; + + span getAnimationStacks() const; + AnimationStack* findAnimationStack(string_view name) const; + + AnimationStack* getCurrentTake() const; + void setCurrentTake(AnimationStack* v); + + void constructNodes(); + std::string toString(); + + + // utils + template + size_t countObjects() const + { + return count(m_objects, [](auto& p) { return as(p.get()) && p->getID() != 0; }); + } + +private: + void initialize(); + + FileVersion m_version = FileVersion::Default; + + std::vector m_nodes; + std::vector m_root_nodes; + + std::vector m_objects; + std::vector m_root_objects; + std::vector m_anim_stacks; + Model* m_root_model{}; + AnimationStack* m_current_take{}; +}; + +template +inline DocumentPtr MakeDocument(T&&... v) +{ + return std::make_shared(std::forward(v)...); +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxGeometry.cpp b/src/SmallFBX/sfbxGeometry.cpp new file mode 100644 index 0000000..4f24c48 --- /dev/null +++ b/src/SmallFBX/sfbxGeometry.cpp @@ -0,0 +1,351 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxObject.h" +#include "sfbxModel.h" +#include "sfbxGeometry.h" +#include "sfbxDeformer.h" + +namespace sfbx { + +ObjectClass Geometry::getClass() const { return ObjectClass::Geometry; } + +void Geometry::addChild(Object* v) +{ + super::addChild(v); + if (auto deformer = as(v)) + m_deformers.push_back(deformer); +} + +void Geometry::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto deformer = as(v)) + erase(m_deformers, deformer); +} + +Model* Geometry::getModel() const +{ + for (auto p : m_parents) + if (auto model = as(p)) + return model; + return nullptr; +} + +bool Geometry::hasDeformer() const +{ + return !m_deformers.empty(); +} + +bool Geometry::hasSkinDeformer() const +{ + for (auto d : m_deformers) + if (auto skin = as(d)) + return true; + return false; +} + +span Geometry::getDeformers() const +{ + return make_span(m_deformers); +} + +template<> Skin* Geometry::createDeformer() +{ + auto ret = createChild(); + //ret->setName(getName()); // FBX SDK seems don't do this + return ret; +} + +template<> BlendShape* Geometry::createDeformer() +{ + auto ret = createChild(); + ret->setName(getName()); + return ret; +} + + +ObjectSubClass GeomMesh::getSubClass() const { return ObjectSubClass::Mesh; } + +void GeomMesh::importFBXObjects() +{ + super::importFBXObjects(); + + for (auto n : getNode()->getChildren()) { + if (n->getName() == sfbxS_Vertices) { + // points + GetPropertyValue(m_points, n); + } + else if (n->getName() == sfbxS_PolygonVertexIndex) { + // counts & indices + GetPropertyValue(m_indices, n); + m_counts.resize(m_indices.size()); // reserve + int* dst_counts = m_counts.data(); + size_t cfaces = 0; + size_t cpoints = 0; + for (int& i : m_indices) { + ++cpoints; + if (i < 0) { // negative value indicates the last index in the face + i = ~i; + dst_counts[cfaces++] = cpoints; + cpoints = 0; + } + } + m_counts.resize(cfaces); // fit to actual size + } + else if (n->getName() == sfbxS_LayerElementNormal) { + // normals + //auto mapping = n->findChildProperty(sfbxS_MappingInformationType); + //auto ref = n->findChildProperty(sfbxS_ReferenceInformationType); + LayerElementF3 tmp; + tmp.name = GetChildPropertyString(n, sfbxS_Name); + GetChildPropertyValue(tmp.data, n, sfbxS_Normals); + GetChildPropertyValue(tmp.indices, n, sfbxS_NormalsIndex); + m_normal_layers.push_back(std::move(tmp)); + } + else if (n->getName() == sfbxS_LayerElementUV) { + // uv + LayerElementF2 tmp; + tmp.name = GetChildPropertyString(n, sfbxS_Name); + GetChildPropertyValue(tmp.data, n, sfbxS_UV); + GetChildPropertyValue(tmp.indices, n, sfbxS_UVIndex); + m_uv_layers.push_back(std::move(tmp)); + } + else if (n->getName() == sfbxS_LayerElementColor) { + // colors + LayerElementF4 tmp; + tmp.name = GetChildPropertyString(n, sfbxS_Name); + GetChildPropertyValue(tmp.data, n, sfbxS_Colors); + GetChildPropertyValue(tmp.indices, n, sfbxS_ColorIndex); + m_color_layers.push_back(std::move(tmp)); + } + } +} + +void GeomMesh::exportFBXObjects() +{ + super::exportFBXObjects(); + + Node* n = getNode(); + + n->createChild(sfbxS_GeometryVersion, sfbxI_GeometryVersion); + + // points + n->createChild(sfbxS_Vertices, make_adaptor(m_points)); + + // indices + { + // check if counts and indices are valid + size_t total_counts = 0; + for (int c : m_counts) + total_counts += c; + + if (total_counts != m_indices.size()) { + sfbxPrint("sfbx::Mesh: *** indices mismatch with counts ***\n"); + } + else { + auto* src_counts = m_counts.data(); + auto dst_node = n->createChild(sfbxS_PolygonVertexIndex); + auto dst_prop = dst_node->createProperty(); + auto dst = dst_prop->allocateArray(m_indices.size()).data(); + + size_t cpoints = 0; + for (int i : m_indices) { + if (++cpoints == *src_counts) { + i = ~i; // negative value indicates the last index in the face + cpoints = 0; + ++src_counts; + } + *dst++ = i; + } + } + } + + auto add_mapping_and_reference_info = [this](Node* node, const auto& layer) { + if (layer.data.size() == m_indices.size() || layer.indices.size() == m_indices.size()) + node->createChild(sfbxS_MappingInformationType, "ByPolygonVertex"); + else if (layer.data.size() == m_points.size() && layer.indices.empty()) + node->createChild(sfbxS_MappingInformationType, "ByControllPoint"); + + if (!layer.indices.empty()) + node->createChild(sfbxS_ReferenceInformationType, "IndexToDirect"); + else + node->createChild(sfbxS_ReferenceInformationType, "Direct"); + }; + + int clayers = 0; + + // normal layers + for (auto& layer : m_normal_layers) { + if (layer.data.empty()) + continue; + + ++clayers; + auto l = n->createChild(sfbxS_LayerElementNormal); + l->createChild(sfbxS_Version, sfbxI_LayerElementNormalVersion); + l->createChild(sfbxS_Name, layer.name); + + add_mapping_and_reference_info(l, layer); + l->createChild(sfbxS_Normals, make_adaptor(layer.data)); + if (!layer.indices.empty()) + l->createChild(sfbxS_NormalsIndex, layer.indices); + } + + // uv layers + for (auto& layer : m_uv_layers) { + if (layer.data.empty()) + continue; + + ++clayers; + auto l = n->createChild(sfbxS_LayerElementUV); + l->createChild(sfbxS_Version, sfbxI_LayerElementUVVersion); + l->createChild(sfbxS_Name, layer.name); + + add_mapping_and_reference_info(l, layer); + l->createChild(sfbxS_UV, make_adaptor(layer.data)); + if (!layer.indices.empty()) + l->createChild(sfbxS_UVIndex, layer.indices); + } + + // color layers + for (auto& layer : m_color_layers) { + if (layer.data.empty()) + continue; + + ++clayers; + auto l = n->createChild(sfbxS_LayerElementColor); + l->createChild(sfbxS_Version, sfbxI_LayerElementColorVersion); + l->createChild(sfbxS_Name, layer.name); + + add_mapping_and_reference_info(l, layer); + l->createChild(sfbxS_Colors, make_adaptor(layer.data)); + if (!layer.indices.empty()) + l->createChild(sfbxS_ColorIndex, layer.indices); + } + + if (clayers) { + // layer info + auto l = n->createChild(sfbxS_Layer, 0); + l->createChild(sfbxS_Version, sfbxI_LayerVersion); + if (!m_normal_layers.empty()) { + auto le = l->createChild(sfbxS_LayerElement); + le->createChild(sfbxS_Type, sfbxS_LayerElementNormal); + le->createChild(sfbxS_TypeIndex, 0); + } + if (!m_uv_layers.empty()) { + auto le = l->createChild(sfbxS_LayerElement); + le->createChild(sfbxS_Type, sfbxS_LayerElementUV); + le->createChild(sfbxS_TypeIndex, 0); + } + if (!m_color_layers.empty()) { + auto le = l->createChild(sfbxS_LayerElement); + le->createChild(sfbxS_Type, sfbxS_LayerElementColor); + le->createChild(sfbxS_TypeIndex, 0); + } + } +} + +span GeomMesh::getCounts() const { return make_span(m_counts); } +span GeomMesh::getIndices() const { return make_span(m_indices); } +span GeomMesh::getPoints() const { return make_span(m_points); } +span GeomMesh::getNormalLayers() const { return make_span(m_normal_layers); } +span GeomMesh::getUVLayers() const { return make_span(m_uv_layers); } +span GeomMesh::getColorLayers() const { return make_span(m_color_layers); } + +void GeomMesh::setCounts(span v) { m_counts = v; } +void GeomMesh::setIndices(span v) { m_indices = v; } +void GeomMesh::setPoints(span v) { m_points = v; } +void GeomMesh::addNormalLayer(LayerElementF3&& v) { m_normal_layers.push_back(std::move(v)); } +void GeomMesh::addUVLayer(LayerElementF2&& v) { m_uv_layers.push_back(std::move(v)); } +void GeomMesh::addColorLayer(LayerElementF4&& v) { m_color_layers.push_back(std::move(v)); } + +span GeomMesh::getPointsDeformed(bool apply_transform) +{ + if (m_deformers.empty() && !apply_transform) + return make_span(m_points); + + m_points_deformed = m_points; + auto dst = make_span(m_points_deformed); + bool is_skinned = false; + for (auto deformer : m_deformers) { + deformer->deformPoints(dst); + if (as(deformer)) + is_skinned = true; + } + if (!is_skinned && apply_transform) { + if (auto model = getModel()) { + auto mat = model->getGlobalMatrix(); + for (auto& v : dst) + v = mul_p(mat, v); + } + } + return dst; +} + +span GeomMesh::getNormalsDeformed(size_t layer_index, bool apply_transform) +{ + if (layer_index >= m_normal_layers.size()) + return {}; + + auto& l = m_normal_layers[layer_index]; + if (m_deformers.empty() && !apply_transform) + return make_span(l.data); + + l.data_deformed = l.data; + auto dst = make_span(l.data_deformed); + bool is_skinned = false; + for (auto deformer : m_deformers) { + deformer->deformNormals(dst); + if (as(deformer)) + is_skinned = true; + } + if (!is_skinned && apply_transform) { + if (auto model = getModel()) { + auto mat = model->getGlobalMatrix(); + for (auto& v : dst) + v = normalize(mul_v(mat, v)); + } + } + return dst; +} + + +ObjectSubClass Shape::getSubClass() const { return ObjectSubClass::Shape; } + +void Shape::importFBXObjects() +{ + super::importFBXObjects(); + + for (auto n : getNode()->getChildren()) { + auto name = n->getName(); + if (name == sfbxS_Indexes) + GetPropertyValue(m_indices, n); + else if (name == sfbxS_Vertices) + GetPropertyValue(m_delta_points, n); + else if (name == sfbxS_Normals) + GetPropertyValue(m_delta_normals, n); + } +} + +void Shape::exportFBXObjects() +{ + super::exportFBXObjects(); + + Node* n = getNode(); + n->createChild(sfbxS_Version, sfbxI_ShapeVersion); + if (!m_indices.empty()) + n->createChild(sfbxS_Indexes, m_indices); + if (!m_delta_points.empty()) + n->createChild(sfbxS_Vertices, make_adaptor(m_delta_points)); + if (!m_delta_normals.empty()) + n->createChild(sfbxS_Normals, make_adaptor(m_delta_normals)); +} + +span Shape::getIndices() const { return make_span(m_indices); } +span Shape::getDeltaPoints() const { return make_span(m_delta_points); } +span Shape::getDeltaNormals() const { return make_span(m_delta_normals); } + +void Shape::setIndices(span v) { m_indices = v; } +void Shape::setDeltaPoints(span v) { m_delta_points = v; } +void Shape::setDeltaNormals(span v) { m_delta_normals = v; } + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxGeometry.h b/src/SmallFBX/sfbxGeometry.h new file mode 100644 index 0000000..dbc933a --- /dev/null +++ b/src/SmallFBX/sfbxGeometry.h @@ -0,0 +1,106 @@ +#pragma once +#include "sfbxObject.h" + +namespace sfbx { + +// Geometry and its subclasses: +// (Mesh, Shape) + +template +inline constexpr bool is_deformer = std::is_base_of_v; + +class Geometry : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + Model* getModel() const; + bool hasDeformer() const; + bool hasSkinDeformer() const; + span getDeformers() const; + + // T: Skin, BlendShape + template)> + T* createDeformer(); + +protected: + std::vector m_deformers; +}; + + +template +struct LayerElement +{ + std::string name; + RawVector indices; // can be empty. in that case, size of data must equal with vertex count or index count. + RawVector data; + RawVector data_deformed; +}; +using LayerElementF2 = LayerElement; +using LayerElementF3 = LayerElement; +using LayerElementF4 = LayerElement; + +class GeomMesh : public Geometry +{ +using super = Geometry; +public: + ObjectSubClass getSubClass() const override; + + span getCounts() const; + span getIndices() const; + span getPoints() const; + span getNormalLayers() const; + span getUVLayers() const; + span getColorLayers() const; + + void setCounts(span v); + void setIndices(span v); + void setPoints(span v); + void addNormalLayer(LayerElementF3&& v); + void addUVLayer(LayerElementF2&& v); + void addColorLayer(LayerElementF4&& v); + + span getPointsDeformed(bool apply_transform = false); + span getNormalsDeformed(size_t layer_index = 0, bool apply_transform = false); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + RawVector m_counts; + RawVector m_indices; + RawVector m_points; + RawVector m_points_deformed; + std::vector m_normal_layers; + std::vector m_uv_layers; + std::vector m_color_layers; +}; + +class Shape : public Geometry +{ +using super = Geometry; +public: + ObjectSubClass getSubClass() const override; + + span getIndices() const; + span getDeltaPoints() const; + span getDeltaNormals() const; + + void setIndices(span v); + void setDeltaPoints(span v); + void setDeltaNormals(span v); + +public: + void importFBXObjects() override; + void exportFBXObjects() override; + + RawVector m_indices; + RawVector m_delta_points; + RawVector m_delta_normals; +}; + + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxInternal.h b/src/SmallFBX/sfbxInternal.h new file mode 100644 index 0000000..5f3d433 --- /dev/null +++ b/src/SmallFBX/sfbxInternal.h @@ -0,0 +1,189 @@ +#pragma once +#include "sfbxTypes.h" +#include "sfbxAlgorithm.h" +#include "sfbxMath.h" +#include "sfbxTokens.h" +#include "sfbxNode.h" +#include "sfbxUtil.h" + +#define sfbxEnableLegacyFormatSupport + +namespace sfbx { + +template && !std::is_pointer_v)> +inline T read1(std::istream& is) +{ + T r; + is.read((char*)&r, sizeof(T)); + return r; +} +inline void readv(std::istream& is, void* dst, size_t size) +{ + is.read((char*)dst, size); +} +inline void readv(std::istream& is, std::string& dst, size_t s) +{ + dst.resize(s); + is.read(dst.data(), s); +} + +template && !std::is_pointer_v)> +inline void writev(std::ostream& os, T v) +{ + os.write((const char*)&v, sizeof(T)); +} +inline void writev(std::ostream& os, const void* src, size_t size) +{ + os.write((const char*)src, size); +} +template +inline void writev(std::ostream& os, span v) +{ + writev(os, v.data(), v.size_bytes()); +} +template)> +inline void writev(std::ostream& os, const Cont& v) +{ + writev(os, make_span(v)); +} +template +inline void writev(std::ostream& os, const T(&v)[N]) +{ + writev(os, make_span(v)); +} + + +inline void AddTabs(std::string& dst, int n) +{ + for (int i = 0; i < n; ++i) + dst += '\t'; +} + +inline int64 ToTicks(float v) +{ + return int64((float64)v * sfbxI_TicksPerSecond); +} +inline float FromTicks(int64 v) +{ + return float((float64)v / sfbxI_TicksPerSecond); +} + + +inline size_t GetPropertyCount(Node* node) +{ + if (node) + return node->getProperties().size(); + return 0; +} + +inline PropertyType GetPropertyType(Node* node, size_t pi = 0) +{ + if (node) + if (Property* prop = node->getProperty(pi)) + return prop->getType(); + return PropertyType::Unknown; +} + +template)> +inline T GetPropertyValue(Node* node, size_t pi = 0) +{ + if (node) + if (Property* prop = node->getProperty(pi)) + return prop->getValue(); + return {}; +} + +inline string_view GetPropertyString(Node* node, size_t pi = 0) +{ + if (node) + if (Property* prop = node->getProperty(pi)) + return prop->getString(); + return {}; +} + + +template)> +inline void GetPropertyValue(Dst& dst, Node* node) +{ + if (node) { + if (Property* prop = node->getProperty(0)) { + if (prop->isArray()) + dst = prop->getArray(); +#ifdef sfbxEnableLegacyFormatSupport + else + node->getPropertiesValues(dst); +#endif + } + } +} +template)> +inline void GetPropertyValue(Dst& dst, Node* node) +{ + if (node) { + if (Property* prop = node->getProperty(0)) { + if (prop->isArray()) + dst = prop->getValue(); +#ifdef sfbxEnableLegacyFormatSupport + else + node->getPropertiesValues(dst); +#endif + } + } +} + +template)> +inline T GetChildPropertyValue(Node* node, string_view name, size_t pi = 0) +{ + if (node) + return GetPropertyValue(node->findChild(name), pi); + return {}; +} +template || is_vector)> +inline void GetChildPropertyValue(Dst& dst, Node* node, string_view name) +{ + if (node) + GetPropertyValue(dst, node->findChild(name)); +} +inline string_view GetChildPropertyString(Node* node, string_view name, size_t pi = 0) +{ + if (node) + return GetPropertyString(node->findChild(name)); + return {}; +} + + +template +inline void EnumerateProperties(Node* n, const Body& body) +{ + for (Node* props : n->getChildren()) { + if (starts_with(props->getName(), sfbxS_Properties)) { + for (Node* p : props->getChildren()) + body(p); + break; + } + } +} + +class CounterStream : public std::ostream +{ +public: + CounterStream(); + uint64_t size(); + void reset(); + +private: + class StreamBuf : public std::streambuf + { + public: + static char s_dummy_buf[1024]; + + StreamBuf(); + int overflow(int c) override; + int sync() override; + void reset(); + + uint64_t m_size = 0; + } m_buf; +}; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxMaterial.cpp b/src/SmallFBX/sfbxMaterial.cpp new file mode 100644 index 0000000..271e028 --- /dev/null +++ b/src/SmallFBX/sfbxMaterial.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxMaterial.h" + +namespace sfbx { + +ObjectClass Video::getClass() const { return ObjectClass::Video; } +ObjectSubClass Video::getSubClass() const { return ObjectSubClass::Clip; } + +void Video::importFBXObjects() +{ + super::importFBXObjects(); + // todo +} + +void Video::exportFBXObjects() +{ + super::exportFBXObjects(); + // todo +} + + + +ObjectClass Material::getClass() const { return ObjectClass::Material; } + +void Material::importFBXObjects() +{ + super::importFBXObjects(); + // todo +} + +void Material::exportFBXObjects() +{ + super::exportFBXObjects(); + // todo +} +} // namespace sfbx diff --git a/src/SmallFBX/sfbxMaterial.h b/src/SmallFBX/sfbxMaterial.h new file mode 100644 index 0000000..cb89845 --- /dev/null +++ b/src/SmallFBX/sfbxMaterial.h @@ -0,0 +1,33 @@ +#pragma once +#include "sfbxObject.h" + +namespace sfbx { + +// texture & material + +// Video represents texture data +class Video : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + ObjectSubClass getSubClass() const override; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; +}; + +class Material : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; +}; + + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxMath.h b/src/SmallFBX/sfbxMath.h new file mode 100644 index 0000000..622bba5 --- /dev/null +++ b/src/SmallFBX/sfbxMath.h @@ -0,0 +1,334 @@ +#pragma once +#include +#include "sfbxTypes.h" + +namespace sfbx { + +constexpr float PI = 3.14159265358979323846264338327950288419716939937510f; +constexpr float DegToRad = PI / 180.0f; + +template inline tvec2 operator-(const tvec2& v) { return{ -v.x, -v.y }; } +template inline tvec2 operator+(const tvec2& l, const tvec2& r) { return{ l.x + r.x, l.y + r.y }; } +template inline tvec2 operator-(const tvec2& l, const tvec2& r) { return{ l.x - r.x, l.y - r.y }; } +template inline tvec2 operator*(const tvec2& l, const tvec2& r) { return{ l.x * r.x, l.y * r.y }; } +template inline tvec2 operator/(const tvec2& l, const tvec2& r) { return{ l.x / r.x, l.y / r.y }; } +template inline tvec2 operator+(T l, const tvec2& r) { return{ l + r.x, l + r.y }; } +template inline tvec2 operator-(T l, const tvec2& r) { return{ l - r.x, l - r.y }; } +template inline tvec2 operator*(T l, const tvec2& r) { return{ l * r.x, l * r.y }; } +template inline tvec2 operator/(T l, const tvec2& r) { return{ l / r.x, l / r.y }; } +template inline tvec2 operator+(const tvec2& l, T r) { return{ l.x + r, l.y + r }; } +template inline tvec2 operator-(const tvec2& l, T r) { return{ l.x - r, l.y - r }; } +template inline tvec2 operator*(const tvec2& l, T r) { return{ l.x * r, l.y * r }; } +template inline tvec2 operator/(const tvec2& l, T r) { return{ l.x / r, l.y / r }; } +template inline tvec2& operator+=(tvec2& l, const tvec2& r) { l.x += r.x; l.y += r.y; return l; } +template inline tvec2& operator-=(tvec2& l, const tvec2& r) { l.x -= r.x; l.y -= r.y; return l; } +template inline tvec2& operator*=(tvec2& l, const tvec2& r) { l.x *= r.x; l.y *= r.y; return l; } +template inline tvec2& operator/=(tvec2& l, const tvec2& r) { l.x /= r.x; l.y /= r.y; return l; } +template inline tvec2& operator+=(tvec2& l, T r) { l.x += r; l.y += r; return l; } +template inline tvec2& operator-=(tvec2& l, T r) { l.x -= r; l.y -= r; return l; } +template inline tvec2& operator*=(tvec2& l, T r) { l.x *= r; l.y *= r; return l; } +template inline tvec2& operator/=(tvec2& l, T r) { l.x /= r; l.y /= r; return l; } + +template inline tvec3 operator-(const tvec3& v) { return{ -v.x, -v.y, -v.z }; } +template inline tvec3 operator+(const tvec3& l, const tvec3& r) { return{ l.x + r.x, l.y + r.y, l.z + r.z }; } +template inline tvec3 operator-(const tvec3& l, const tvec3& r) { return{ l.x - r.x, l.y - r.y, l.z - r.z }; } +template inline tvec3 operator*(const tvec3& l, const tvec3& r) { return{ l.x * r.x, l.y * r.y, l.z * r.z }; } +template inline tvec3 operator/(const tvec3& l, const tvec3& r) { return{ l.x / r.x, l.y / r.y, l.z / r.z }; } +template inline tvec3 operator+(T l, const tvec3& r) { return{ l + r.x, l + r.y, l + r.z }; } +template inline tvec3 operator-(T l, const tvec3& r) { return{ l - r.x, l - r.y, l - r.z }; } +template inline tvec3 operator*(T l, const tvec3& r) { return{ l * r.x, l * r.y, l * r.z }; } +template inline tvec3 operator/(T l, const tvec3& r) { return{ l / r.x, l / r.y, l / r.z }; } +template inline tvec3 operator+(const tvec3& l, T r) { return{ l.x + r, l.y + r, l.z + r }; } +template inline tvec3 operator-(const tvec3& l, T r) { return{ l.x - r, l.y - r, l.z - r }; } +template inline tvec3 operator*(const tvec3& l, T r) { return{ l.x * r, l.y * r, l.z * r }; } +template inline tvec3 operator/(const tvec3& l, T r) { return{ l.x / r, l.y / r, l.z / r }; } +template inline tvec3& operator+=(tvec3& l, const tvec3& r) { l.x += r.x; l.y += r.y; l.z += r.z; return l; } +template inline tvec3& operator-=(tvec3& l, const tvec3& r) { l.x -= r.x; l.y -= r.y; l.z -= r.z; return l; } +template inline tvec3& operator*=(tvec3& l, const tvec3& r) { l.x *= r.x; l.y *= r.y; l.z *= r.z; return l; } +template inline tvec3& operator/=(tvec3& l, const tvec3& r) { l.x /= r.x; l.y /= r.y; l.z /= r.z; return l; } +template inline tvec3& operator+=(tvec3& l, T r) { l.x += r; l.y += r; l.z += r; return l; } +template inline tvec3& operator-=(tvec3& l, T r) { l.x -= r; l.y -= r; l.z -= r; return l; } +template inline tvec3& operator*=(tvec3& l, T r) { l.x *= r; l.y *= r; l.z *= r; return l; } +template inline tvec3& operator/=(tvec3& l, T r) { l.x /= r; l.y /= r; l.z /= r; return l; } + +template inline tvec4 operator-(const tvec4& v) { return{ -v.x, -v.y, -v.z, -v.w }; } +template inline tvec4 operator+(const tvec4& l, const tvec4& r) { return{ l.x + r.x, l.y + r.y, l.z + r.z, l.w + r.w }; } +template inline tvec4 operator-(const tvec4& l, const tvec4& r) { return{ l.x - r.x, l.y - r.y, l.z - r.z, l.w - r.w }; } +template inline tvec4 operator*(const tvec4& l, const tvec4& r) { return{ l.x * r.x, l.y * r.y, l.z * r.z, l.w * r.w }; } +template inline tvec4 operator/(const tvec4& l, const tvec4& r) { return{ l.x / r.x, l.y / r.y, l.z / r.z, l.w / r.w }; } +template inline tvec4 operator+(T l, const tvec4& r) { return{ l + r.x, l + r.y, l + r.z, l + r.w }; } +template inline tvec4 operator-(T l, const tvec4& r) { return{ l - r.x, l - r.y, l - r.z, l - r.w }; } +template inline tvec4 operator*(T l, const tvec4& r) { return{ l * r.x, l * r.y, l * r.z, l * r.w }; } +template inline tvec4 operator/(T l, const tvec4& r) { return{ l / r.x, l / r.y, l / r.z, l / r.w }; } +template inline tvec4 operator+(const tvec4& l, T r) { return{ l.x + r, l.y + r, l.z + r, l.w + r }; } +template inline tvec4 operator-(const tvec4& l, T r) { return{ l.x - r, l.y - r, l.z - r, l.w - r }; } +template inline tvec4 operator*(const tvec4& l, T r) { return{ l.x * r, l.y * r, l.z * r, l.w * r }; } +template inline tvec4 operator/(const tvec4& l, T r) { return{ l.x / r, l.y / r, l.z / r, l.w / r }; } +template inline tvec4& operator+=(tvec4& l, const tvec4& r) { l.x += r.x; l.y += r.y; l.z += r.z; l.w += r.w; return l; } +template inline tvec4& operator-=(tvec4& l, const tvec4& r) { l.x -= r.x; l.y -= r.y; l.z -= r.z; l.w -= r.w; return l; } +template inline tvec4& operator*=(tvec4& l, const tvec4& r) { l.x *= r.x; l.y *= r.y; l.z *= r.z; l.w *= r.w; return l; } +template inline tvec4& operator/=(tvec4& l, const tvec4& r) { l.x /= r.x; l.y /= r.y; l.z /= r.z; l.w /= r.w; return l; } +template inline tvec4& operator+=(tvec4& l, T r) { l.x += r; l.y += r; l.z += r; l.w += r; return l; } +template inline tvec4& operator-=(tvec4& l, T r) { l.x -= r; l.y -= r; l.z -= r; l.w -= r; return l; } +template inline tvec4& operator*=(tvec4& l, T r) { l.x *= r; l.y *= r; l.z *= r; l.w *= r; return l; } +template inline tvec4& operator/=(tvec4& l, T r) { l.x /= r; l.y /= r; l.z /= r; l.w /= r; return l; } + +template inline tquat operator*(const tquat& l, T r) { return{ l.x * r, l.y * r, l.z * r, l.w * r }; } +template inline tquat operator*(const tquat& l, const tquat& r) +{ + return{ + l.w * r.x + l.x * r.w + l.y * r.z - l.z * r.y, + l.w * r.y + l.y * r.w + l.z * r.x - l.x * r.z, + l.w * r.z + l.z * r.w + l.x * r.y - l.y * r.x, + l.w * r.w - l.x * r.x - l.y * r.y - l.z * r.z, + }; +} +template inline tquat& operator*=(tquat& l, T r) +{ + l = l * r; + return l; +} +template inline tquat& operator*=(tquat& l, const tquat& r) +{ + l = l * r; + return l; +} + +template inline T dot(const tvec2& l, const tvec2& r) { return l.x * r.x + l.y * r.y; } +template inline T dot(const tvec3& l, const tvec3& r) { return l.x * r.x + l.y * r.y + l.z * r.z; } +template inline T dot(const tvec4& l, const tvec4& r) { return l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w; } +template inline T dot(const tquat& l, const tquat& r) { return dot((const tvec4&)l, (const tvec4&)r); } +template inline T length_sq(const tvec2& v) { return dot(v, v); } +template inline T length_sq(const tvec3& v) { return dot(v, v); } +template inline T length_sq(const tvec4& v) { return dot(v, v); } +template inline T length(const tvec2& v) { return sqrt(length_sq(v)); } +template inline T length(const tvec3& v) { return sqrt(length_sq(v)); } +template inline T length(const tvec4& v) { return sqrt(length_sq(v)); } +template inline tvec2 normalize(const tvec2& v) { return v / length(v); } +template inline tvec3 normalize(const tvec3& v) { return v / length(v); } +template inline tvec4 normalize(const tvec4& v) { return v / length(v); } +template inline tquat normalize(const tquat& v) +{ + auto r = normalize((const tvec4&)v); + return (const tquat&)r; +} +template inline tvec3 cross(const tvec3& l, const tvec3& r) +{ + return{ + l.y * r.z - l.z * r.y, + l.z * r.x - l.x * r.z, + l.x * r.y - l.y * r.x }; +} + +template inline T sum(const tvec2& v) { return v.x + v.y; } +template inline T sum(const tvec3& v) { return v.x + v.y + v.z; } +template inline T sum(const tvec4& v) { return v.x + v.y + v.z + v.w; } + +template inline tquat rotate_x(T angle) +{ + T c = std::cos(angle * T(0.5)); + T s = std::sin(angle * T(0.5)); + return{ s, T(0.0), T(0.0), c }; +} +template inline tquat rotate_y(T angle) +{ + T c = std::cos(angle * T(0.5)); + T s = std::sin(angle * T(0.5)); + return{ T(0.0), s, T(0.0), c }; +} +template inline tquat rotate_z(T angle) +{ + T c = std::cos(angle * T(0.5)); + T s = std::sin(angle * T(0.5)); + return{ T(0.0), T(0.0), s, c }; +} + +template inline tmat4x4 rotate44_x(T angle) +{ + T c = std::cos(angle); + T s = std::sin(angle); + return tmat4x4 { { + { 1, 0, 0, 0 }, + { 0, c,-s, 0 }, + { 0, s, c, 0 }, + { 0, 0, 0, 1 }, + }}; +} +template inline tmat4x4 rotate44_y(T angle) +{ + T c = std::cos(angle); + T s = std::sin(angle); + return tmat4x4 { { + { c, 0, s, 0 }, + { 0, 1, 0, 0 }, + {-s, 0, c, 0 }, + { 0, 0, 0, 1 }, + }}; +} +template inline tmat4x4 rotate44_z(T angle) +{ + T c = std::cos(angle); + T s = std::sin(angle); + return tmat4x4 { { + { c,-s, 0, 0 }, + { s, c, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, + }}; +} + +template inline tquat rotate_euler(RotationOrder order, const tvec3& euler) +{ + auto rx = rotate_x(euler.x); + auto ry = rotate_y(euler.y); + auto rz = rotate_z(euler.z); + switch (order) + { + case RotationOrder::XYZ: return rz * ry * rx; + case RotationOrder::XZY: return ry * rz * rx; + case RotationOrder::YZX: return rx * rz * ry; + case RotationOrder::YXZ: return rz * rx * ry; + case RotationOrder::ZXY: return ry * rx * rz; + case RotationOrder::ZYX: return rx * ry * rz; + case RotationOrder::SphericXYZ: return rz * ry * rx; // todo + default: return tquat::identity(); + } +} + +template inline tmat4x4 to_mat4x4(const tquat& v) +{ + return tmat4x4{{ + {T(1.0)-T(2.0)*v.y*v.y - T(2.0)*v.z*v.z,T(2.0)*v.x*v.y - T(2.0)*v.z*v.w, T(2.0)*v.x*v.z + T(2.0)*v.y*v.w, T(0.0)}, + {T(2.0)*v.x*v.y + T(2.0)*v.z*v.w, T(1.0) - T(2.0)*v.x*v.x - T(2.0)*v.z*v.z,T(2.0)*v.y*v.z - T(2.0)*v.x*v.w, T(0.0)}, + {T(2.0)*v.x*v.z - T(2.0)*v.y*v.w, T(2.0)*v.y*v.z + T(2.0)*v.x*v.w, T(1.0) - T(2.0)*v.x*v.x - T(2.0)*v.y*v.y,T(0.0)}, + {T(0.0), T(0.0), T(0.0), T(1.0)} + }}; +} + +template inline tmat4x4 translate(const tvec3& v) +{ + return tmat4x4{{ + {T(1.0), T(0.0), T(0.0), T(0.0)}, + {T(0.0), T(1.0), T(0.0), T(0.0)}, + {T(0.0), T(0.0), T(1.0), T(0.0)}, + { v.x, v.y, v.z, T(1.0)} + }}; +} + +template inline tmat4x4 scale44(const tvec3& v) +{ + return tmat4x4{{ + { v.x, T(0.0), T(0.0), T(0.0)}, + {T(0.0), v.y, T(0.0), T(0.0)}, + {T(0.0), T(0.0), v.z, T(0.0)}, + {T(0.0), T(0.0), T(0.0), T(1.0)}, + }}; +} + +template inline tmat4x4 transform(const tvec3& t, const tquat& r, const tvec3& s) +{ + auto ret = scale44(s); + ret *= to_mat4x4(r); + (tvec3&)ret[3] = t; + return ret; +} + +template inline tmat4x4 transpose(const tmat4x4& v) +{ + return tmat4x4{{ + {v[0][0], v[1][0], v[2][0], v[3][0]}, + {v[0][1], v[1][1], v[2][1], v[3][1]}, + {v[0][2], v[1][2], v[2][2], v[3][2]}, + {v[0][3], v[1][3], v[2][3], v[3][3]}, + }}; +} + +template inline tmat4x4 operator*(const tmat4x4& a, const tmat4x4& b_) +{ + const auto b = transpose(b_); + return tmat4x4{{ + { sum(a[0] * b[0]), sum(a[0] * b[1]), sum(a[0] * b[2]), sum(a[0] * b[3]) }, + { sum(a[1] * b[0]), sum(a[1] * b[1]), sum(a[1] * b[2]), sum(a[1] * b[3]) }, + { sum(a[2] * b[0]), sum(a[2] * b[1]), sum(a[2] * b[2]), sum(a[2] * b[3]) }, + { sum(a[3] * b[0]), sum(a[3] * b[1]), sum(a[3] * b[2]), sum(a[3] * b[3]) }, + }}; +} +template inline tmat4x4& operator*=(tmat4x4& a, const tmat4x4& b) +{ + a = a * b; + return a; +} + +template +inline static tvec3 mul_v(const tmat4x4& m, const tvec3& v) +{ + return { + m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2], + m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2], + m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2], + }; +} + +template +inline static tvec3 mul_p(const tmat4x4& m, const tvec3& v) +{ + return { + m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0], + m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1], + m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2], + }; +} + +template inline tmat4x4 invert(const tmat4x4& x) +{ + tmat4x4 s = { + x[1][1] * x[2][2] - x[2][1] * x[1][2], + x[2][1] * x[0][2] - x[0][1] * x[2][2], + x[0][1] * x[1][2] - x[1][1] * x[0][2], + 0, + + x[2][0] * x[1][2] - x[1][0] * x[2][2], + x[0][0] * x[2][2] - x[2][0] * x[0][2], + x[1][0] * x[0][2] - x[0][0] * x[1][2], + 0, + + x[1][0] * x[2][1] - x[2][0] * x[1][1], + x[2][0] * x[0][1] - x[0][0] * x[2][1], + x[0][0] * x[1][1] - x[1][0] * x[0][1], + 0, + + 0, 0, 0, 1, + }; + + auto r = x[0][0] * s[0][0] + x[0][1] * s[1][0] + x[0][2] * s[2][0]; + + if (abs(r) >= 1) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + s[i][j] /= r; + } + } + } + else { + auto mr = abs(r) / std::numeric_limits::min(); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (mr > abs(s[i][j])) { + s[i][j] /= r; + } + else { + // error + return tmat4x4::identity(); + } + } + } + } + + s[3][0] = -x[3][0] * s[0][0] - x[3][1] * s[1][0] - x[3][2] * s[2][0]; + s[3][1] = -x[3][0] * s[0][1] - x[3][1] * s[1][1] - x[3][2] * s[2][1]; + s[3][2] = -x[3][0] * s[0][2] - x[3][1] * s[1][2] - x[3][2] * s[2][2]; + return s; +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxMeta.h b/src/SmallFBX/sfbxMeta.h new file mode 100644 index 0000000..c7afb71 --- /dev/null +++ b/src/SmallFBX/sfbxMeta.h @@ -0,0 +1,32 @@ +#pragma once +#include + +#define sfbxRestrict(...) std::enable_if_t<__VA_ARGS__, bool> = true + +namespace sfbx { + +// is_contiguous_container : true if T has data() and size(). intended to detect std::vector, sfbx::RawVector and sfbx::span. +template +inline constexpr bool is_contiguous_container = false; +template +inline constexpr bool is_contiguous_container().data()), decltype(std::declval().size())>> = true; + + +template +inline constexpr bool has_value_type = false; +template +inline constexpr bool has_value_type> = true; + +template)> inline typename T::value_type get_value_type_impl(const T&); +template)> inline T get_value_type_impl(const T&); +template)> inline std::remove_pointer_t get_value_type_impl(T); +template && !std::is_pointer_v)> inline T get_value_type_impl(T); +template using get_value_type = decltype(get_value_type_impl(std::declval())); + + +template +inline constexpr bool has_resize = false; +template +inline constexpr bool has_resize().resize(0))>> = true; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxModel.cpp b/src/SmallFBX/sfbxModel.cpp new file mode 100644 index 0000000..13e5ae7 --- /dev/null +++ b/src/SmallFBX/sfbxModel.cpp @@ -0,0 +1,404 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxModel.h" +#include "sfbxGeometry.h" +#include "sfbxMaterial.h" + +namespace sfbx { + +ObjectClass NodeAttribute::getClass() const +{ + return ObjectClass::NodeAttribute; +} + +ObjectSubClass NullAttribute::getSubClass() const { return ObjectSubClass::Null; } + +void NullAttribute::exportFBXObjects() +{ + super::exportFBXObjects(); + getNode()->createChild(sfbxS_TypeFlags, sfbxS_Null); +} + +ObjectSubClass RootAttribute::getSubClass() const { return ObjectSubClass::Root; } + +void RootAttribute::exportFBXObjects() +{ + super::exportFBXObjects(); + getNode()->createChild(sfbxS_TypeFlags, sfbxS_Null, sfbxS_Skeleton, sfbxS_Root); +} + +ObjectSubClass LimbNodeAttribute::getSubClass() const { return ObjectSubClass::LimbNode; } + +void LimbNodeAttribute::exportFBXObjects() +{ + super::exportFBXObjects(); + getNode()->createChild(sfbxS_TypeFlags, sfbxS_Skeleton); +} + +ObjectSubClass LightAttribute::getSubClass() const { return ObjectSubClass::Light; } + +void LightAttribute::exportFBXObjects() +{ + super::exportFBXObjects(); + // todo +} + +ObjectSubClass CameraAttribute::getSubClass() const { return ObjectSubClass::Camera; } + +void CameraAttribute::exportFBXObjects() +{ + super::exportFBXObjects(); + // todo +} + + + +ObjectClass Model::getClass() const { return ObjectClass::Model; } + +void Model::importFBXObjects() +{ + super::importFBXObjects(); + auto n = getNode(); + if (!n) + return; + + EnumerateProperties(n, [this](Node* p) { + auto get_int = [p]() -> int { + if (GetPropertyCount(p) == 5) + return GetPropertyValue(p, 4); +#ifdef sfbxEnableLegacyFormatSupport + else if (GetPropertyCount(p) == 4) { + return GetPropertyValue(p, 3); + } +#endif + return 0; + }; + + auto get_float3 = [p]() -> float3 { + if (GetPropertyCount(p) == 7) { + return float3{ + (float)GetPropertyValue(p, 4), + (float)GetPropertyValue(p, 5), + (float)GetPropertyValue(p, 6), + }; + } +#ifdef sfbxEnableLegacyFormatSupport + else if (GetPropertyCount(p) == 6) { + return float3{ + (float)GetPropertyValue(p, 3), + (float)GetPropertyValue(p, 4), + (float)GetPropertyValue(p, 5), + }; + } +#endif + return {}; + }; + + auto pname = GetPropertyString(p); + if (pname == sfbxS_Visibility) { + m_visibility = GetPropertyValue(p, 4); + } + else if (pname == sfbxS_LclTranslation) + m_position = get_float3(); + else if (pname == sfbxS_RotationOrder) + m_rotation_order = (RotationOrder)get_int(); + else if (pname == sfbxS_PreRotation) + m_pre_rotation = get_float3(); + else if (pname == sfbxS_PostRotation) + m_post_rotation = get_float3(); + else if (pname == sfbxS_LclRotation) + m_rotation = get_float3(); + else if (pname == sfbxS_LclScale) + m_scale = get_float3(); + }); +} + +#define sfbxVector3d(V) (float64)V.x, (float64)V.y, (float64)V.z + +void Model::exportFBXObjects() +{ + super::exportFBXObjects(); + auto n = getNode(); + if (!n) + return; + + // version + n->createChild(sfbxS_Version, sfbxI_ModelVersion); + + auto properties = n->createChild(sfbxS_Properties70); + + // attribute + properties->createChild(sfbxS_P, "DefaultAttributeIndex", "int", "Integer", "", 0); + + // position + if (m_position != float3::zero()) + properties->createChild(sfbxS_P, + sfbxS_LclTranslation, sfbxS_LclTranslation, sfbxS_Empty, sfbxS_A, sfbxVector3d(m_position)); + + // rotation + if (m_pre_rotation != float3::zero() || m_post_rotation != float3::zero() || m_rotation != float3::zero()) { + // rotation active + properties->createChild(sfbxS_P, + sfbxS_RotationActive, sfbxS_bool, sfbxS_Empty, sfbxS_Empty, (int32)1); + // rotation order + if (m_rotation_order != RotationOrder::XYZ) + properties->createChild(sfbxS_P, + sfbxS_RotationOrder, sfbxS_RotationOrder, sfbxS_Empty, sfbxS_A, (int32)m_rotation_order); + // pre-rotation + if (m_pre_rotation != float3::zero()) + properties->createChild(sfbxS_P, + sfbxS_PreRotation, sfbxS_Vector3D, sfbxS_Vector, sfbxS_Empty, sfbxVector3d(m_pre_rotation)); + // post-rotation + if (m_post_rotation != float3::zero()) + properties->createChild(sfbxS_P, + sfbxS_PostRotation, sfbxS_Vector3D, sfbxS_Vector, sfbxS_Empty, sfbxVector3d(m_post_rotation)); + // rotation + if (m_rotation != float3::zero()) + properties->createChild(sfbxS_P, + sfbxS_LclRotation, sfbxS_LclRotation, sfbxS_Empty, sfbxS_A, sfbxVector3d(m_rotation)); + } + + // scale + if (m_scale!= float3::one()) + properties->createChild(sfbxS_P, + sfbxS_LclScale, sfbxS_LclScale, sfbxS_Empty, sfbxS_A, sfbxVector3d(m_scale)); +} + +void Model::addChild(Object* v) +{ + super::addChild(v); + if (auto model = as(v)) + m_child_models.push_back(model); +} + +void Model::eraseChild(Object* v) +{ + super::eraseChild(v); + if (auto model = as(v)) + erase(m_child_models, model); +} + +void Model::addParent(Object* v) +{ + super::addParent(v); + if (auto model = as(v)) + m_parent_model = model; +} + +void Model::eraseParent(Object* v) +{ + super::eraseParent(v); + if (v == m_parent_model) + m_parent_model = nullptr; +} + +Model* Model::getParentModel() const { return m_parent_model; } + +bool Model::getVisibility() const { return m_visibility; } +RotationOrder Model::getRotationOrder() const { return m_rotation_order; } +float3 Model::getPosition() const { return m_position; } + +float3 Model::getPreRotation() const { return m_pre_rotation; } +float3 Model::getRotation() const { return m_rotation; } +float3 Model::getPostRotation() const { return m_post_rotation; } +float3 Model::getScale() const { return m_scale; } + +void Model::updateMatrices() const +{ + if (m_matrix_dirty) { + // scale + float4x4 r = scale44(m_scale); + + // rotation + if (m_post_rotation != float3::zero()) + r *= transpose(to_mat4x4(rotate_euler(m_rotation_order, m_post_rotation * DegToRad))); + if (m_rotation != float3::zero()) + r *= transpose(to_mat4x4(rotate_euler(m_rotation_order, m_rotation * DegToRad))); + if (m_pre_rotation != float3::zero()) + r *= transpose(to_mat4x4(rotate_euler(m_rotation_order, m_pre_rotation * DegToRad))); + + // translation + (float3&)r[3] = m_position; + + m_matrix_local = r; + m_matrix_global = m_matrix_local; + if (m_parent_model) + m_matrix_global *= m_parent_model->getGlobalMatrix(); + + m_matrix_dirty = false; + } +} + +float4x4 Model::getLocalMatrix() const +{ + updateMatrices(); + return m_matrix_local; +} + +float4x4 Model::getGlobalMatrix() const +{ + updateMatrices(); + return m_matrix_global; +} + +void Model::setVisibility(bool v) { m_visibility = v; } +void Model::setRotationOrder(RotationOrder v) { m_rotation_order = v; } + +void Model::propagateDirty() +{ + // note: + // this is needlessly slow on huge joint structure + animations. + // we need to separate update transform phase and propagate dirty phase for optimal animation playback. + m_matrix_dirty = true; + for (auto c : m_child_models) + c->propagateDirty(); +} + +#define MarkDirty(V, A) if (A != V) { V = A; propagateDirty(); } +void Model::setPosition(float3 v) { MarkDirty(m_position, v); } +void Model::setPreRotation(float3 v) { MarkDirty(m_pre_rotation, v); } +void Model::setRotation(float3 v) { MarkDirty(m_rotation, v); } +void Model::setPostRotation(float3 v) { MarkDirty(m_post_rotation, v); } +void Model::setScale(float3 v) { MarkDirty(m_scale, v); } +#undef MarkDirty + +ObjectSubClass Null::getSubClass() const { return ObjectSubClass::Null; } + +void Null::exportFBXObjects() +{ + if (!m_attr) + m_attr = createChild(); + super::exportFBXObjects(); +} + +void Null::addChild(Object* v) +{ + super::addChild(v); + if (auto attr = as(v)) + m_attr = attr; +} + + +ObjectSubClass Root::getSubClass() const { return ObjectSubClass::Root; } + +void Root::exportFBXObjects() +{ + if (!m_attr) + m_attr = createChild(); + super::exportFBXObjects(); +} + +void Root::addChild(Object* v) +{ + super::addChild(v); + if (auto attr = as(v)) + m_attr = attr; +} + + +ObjectSubClass LimbNode::getSubClass() const { return ObjectSubClass::LimbNode; } + +void LimbNode::exportFBXObjects() +{ + if (!m_attr) + m_attr = createChild(); + super::exportFBXObjects(); +} + + +void LimbNode::addChild(Object* v) +{ + super::addChild(v); + if (auto attr = as(v)) + m_attr = attr; +} + + +ObjectSubClass Mesh::getSubClass() const { return ObjectSubClass::Mesh; } + +void Mesh::importFBXObjects() +{ + super::importFBXObjects(); + +#ifdef sfbxEnableLegacyFormatSupport + // in old fbx, Model::Mesh has geometry data + auto n = getNode(); + if (n->findChild(sfbxS_Vertices)) { + getGeometry()->setNode(n); + } +#endif +} + +void Mesh::addChild(Object* v) +{ + super::addChild(v); + if (auto geom = as(v)) + m_geom = geom; + else if (auto material = as(v)) + m_materials.push_back(material); +} + +GeomMesh* Mesh::getGeometry() +{ + if (!m_geom) + m_geom = createChild(getName()); + return m_geom; +} + +span Mesh::getMaterials() const +{ + return make_span(m_materials); +} + + +ObjectSubClass Light::getSubClass() const { return ObjectSubClass::Light; } + +void Light::importFBXObjects() +{ + super::importFBXObjects(); + auto n = getNode(); + // todo +} + +void Light::exportFBXObjects() +{ + if (!m_attr) + m_attr = createChild(); + super::exportFBXObjects(); + + // todo +} + +void Light::addChild(Object* v) +{ + super::addChild(v); + if (auto attr = as(v)) + m_attr = attr; +} + + +ObjectSubClass Camera::getSubClass() const { return ObjectSubClass::Camera; } + +void Camera::importFBXObjects() +{ + super::importFBXObjects(); + auto n = getNode(); + // todo +} + +void Camera::exportFBXObjects() +{ + if (!m_attr) + m_attr = createChild(); + super::exportFBXObjects(); + // todo +} + +void Camera::addChild(Object* v) +{ + super::addChild(v); + if (auto attr = as(v)) + m_attr = attr; +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxModel.h b/src/SmallFBX/sfbxModel.h new file mode 100644 index 0000000..cca34ef --- /dev/null +++ b/src/SmallFBX/sfbxModel.h @@ -0,0 +1,207 @@ +#pragma once +#include "sfbxObject.h" + +namespace sfbx { + +// NodeAttribute and its subclasses: +// (NullAttribute, RootAttribute, LimbNodeAttribute, LightAttribute, CameraAttribute) + +class NodeAttribute : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; +}; + + +class NullAttribute : public NodeAttribute +{ +using super = NodeAttribute; +public: + ObjectSubClass getSubClass() const override; + void exportFBXObjects() override; +}; + + +class RootAttribute : public NodeAttribute +{ +using super = NodeAttribute; +public: + ObjectSubClass getSubClass() const override; + void exportFBXObjects() override; +}; + + +class LimbNodeAttribute : public NodeAttribute +{ +using super = NodeAttribute; +public: + ObjectSubClass getSubClass() const override; + void exportFBXObjects() override; +}; + + +class LightAttribute : public NodeAttribute +{ +using super = NodeAttribute; +public: + ObjectSubClass getSubClass() const override; + void exportFBXObjects() override; +}; + + +class CameraAttribute : public NodeAttribute +{ +using super = NodeAttribute; +public: + ObjectSubClass getSubClass() const override; + void exportFBXObjects() override; +}; + + + +// Model and its subclasses: +// (Null, Root, LimbNode, Light, Camera) + +class Model : public Object +{ +using super = Object; +public: + ObjectClass getClass() const override; + void addChild(Object* v) override; + void eraseChild(Object* v) override; + + Model* getParentModel() const; + + bool getVisibility() const; + RotationOrder getRotationOrder() const; + float3 getPosition() const; + float3 getPreRotation() const; + float3 getRotation() const; + float3 getPostRotation() const; + float3 getScale() const; + float4x4 getLocalMatrix() const; + float4x4 getGlobalMatrix() const; + + void setVisibility(bool v); + void setRotationOrder(RotationOrder v); + void setPosition(float3 v); + void setPreRotation(float3 v); + void setRotation(float3 v); + void setPostRotation(float3 v); + void setScale(float3 v); + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + void addParent(Object* v) override; + void eraseParent(Object* v) override; + void propagateDirty(); + void updateMatrices() const; + + Model* m_parent_model{}; + std::vector m_child_models; + + bool m_visibility = true; + RotationOrder m_rotation_order = RotationOrder::XYZ; + float3 m_position{}; + float3 m_pre_rotation{}; + float3 m_rotation{}; + float3 m_post_rotation{}; + float3 m_scale = float3::one(); + + mutable bool m_matrix_dirty = true; + mutable float4x4 m_matrix_local = float4x4::identity(); + mutable float4x4 m_matrix_global = float4x4::identity(); +}; + + +class Null : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + +protected: + void exportFBXObjects() override; + + NullAttribute* m_attr{}; +}; + +class Root : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + +protected: + void exportFBXObjects() override; + + RootAttribute* m_attr{}; +}; + + +class LimbNode : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + +protected: + void exportFBXObjects() override; + + LimbNodeAttribute* m_attr{}; +}; + + +class Mesh : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + + GeomMesh* getGeometry(); + span getMaterials() const; + +protected: + void importFBXObjects() override; + + GeomMesh* m_geom{}; + std::vector m_materials; +}; + + +class Light : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void addChild(Object* v) override; + +protected: + void importFBXObjects() override; + void exportFBXObjects() override; + + LightAttribute* m_attr{}; +}; + + +class Camera : public Model +{ +using super = Model; +public: + ObjectSubClass getSubClass() const override; + void importFBXObjects() override; + void exportFBXObjects() override; + void addChild(Object* v) override; + +protected: + CameraAttribute* m_attr{}; +}; + + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxNode.cpp b/src/SmallFBX/sfbxNode.cpp new file mode 100644 index 0000000..5f65700 --- /dev/null +++ b/src/SmallFBX/sfbxNode.cpp @@ -0,0 +1,265 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxNode.h" +#include "sfbxDocument.h" + +namespace sfbx { + +Node::Node() +{ +} + +uint64_t Node::read(std::istream& is, uint64_t start_offset) +{ + uint64_t ret = 0; + + uint64_t end_offset, num_props, prop_size; + if (getDocumentVersion() >= sfbxI_FBX2016_FileVersion) { + // size records are 64bit since FBX 2016 + end_offset = read1(is); + num_props = read1(is); + prop_size = read1(is); + ret += 24; + } + else { + end_offset = read1(is); + num_props = read1(is); + prop_size = read1(is); + ret += 12; + } + + uint8_t name_len = read1(is); + readv(is, m_name, name_len); + ret += 1; + ret += name_len; + + reserveProperties(num_props); + for (uint32_t i = 0; i < num_props; i++) + createProperty()->read(is); + ret += prop_size; + + while (start_offset + ret < end_offset) { + auto child = createChild(); + ret += child->read(is, start_offset + ret); + if (child->isNull()) + eraseChild(child); + } + return ret; +} + +uint64_t Node::write(std::ostream& os, uint64_t start_offset) +{ + uint32_t header_size = getHeaderSize() + m_name.size(); + if (isNull()) { + for (uint32_t i = 0; i < header_size; i++) + writev(os, (uint8_t)0); + return header_size; + } + + Node null_node; + null_node.m_document = m_document; + + uint64_t property_size = 0; + uint64_t children_size = 0; + bool null_terminate = !m_children.empty() || m_properties.empty(); + { + CounterStream cs; + for (auto& prop : m_properties) + prop.write(cs); + property_size = cs.size(); + } + { + CounterStream cs; + for (auto child : m_children) + child->write(cs, 0); + if (null_terminate) + null_node.write(cs, 0); + children_size = cs.size(); + } + + uint64_t end_offset = start_offset + header_size + property_size + children_size; + if (getDocumentVersion() >= sfbxI_FBX2016_FileVersion) { + // size records are 64bit since FBX 2016 + writev(os, uint64_t(end_offset)); + writev(os, uint64_t(m_properties.size())); + writev(os, uint64_t(property_size)); + } + else { + writev(os, uint32_t(end_offset)); + writev(os, uint32_t(m_properties.size())); + writev(os, uint32_t(property_size)); + } + writev(os, uint8_t(m_name.size())); + writev(os, m_name); + + for (auto& prop : m_properties) + prop.write(os); + + uint64_t pos = header_size + property_size; + for (auto child : m_children) + pos += child->write(os, start_offset + pos); + if (null_terminate) + pos += null_node.write(os, start_offset + pos); + return pos; +} + +bool Node::isNull() const +{ + return m_name.empty() && m_children.empty() && m_properties.empty(); +} + +bool Node::isRoot() const +{ + return m_parent == nullptr; +} + +void Node::setName(string_view v) +{ + m_name = v; +} + +void Node::reserveProperties(size_t v) +{ + m_properties.reserve(v); +} + +Property* Node::createProperty() +{ + m_properties.emplace_back(); + return &m_properties.back(); +} + +Node* Node::createChild(string_view name) +{ + auto p = m_document->createChildNode(name); + m_children.push_back(p); + p->m_parent = this; + return p; +} + +void Node::eraseChild(Node* n) +{ + m_document->eraseNode(n); + + auto it = std::find(m_children.begin(), m_children.end(), n); + if (it != m_children.end()) + m_children.erase(it); +} + +string_view Node::getName() const +{ + return m_name; +} + +span Node::getProperties() const +{ + return make_span(m_properties); +} + +Property* Node::getProperty(size_t i) +{ + if (i < m_properties.size()) + return &m_properties[i]; + return nullptr; +} + +#ifdef sfbxEnableLegacyFormatSupport + +template +static inline void ToArray(span props, RawVector& dst) +{ + dst.resize(props.size() / get_vector_size); + auto* d = (get_scalar_t*)dst.data(); + for (auto& prop : props) + *d++ = prop.getValue>(); +} + +#define Def(S, D)\ + template<> void Node::getPropertiesValues(RawVector& dst) const { ToArray(getProperties(), dst); } + +Def(int32, int32); +Def(int64, int64); +Def(float32, float32); +Def(float64, float32); +Def(double2, float2); +Def(double3, float3); +Def(double4, float4); +#undef Def + + +template +void ToVector(span props, D& dst) +{ + if (props.size() == get_vector_size) { + auto* d = dst.data(); + for (auto& prop : props) + *d++ = prop.getValue>(); + } +} +template<> void Node::getPropertiesValues(float4x4& dst) const { ToVector(getProperties(), dst); } + +#endif + + +Node* Node::getParent() const +{ + return m_parent; +} + +span Node::getChildren() const +{ + return make_span(m_children); +} + +Node* Node::getChild(size_t i) const +{ + return i < m_children.size() ? m_children[i] : nullptr; +} + +Node* Node::findChild(string_view name) const +{ + auto it = std::find_if(m_children.begin(), m_children.end(), + [name](Node* p) { return p->getName() == name; }); + return it != m_children.end() ? *it : nullptr; +} + +std::string Node::toString(int depth) const +{ + std::string s; + AddTabs(s, depth); + s += getName(); + s += ": "; + join(s, m_properties, ", ", + [depth](auto& p) { return p.toString(depth); }); + s += " "; + + if (!m_children.empty() || (m_children.empty() && m_properties.empty())) { + s += "{\n"; + for (auto* c : m_children) + s += c->toString(depth + 1); + AddTabs(s, depth); + s += "}"; + } + s += "\n"; + + return s; +} + +uint32_t Node::getDocumentVersion() const +{ + return (uint32_t)m_document->getVersion(); +} + +uint32_t Node::getHeaderSize() const +{ + if (getDocumentVersion() >= sfbxI_FBX2016_FileVersion) { + // sizeof(uint64_t) * 3 + 1 + return 25; + } + else { + // sizeof(uint32_t) * 3 + 1 + return 13; + } +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxNode.h b/src/SmallFBX/sfbxNode.h new file mode 100644 index 0000000..9b805b9 --- /dev/null +++ b/src/SmallFBX/sfbxNode.h @@ -0,0 +1,61 @@ +#pragma once +#include "sfbxProperty.h" + +namespace sfbx { + +class Node +{ +friend class Document; +public: + Node(); + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + + uint64_t read(std::istream &input, uint64_t start_offset); + uint64_t write(std::ostream &output, uint64_t start_offset); + bool isNull() const; + bool isRoot() const; + + void setName(string_view v); + + void reserveProperties(size_t v); + Property* createProperty(); + Node* createChild(string_view name = {}); + void eraseChild(Node* n); + + // utils + template void addProperty(const T& v) { createProperty()->assign(v); } + template void addProperties(T&&... v) { reserveProperties(getProperties().size() + sizeof...(T)); addProperties_(v...); } + template Node* createChild(string_view name, T&&... v) { auto r = createChild(name); r->addProperties(v...); return r; } + + + string_view getName() const; + span getProperties() const; + Property* getProperty(size_t i); + // for legacy format. there are no array types and arrays are represented as a huge list of properties. + template void getPropertiesValues(RawVector& dst) const; + template)> void getPropertiesValues(Dst& dst) const; + + Node* getParent() const; + span getChildren() const; + Node* getChild(size_t i) const; + Node* findChild(string_view name) const; + + std::string toString(int depth = 0) const; + +private: + void addProperties_() {} + template void addProperties_(T&& v, U&&... a) { addProperty(v); addProperties_(a...); } + + uint32_t getDocumentVersion() const; + uint32_t getHeaderSize() const; + + Document* m_document{}; + std::string m_name; + std::vector m_properties; + + Node* m_parent{}; + std::vector m_children; +}; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxObject.cpp b/src/SmallFBX/sfbxObject.cpp new file mode 100644 index 0000000..c3f694d --- /dev/null +++ b/src/SmallFBX/sfbxObject.cpp @@ -0,0 +1,257 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxObject.h" +#include "sfbxModel.h" +#include "sfbxGeometry.h" +#include "sfbxDeformer.h" +#include "sfbxMaterial.h" +#include "sfbxAnimation.h" +#include "sfbxDocument.h" + +namespace sfbx { + +ObjectClass GetObjectClass(string_view n) +{ + if (n.empty()) { + return ObjectClass::Unknown; + } +#define Case(T) else if (n == sfbxS_##T) { return ObjectClass::T; } + sfbxEachObjectClass(Case) +#undef Case + else { + sfbxPrint("GetFbxObjectClass(): unknown type \"%s\"\n", std::string(n).c_str()); + return ObjectClass::Unknown; + } +} +ObjectClass GetObjectClass(Node* n) +{ + return GetObjectClass(n->getName()); +} + +string_view GetObjectClassName(ObjectClass t) +{ + switch (t) { +#define Case(T) case ObjectClass::T: return sfbxS_##T; + sfbxEachObjectClass(Case) +#undef Case + default: + return ""; + } +} + + +ObjectSubClass GetObjectSubClass(string_view n) +{ + if (n.empty()) { + return ObjectSubClass::Unknown; + } +#define Case(T) else if (n == sfbxS_##T) { return ObjectSubClass::T; } + sfbxEachObjectSubClass(Case) +#undef Case + else { + sfbxPrint("GetFbxObjectSubClass(): unknown subtype \"%s\"\n", std::string(n).c_str()); + return ObjectSubClass::Unknown; + } +} + +ObjectSubClass GetObjectSubClass(Node* n) +{ + if (GetPropertyCount(n) == 3) + return GetObjectSubClass(GetPropertyString(n, 2)); +#ifdef sfbxEnableLegacyFormatSupport + else if (GetPropertyCount(n) == 2) + return GetObjectSubClass(GetPropertyString(n, 1)); +#endif + else + return ObjectSubClass::Unknown; +} + +string_view GetObjectSubClassName(ObjectSubClass t) +{ + switch (t) { +#define Case(T) case ObjectSubClass::T: return sfbxS_##T; + sfbxEachObjectSubClass(Case) +#undef Case + default: return ""; + } +} + + +static constexpr inline string_view GetInternalObjectClassName(ObjectClass t, ObjectSubClass st) +{ +#define Case1(T, ST, Ret) if (t == ObjectClass::T && st == ObjectSubClass::ST) return Ret +#define Case2(T, Ret) if (t == ObjectClass::T) return Ret + + Case1(Deformer, Cluster, sfbxS_SubDeformer); + Case1(Deformer, BlendShapeChannel, sfbxS_SubDeformer); + Case2(AnimationStack, sfbxS_AnimStack); + Case2(AnimationLayer, sfbxS_AnimLayer); + Case2(AnimationCurveNode, sfbxS_AnimCurveNode); + Case2(AnimationCurve, sfbxS_AnimCurve); + +#undef Case1 +#undef Case2 + + return GetObjectClassName(t); +} + + +std::string MakeFullName(string_view display_name, string_view class_name) +{ + std::string ret; + size_t pos = display_name.find('\0'); // ignore class name part + if (pos == std::string::npos) + ret = display_name; + else + ret.assign(display_name.data(), pos); + + ret += (char)0x00; + ret += (char)0x01; + ret += class_name; + return ret; +} + +bool IsFullName(string_view name) +{ + size_t n = name.size(); + if (n > 2) { + for (size_t i = 0; i < n - 1; ++i) + if (name[i] == 0x00 && name[i + 1] == 0x01) + return true; + } + return false; +} + +bool SplitFullName(string_view full_name, string_view& display_name, string_view& class_name) +{ + const char* str = full_name.data(); + size_t n = full_name.size(); + if (n > 2) { + for (size_t i = 0; i < n - 1; ++i) { + if (str[i] == 0x00 && str[i + 1] == 0x01) { + display_name = string_view(str, i); + i += 2; + class_name = string_view(str + i, n - i); + return true; + } + } + } + display_name = string_view(full_name); + return false; +} + + + +Object::Object() +{ + m_id = (int64)this; +} + +Object::~Object() +{ +} + +ObjectClass Object::getClass() const { return ObjectClass::Unknown; } +ObjectSubClass Object::getSubClass() const { return ObjectSubClass::Unknown; } +string_view Object::getInternalClassName() const { return GetInternalObjectClassName(getClass(), getSubClass()); } + +void Object::setNode(Node* n) +{ + m_node = n; + if (n) { + // do these in constructObject() is too late because of referencing other objects... + size_t cprops = GetPropertyCount(n); + if (cprops == 3) { + m_id = GetPropertyValue(n, 0); + m_name = GetPropertyString(n, 1); + } +#ifdef sfbxEnableLegacyFormatSupport + else if (cprops == 2) { + // no ID in legacy format + m_name = GetPropertyString(n, 0); + } +#endif + } +} + +void Object::importFBXObjects() +{ +} + +void Object::exportFBXObjects() +{ + if (m_id == 0) + return; + + auto objects = m_document->findNode(sfbxS_Objects); + m_node = objects->createChild( + GetObjectClassName(getClass()), m_id, getFullName(), GetObjectSubClassName(getSubClass())); +} + +void Object::exportFBXConnections() +{ + for (auto parent : getParents()) + m_document->createLinkOO(this, parent); +} + +template T* Object::createChild(string_view name) +{ + auto ret = m_document->createObject(name); + addChild(ret); + return ret; +} +#define Body(T) template T* Object::createChild(string_view name); +sfbxEachObjectType(Body) +#undef Body + + +void Object::addChild(Object* v) +{ + if (v) { + m_children.push_back(v); + v->addParent(this); + } +} + +void Object::eraseChild(Object* v) +{ + if (erase(m_children, v)) + v->eraseParent(this); +} + +void Object::addParent(Object* v) +{ + if (v) + m_parents.push_back(v); +} + +void Object::eraseParent(Object* v) +{ + erase(m_parents, v); +} + + +int64 Object::getID() const { return m_id; } +string_view Object::getFullName() const { return m_name; } +string_view Object::getName() const { return m_name.c_str(); } + +Node* Object::getNode() const { return m_node; } + +span Object::getParents() const { return make_span(m_parents); } +span Object::getChildren() const { return make_span(m_children); } +Object* Object::getParent(size_t i) const { return i < m_parents.size() ? m_parents[i] : nullptr; } +Object* Object::getChild(size_t i) const { return i < m_children.size() ? m_children[i] : nullptr; } + +Object* Object::findChild(string_view name) const +{ + for (auto c : m_children) + if (c->getFullName() == name) + return c; + return nullptr; +} + +void Object::setID(int64 id) { m_id = id; } +void Object::setName(string_view v) { m_name = MakeFullName(v, getInternalClassName()); } + + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxObject.h b/src/SmallFBX/sfbxObject.h new file mode 100644 index 0000000..f40edb2 --- /dev/null +++ b/src/SmallFBX/sfbxObject.h @@ -0,0 +1,128 @@ +#pragma once +#include "sfbxTypes.h" + +namespace sfbx { + +enum class ObjectClass : int +{ + Unknown, + NodeAttribute, + Model, + Geometry, + Deformer, + Pose, + Video, + Material, + AnimationStack, + AnimationLayer, + AnimationCurveNode, + AnimationCurve, +}; + +enum class ObjectSubClass : int +{ + Unknown, + Null, + Root, + LimbNode, + Light, + Camera, + Mesh, + Shape, + BlendShape, + BlendShapeChannel, + Skin, + Cluster, + BindPose, + Clip, +}; + +#define sfbxEachObjectClass(Body)\ + Body(NodeAttribute) Body(Model) Body(Geometry) Body(Deformer) Body(Pose) Body(Video) Body(Material)\ + Body(AnimationStack) Body(AnimationLayer) Body(AnimationCurveNode) Body(AnimationCurve) + +#define sfbxEachObjectSubClass(Body)\ + Body(Null) Body(Root) Body(LimbNode) Body(Light) Body(Camera) Body(Mesh) Body(Shape)\ + Body(BlendShape) Body(BlendShapeChannel) Body(Skin) Body(Cluster) Body(BindPose) Body(Clip) + +#define sfbxEachObjectType(Body)\ + Body(NodeAttribute) Body(NullAttribute) Body(RootAttribute) Body(LimbNodeAttribute) Body(LightAttribute) Body(CameraAttribute)\ + Body(Model) Body(Null) Body(Root) Body(LimbNode) Body(Light) Body(Camera) Body(Mesh)\ + Body(Geometry) Body(GeomMesh) Body(Shape)\ + Body(Deformer) Body(Skin) Body(Cluster) Body(BlendShape) Body(BlendShapeChannel)\ + Body(Pose) Body(BindPose)\ + Body(Video) Body(Material)\ + Body(AnimationStack) Body(AnimationLayer) Body(AnimationCurveNode) Body(AnimationCurve) + +#define Decl(T) class T; +sfbxEachObjectType(Decl) +#undef Decl + + +ObjectClass GetObjectClass(string_view n); +ObjectClass GetObjectClass(Node* n); +string_view GetObjectClassName(ObjectClass t); +ObjectSubClass GetObjectSubClass(string_view n); +ObjectSubClass GetObjectSubClass(Node* n); +string_view GetObjectSubClassName(ObjectSubClass t); + +// return full name (display name + \x00 \x01 + class name) +std::string MakeFullName(string_view display_name, string_view class_name); +// true if name is in full name (display name + \x00 \x01 + class name) +bool IsFullName(string_view name); +// split full name into display name & class name (e.g. "hoge\x00\x01Mesh" -> "hoge" & "Mesh") +bool SplitFullName(string_view full_name, string_view& display_name, string_view& class_name); + + +// base object class + +class Object : public std::enable_shared_from_this +{ +friend class Document; +public: + virtual ~Object(); + virtual ObjectClass getClass() const; + virtual ObjectSubClass getSubClass() const; + + template T* createChild(string_view name = {}); + virtual void addChild(Object* v); + virtual void eraseChild(Object* v); + + int64 getID() const; + string_view getFullName() const; // in object name format (e.g. "hoge\x00\x01Mesh") + string_view getName() const; // return display part (e.g. "hoge" if object name is "hoge\x00\x01Mesh") + Node* getNode() const; + + span getParents() const; + span getChildren() const; + Object* getParent(size_t i = 0) const; + Object* getChild(size_t i = 0) const; + Object* findChild(string_view name) const; + + void setID(int64 v); + void setName(string_view v); + void setNode(Node* v); + +protected: + Object(); + Object(const Object&) = delete; + Object& operator=(const Object) = delete; + + virtual void importFBXObjects(); + virtual void exportFBXObjects(); + virtual void exportFBXConnections(); + virtual string_view getInternalClassName() const; + virtual void addParent(Object* v); + virtual void eraseParent(Object* v); + + Document* m_document{}; + Node* m_node{}; + int64 m_id{}; + std::string m_name; + + std::vector m_parents; + std::vector m_children; +}; + + +} // sfbx diff --git a/src/SmallFBX/sfbxProperty.cpp b/src/SmallFBX/sfbxProperty.cpp new file mode 100644 index 0000000..3e5019c --- /dev/null +++ b/src/SmallFBX/sfbxProperty.cpp @@ -0,0 +1,353 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxProperty.h" +#include "sfbxObject.h" + +#include +#pragma comment(lib, "zlib.lib") + + +namespace sfbx { + +uint32_t SizeOfElement(PropertyType type) +{ + switch (type) { + case PropertyType::Int16: + case PropertyType::Int16Array: + return 2; + + case PropertyType::Int32: + case PropertyType::Int32Array: + case PropertyType::Float32: + case PropertyType::Float32Array: + return 4; + + case PropertyType::Int64: + case PropertyType::Int64Array: + case PropertyType::Float64: + case PropertyType::Float64Array: + return 8; + + case PropertyType::Bool: + case PropertyType::BoolArray: + case PropertyType::String: + case PropertyType::Blob: + default: + return 1; + } +} + +Property::Property() {} + +Property::Property(Property&& v) + : m_type(v.m_type) + , m_scalar(v.m_scalar) + , m_data(std::move(v.m_data)) +{} + +void Property::read(std::istream& is) +{ + m_type = read1(is); + if (m_type == PropertyType::String || m_type == PropertyType::Blob) { + uint32_t length = read1(is); + m_data.resize(length); + is.read(m_data.data(), length); + } + else if (!isArray()) { + switch (m_type) { + case PropertyType::Bool: + m_scalar.b = read1(is); + break; + case PropertyType::Int16: + m_scalar.i16 = read1(is); + break; + case PropertyType::Int32: + case PropertyType::Float32: + m_scalar.i32 = read1(is); + break; + case PropertyType::Int64: + case PropertyType::Float64: + m_scalar.i64 = read1(is); + break; + default: + throw std::runtime_error(std::string("sfbx::Property::read(): Unsupported property type ") + std::to_string((char)m_type)); + break; + } + } + else { + uint32_t array_size = read1(is); + uint32_t encoding = read1(is); // 0: plain 1: zlib-compressed + + uLong src_size = read1(is); + uLong dest_size = SizeOfElement(m_type) * array_size; + m_data.resize(dest_size); + + if (encoding == 0) { + readv(is, m_data.data(), dest_size); + } + else if (encoding == 1) { + RawVector compressed(src_size); + readv(is, compressed.data(), src_size); + uncompress2((Bytef*)m_data.data(), &dest_size, (const Bytef*)compressed.data(), &src_size); + } + } +} + +void Property::write(std::ostream& os) +{ + writev(os, m_type); + if (m_type == PropertyType::Blob || m_type == PropertyType::String) { + writev(os, (uint32_t)m_data.size()); + writev(os, m_data); + } + else if (!isArray()) { + // scalar + switch (m_type) { + case PropertyType::Bool: + writev(os, m_scalar.b); + break; + case PropertyType::Int16: + writev(os, m_scalar.i16); + break; + case PropertyType::Int32: + case PropertyType::Float32: + writev(os, m_scalar.i32); + break; + case PropertyType::Int64: + case PropertyType::Float64: + writev(os, m_scalar.i64); + break; + default: + sfbxPrint("sfbx::Property::write(): Unsupported property type %c\n", (char)m_type); + break; + } + } + else { + // array + // use zlib if the data size is reasonably large. + bool use_zlib = m_data.size() >= 128; + + if (use_zlib) { + // with zlib compression + writev(os, (uint32_t)getArraySize()); + writev(os, (uint32_t)1); // encoding: zlib + + uLong src_size = m_data.size(); + uLong dest_size = compressBound(src_size); + RawVector compressed(dest_size); + compress((Bytef*)compressed.data(), &dest_size, (const Bytef*)m_data.data(), src_size); + compressed.resize(dest_size); + + writev(os, (uint32_t)compressed.size()); + writev(os, compressed); + } + else { + // without zlib compression + writev(os, (uint32_t)getArraySize()); + writev(os, (uint32_t)0); // encoding: plain + writev(os, (uint32_t)m_data.size()); + writev(os, m_data); + } + } +} + + +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Int32Array; + m_data.resize(size * sizeof(int32)); + return make_span((int32*)m_data.data(), size); +} + +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Float32Array; + m_data.resize(size * sizeof(float32)); + return make_span((float32*)m_data.data(), size); +} + +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Float64Array; + m_data.resize(size * sizeof(float64)); + return make_span((float64*)m_data.data(), size); +} +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Float64Array; + m_data.resize(size * sizeof(double2)); + return make_span((double2*)m_data.data(), size); +} +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Float64Array; + m_data.resize(size * sizeof(double3)); + return make_span((double3*)m_data.data(), size); +} +template<> span Property::allocateArray(size_t size) +{ + m_type = PropertyType::Float64Array; + m_data.resize(size * sizeof(double4)); + return make_span((double4*)m_data.data(), size); +} + +template +static inline void Assign(RawVector& dst, const span& v) +{ + size_t s = v.size_bytes(); + dst.resize(s); + dst.assign((char*)v.data(), s); +} + +template<> void Property::assign(boolean v) { m_type = PropertyType::Bool; m_scalar.b = v; } +template<> void Property::assign(bool v) { m_type = PropertyType::Bool; m_scalar.b = v; } +template<> void Property::assign(int16 v) { m_type = PropertyType::Int16; m_scalar.i16 = v; } +template<> void Property::assign(int32 v) { m_type = PropertyType::Int32; m_scalar.i32 = v; } +template<> void Property::assign(int64 v) { m_type = PropertyType::Int64; m_scalar.i64 = v; } +template<> void Property::assign(float32 v) { m_type = PropertyType::Float32; m_scalar.f32 = v; } +template<> void Property::assign(float64 v) { m_type = PropertyType::Float64; m_scalar.f64 = v; } + +template<> void Property::assign(double2 v) { m_type = PropertyType::Float64Array; Assign(m_data, make_span(v)); } +template<> void Property::assign(double3 v) { m_type = PropertyType::Float64Array; Assign(m_data, make_span(v)); } +template<> void Property::assign(double4 v) { m_type = PropertyType::Float64Array; Assign(m_data, make_span(v)); } +template<> void Property::assign(double4x4 v) { m_type = PropertyType::Float64Array; Assign(m_data, make_span(v)); } + +template<> void Property::assign(span v) { m_type = PropertyType::Blob; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::BoolArray; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::Int16Array; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::Int32Array; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::Int64Array; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::Float32Array; Assign(m_data, v); } +template<> void Property::assign(span v) { m_type = PropertyType::Float64Array; Assign(m_data, v); } + +template<> void Property::assign(span v) { assign(span{ (float32*)v.data(), v.size() * 2 }); } +template<> void Property::assign(span v) { assign(span{ (float32*)v.data(), v.size() * 3 }); } +template<> void Property::assign(span v) { assign(span{ (float32*)v.data(), v.size() * 4 }); } +template<> void Property::assign(span v) { assign(span{ (float64*)v.data(), v.size() * 2 }); } +template<> void Property::assign(span v) { assign(span{ (float64*)v.data(), v.size() * 3 }); } +template<> void Property::assign(span v) { assign(span{ (float64*)v.data(), v.size() * 4 }); } + +void Property::assign(string_view v) +{ + m_type = PropertyType::String; + m_data.assign(v.begin(), v.end()); +} + +PropertyType Property::getType() const +{ + return m_type; +} + +bool Property::isArray() const +{ + return (char)m_type > 'Z'; +} + +uint64_t Property::getArraySize() const +{ + return m_data.size() / SizeOfElement(m_type); +} + +template<> boolean Property::getValue() const { return m_scalar.b; } +template<> bool Property::getValue() const { return m_scalar.b; } +template<> int16 Property::getValue() const { return m_scalar.i16; } +template<> int32 Property::getValue() const { return m_scalar.i32; } +template<> int64 Property::getValue() const { return m_scalar.i64; } +template<> float32 Property::getValue() const { return m_scalar.f32; } +template<> float64 Property::getValue() const { return m_scalar.f64; } + +template<> double2 Property::getValue() const { return *(double2*)m_data.data(); } +template<> double3 Property::getValue() const { return *(double3*)m_data.data(); } +template<> double4 Property::getValue() const { return *(double4*)m_data.data(); } +template<> double4x4 Property::getValue() const { return *(double4x4*)m_data.data(); } + +template<> span Property::getArray() const { return make_span((boolean*)m_data.data(), getArraySize()); } +template<> span Property::getArray() const { return make_span((int16*)m_data.data(), getArraySize()); } +template<> span Property::getArray() const { return make_span((int32*)m_data.data(), getArraySize()); } +template<> span Property::getArray() const { return make_span((int64*)m_data.data(), getArraySize()); } +template<> span Property::getArray() const { return make_span((float32*)m_data.data(), getArraySize()); } +template<> span Property::getArray() const { return make_span((float64*)m_data.data(), getArraySize()); } + +template<> span Property::getArray() const { return make_span((double2*)m_data.data(), getArraySize() / 2); } +template<> span Property::getArray() const { return make_span((double3*)m_data.data(), getArraySize() / 3); } +template<> span Property::getArray() const { return make_span((double4*)m_data.data(), getArraySize() / 4); } + +string_view Property::getString() const { return make_view(m_data); } + +std::string Property::toString(int depth) const +{ + if (isArray()) { + auto toS = [depth](const auto& span) { + std::string s = "*"; + s += std::to_string(span.size()); + s += " {\n"; + AddTabs(s, depth + 1); + s += "a: "; + join(s, span, ","); + s += "\n"; + AddTabs(s, depth); + s += "}"; + return s; + }; + switch (m_type) { + case PropertyType::BoolArray: return toS(getArray()); + case PropertyType::Int16Array: return toS(getArray()); + case PropertyType::Int32Array: return toS(getArray()); + case PropertyType::Int64Array: return toS(getArray()); + case PropertyType::Float32Array: return toS(getArray()); + case PropertyType::Float64Array: return toS(getArray()); + default: break; + } + } + else { + switch (m_type) { + case PropertyType::Bool: return std::to_string(getValue()); + case PropertyType::Int16: return std::to_string(getValue()); + case PropertyType::Int32: return std::to_string(getValue()); + case PropertyType::Int64: return std::to_string(getValue()); + case PropertyType::Float32: return std::to_string(getValue()); + case PropertyType::Float64: return std::to_string(getValue()); + + case PropertyType::Blob: + { + std::string s; + s += '"'; + s += Base64Encode(make_span(m_data)); + s += '"'; + return s; + } + case PropertyType::String: + { + std::string s; + s += " "; // just reserve space to avoid escape + if (!m_data.empty()) { + auto get_span = [](const char* s, size_t n) { + size_t i = 0; + for (; s[i] != '\0' && i < n; ++i) {} + return make_span(s, i); + }; + + string_view obj_name, class_name; + if (SplitFullName(make_view(m_data), obj_name, class_name)) { + s.insert(s.end(), class_name.begin(), class_name.end()); + s += "::"; + s.insert(s.end(), obj_name.begin(), obj_name.end()); + } + else { + s.insert(s.end(), m_data.begin(), m_data.end()); + } + Escape(s); + } + s[0] = '"'; // replace reserved space + s += '"'; + return s; + } + + default: + break; + } + } + return ""; +} + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxProperty.h b/src/SmallFBX/sfbxProperty.h new file mode 100644 index 0000000..ce47fbe --- /dev/null +++ b/src/SmallFBX/sfbxProperty.h @@ -0,0 +1,93 @@ +#pragma once + +#include "sfbxTypes.h" +#include "sfbxRawVector.h" +#include "sfbxAlgorithm.h" + +namespace sfbx { + +enum class PropertyType : uint8_t +{ + Unknown, + + Bool = 'C', // boolean (not built-in bool. see struct boolean in sfbxTypes.h) + Int16 = 'Y', // int16 + Int32 = 'I', // int32 + Int64 = 'L', // int64 + Float32 = 'F', // float32 + Float64 = 'D', // float64 + + String = 'S', // std::string + Blob = 'R', // span + + BoolArray = 'b', // span + Int16Array = 'y', // span + Int32Array = 'i', // span + Int64Array = 'l', // span + Float32Array = 'f', // span + Float64Array = 'd', // span +}; +uint32_t SizeOfElement(PropertyType type); + + +template inline constexpr bool is_propery_pod = false; +#define PropPOD(T) template<> inline constexpr bool is_propery_pod = true; +PropPOD(bool); +PropPOD(boolean); +PropPOD(int16); +PropPOD(int32); +PropPOD(int64); +PropPOD(float32); +PropPOD(float64); + +PropPOD(double2); +PropPOD(double3); +PropPOD(double4); +PropPOD(double4x4); +#undef PropPOD + + +class Property +{ +public: + Property(); + Property(Property&& v); + Property(const Property&) = delete; + Property& operator=(const Property&) = delete; + + void read(std::istream& input); + void write(std::ostream& output); + + template span allocateArray(size_t size); + + template)> void assign(T v); + template void assign(span v); + template void assign(const RawVector& v) { assign(make_span(v)); } + template void assign(array_adaptor v) { copy(allocateArray(v.values.size()), v.values); } + void assign(string_view v); + + + PropertyType getType() const; + bool isArray() const; + uint64_t getArraySize() const; + + template T getValue() const; + template span getArray() const; + string_view getString() const; + + std::string toString(int depth = 0) const; + +private: + PropertyType m_type{}; + union { + boolean b; + int16 i16; + int32 i32; + int64 i64; + float32 f32; + float64 f64; + } m_scalar{}; + RawVector m_data; +}; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxRawVector.h b/src/SmallFBX/sfbxRawVector.h new file mode 100644 index 0000000..2c0fe61 --- /dev/null +++ b/src/SmallFBX/sfbxRawVector.h @@ -0,0 +1,235 @@ +#pragma once + +#include "sfbxTypes.h" + +namespace sfbx { + +// simpler version of std::vector. +// T must be POD types because its constructor and destructor are never called. +// that also means this can be significantly faster than std::vector in some specific situations. +// (e.g. temporary buffers that can be very large and frequently resized) +template +class RawVector +{ +public: + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = pointer; + using const_iterator = const_pointer; + + RawVector() {} + RawVector(RawVector&& v) noexcept { swap(v); } + RawVector(const RawVector& v) { assign(v.begin(), v.end()); } + RawVector(std::initializer_list v) { assign(v); } + RawVector(const_iterator b, const_iterator e) { assign(b, e); } + template RawVector(span v) { assign(v); } + template RawVector(U(&v)[N]) { assign(v); } + explicit RawVector(size_t initial_size) { resize(initial_size); } + + void operator=(RawVector&& v) noexcept { swap(v); } + void operator=(const RawVector& v) { assign(v.begin(), v.end()); } + void operator=(std::initializer_list v) { assign(v); } + template void operator=(span v) { assign(v); } + template void operator=(U (&v)[N]) { assign(v, v + N); } + + ~RawVector() + { + clear(); + shrink_to_fit(); + } + + bool empty() const { return m_size == 0; } + size_t size() const { return m_size; } + size_t capacity() const { return m_capacity; } + size_t size_bytes() const { return sizeof(T) * m_size; } + size_t capacity_bytes() const { return sizeof(T) * m_capacity; } + + T* data() { return m_data; } + const T* data() const { return m_data; } + + T& at(size_t i) { return m_data[i]; } + const T& at(size_t i) const { return m_data[i]; } + T& operator[](size_t i) { return m_data[i]; } + const T& operator[](size_t i) const { return m_data[i]; } + + T& front() { return m_data[0]; } + T& back() { return m_data[m_size - 1]; } + const T& front() const { return m_data[0]; } + const T& back() const { return m_data[m_size - 1]; } + + iterator begin() { return m_data; } + iterator end() { return m_data + m_size; } + const_iterator begin() const { return m_data; } + const_iterator end() const { return m_data + m_size; } + + static T* allocate(size_t size) { return (T*)malloc(size); } + static void deallocate(T* addr, size_t /*size*/) { free(addr); } + + void reserve(size_t s) + { + if (s > m_capacity) { + s = std::max(s, m_size * 2); + size_t newsize = sizeof(T) * s; + size_t oldsize = sizeof(T) * m_size; + + T* newdata = allocate(newsize); + memcpy(newdata, m_data, oldsize); + deallocate(m_data, oldsize); + m_data = newdata; + m_capacity = s; + } + } + + void shrink_to_fit() + { + if (m_size == 0) { + deallocate(m_data, m_size); + m_data = nullptr; + m_size = m_capacity = 0; + } + else if (m_size == m_capacity) { + // nothing to do + return; + } + else { + size_t newsize = sizeof(T) * m_size; + size_t oldsize = sizeof(T) * m_capacity; + T* newdata = allocate(newsize); + memcpy(newdata, m_data, newsize); + deallocate(m_data, oldsize); + m_data = newdata; + m_capacity = m_size; + } + } + + void resize(size_t s) + { + reserve(s); + m_size = s; + } + void resize(size_t s, const T& v) + { + size_t pos = size(); + resize(s); + // std::fill() can be significantly slower than plain copy + for (size_t i = pos; i < s; ++i) + m_data[i] = v; + } + + void clear() + { + m_size = 0; + } + + void swap(RawVector& other) + { + std::swap(m_data, other.m_data); + std::swap(m_size, other.m_size); + std::swap(m_capacity, other.m_capacity); + } + + template + void assign(Iter first, Iter last) + { + resize(std::distance(first, last)); + std::copy(first, last, begin()); + } + void assign(std::initializer_list v) + { + assign(v.begin(), v.end()); + } + void assign(span v) + { + resize(v.size()); + memcpy(data(), v.data(), size_bytes()); + } + template + void assign(span v) + { + size_t n = v.size(); + resize(n); + for (size_t i = 0; i < n; ++i) + m_data[i] = T(v[i]); + } + template + void assign(const U* v, size_t n) + { + assign(make_span(v, n)); + } + + template + void insert(iterator pos, ForwardIter first, ForwardIter last) + { + size_t d = std::distance(begin(), pos); + size_t s = std::distance(first, last); + resize(d + s); + std::copy(first, last, begin() + d); + } + void insert(iterator pos, const_pointer first, const_pointer last) + { + size_t d = std::distance(begin(), pos); + size_t s = std::distance(first, last); + resize(d + s); + memcpy(m_data + d, first, sizeof(value_type) * s); + } + void insert(iterator pos, const_reference v) + { + insert(pos, &v, &v + 1); + } + + void erase(iterator first, iterator last) + { + size_t s = std::distance(first, last); + std::copy(last, end(), first); + m_size -= s; + } + + void erase(iterator pos) + { + erase(pos, pos + 1); + } + + void push_back(const T& v) + { + resize(m_size + 1); + back() = v; + } + void push_back(T&& v) + { + resize(m_size + 1); + back() = std::move(v); + } + + void pop_back() + { + if (m_size > 0) + --m_size; + } + + bool operator==(const RawVector& v) const + { + return m_size == v.m_size && memcmp(m_data, v.m_data, sizeof(T) * m_size) == 0; + } + bool operator!=(const RawVector& v) const + { + return !(*this == v); + } + + void zeroclear() + { + memset(m_data, 0, sizeof(T) * m_size); + } + +private: + T* m_data = nullptr; + size_t m_size = 0; + size_t m_capacity = 0; +}; + +template inline constexpr bool is_RawVector = false; +template inline constexpr bool is_RawVector> = true; + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxTokens.h b/src/SmallFBX/sfbxTokens.h new file mode 100644 index 0000000..03f9065 --- /dev/null +++ b/src/SmallFBX/sfbxTokens.h @@ -0,0 +1,209 @@ +#pragma once + +#define sfbxS_FBXHeaderExtension "FBXHeaderExtension" +#define sfbxS_FBXHeaderVersion "FBXHeaderVersion" +#define sfbxS_FBXVersion "FBXVersion" +#define sfbxS_EncryptionType "EncryptionType" +#define sfbxS_CreationTimeStamp "CreationTimeStamp" +#define sfbxS_OtherFlags "OtherFlags" +#define sfbxS_TCDefinition "TCDefinition" +#define sfbxS_Creator "Creator" +#define sfbxS_GlobalInfo "GlobalInfo" +#define sfbxS_SceneInfo "SceneInfo" +#define sfbxS_MetaData "MetaData" +#define sfbxS_FileId "FileId" +#define sfbxS_CreationTime "CreationTime" + +#define sfbxS_Empty "" +#define sfbxS_A "A" +#define sfbxS_C "C" +#define sfbxS_P "P" +#define sfbxS_OO "OO" +#define sfbxS_OP "OP" +#define sfbxS_Version "Version" +#define sfbxS_Name "Name" + +#define sfbxS_Year "Year" +#define sfbxS_Month "Month" +#define sfbxS_Day "Day" +#define sfbxS_Hour "Hour" +#define sfbxS_Minute "Minute" +#define sfbxS_Second "Second" +#define sfbxS_Millisecond "Millisecond" +#define sfbxS_UserData "UserData" +#define sfbxS_Type "Type" +#define sfbxS_TypeIndex "TypeIndex" +#define sfbxS_TypeFlags "TypeFlags" +#define sfbxS_Null "Null" +#define sfbxS_Skeleton "Skeleton" + +#define sfbxS_Title "Title" +#define sfbxS_Subject "Subject" +#define sfbxS_Author "Author" +#define sfbxS_Keywords "Keywords" +#define sfbxS_Revision "Revision" +#define sfbxS_Comment "Comment" +#define sfbxS_Url "Url" +#define sfbxS_DocumentUrl "DocumentUrl" +#define sfbxS_SrcDocumentUrl "SrcDocumentUrl" +#define sfbxS_Original "Original" +#define sfbxS_Compound "Compound" +#define sfbxS_DateTime "DateTime" +#define sfbxS_LastSaved "LastSaved" +#define sfbxS_KString "KString" +#define sfbxS_OriginalApplicationVendor "Original|ApplicationVendor" +#define sfbxS_OriginalApplicationName "Original|ApplicationName" +#define sfbxS_OriginalApplicationVersion "Original|ApplicationVersion" +#define sfbxS_OriginalDateTime_GMT "Original|DateTime_GMT" +#define sfbxS_OriginalFileName "Original|FileName" +#define sfbxS_LastSavedApplicationVendor "LastSaved|ApplicationVendor" +#define sfbxS_LastSavedApplicationName "LastSaved|ApplicationName" +#define sfbxS_LastSavedApplicationVersion "LastSaved|ApplicationVersion" +#define sfbxS_LastSavedDateTime_GMT "LastSaved|DateTime_GMT" + + + +#define sfbxS_GlobalSettings "GlobalSettings" +#define sfbxS_Documents "Documents" +#define sfbxS_References "References" +#define sfbxS_Definitions "Definitions" +#define sfbxS_Objects "Objects" +#define sfbxS_Connections "Connections" +#define sfbxS_Takes "Takes" + +#define sfbxS_ObjectType "ObjectType" +#define sfbxS_Count "Count" +#define sfbxS_Current "Current" +#define sfbxS_Document "Document" +#define sfbxS_RootNode "RootNode" + +#define sfbxS_NodeAttribute "NodeAttribute" +#define sfbxS_Model "Model" +#define sfbxS_Geometry "Geometry" +#define sfbxS_Deformer "Deformer" +#define sfbxS_Pose "Pose" +#define sfbxS_Video "Video" +#define sfbxS_Material "Material" +#define sfbxS_AnimationStack "AnimationStack" +#define sfbxS_AnimationLayer "AnimationLayer" +#define sfbxS_AnimationCurveNode "AnimationCurveNode" +#define sfbxS_AnimationCurve "AnimationCurve" + +#define sfbxS_Light "Light" +#define sfbxS_Camera "Camera" +#define sfbxS_Mesh "Mesh" +#define sfbxS_Shape "Shape" +#define sfbxS_Root "Root" +#define sfbxS_LimbNode "LimbNode" +#define sfbxS_Skin "Skin" +#define sfbxS_Cluster "Cluster" +#define sfbxS_BindPose "BindPose" +#define sfbxS_BlendShape "BlendShape" +#define sfbxS_BlendShapeChannel "BlendShapeChannel" +#define sfbxS_Clip "Clip" + +#define sfbxS_SubDeformer "SubDeformer" +#define sfbxS_AnimStack "AnimStack" +#define sfbxS_AnimLayer "AnimLayer" +#define sfbxS_AnimCurveNode "AnimCurveNode" +#define sfbxS_AnimCurve "AnimCurve" + +#define sfbxS_Properties "Properties" +#define sfbxS_Properties70 "Properties70" +#define sfbxS_bool "bool" +#define sfbxS_Vector "Vector" +#define sfbxS_Vector3D "Vector3D" +#define sfbxS_Visibility "Visibility" +#define sfbxS_RotationOrder "RotationOrder" +#define sfbxS_RotationActive "RotationActive" +#define sfbxS_PreRotation "PreRotation" +#define sfbxS_PostRotation "PostRotation" +#define sfbxS_LclTranslation "Lcl Translation" +#define sfbxS_LclRotation "Lcl Rotation" +#define sfbxS_LclScale "Lcl Scaling" +#define sfbxS_Transform "Transform" +#define sfbxS_TransformLink "TransformLink" +#define sfbxS_Node "Node" +#define sfbxS_Mode "Mode" +#define sfbxS_Matrix "Matrix" +#define sfbxS_Weights "Weights" +#define sfbxS_PoseNode "PoseNode" +#define sfbxS_NbPoseNodes "NbPoseNodes" +#define sfbxS_Total1 "Total1" +#define sfbxS_Link_DeformAcuracy "Link_DeformAcuracy" +#define sfbxS_DeformPercent "DeformPercent" +#define sfbxS_FullWeights "FullWeights" +#define sfbxS_Connect "Connect" + +#define sfbxS_GeometryVersion "GeometryVersion" +#define sfbxS_Indexes "Indexes" +#define sfbxS_Vertices "Vertices" +#define sfbxS_Normals "Normals" +#define sfbxS_NormalsW "NormalsW" +#define sfbxS_NormalsIndex "NormalsIndex" +#define sfbxS_UV "UV" +#define sfbxS_UVIndex "UVIndex" +#define sfbxS_Colors "Colors" +#define sfbxS_ColorIndex "ColorIndex" +#define sfbxS_PolygonVertexIndex "PolygonVertexIndex" +#define sfbxS_LayerElementNormal "LayerElementNormal" +#define sfbxS_LayerElementUV "LayerElementUV" +#define sfbxS_LayerElementColor "LayerElementColor" +#define sfbxS_LayerLayerElementMaterial "LayerElementMaterial" +#define sfbxS_MappingInformationType "MappingInformationType" +#define sfbxS_ReferenceInformationType "ReferenceInformationType" +#define sfbxS_Layer "Layer" +#define sfbxS_LayerElement "LayerElement" + + +#define sfbxS_Default "Default" +#define sfbxS_KeyVer "KeyVer" +#define sfbxS_KeyTime "KeyTime" +#define sfbxS_KeyValueFloat "KeyValueFloat" +#define sfbxS_KeyAttrFlags "KeyAttrFlags" +#define sfbxS_KeyAttrDataFloat "KeyAttrDataFloat" +#define sfbxS_KeyAttrRefCount "KeyAttrRefCount" +#define sfbxS_T "T" +#define sfbxS_R "R" +#define sfbxS_S "S" +#define sfbxS_FocalLength "FocalLength" +#define sfbxS_LocalStart "LocalStart" +#define sfbxS_LocalStop "LocalStop" +#define sfbxS_ReferenceStart "ReferenceStart" +#define sfbxS_ReferenceStop "ReferenceStop" +#define sfbxS_Time "Time" +#define sfbxS_KTime "KTime" +#define sfbxS_Number "Number" +#define sfbxS_Take "Take" +#define sfbxS_FileName "FileName" +#define sfbxS_LocalTime "LocalTime" +#define sfbxS_ReferenceTime "ReferenceTime" + + +#define sfbxI_TCDefinition 127 +#define sfbxI_GlobalSettingsVersion 1000 +#define sfbxI_ModelVersion 232 +#define sfbxI_GeometryVersion 124 +#define sfbxI_LayerVersion 100 +#define sfbxI_LayerElementNormalVersion 101 +#define sfbxI_LayerElementUVVersion 101 +#define sfbxI_LayerElementColorVersion 101 +#define sfbxI_LayerElementMaterialVersion 101 +#define sfbxI_ShapeVersion 100 +#define sfbxI_BindPoseVersion 100 +#define sfbxI_SkinVersion 101 +#define sfbxI_ClusterVersion 100 +#define sfbxI_BlendShapeVersion 100 +#define sfbxI_BlendShapeChannelVersion 100 +#define sfbxI_MaterialVersion 102 +#define sfbxI_KeyVer 4008 + +#define sfbxI_TicksPerSecond 46186158000 + +#define sfbxI_FBX2014_FileVersion 7400 +#define sfbxI_FBX2015_FileVersion 7400 +#define sfbxI_FBX2016_FileVersion 7500 +#define sfbxI_FBX2017_FileVersion 7500 +#define sfbxI_FBX2018_FileVersion 7500 +#define sfbxI_FBX2019_FileVersion 7700 +#define sfbxI_FBX2020_FileVersion 7700 diff --git a/src/SmallFBX/sfbxTypes.h b/src/SmallFBX/sfbxTypes.h new file mode 100644 index 0000000..63c1862 --- /dev/null +++ b/src/SmallFBX/sfbxTypes.h @@ -0,0 +1,358 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#ifdef __cpp_lib_span + #include +#endif +#include "sfbxMeta.h" + +namespace sfbx { + + +// SmallFBX require C++17, but not C++20. use std::span only if C++20 is available and our own version if not. + +#ifdef __cpp_lib_span + +template using span = std::span; + +#else + +template +class span +{ +public: + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = pointer; + using const_iterator = const_pointer; + + span() {} + span(const T* d, size_t s) : m_data(const_cast(d)), m_size(s) {} + + template + span(const T(&v)[N]) : m_data(const_cast(v)), m_size(N) {} + + template)> + span(const Cont& v) : m_data(const_cast(v.data())), m_size(v.size()) {} + + span& operator=(const span& v) { m_data = const_cast(v.m_data); m_size = v.m_size; return *this; } + + bool empty() const { return m_size == 0; } + size_t size() const { return m_size; } + size_t size_bytes() const { return sizeof(T) * m_size; } + + T* data() { return m_data; } + const T* data() const { return m_data; } + + T& operator[](size_t i) { return m_data[i]; } + const T& operator[](size_t i) const { return m_data[i]; } + + T& front() { return m_data[0]; } + const T& front() const { return m_data[0]; } + T& back() { return m_data[m_size - 1]; } + const T& back() const { return m_data[m_size - 1]; } + + iterator begin() { return m_data; } + const_iterator begin() const { return m_data; } + iterator end() { return m_data + m_size; } + const_iterator end() const { return m_data + m_size; } + +private: + T* m_data{}; + size_t m_size{}; +}; +#endif + +template +struct array_to_span +{ + span operator()(const T(&v)[N]) const { return { const_cast(v), N }; } +}; +template +struct array_to_span +{ + span operator()(const char(&v)[N]) const { return { const_cast(v), N - 1 }; } // -1 to ignore null terminator +}; +// from array +template +inline constexpr span make_span(const T (&v)[N]) { return array_to_span()(v); } + +// from contagious container (std::vector, etc) +template)> +inline constexpr span> make_span(const Cont& v) { return { const_cast*>(v.data()), v.size() }; } + +// from pointer & size +template +inline constexpr span make_span(const T* v, size_t n) { return { const_cast(v), n }; } + + +using std::string_view; + +template +inline constexpr string_view make_view(const char(&v)[N]) { return string_view{ v, N - 1 }; } // -1 to ignore null terminator + +template)> +inline constexpr string_view make_view(const Cont& v) { return { const_cast*>(v.data()), v.size() }; } + + +using int8 = int8_t; +using int16 = int16_t; +using int32 = int32_t; +using int64 = int64_t; +using float32 = float; +using float64 = double; + +template +struct tvec2 +{ + using value_type = T; + + T x, y; + + static constexpr size_t size() { return 2; } + T* data() { return (T*)this; } + const T* data() const { return (T*)this; } + T& operator[](int i) { return data()[i]; } + const T& operator[](int i) const { return data()[i]; } + bool operator==(const tvec2& v) const { return x == v.x && y == v.y; } + bool operator!=(const tvec2& v) const { return !((*this) == v); } + template operator tvec2() const { return { (U)x, (U)y }; } + + template void assign(const U* v) + { + T* d = data(); + for (size_t i = 0; i < size(); ++i) + *d++ = T(*v++); + } + template void operator=(span v) { assign(v.data()); } + + static constexpr tvec2 zero() { return { (T)0, (T)0 }; } + static constexpr tvec2 one() { return { (T)1, (T)1 }; } +}; + +template +struct tvec3 +{ + using value_type = T; + + T x, y, z; + + static constexpr size_t size() { return 3; } + T* data() { return (T*)this; } + const T* data() const { return (T*)this; } + T& operator[](int i) { return data()[i]; } + const T& operator[](int i) const { return data()[i]; } + bool operator==(const tvec3& v) const { return x == v.x && y == v.y && z == v.z; } + bool operator!=(const tvec3& v) const { return !((*this) == v); } + template operator tvec3() const { return { (U)x, (U)y, (U)z }; } + + template void assign(const U* v) + { + T* d = data(); + for (size_t i = 0; i < size(); ++i) + *d++ = T(*v++); + } + template void operator=(span v) { assign(v.data()); } + + static constexpr tvec3 zero() { return { (T)0, (T)0, (T)0 }; } + static constexpr tvec3 one() { return { (T)1, (T)1, (T)1 }; } +}; + +template +struct tvec4 +{ + using value_type = T; + + T x, y, z, w; + + static constexpr size_t size() { return 4; } + T* data() { return (T*)this; } + const T* data() const { return (T*)this; } + T& operator[](int i) { return data()[i]; } + const T& operator[](int i) const { return data()[i]; } + bool operator==(const tvec4& v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } + bool operator!=(const tvec4& v) const { return !((*this) == v); } + template operator tvec4() const { return { (U)x, (U)y, (U)z, (U)w }; } + + template void assign(const U* v) + { + T* d = data(); + for (size_t i = 0; i < size(); ++i) + *d++ = T(*v++); + } + template void operator=(span v) { assign(v.data()); } + + static constexpr tvec4 zero() { return { (T)0, (T)0, (T)0, (T)0 }; } + static constexpr tvec4 one() { return { (T)1, (T)1, (T)1, (T)1 }; } +}; + +template +struct tquat +{ + using value_type = T; + + T x, y, z, w; + + static constexpr size_t size() { return 4; } + T* data() { return (T*)this; } + const T* data() const { return (T*)this; } + T& operator[](int i) { return data()[i]; } + const T& operator[](int i) const { return data()[i]; } + bool operator==(const tquat& v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } + bool operator!=(const tquat& v) const { return !((*this) == v); } + template operator tquat() const { return { (U)x, (U)y, (U)z, (U)w }; } + + template void assign(const U* v) + { + T* d = data(); + for (size_t i = 0; i < size(); ++i) + *d++ = T(*v++); + } + template void operator=(span v) { assign(v.data()); } + + static constexpr tquat identity() { return{ (T)0, (T)0, (T)0, (T)1 }; } +}; + +template +struct tmat4x4 +{ + using value_type = T; + + tvec4 m[4]; + + static constexpr size_t size() { return 16; } + T* data() { return (T*)this; } + const T* data() const { return (T*)this; } + tvec4& operator[](int i) { return m[i]; } + const tvec4& operator[](int i) const { return m[i]; } + + bool operator==(const tmat4x4& v) const + { + for (size_t i = 0; i < size(); ++i) + if (data()[i] != v.data()[i]) + return false; + return true; + } + bool operator!=(const tmat4x4& v) const { return !((*this) == v); } + + template operator tmat4x4() const + { + return { { tvec4(m[0]), tvec4(m[1]), tvec4(m[2]), tvec4(m[3]) } }; + } + + template void assign(const U* v) + { + T* d = data(); + for (size_t i = 0; i < size(); ++i) + *d++ = T(*v++); + } + template void operator=(span v) { assign(v.data()); } + + static constexpr tmat4x4 identity() + { + return{ { + { (T)1, (T)0, (T)0, (T)0 }, + { (T)0, (T)1, (T)0, (T)0 }, + { (T)0, (T)0, (T)1, (T)0 }, + { (T)0, (T)0, (T)0, (T)1 } + } }; + } +}; + +template inline constexpr size_t get_vector_size = 1; +template inline constexpr size_t get_vector_size> = 2; +template inline constexpr size_t get_vector_size> = 3; +template inline constexpr size_t get_vector_size> = 4; +template inline constexpr size_t get_vector_size> = 4; +template inline constexpr size_t get_vector_size> = 16; + +template inline constexpr bool is_scalar = get_vector_size == 1; +template inline constexpr bool is_vector = get_vector_size != 1; + +template struct get_scalar { using type = T; }; +template struct get_scalar> { using type = T; }; +template struct get_scalar> { using type = T; }; +template struct get_scalar> { using type = T; }; +template struct get_scalar> { using type = T; }; +template struct get_scalar> { using type = T; }; +template using get_scalar_t = typename get_scalar::type; + +using int2 = tvec2; +using int3 = tvec3; +using int4 = tvec4; + +using float2 = tvec2; +using float3 = tvec3; +using float4 = tvec4; +using quatf = tquat; +using float4x4 = tmat4x4; + +using double2 = tvec2; +using double3 = tvec3; +using double4 = tvec4; +using quatd = tquat; +using double4x4 = tmat4x4; + + +enum class RotationOrder : int +{ + XYZ, + XZY, + YZX, + YXZ, + ZXY, + ZYX, + SphericXYZ +}; + + +// FBX's bool property represents true as 'Y' and false as 'T' +struct boolean +{ + char value; + + operator bool() const { return value == 'Y'; } + void operator=(bool v) { value = v ? 'Y' : 'T'; } +}; + + +template +struct array_adaptor +{ + using dst_type = D; + using src_type = S; + + span values; + template array_adaptor(T&&... v) : values(make_span(v...)) {} +}; +template +inline auto make_adaptor(const S& src, T&&... a) { return array_adaptor>(src, a...); } + + +class Node; using NodePtr = std::shared_ptr; +class Object; using ObjectPtr = std::shared_ptr; +class Document; using DocumentPtr = std::shared_ptr; + +template +inline T* as(U* v) { return dynamic_cast(v); } + +} // namespace sfbx + + +namespace std { + +inline std::string to_string(sfbx::boolean v) +{ + std::string ret; + ret += v.value; + return ret; +} + +} // namespace std diff --git a/src/SmallFBX/sfbxUtil.h b/src/SmallFBX/sfbxUtil.h new file mode 100644 index 0000000..5f77d62 --- /dev/null +++ b/src/SmallFBX/sfbxUtil.h @@ -0,0 +1,18 @@ +#pragma once + +#define sfbxPrint(...) printf(__VA_ARGS__) + +namespace sfbx { + +// return false if no escape is needed +bool Escape(std::string& v); +std::string Base64Encode(span src); + +RawVector Triangulate(span counts, span indices); + +struct JointWeights; +struct JointMatrices; +bool DeformPoints(span dst, const JointWeights& jw, const JointMatrices& jm, span src); +bool DeformVectors(span dst, const JointWeights& jw, const JointMatrices& jm, span src); + +} // namespace sfbx diff --git a/src/SmallFBX/sfbxUtils.cpp b/src/SmallFBX/sfbxUtils.cpp new file mode 100644 index 0000000..a559068 --- /dev/null +++ b/src/SmallFBX/sfbxUtils.cpp @@ -0,0 +1,167 @@ +#include "pch.h" +#include "sfbxInternal.h" +#include "sfbxObject.h" +#include "sfbxGeometry.h" +#include "sfbxDeformer.h" +#include "sfbxUtil.h" + +namespace sfbx { + +bool Escape(std::string& v) +{ + bool ret = false; + for (char c : v) { + if (c == '"' || c == '\n' || c == '\r') { + ret = true; + break; + } + } + if (!ret) + return ret; + + std::string tmp; + for (char c : v) { + if (c == '"') + tmp += """; + else if (c == '\n') + tmp += "&lf;"; + else if (c == '\r') + tmp += "&cr;"; + else + tmp += c; + } + v.swap(tmp); + return ret; +} + +std::string Base64Encode(span src) +{ + static const char* s_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string dst; + // result size will be about 133% of src. so this is a bit overkill. + dst.reserve(src.size() * 2); + + size_t n = src.size(); + for (size_t i = 0; i < n; ++i) { + switch (i % 3) { + case 0: + dst.push_back(s_table[(src[i] & 0xFC) >> 2]); + if (i + 1 == n) { + dst.push_back(s_table[(src[i] & 0x03) << 4]); + dst.push_back('='); + dst.push_back('='); + } + break; + + case 1: + dst.push_back(s_table[((src[i - 1] & 0x03) << 4) | ((src[i + 0] & 0xF0) >> 4)]); + if (i + 1 == n) { + dst.push_back(s_table[(src[i] & 0x0F) << 2]); + dst.push_back('='); + } + break; + + case 2: + dst.push_back(s_table[((src[i - 1] & 0x0F) << 2) | ((src[i + 0] & 0xC0) >> 6)]); + dst.push_back(s_table[src[i] & 0x3F]); + break; + } + } + return dst; +} + +RawVector Triangulate(span counts, span indices) +{ + size_t num_triangles = 0; + for (int c : counts) { + if (c >= 3) + num_triangles += c - 2; + } + + RawVector ret(num_triangles * 3); + int* dst = ret.data(); + for (int c : counts) { + if (c >= 3) { + for (int fi = 0; fi < c - 2; ++fi) { + *dst++ = indices[0]; + *dst++ = indices[1 + fi]; + *dst++ = indices[2 + fi]; + } + } + } + return ret; +} + +// Mul: e.g. [](float4x4, float3) -> float3 +template +static bool DeformImpl(span dst, const JointWeights& jw, const JointMatrices& jm, span src, const Mul& mul) +{ + if (jw.counts.size() != src.size() || jw.counts.size() != dst.size()) { + sfbxPrint("Skin::deformImpl(): vertex count mismatch\n"); + return false; + } + + const JointWeight* weights = jw.weights.data(); + const float4x4* matrices = jm.joint_transform.data(); + size_t nvertices = src.size(); + for (size_t vi = 0; vi < nvertices; ++vi) { + Vec p = src[vi]; + Vec r{}; + int cjoints = jw.counts[vi]; + for (int bi = 0; bi < cjoints; ++bi) { + JointWeight w = weights[bi]; + r += mul(matrices[w.index], p) * w.weight; + } + dst[vi] = r; + weights += cjoints; + } + return true; +} + +bool DeformPoints(span dst, const JointWeights& jw, const JointMatrices& jm, span src) +{ + return DeformImpl(dst, jw, jm, src, + [](float4x4 m, float3 p) { return mul_p(m, p); }); +} + +bool DeformVectors(span dst, const JointWeights& jw, const JointMatrices& jm, span src) +{ + return DeformImpl(dst, jw, jm, src, + [](float4x4 m, float3 p) { return mul_v(m, p); }); +} + + + +char CounterStream::StreamBuf::s_dummy_buf[1024]; + +CounterStream::StreamBuf::StreamBuf() +{ + this->setp(s_dummy_buf, s_dummy_buf + std::size(s_dummy_buf)); +} + +int CounterStream::StreamBuf::overflow(int c) +{ + m_size += uint64_t(this->pptr() - this->pbase()) + 1; + this->setp(s_dummy_buf, s_dummy_buf + std::size(s_dummy_buf)); + return c; +} + +int CounterStream::StreamBuf::sync() +{ + m_size += uint64_t(this->pptr() - this->pbase()); + this->setp(s_dummy_buf, s_dummy_buf + std::size(s_dummy_buf)); + return 0; +} + +void CounterStream::StreamBuf::reset() +{ + m_size = 0; + this->setp(s_dummy_buf, s_dummy_buf + std::size(s_dummy_buf)); +} + +CounterStream::CounterStream() : std::ostream(&m_buf) {} +uint64_t CounterStream::size() { m_buf.sync(); return m_buf.m_size; } +void CounterStream::reset() { m_buf.reset(); } + +} // namespace sfbx diff --git a/src/Test.vcxproj b/src/Test.vcxproj new file mode 100644 index 0000000..1bc5290 --- /dev/null +++ b/src/Test.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + Create + + + + + + + + + + {3c7d88e0-b0d0-4e63-9f3f-75530b2da797} + + + + + + + {A369E5E1-3695-4AE0-8EBB-1826839687D1} + Win32Proj + 10.0 + + + + v142 + Application + Unicode + + + v142 + Application + true + Unicode + + + + + + + $(ProjectDir);$(ProjectDir)Externals\include;$(IncludePath) + $(ProjectDir)Externals\lib_win64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(SolutionDir)_build_msvc\$(Platform)_$(Configuration)\ + $(SolutionDir)_build_msvc\obj\$(Platform)_$(Configuration)\$(ProjectName)\ + + + + Use + pch.h + true + stdcpp17 + + + Console + + + PerMonitorHighDPIAware + + + + + mrDebug;%(PreprocessorDefinitions) + Disabled + + + true + true + + + + + /Zo %(AdditionalOptions) + %(PreprocessorDefinitions) + Full + AnySuitable + true + Speed + false + true + false + false + true + Fast + false + true + + + true + true + true + true + UseLinkTimeCodeGeneration + + + + + + \ No newline at end of file diff --git a/src/Test/Test.cpp b/src/Test/Test.cpp new file mode 100644 index 0000000..f623072 --- /dev/null +++ b/src/Test/Test.cpp @@ -0,0 +1,210 @@ +#include "pch.h" +#include "Test.h" + +namespace test { + +nanosec Now() +{ + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +static std::string g_log; + +std::string FormatImpl(const char* format, va_list args) +{ + const int MaxBuf = 4096; + char buf[MaxBuf]; + vsprintf(buf, format, args); + return buf; +} + +std::string Format(const char* format, ...) +{ + std::string ret; + va_list args; + va_start(args, format); + ret = FormatImpl(format, args); + va_end(args); + fflush(stdout); + return ret; +} + +void PrintImpl(const char *format, ...) +{ + va_list args; + va_start(args, format); + + auto txt = FormatImpl(format, args); + g_log += txt; +#ifdef _WIN32 + ::OutputDebugStringA(txt.c_str()); +#endif + printf("%s", txt.c_str()); + + va_end(args); + fflush(stdout); +} + +void PutsImpl(const char* s) +{ +#ifdef _WIN32 + ::OutputDebugStringA(s); +#endif + printf(s); +} + +struct ArgEntry +{ + std::string name; + std::string value; + + template bool getValue(T& dst) const; +}; +template<> bool ArgEntry::getValue(std::string& dst) const +{ + dst = value; + return true; +} +template<> bool ArgEntry::getValue(bool& dst) const +{ + if (value == "true" || std::atoi(value.c_str()) != 0) { + dst = true; + return true; + } + else if (value == "false" || value == "0") { + dst = false; + return true; + } + return false; +} +template<> bool ArgEntry::getValue(int& dst) const +{ + dst = std::atoi(value.c_str()); + return dst != 0; +} +template<> bool ArgEntry::getValue(float& dst) const +{ + dst = (float)std::atof(value.c_str()); + return dst != 0.0f; +} + +static std::vector& GetArgs() +{ + static std::vector s_instance; + return s_instance; +} +template bool GetArg(const char *name, T& dst) +{ + auto& args = GetArgs(); + auto it = std::find_if(args.begin(), args.end(), [name](auto& e) { return e.name == name; }); + if (it != args.end()) + return it->getValue(dst); + return false; +} +template bool GetArg(const char *name, std::string& dst); +template bool GetArg(const char* name, bool& dst); +template bool GetArg(const char* name, int& dst); +template bool GetArg(const char *name, float& dst); + + + +struct TestInitializer +{ + std::function initializer; + std::function finalizer; +}; + +struct TestEntry +{ + std::string name; + std::function body; +}; + +static std::vector& GetInitializers() +{ + static std::vector s_instance; + return s_instance; +} + +static std::vector& GetTests() +{ + static std::vector s_instance; + return s_instance; +} + +void RegisterInitializer(const std::function& init, const std::function& fini) +{ + GetInitializers().push_back({ init, fini }); +} + +void RegisterTestCaseImpl(const char *name, const std::function& body) +{ + GetTests().push_back({name, body}); +} + +static void RunTestImpl(const TestEntry& v) +{ + testPrint("%s begin\n", v.name.c_str()); + auto begin = Now(); + try { + v.body(); + } + catch (const std::runtime_error& e) { + testPrint("*** failed: %s ***\n", e.what()); + } + auto end = Now(); + testPrint("%s end (%.2fms)\n\n", v.name.c_str(), NS2MS(end - begin)); +} + +} // namespace test + +testExport const char* GetLogMessage() +{ + return test::g_log.c_str(); +} + +testExport void RunTest(char *name) +{ + test::g_log.clear(); + for (auto& entry : test::GetTests()) { + if (entry.name == name) { + test::RunTestImpl(entry); + } + } +} + +testExport void RunAllTests() +{ + test::g_log.clear(); + for (auto& entry : test::GetTests()) { + test::RunTestImpl(entry); + } +} + +int main(int argc, char *argv[]) +{ + for (auto& i : test::GetInitializers()) + i.initializer(); + + int run_count = 0; + for (int i = 1; i < argc; ++i) { + if (char *sep = std::strstr(argv[i], "=")) { + *(sep++) = '\0'; + test::GetArgs().push_back({ argv[i] , sep }); + } + } + for (int i = 1; i < argc; ++i) { + if (!std::strstr(argv[i], "=")) { + RunTest(argv[i]); + ++run_count; + } + } + + if (run_count == 0) { + RunAllTests(); + } + + for (auto& i : test::GetInitializers()) + i.finalizer(); +} diff --git a/src/Test/Test.h b/src/Test/Test.h new file mode 100644 index 0000000..3b21918 --- /dev/null +++ b/src/Test/Test.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef _WIN32 + #define testExport extern "C" __declspec(dllexport) +#else + #define testExport extern "C" +#endif + +#define testPrint(...) ::test::PrintImpl(__VA_ARGS__) +#define testPuts(...) ::test::PutsImpl(__VA_ARGS__) + +#define testRegisterInitializer(Name, Init, Fini)\ + static struct _TestInitializer_##Name {\ + _TestInitializer_##Name() { ::test::RegisterInitializer(Init, Fini); }\ + } g_TestInitializer_##Name; + +#define testRegisterTestCase(Name)\ + static struct _TestCase_##Name {\ + _TestCase_##Name() { ::test::RegisterTestCaseImpl(#Name, Name); }\ + } g_TestCase_##Name; + +#define testCase(Name) void Name(); testRegisterTestCase(Name); void Name() +#define testExpect(Body) if(!(Body)) { throw std::runtime_error(::test::Format("%s(%d): failed - " #Body "\n", __FILE__, __LINE__)); } + + +namespace test { + +using nanosec = uint64_t; +nanosec Now(); +inline float NS2MS(nanosec ns) { return float(double(ns) / 1000000.0); } + +void RegisterInitializer(const std::function& init, const std::function& fini); +void RegisterTestCaseImpl(const char* name, const std::function& body); +std::string Format(const char* format, ...); +void PrintImpl(const char* format, ...); +void PutsImpl(const char* format); +template bool GetArg(const char* name, T& dst); + +template +inline void TestScope(const char *name, const Body& body, int num_try = 1) +{ + auto begin = Now(); + for (int i = 0; i < num_try; ++i) + body(); + auto end = Now(); + + float elapsed = NS2MS(end - begin); + testPrint(" %s: %.2fms", name, elapsed / num_try); + if (num_try > 1) { + testPrint(" (%.2fms in total)", elapsed); + } + testPrint("\n"); +} + +} // namespace test diff --git a/src/Test/TestFBX.cpp b/src/Test/TestFBX.cpp new file mode 100644 index 0000000..3452944 --- /dev/null +++ b/src/Test/TestFBX.cpp @@ -0,0 +1,305 @@ +#include "pch.h" +#include "Test.h" +#include "SmallFBX.h" + +using sfbx::as; +using sfbx::span; +using sfbx::make_span; +using sfbx::RawVector; +using sfbx::float3; + +static void PrintObject(sfbx::Object* obj, int depth = 0) +{ + // indent + for (int i = 0; i < depth; ++i) + testPrint(" "); + + testPrint("\"%s\" [0x%llx] (%s : %s)\n", + obj->getFullName().data(), + obj->getID(), + sfbx::GetObjectClassName(obj->getClass()).data(), + sfbx::GetObjectSubClassName(obj->getSubClass()).data()); + + // for test + if (auto skin = as(obj)) { + auto weights_v = skin->getJointWeights(); + auto weights_4 = skin->createFixedJointWeights(4); + auto matrices = skin->getJointMatrices(); + testPrint(""); + } + else if (auto anim = as(obj)) { + for (auto n : anim->getAnimationCurveNodes()) { + if (n->getAnimationKind() == sfbx::AnimationKind::Position) { + float start = n->getStartTime(); + float stop = n->getStopTime(); + for (float t = start; t <= stop; t += 0.033334f) { + auto v = n->evaluate3(t); + testPrint(""); + } + } + } + } + + if (!as(obj)) { + // avoid printing children of Cluster because it may result in a nearly endless list + + for (auto child : obj->getChildren()) + PrintObject(child, depth + 1); + + } +} + +testCase(fbxRead) +{ + std::string path, path2, path3; + test::GetArg("path", path); + test::GetArg("path2", path2); + test::GetArg("path3", path3); + if (path.empty()) + return; + + sfbx::DocumentPtr doc = sfbx::MakeDocument(); + if (doc->read(path)) { + if (!path2.empty()) { + sfbx::DocumentPtr doc2 = sfbx::MakeDocument(); + if (doc2->read(path2)) { + auto takes = doc2->getAnimationStacks(); + if (!takes.empty()) { + if (takes[0]->remap(doc)) { + doc->setCurrentTake(takes[0]); + } + } + } + } + if (!path3.empty()) { + sfbx::DocumentPtr doc3 = sfbx::MakeDocument(); + if (doc3->read(path3)) { + auto takes = doc3->getAnimationStacks(); + if (!takes.empty()) { + if (takes[0]->remap(doc)) { + doc->setCurrentTake(takes[0]); + } + } + } + } + + testPrint("Objects:\n"); + for (auto obj : doc->getRootObjects()) + PrintObject(obj); + + doc->writeBinary("out_b.fbx"); + doc->writeAscii("out_a.fbx"); + } + +} + +testCase(fbxWrite) +{ + { + sfbx::DocumentPtr doc = sfbx::MakeDocument(); + sfbx::Model* root = doc->getRootModel(); + + sfbx::Mesh* node = root->createChild("mesh"); + node->setPosition({ 0.0f, 0.0f, 0.0f }); + node->setRotation({ 0.0f, 0.0f, 0.0f }); + node->setScale({ 1.0f, 1.0f, 1.0f }); + + float s = 10.0f; + sfbx::GeomMesh* mesh = node->getGeometry(); + + { + // counts & indices & points + int counts[]{ + 4, 4, 4, 4, + }; + int indices[]{ + 0, 1, 3, 2, + 2, 3, 5, 4, + 4, 5, 7, 6, + 6, 7, 9, 8, + }; + float3 points[]{ + {-s * 0.5f, s * 0, 0}, {s * 0.5f, s * 0, 0}, + {-s * 0.5f, s * 1, 0}, {s * 0.5f, s * 1, 0}, + {-s * 0.5f, s * 2, 0}, {s * 0.5f, s * 2, 0}, + {-s * 0.5f, s * 3, 0}, {s * 0.5f, s * 3, 0}, + {-s * 0.5f, s * 4, 0}, {s * 0.5f, s * 4, 0}, + }; + mesh->setCounts(counts); + mesh->setIndices(indices); + mesh->setPoints(points); + + // normals + { + sfbx::LayerElementF3 normals; + normals.data = { + { 0, 0, -1}, { 0, 0, -1}, + { 0, 0, -1}, { 0, 0, -1}, + { 0, 0, -1}, { 0, 0, -1}, + { 0, 0, -1}, { 0, 0, -1}, + { 0, 0, -1}, { 0, 0, -1}, + }; + normals.indices = indices; + mesh->addNormalLayer(std::move(normals)); + } + + // uv + { + sfbx::LayerElementF2 uv; + uv.data = { + { 0, 0.00f }, { 1, 0.00f }, + { 0, 0.25f }, { 1, 0.25f }, + { 0, 0.50f }, { 1, 0.50f }, + { 0, 0.75f }, { 1, 0.75f }, + { 0, 1.00f }, { 1, 1.00f }, + }; + uv.indices = indices; + mesh->addUVLayer(std::move(uv)); + } + + // colors + { + sfbx::LayerElementF4 colors; + colors.data = { + { 1, 0, 0, 1}, { 0, 1, 0, 1}, + { 0, 0, 1, 1}, { 0, 0, 0, 1}, + { 1, 0, 0, 1}, { 0, 1, 0, 1}, + { 0, 0, 1, 1}, { 0, 0, 0, 1}, + { 0, 0, 1, 1}, { 0, 0, 0, 1}, + }; + colors.indices = indices; + mesh->addColorLayer(std::move(colors)); + + } + } + + // blend shape + sfbx::BlendShape* blendshape{}; + sfbx::BlendShapeChannel* bschannel{}; + { + int indices[]{ + 6, 7, 8, 9, + }; + float3 delta_points[]{ + {-s, 0, 0}, {s, 0, 0}, + {-s, 0, 0}, {s, 0, 0}, + }; + + sfbx::Shape* shape = doc->createObject("shape"); + shape->setIndices(indices); + shape->setDeltaPoints(delta_points); + + blendshape = mesh->createDeformer(); + bschannel = blendshape->createChannel(shape); + } + + // joints & skin + sfbx::Model* joints[5]{}; + sfbx::Skin* skin{}; + { + joints[0] = root->createChild("joint1"); + joints[1] = joints[0]->createChild("joint2"); + joints[2] = joints[1]->createChild("joint3"); + joints[3] = joints[2]->createChild("joint4"); + joints[4] = joints[3]->createChild("joint5"); + for (int i = 1; i < 5; ++i) + joints[i]->setPosition({ 0, s, 0 }); + + skin = mesh->createDeformer(); + for (int i = 0; i < 5; ++i) { + sfbx::Cluster* cluster = skin->createCluster(joints[i]); + int indices[2]{ i * 2 + 0, i * 2 + 1 }; + float weights[2]{ 1.0f, 1.0f }; + cluster->setIndices(make_span(indices)); + cluster->setWeights(make_span(weights)); + cluster->setBindMatrix(joints[i]->getGlobalMatrix()); + } + } + + // animation + { + sfbx::AnimationStack* take = doc->createObject("take1"); + sfbx::AnimationLayer* layer = take->createLayer("deform"); + sfbx::AnimationCurveNode* n1 = layer->createCurveNode(sfbx::AnimationKind::Rotation, joints[1]); + n1->addValue(0.0f, float3{ 0.0f, 0.0f, 0.0f }); + n1->addValue(3.0f, float3{ 30.0f, 0.0f, 0.0f }); + n1->addValue(6.0f, float3{ 0.0f, 0.0f, 0.0f }); + n1->addValue(9.0f, float3{-30.0f, 0.0f, 0.0f }); + + sfbx::AnimationCurveNode* bsw = layer->createCurveNode(sfbx::AnimationKind::DeformWeight, bschannel); + bsw->addValue(0.0f, 0.0f); + bsw->addValue(4.5f, 100.0f); + bsw->addValue(9.0f, 0.0f); + + doc->setCurrentTake(take); + } + + + doc->constructNodes(); + doc->writeBinary("test_base_bin.fbx"); + doc->writeAscii("test_base_ascii.fbx"); + } + { + sfbx::DocumentPtr doc = sfbx::MakeDocument(); + sfbx::Model* root = doc->getRootModel(); + + sfbx::Model* joints[5]{}; + joints[0] = root->createChild("joint1"); + joints[1] = joints[0]->createChild("joint2"); + joints[2] = joints[1]->createChild("joint3"); + joints[3] = joints[2]->createChild("joint4"); + joints[4] = joints[3]->createChild("joint5"); + + + // animation + { + sfbx::AnimationStack* take = doc->createObject("take1"); + sfbx::AnimationLayer* layer = take->createLayer("deform"); + sfbx::AnimationCurveNode* n1 = layer->createCurveNode(sfbx::AnimationKind::Rotation, joints[1]); + n1->addValue(0.0f, float3{ 0.0f, 0.0f, 0.0f }); + n1->addValue(3.0f, float3{ 30.0f, 0.0f, 0.0f }); + n1->addValue(6.0f, float3{ 0.0f, 0.0f, 0.0f }); + n1->addValue(9.0f, float3{ -30.0f, 0.0f, 0.0f }); + n1->addValue(12.0f, float3{ 0.0f, 0.0f, 0.0f }); + } + + doc->constructNodes(); + doc->writeBinary("test_anim_bin.fbx"); + doc->writeAscii("test_anim_ascii.fbx"); + } + + + { + sfbx::DocumentPtr base = sfbx::MakeDocument(); + base->read("test_base_bin.fbx"); + + { + sfbx::DocumentPtr anim = sfbx::MakeDocument(); + anim->read("test_anim_bin.fbx"); + auto takes = anim->getAnimationStacks(); + if (!takes.empty()) { + if (takes[0]->remap(base)) { + base->setCurrentTake(takes[0]); + } + } + } + if (auto take = base->getCurrentTake()) { + take->applyAnimation(11.0f); + } + } +} + +testCase(fbxAnimationCurve) +{ + sfbx::DocumentPtr doc = sfbx::MakeDocument(); + auto curve = doc->createObject("TestCurve"); + + float times[]{ 0.0f, 1.0f, 2.0f }; + float values[]{ 0.0f, 100.0f, 400.0f }; + curve->setTimes(times); + curve->setValues(values); + + for (float t = -0.5f; t < 2.5f; t += 0.1f) { + printf("time: %f, value: %f\n", t, curve->evaluate(t)); + } +} diff --git a/src/Test/pch.cpp b/src/Test/pch.cpp new file mode 100644 index 0000000..1d9f38c --- /dev/null +++ b/src/Test/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/Test/pch.h b/src/Test/pch.h new file mode 100644 index 0000000..52f48ee --- /dev/null +++ b/src/Test/pch.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef _WIN32 + #define NOMINMAX + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_span + #include +#endif diff --git a/src/setup.bat b/src/setup.bat new file mode 100644 index 0000000..e7157b9 --- /dev/null +++ b/src/setup.bat @@ -0,0 +1,14 @@ +@echo off + +cd Externals + +IF NOT EXIST "7za.exe" ( + echo "downloading 7za.exe..." + powershell.exe -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol=[System.Net.SecurityProtocolType]::Tls12; wget https://github.com/i-saint/SmallFBX/releases/download/data/7za.exe -OutFile 7za.exe" +) + +echo "downloading external libararies..." +powershell.exe -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol=[System.Net.SecurityProtocolType]::Tls12; wget https://github.com/i-saint/SmallFBX/releases/download/data/Externals.7z -OutFile Externals.7z" +7za.exe x -aos Externals.7z + +cd .. diff --git a/src/setup.vcxproj b/src/setup.vcxproj new file mode 100644 index 0000000..4746894 --- /dev/null +++ b/src/setup.vcxproj @@ -0,0 +1,45 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + Document + %(Identity) + Externals\Externals.7z + %(Identity) + false + + + + {1C5DE91B-7AE9-4304-9FA1-0DE1ABA8C02D} + MakeFileProj + 10.0 + + + + Utility + true + v142 + + + + + + + + $(SolutionDir)_build_msvc\obj\setup\ + $(SolutionDir)_build_msvc\obj\setup\ + + + + + \ No newline at end of file